Django Tutorial: Getting Started with Django

 



About this Tutorial

What will we cover?

In this tutorial I’ll show you the basics ideas involved in creating a website with Django starting from scratch. Follow along with the code samples as I build a tiny book database and by the end you’ll understand the pieces that make up a typical Django project and how they fit together. This tutorial should take less than two hours to complete and won’t demonstrate every part of Django or every best practice – but if you complete it you’ll be well prepared to tackle more substantial tutorials and projects.

Tutorial Audience (that’s you!)

This tutorial is aimed at beginners to the world of web development. Hopefully you have experimented with Python and HTML but have never used Django before.

Don’t worry if you’re new to Python. There are many resources for learning Python – but by following along you’ll gain some basic Python skills as you learn Django. (More than one programmer has learned Python by learning Django!)

You should also take comfort in the excellent documentation! Django’s docs are excellent and comprehensive – throughout I’ll highlight areas of the documentation you’ll want to read to learn more.

All the tools we’ll use run on Windows, OS X, and Linux alike. I’ll be working from OS X but will point out where details may differ depending on your operating system so you can follow along.

Installation

Before we get started learning Django we need to make sure we’ve got the right tools. We’ll make sure you have:

  • Python 3.4
  • Django 1.8
  • A coding text-editor

And it goes without saying that you need an up-to-date web browser.

Python

You may already have Python installed on your system. In this tutorial I’ll be using Python 3.4 – the latest version of Python currently released.

If you currently do not have Python installed I recommend Anaconda’s Python distribution available at here.

Anaconda Python includes many 3rdparty Python packages by default and installation help is available here.

If you are comfortable with the standard Python installer from python.org or are fortunate enough to use an OS with a package manager you can install Python 3.4 from standard sources.

You are successful when you can start Python from a shell prompt and enter the interactive shell for Python 3.4. On my system it looks like this:

$ python
Python 3.4.3 |Anaconda 2.3.0 (x86_64)| (default, Mar  6 2015, 12:07:41)
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
NOTE

Code Samples – I’ll use a lot of code samples in this tutorial. Prompt characters at the beginning of the line indicate that I’m in a shell of some kind like $ (bash) or >>> (Python interactive shell). Lines that don’t start with a prompt are generally the output that results from running a bash command or snippet of python code. You should follow along whenever you see a code sample: typing the same commands after the prompt should result in the same output.

Django 1.8

Next we need to install Django 1.8. We’ll use pip and venv. If these are new tools to you suffice it to say venv let’s us make separate Python environments and pip can install/uninstall Python packages.

We start out by running the venv module to create a virtual environment. Once you’ve activated the environment pipwill install packages into it. Let’s see how it works! Type the following commands in your shell starting from a directory you can find again like your home directory.

$ mkdir django-tutorial
$ cd django-tutorial
$ python -m venv DJANGO
$ ls DJANGO
bin     include    lib    pyvenv.cfg
$ source DJANGO/bin/activate   # Run the activation script
(DJANGO)$

First we create a directory to work out of. Next, running python -m venv DJANGO told Python to create a virtual environment in the current directory called DJANGO. Of course if you’re following along on Windows you’ll have to use an equivalent command like dir DJANGO to list your files.

NOTE

The -m flag to Python tells it to run a module as a program. Some Python modules are useful both as a library and as a program.

You can explicitly run the programs in DJANGO/bin (python, pip, etc) or simply activate the environment to add this directory to your $PATH. On systems with bash we source the activation script but support is included for other shells and for the Windows traditional command prompt or PowerShell.

TIP

On Windows run DJANGO/Scripts/activate.bat. See here for details
Activating the environment should modify the prompt so we have a visual clue to the running environment and adds adeactivate command we can run. While the env is activated pip install will run pip in this venv and typing pythonwill get you the python in this venv.

When we run deactivate we’ll be back to the Python and tools that are globally installed.

For the moment lets install Django and confirm that it is installed just in this virtual environment.

(DJANGO)$ pip install django
You are using pip version 6.0.8, however version 7.1.0 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting django
  Downloading Django-1.8.3-py2.py3-none-any.whl (6.2MB)
    100% |################################| 6.2MB 37kB/s
Installing collected packages: django

Successfully installed django-1.8.3

Apparently the pip shipping with Anaconda’s Python isn’t the newest version so I’ll run the suggested command to upgrade.

(DJANGO)$ pip install --upgrade pip
... snip ...
Successfully installed pip-7.1.0

OK – now that the boilerplate of installation is over let’s check that we were successful. Successfully installing a package in Python means you should be able to import it.

(DJANGO)$ python
Python 3.4.3 |Anaconda 2.3.0 (x86_64)| (default, Mar  6 2015, 12:07:41)
>>> import django
>>> print(django.get_version())
1.8.3

Success!

Remember – django is only installed in this particular virtual environment. If we deactivate the environment and use the system Python we shouldn’t be able to import Django (unless you’ve previously installed it system-wide).

(DJANGO)$ deactivate
$ python
Python 3.4.3 |Anaconda 2.3.0 (x86_64)| (default, Mar  6 2015, 12:07:41)
>>> import django
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'django'
$ source DJANGO/bin/activate  # Don't forget to activate the venv again

Other tools

Hopefully you’re still with me and about ready to begin learning how to use Django.
Before we do so let me quickly recommend a couple of tools.

Editor or IDE

You’ll want a programming text editor. If you have an editor preference feel free to stick with it but if you haven’t used a programming editor before I recommend trying out PyCharm Community Edition which is the best free IDE for Django development. See here for the download. If you’d like a lighter-weight tool I recommend Sublime Text as a good coding editor.

NOTE


If you already have a $EDITOR you love you can just keep using what you’re used to.

IPython

I always use IPython – the improved interactive Python shell – whenever I can. The Django framework evidently shares my fondness for IPython – if you install it Django will automatically use it as the Python shell of choice.

If you want to use it as well you can pip install ipython. Don’t worry if this doesn’t work for you however – it isn’t necessary to complete this tutorial.

Git

This tutorial is not long enough to teach git. But if you already know about git I encourage you to use git to track your changes. My completed source code is available and any fixes for bugs in the sample code will be published there.

Django First Steps

Finally! We’re ready to get started actually creating our Django project.

I frequently borrow books from my friends. I lend them out too so sometimes I lose track of what I’ve read and when. This is annoying when I’m in the middle of several series at once! Let’s build a little web app that will let me keep track of the books I’ve read.

Understanding the Project

Django is a web framework that comes with strong conventions about how we’ll lay out our code, what parts we’ll need, and how they all relate.

This can feel a little overwhelming at first but learning some vocabulary will help a lot.

The Django documentation refers to a project. A project is the collection of all the code and resources needed to produce a website. To start a new project we typically use a helper command that comes with django to generate our project structure. Let’s try that together:

(DJANGO) $ django-admin startproject mybooks
(DJANGO) $ ls mybooks
manage.py mybooks

TIP

Can’t find the django-admin command? It should be in the virtual environment’s bin directory on Mac/Linux – see DJANGO/bin/django-admin. On Windows there should be a django-admin.exe in theDJANGO/Scripts directory.

The startproject command creates a nested directory structure. The project directory contains the manage.py script that we’ll use to run utility commands from here out. It also creates the settings directory which has the same name as the project directory.

Let’s peek in the settings directory:

(DJANGO) $ ls mybooks/mybooks
__init__.py settings.py urls.py     wsgi.py

The settings directory contains an init.py file which tells python this directory can be imported as a module.

It also contains three files with code in them.

  • settings.py – this file contains directives about our project (think database settings, administrative users, enabled apps, etc).
  • urls.py – this is the top-level mapping of urls to Django views. We’ll those shortly.
  • wsgi.py – this file can be used when we deploy our Django project. wsgi is a language independent spec that lets an application (like our Django project) talk to a web server like Apache.

Using manage.py

So what can we do so far?

Because Django is a web framework you have to run an HTTP server to see your work in progress. Fortunately Django comes with a development HTTP server – it shouldn’t be used in production for various performance and security reasons but you can use it while working on your project.

You start the development HTTP server with the manage.py script.

(DJANGO) $ cd mybooks
(DJANGO) $ python manage.py runserver
... some logging output ...
Django version 1.8.3, using settings 'mybooks.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Open http://127.0.0.1:8000/ in your browser of choice.

first-run

The manage.py utility gets used a lot – to start utilities like the server and to generate code. You’ll want to keep a terminal window open in the project directory to have convenient access to the manage.py utility.

TIP


You’re going to always want to keep a separate terminal window open just to keep the runserver command running.

 

Django applications

A Django project is typically made up of multiple applications and most of the code we write will be in an app.

As the start up page suggests we can create the skeleton of our first application using the manage.py command. Let’s create it and take a look at the files it creates.

(DJANGO) $ python manage.py startapp books
(DJANGO) $ ls
books      db.sqlite3 manage.py  mybooks

We have a new directory books which represents our application. Hmmm and where did that sqlite database come from?

Let’s look inside the application

(DJANGO) $ ls books/
__init__.py admin.py    migrations  models.py   tests.py    views.py

That’s quite a few files to start with! But each one has a specific role to play and a specific kind of code to hold.

Modeling your data

The first step in creating most Django apps is modeling your data. Models, I’m sure you will be suprised to know, go inmodels.py. The startapp command helped us out by generating the file

mybooks/books/models.py
from django.db import models

# Create your models here.

That’s a helpful comment - but we’re responsible for creating our models. Let’s add a little code and define just whatmodels mean in Django. Put this code in your models.py:

mybooks/books/models.py
from django.db import models


class Book(models.Model):
    title = models.CharField(max_length=200)
    isbn = models.CharField(max_length=200)
    read_on = models.DateTimeField()

In Django models are Python classes which extend django.db.models.Model. If you aren’t used to object oriented programming in Python this may sound scary (and if you are experienced it may occur to you that this is a pretty strange class!)

Not to worry – Django uses class definitions as a DSL (Domain Specific Language) for configuring various parts of the framework and classes like this will soon become a familiar sight.

A models.Model class roughly corresponds to a table in a database. Django can figure out from our Book class how to create a book table in our database. Each of the class attributes like title and isbn will become columns in the booktable with the type taken from the Field class.

NOTE


Django defines a rich set of classes that represent various kinds of database columns. See here for the complete list.

 

Database, what Database?

It may have occurred to you that we haven’t defined a database anywhere. Actually we have! Remember the settings.py file in the project settings directory? The settings.py generated by startproject is over a 100 lines but near the bottom are our DATABASE settings. (You don’t need to edit anything yet).

mybooks/mybooks/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Django uses Python code for configuration – settings.DATABASES is a dict with entries for each database connection we need. The default connection is an sqlite database stored in a file called db.sqlite3.

NOTE


Python comes with database drivers for sqlite and there is no separate server to install so it makes a great choice for learning projects. For more substantial projects Django supports a wide variety of free and commercial database systems.

 

Go ahead and keep that file open – we need to edit our settings in order to use our newly defined models. Django projects are made up applications – and Django comes with a variety of applications for sessions, serving up files, authentication and other tasks. Look for your INSTALLED_APPS definition which should look like:

mybooks/mybooks/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

Let’s add our app to the INSTALLED_APPS setting:

mybooks/mybooks/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'books',
)

One more thing – installing an app doesn’t create the database tables described in its models. To do that we use themigrate command from the manage.py utility. Fire up another shell (or stop the runserver with CTRL-C) and run

(DJANGO) $ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: messages, staticfiles
  Apply all migrations: sessions, admin, auth, contenttypes
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK

Ah – we had some other applications installed as well and now we’ve created the initial tables for them as well. I didn’t see any output about creating the book table though!

That’s because we didn’t create the migration for our model yet. Django handles database modifications by running migration scripts that modify your database. You generate a migration script every time you make changes to your model by running the makemigration command on the app that changed. Let’s try it out:

(DJANGO) $ python manage.py makemigrations books
Migrations for 'books':
  0001_initial.py:
    - Create model Book
(DJANGO) $ ls books/migrations/
0001_initial.py __init__.py

Now that we have generated a migration script for our models we need to run it:

(DJANGO) $ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages
  Apply all migrations: auth, admin, books, sessions, contenttypes
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying books.0001_initial... OK

Creating Data

We’ve got tables, let’s create some data! The model classes that we create not only define the tables in the database but also provide an convenient Pythonic API for accessing data in the tables. This API is referred to as Django’s ORM, or Object Relational Mapper. This API makes it easy to run simple queries even if you aren’t familiar with SQL, the underlying language that relational databases speak.

We’ll write code using the ORM later but we can start out exploring it interactively. To get a Python shell that has oursettings loaded and access to all our code we can use the shell command from the manage.py utility.

Starting up a shell
(DJANGO) $ python manage.py shell
Python 3.4.3 |Anaconda 2.3.0 (x86_64)| (default, Mar  6 2015, 12:07:41)
IPython 3.2.1 -- An enhanced Interactive Python.

In [1]: from books.models import Book
In [2]: new_book = Book(title="Snow Crash", isbn="0553380958")

I’ve got IPython installed so Django uses it when I run the shell command. Don’t panic if you just see the standard Python prompt with >>> – just type the code to the right of the colon to follow along.

On line 1 I import our model class and on line 2 I’m creating an instance of it. Model classes can be called with keyword arguments that match the column names. We can also assign to the attributes of the Book instance but its important to save the right kind of data. Because read_on was a DateTimeField I need to be sure to give it a Python datetime object which I can get from the datetime module. Still in the shell:

Interactive coding in the shell
In [3]: import datetime
In [4]: new_book.read_on = datetime.datetime.now()
In [5]: new_book.save()
In [6]: new_book.id
Out[6]: 1

On line 5 I call the .save() method. This method actually talks to the database for us and runs an SQL INSERT statement that puts our data into the book table. Interestingly on line 6 you can see that our book instance now has a new property, one we didn’t explicitly define on our Book model class. (The Out[6] is the output from line 6 – you don’t need to type this.)

Django’s default behavior is to add a PRIMARY KEY column to our table named id. This attribute is unset on the object before we called the save() method as our data isn’t yet in the database. Afterwards it shows us the unique id of this record in the book table.

NOTE


Don’t worry if you use IPython and see a RuntimeWarning about the datetime – this isn’t really a bug and has been fixed in the development version of Django

 

Querying Data

We can check to see if our data actually got into the database by using the Querying API. Each model subclass has a.objects attribute with query methods like .all() and .filter().

If you know SQL the .all() method issues a SELECT * from book query while the .filter() method adds a WHEREclause to the query. The results of most query methods are an iterable of model instances:

In [7]: Book.objects.all()       # All the records in the book table
Out[7]: [<Book: Book object>]    # Looks like a list
In [8]: Book.objects.query(id=1) # All the records where id=1
Out[8]: [<Book: Book object>]    # Looks like a list

It’s important to realize that most model methods return a QuerySet object. It may look like a list in the console but its really just an SQL statement until it’s evaluated. We can peek at the .query property to see this:

In [9]: qs = Book.objects.all()
In [10]: str(qs.query)
Out[10]: 'SELECT "books_book"."id", "books_book"."title",
          "books_book"."isbn", "books_book"."read_on" FROM "books_book"'

Even if you know SQL line 9 is a lot nicer to write than the query output on line 10! We can make a new query by calling additional query methods on a queryset:

In [11]: qs = qs.order_by('title')
In [12]: str(qs.query)
Out[12]: 'SELECT "books_book"."id", "books_book"."title", "books_book"."isbn",
          "books_book"."read_on"
          FROM "books_book"
          ORDER BY "books_book"."title" ASC'

You can use the order_by() method to modify the order of the records – or you could call filter() again to make a more complicated query.

TIP


You can also use the dbshell management command to open up your database CLI connected to the database in your settings for adhoc querying in SQL. (This is very convenient when using a remote database server like MySQL.) This still works with our sqlite database file but you have to have thesqlite command line utility installed. You can download precompiled sqlite-shell binaries for your platform here.

Getting a single record

It isn’t always convenient to get a list or results when we just want a single record. We can use methods that don’t return a queryset but instead return a single object.

In [13]: Book.objects.get(id=1)
Out[13]: <Book: Book object>     # Just the one object matching our query

In [14]: Book.objects.get(id=2)  # Raises an exception if no records match
DoesNotExist    Traceback
 ... snip ...
DoesNotExist: Book matching query does not exist.

That’s probably enough exploration of the ORM for now – let’s figure out how to make our data show up on a web page!

TIP


You can make useful applications with very simple ORM calls. As your application grows you’ll need to understand more advanced concepts particularly around relationships between models. At some point you’ll want to read the docs on queries.

Views, URLs and Templates

Ok – let’s stop to review briefly where we are. We:

  • created a project with django-admin command
  • ran the development http server with the manage.py command
  • created an app inside the project with the manage.py command and added it to the settingsINSTALLED_APPS
  • created a model for our data by writing some Python code
  • added our model to the database with the manage.py migration commands to generate and run migration scripts
  • and experimented with the ORM in an interactive Python shell

You’ll notice a few terms highlighted in that description – part of learning Django is learning the vocabulary of project organization. So far we’ve learned about projects, apps, settings, and models.

We still need to learn about views, urls, and the admin.

We will also continue to discover that there’s a management command for almost everything!

What are views?

If the models for our app live in models.py, where do the views live? That’s right – startproject generated aviews.py file right next to the models.py file. As with our generated models.py file it has a helpful import and a helpful comment:

1 from django.shortcuts import render
2 
3 # Create your views here.

But what is a view? In Django a view is is just a Python callable (like a function) that accepts an HTTP request and returns an HTTP response. In fact the entire Django framework is essentially about accepting HTTP requests and returning HTTP responses.

A little HTTP

You can use the developer tools in a browser like Google’s Chrome to inspect the underlying conversation that is going on when the browser requests a page from a server. The browser sends formatted text that contains information about the request:

Sample HTTP request
GET / HTTP/1.1           (1)
Host: localhost:8000     (2)
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
  1. This is the actual request line – GET is an HTTP verb and / is the url requested.
  2. Followed by many header lines with a key (like Host) on the left of the colon. These headers tell the server information about the client making the request and its capabilities.

The response looks similar – an HTTP response has an initial line with a status code, some header lines, and the actual content of the response. Parsing the request and generating the response would be a little tricky!

Our first view

Fortunately Django handles the heavy lifting for us. A view gets the incoming request in form of an HttpRequest object and there are convenient helpers to create the response.

The most common response will be a bunch of HTML – but we don’t want to put a lot of HTML strings in our Python code. To avoid this we’ll use Django’s template support and keep our view nice and short.

Edit your views.py in the books app to look like this:

mybooks/books/views.py
1 from django.shortcuts import render
2 
3 def index(request):
4     return render(request, "index.html")

This is a very simple view that uses the render shortcut Django provides to load a template called index.html and create an HttpResponse from it. But where does that template live?

Creating Templates

By default Django will load templates from a directory named templates in your app. Later on we might want to customize settings.TEMPLATES but for now let’s just make make a directory in our books app.

(DJANGO) $ ls books
__init__.py admin.py    migrations  models.py   tests.py    views.py
(DJANGO) $ mkdir books/templates

We’ll start with a simple Hello World template. Create a new file with your text editor in the books/templates directory called index.html and add the following contents:

<h1>Hello World!</h1>

TIP


The <h1> tags in are template are HTML’s way of making a header element. If you haven’t played with HTML before you might try the This beginning HTML tutorial.

 

Routing URLs

We have one last thing to do in order to run our view and see our template in the browser.

When an HTTP request comes in from the client Django consults its url routing table to decide which view should handle the request and return a response to the client.

graph

We need to give Django a url that routes to our view. URLs are stored (you guessed it) in urls.py. Individual applications can have urls.py but by default there is one global urls.py created in the project configuration directory next to thesettings.py file. There is a long docstring at the top of this file that I’m leaving out but currently it looks like this:

mybooks/mybooks/urls.py
1 from django.conf.urls import include, url
2 from django.contrib import admin
3 
4 urlpatterns = [
5     url(r'^admin/', include(admin.site.urls)),
6 ]

urlpatterns is a list of mappings from URLs represented as regular expressions, or regex, to a particular view or another url configuration file. That is what is happening on line 5 which basically says that if an incoming URL starts withadmin/ the rest of the URL will be handed off to the admin app’s URL for routing.

Don’t worry if this part of Django looks a little complicated. Writing (and reading) regex can be tricky even if you have experience with them. Fortunately mostly here we can just copy existing examples.

Let’s route the front page of our web app to the view we just wrote. Let me show you how to modify the urls and then I’ll explain the code:

mybooks/mybooks/urls.py
1 from django.conf.urls import include, url
2 from django.contrib import admin
3 
4 from books.views import index
5 
6 urlpatterns = [
7     url(r'^admin/', include(admin.site.urls)),
8     url(r'^$', index),
9 ]

On line 4 we have to import the view we want.

On line 8 we added the new the rule. The pattern for an empty url is ^$ (the start of the string and end of string regex symbols with nothing between them) and the url function maps our pattern r'^$' to our view index.

Did it work? Reload your browser pointed at http://localhost:8000/

TIP


If nothing loads in your browser perhaps the runserver command crashed. (You do still have a python manage.py runserver command running in a shell, right?) The runserver command will reload code anytime you save a file it already has loaded – but if you save a file with a Python syntax error it may crash. Don’t worry – just restart it.You may also sometimes find that runserver can’t find new files you’ve added since last running the command. A good first debugging step when things aren’t working is to stop the runserver with CTRL-C and restart it.

Putting data in our template

I know this isn’t very impressive yet – so lets put some data in our template. Edit your views.py to match mine:

mybooks/books/views.py
1 from django.shortcuts import render
2 from .models import Book
3 
4 def index(request):
5   books = Book.objects.all()
6   return render(request, "index.html", {'books': books})

On line 2 we need to import or model class and we can use Python’s relative import syntax to refer to the models.pymodule sitting right beside us.

On line 5 in our index view we add an ORM query to get all the books from the database and on line 6 we pass this data to the template using the context argument to the render shortcut. The dict of data we pass to render will be made available in our template.

And now we can edit our template to make it a little more interesting. Django templates aren’t limited to HTML – they can also include a language for adding output and logic. There’s a bit to learn here but fortunately it looks a lot like Python. Edit your index.html to match mine and I’ll explain:

mybooks/books/templates/index.html
1 <h1>My Books!</h1>
2 
3 <ul>
4   {% for book in books %}
5   <li>{{ book.title }}</li>
6   {% endfor %}
7 </ul>

On line 3 we’re starting an Unordered List with the <ul> tag. The {% and %} delimit what Django calls a template tag. Django comes with many built in tags that resemble Python syntax and you can learn to create your own custom tags as well. In this case the tag is a for loop, looping over the books variable just like you would in Python.

Because HTML isn’t very friendly to indentation delimited blocks the {% for %} tag has a matching {% endfor %} tag and you’ll find this is a common pattern. (As you might expect there’s also an {% if %} tag that looks a lot like Python’sif statement.)

For each book in books we create a List Item with the <li> tag containing the book title. The consecutive curly brace syntax {{ book.title }} is the template language’s equivalent of the print function in Python.

One last thing – the template language was designed to be as easy as possible to use so some syntactic limitations of Python are relaxed in the template language. Attributes that point to methods will automatically be called (no parens necessary) and both dictionary keys and list indexes can be addressed with dot syntax (some_list.0 isn’t valid Python but it’s fine in the template).

TIP


A lot of your early learning will involve mastering the Django template language. Just go ahead and bookmark this page.

Try reloading the front page!

mybooks


The Admin

One of Django’s coolest features is the admin application which comes with the framework.

It’s turned on by default but we need to set it up for our models and create an account to use it.

Accessing the Admin

In order to access the admin we need to create a login account for our Django project. Django comes with a pluggable authentication system which by default stores user logins in our database. To create a user we can use thecreatesuperuser management command. Go ahead and try in your shell – you can provide a made-up username, email address and password to use. It should look something like:

(DJANGO) $ python manage.py createsuperuser
Username (leave blank to use 'sfranklin'): simeon
Email address: email@address.me
Password:
Password (again):
Superuser created successfully.

Once you’ve created a login account, navigate your browser to http://localhost:8000/admin/. (You do have a runserver command running, right?) You can login with the username and password you just created to get in the admin.

admin

Registering our Model

Since we haven’t set up the admin on our models yet the only options available are Groups and Users. Let’s get our Bookmodel registered with the admin.

To do so we’ll edit our app’s admin.py (you may be noticing a trend with Django: a place for everything and everything in its place. It’s kind of nice once you’re used to it!)

As usual the startapp management command generated a skeleton file for us:

mybooks/books/admin.py
from django.contrib import admin

# Register your models here.

Let’s replace that helpful comment. The Django admin can be heavily customized (we’ll do a little customization in a minute) but you can also just import your models and register them. Let’s do that first! Edit your admin.py to look like this:

mybooks/books/admin.py
1 from django.contrib import admin
2 
3 from .models import Book
4 
5 admin.site.register(Book)

On line 3 we add the import of our model and on line 5 we register it with the admin.

Add a record in the Admin

Go ahead and reload your browser at the admin url http://localhost:8000/admin/

If all has gone well you should see a new Books section. Below it is a Books link with an Add and Change link beside it.

The admin interface is an automatically generated CRUD (Create/Read/Update/Delete) interface. We can use it to search and browse records, to edit or delete existing records, and to create new ones. Let’s try creating a new Book. Click on theAdd link – it should direct you to http://localhost:8000/admin/books/book/add/ which looks like this:

add

Yes I’m adding another book – you should too! The admin presents sensible widgets based on our model field types and includes some basic validation (try leaving a field blank and hitting Save).

When you’ve successfully saved a book you’ll be taken to the list view that shows all the records. Unfortunately it doesn’t look very good right now as each entry just says “Book object”.

Customizing the Admin

We can customize the admin in a variety of ways. One way is by editing our model.

Python classes can define a _str_ method that provides a string representation of instances of the class. We didn’t define one of these methods but lets add it to the Book model. Edit your models.py:

mybooks/books/models.py
 1 from django.db import models
 2 
 3 
 4 class Book(models.Model):
 5     title = models.CharField(max_length=200)
 6     isbn = models.CharField(max_length=200)
 7     read_on = models.DateTimeField()
 8 
 9     def __str__(self):
10         return self.title

On line 9 we’re adding a method which per Python’s object oriented model takes an argument self that points to the instance the method is bound to. The _str_ method is suspposed to return a string and we’ll return the title of the book as the string representation.

Go ahead and refresh the admin list view for the Book model at http://localhost:8000/admin/books/book/ – it should look better already. But we can do more!

We told the admin app about our model but we can add additional information by creating a class that extendsadmin.ModelAdmin, defining a variety of attributes that customize admin behavior and registering it along with our model.

You can read about the many attributes you can define on a ModelAdmin class in the Admin Documentation. We’ll just try a couple of simple things here to give you a feel for what’s possible. Edit your admin.py to match mine:

mybooks/books/admin.py
1 from django.contrib import admin
2 
3 from .models import Book
4 
5 class BookAdmin(admin.ModelAdmin):
6     date_hierarchy = 'read_on'
7     list_display = ('title', 'isbn', 'read_on')
8 
9 admin.site.register(Book, BookAdmin)

On line 5 we define our class. On line 6 the date_hierarchy attribute tells the admin to create a date filtering UI at the top of the admin by looking at the dates in the read_on attribute of the Book class. And on line 7 the list_display attribute defines the fields of the model we want to show in the list view in the admin app.

TIP


If the BookAdmin class clde looks a lot like the Book code in structure – well that’s on purpose. Django tends to use class definition as a DSL pattern and many popular Django apps do as well.

 

If you reload the admin list view you should see something like this:

admin-list

Going further

We have a complete working Django project now but it looks a little plain – revisit the front page at http://localhost:8000/ to see. In fact if you know HTML you’ve noticed that we don’t actually have a complete html page. We’re missing theDOCTYPE and don’t even have a <body> tag currently.

We’re also missing some information in our model that would be helpful in keeping track of the books we’ve read.

Let’s do some upgrades so we can admire our finished product!

Bootstrap and Templates

I’m not a designer but I can make my HTML output look decent by using an HTML/CSS framework. There are a bunch to choose from – for this project we’re going to incorporate Bootstrap. You can read more about it here, but I’ll show you the minimum needed to have a base template and include the CSS and Javascript resources needed.

First let’s install django-bootstrap3 – this bundles the framework resources and includes template tags to add them to our templates.

Use pip – be sure your venv is activated
(DJANGO) $ pip install django-bootstrap3
Collecting django-bootstrap3
  Downloading django-bootstrap3-6.1.0.tar.gz
Installing collected packages: django-bootstrap3
  Running setup.py install for django-bootstrap3
Successfully installed django-bootstrap3-6.1.0

You also need to add 'bootstrap3' to settings.INSTALLED_APPS. Find this INSTALLED_APPS in settings.py and edit it to look like this:

mybooks/mybooks/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'books',
    'bootstrap3'
)

base.html

Next we need to create a project wide base.html template. So far we’ve relied on the templates directory we made in our app. Now make a project-level templates directory. It should sit next to your app folder.

(DJANGO) $ mkdir templates
(DJANGO) $ ls
books      db.sqlite3 manage.py  mybooks    templates

We’ll need to add this location to the settings.TEMPLATES setting. The only line you need to change is the DIRSdirective on line 4 in the snippet below. It should look like this:

mybooks/mybooks/settings.py
 1 TEMPLATES = [
 2     {
 3         'BACKEND': 'django.template.backends.django.DjangoTemplates',
 4         'DIRS': [os.path.join(BASE_DIR, 'templates')],
 5         'APP_DIRS': True,
 6         'OPTIONS': {
 7             'context_processors': [
 8                 'django.template.context_processors.debug',
 9                 'django.template.context_processors.request',
10                 'django.contrib.auth.context_processors.auth',
11                 'django.contrib.messages.context_processors.messages',
12             ],
13         },
14     },
15 ]

Now we can add a new template. Create a file base.html in your project level templates directory. Copy the following source into this file and I’ll explain the new concept it introduces.

mybooks/templates/base.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>MyBooks</title>
    {% load bootstrap3 %}
    {% bootstrap_css %}
    {% bootstrap_javascript %}
  </head>
  <body>
   <div class="container">
    {% block content %}
    {% endblock %}
   </div>
  </body>
</html>

Most of this is just HTML boilerplate needed to have an HTML5 page suitable for Bootstrap. The bootstrap3 app doesn’t provide any models or views – it just gives us template tags to add the css/javascript resources in our page.

What is interesting is the new {% block %} template tag we’re using. This allows us to take advantage of template inheritance to organize our html.

Template Inheritance

We don’t want to add the HTML boilerplate to every template we create. It is better to have a base.html template that can be reused by multiple templates in multiple apps. But how can our existing index.html template use this template?

The answer is template inheritance. By defining a block in both templates and having the index.html extend thebase.html template we can get the surrounding content from the base.html when we render the index.htmltemplate.

template-inheritance

It’s important to note that only content in the child template which is in a block of the same name in the parent template will show up on the page. (If you ever wonder why stuff you put in your template just doesn’t show up – make sure you have your block names all lined up!)

Let’s edit the index template to extend the base template. Change your index.html template to look like this:

mybooks/books/templates/index.html
 1 {% extends "base.html" %}
 2 {% block content %}
 3 <h1>My Books!</h1>
 4 
 5 <ul class="list-group">
 6   {% for book in books %}
 7   <li class="list-group-item">{{ book.title }}</li>
 8   {% endfor %}
 9 </ul>
10 {% endblock %}

The new additions are the extends tag which must be at the top of the template and establishes the relationship between index.html and base.html. I also added a {% block content %} around the html so that it shows up in the base template. Finally I added a few bootstrap-specific classes to dress up our header and list a little bit. Reload your front page – it should look a little better!

bootstrap

Because there are multiple places to look for a template (each app/templates directory + the top level project templates directory) it is a good practice to always use the app name when loading app templates. We should move our index.html file one level deeper into a new directory books/templates/books. Then our view could pass to render the path books/index.html template and it wouldn’t run the risk of picking up an index.html defined in some other Django app.

TIP


You’ll want to install django-debug-toolbar. This invaluable tool lets you tell during development what templates were used to render the page. It can also tell you about database queries, total server-side rendering time and more.

More models, views, templates & urls

Let’s make one more change. Maybe we want to optionally keep some notes on each book we read. The process of making changes should be familiar at this point – and I’m going to ask you to make some changes without showing all of the source that I’ve changed. Don’t worry – if you get stuck you can refer to the finished product here.

 

Adding fields to the model

Let’s add a field to the Book model

New line in mybooks/books/models.py
    notes = models.TextField(default="", blank=True)

Whenever we make changes to our models we need to make sure the database is updated. Remember – we do that by creating a migration script and running it.

(DJANGO) $ ./manage.py makemigrations books
Migrations for 'books':
  0002_book_notes.py:
    - Add field notes to book
(DJANGO) $ ./manage.py migrate books
Operations to perform:
  Apply all migrations: books
Running migrations:
  Rendering model states... DONE
  Applying books.0002_book_notes... OK
TIP

If we’ve customized the admin we might need to remember to update the admin.py when we change the models.

Adding a detail view

Let’s add a detail view for our book review. But what should the URL be?

Django allows us to put a variables in the url to allow for dynamic urls. We can add an additional url to our urls.py and point it to a new view that accepts an argument.

New rule in mybooks/mybooks/urls.py
from books.views import index, detail

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^$', index),
    url(r'^([\d]+)/$', detail),
]

The pattern inside the parentheses in the new regex captures any sequence of digits followed by a trailing slash. The parentheses around this pattern creates a regex group. The new view we create must accept a positional argument and the group contents will be passed to it. Despite the fact that we know it will be digits, this value will be passed as a string.

TIP


Python supports named groups in regexes and you can use these names to pass named arguments in your view. See here for more sophisticated ways of doing things.

Our new view in views.py might look like:

Snippet from mybooks/books/views.py
from django.shortcuts import render, get_object_or_404
from .models import Book

def detail(request, id):
    book = get_object_or_404(Book, id=id)
    return render(request, "detail.html", {'book': book})

I’m using an additional shortcut function that does a query and raises an Http404 exception if it can’t find the matching item. The django.shortcuts module is full of useful tidbits like this.

And finally we need a new template. The detail template might look like this:

mybooks/books/templates/detail.html
{% extends "base.html" %}
{% block content %}
<div class="page-header">
<ol class="breadcrumb">
  <li><a href="/">My Books</a></li>
  <li><a href="#">{{ book.title }}</a></li>
</ol>
</div>
<h2>My Notes</h2>
<p>
{{ book.notes|default:"No notes" }}
</p>
<h4><a href="http://www.amazon.com/gp/product/{{ book.isbn }}">amazon link</a></h4>

{% endblock %}

I’m using Bootstrap breadcrumb navigation in the header here and I’ll add matching html to the index as well. The tag that displays the book.notes value uses what Django calls a template filter to show a default message if .notes is not yet filled in. Template filters are handy for many html/string sorts of tasks.

TIP


You’ll want to get acquainted with the built in template tags and filters. See here.

I’m also building a link to Amazon to buy any books I really enjoyed. The link will should work if you use the tradional 10-digit ISBN numbers.

My index template got a few improvements as well:

mybooks/books/templates/detail.html
{% extends "base.html" %}
{% block content %}
<div class="page-header">
 <ol class="breadcrumb">
  <li><a href="#">My Books</a></li>
 </ol>
</div>
<ul class="list-group">
  {% for book in books %}
  <li class="list-group-item"><a href="/{{ book.id }}/">{{ book.title }}</a></li>
  {% endfor %}
</ul>
{% endblock %}

Mainly I’ve added the breadcrumb navigation for consistency. I also wrapped the book titles in a link to the detail page for the book. The url as we’ve defined it looks like /{{ book.id }}/. This isn’t the ideal way to specify urls – eventually you’ll learn to use model methods to provide consistent urls without repetition.

NOTE


In the interests of simplicity I’m doing some things wrong. Django has great facilities for naming urls, specifying prefixes, grouping them by app, internationalizing the URL and dynamically looking up the url pattern in the template with the {% url %} tag. It’s a lot but eventually you’ll want to read the url documentation carefully.

Your detail page ought to look something like this:

finished

Deploying our Application

So far you’ve worked off your own computer using Django’s built-in server. This makes development very easy – but you can’t show your work to anybody else! Let’s fix that!

Deploying?

Deploying is just running your Django project on a real server with appropriate levels of security and performance.

Deploying Django applications for production use is a deep topic! There can be a nearly infinite number of things to consider all of which will take a little research. (Or maybe you can just decide to let somebody else figure it out and use managed hosting offerings like https://gondor.io/ or https://www.heroku.com/).

For the purposes of this tutorial we’re going to ignore many of the complexities of performance and security and get our application set up to run on the free hosting available at https://www.pythonanywhere.com/. Don’t try to run a business from your free hosting account – the free account limits the resources available. But it is cool to have a publicly accessible URL to show off!

Getting Ready to Deploy

We need to do a couple of things to get ready to deploy. We need to be able to reproduce our environment and its dependencies on a remote server. We have also been taking advantage of the staticfiles app without realizing it – but we need to understand how static media works in Django. Let’s look at each of these together.

Reproducing Our Environment

If you transferred your Django project code to another server you still wouldn’t be able to run it unless you also remembered to pip install all of the requirements for our project. Do you remember all the packages we’ve installed in our venv?

Fortunately you don’t have to – pip remembers! Run the following command in your console with your venv activated from your project folder. You should end up with a requirements.txt file next to your manage.py.

(DJANGO) $ pip freeze --local > requirements.txt
(DJANGO) $ ls
books            manage.py        mybooks          requirements.txt templates

This file contains the list of packages we’ve installed with their version numbers. Open it up and inspect it in your editor – it should look like this:

requirements.txt
Django==1.8.3
django-bootstrap3==6.1.0
pytz==2015.4
NOTE


If you installed ipython you might have some additional packages – just delete those lines to match myrequirements.txt.

We can recreate our dependencies on the server by creating a new virtual environment and using pip to install from our requirements file. For the moment let’s just leave the requirements.txt file there.

TIP


If you’re pushing your project to Github it is convention to check your requirements.txt file in as well to document your dependencies.

Handling Static Files

So far we haven’t talked about the problem of static files. Static files are the resources (javascript, css, images, etc) that are needed to render your pages.

Our main site is actually avoiding this problem completely by loading Bootstrap design resources from a 3rdparty url – a CDN or Content Delivery Network – instead of serving the files directly.

However the admin app does have static resources associated with it – and they seem to be showing up fine! How does this work so far?

When Django is in development mode (settings.DEBUG = True) Django will serve static resources. It finds these files in an app-level static directory – just like it automatically loads templates from an app level template directory. The settings.STATIC_URL controls the URL prefix for these files. It is currently set to /static/ so if your browser asks for the url /static/example.txt Django would look in each installed app’s static folder and when it finds a file called example.txt it serves it up.

This is very convenient during development – but it’s kind of expensive and not how we actually want to serve files. A better way would be to put all the files in a single directory and let a real web server serve them.

Fortunately Django comes with tools to help us do exactly that. Our settings file needs a new directive STATIC_ROOT – let’s add it after the existing STATIC_URL directive at the bottom of your settings.py:

mybooks/mybooks/settings.py
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'static')

This calculates the path to a static directory that sits along side our project folder. Lets go ahead and create this directory.

(DJANGO) $ cd ..
(DJANGO) $ mkdir static
(DJANGO) $ ls
mybooks     DJANGO     static

TIP


We don’t want to check the venv (DJANGO) or the static folder into git – they’re essentially output locations into which things are installed.

We can now run a management command to find all the static files included in our installed apps and copy them to to this folder.

(DJANGO) $ cd mybooks
(DJANGO) $ ./manage.py  collectstatic
You have requested to collect static files at the destination
location as specified in your settings:
    /Users/sfranklin/work/django-tutorial/static
This will overwrite existing files!
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
... snip ...
62 static files copied to '/Users/sfranklin/work/django-tutorial/static'.
(DJANGO) $ ls ../static/
admin

As you can see the collectstatic command finds many static files (all from the admin app) and copies them to theSTATIC_ROOT folder. We don’t need to do that while doing development work – but we will need to do this when we deploy.

TIP


Since all the app static files end up copied to the same place we should we follow the pattern recommended for application templates. To avoid naming collisions do not put a file called style.css directly in your application static directory. Instead duplicate your app name in your static directory. For instance we could make a folder books/static/books to put resources in. That way when collectstatic is run the STATIC_ROOT would get a new directory called books.

 

pythonanywhere.com

Thanks to the good folks at pythonanywhere.com we can deploy our project for free. This section may be a little tricky but stick with it!

First we need to sign up for a free account. Visit https://www.pythonanywhere.com/pricing/ and click on the Create a Beginner Account button. Supply a username, email address and password to register your account. You should receive a confirmation email immediately.

Once you’ve created your account you should be redirected to your dashboard.

pa-dashboard

From our dashboard we can view the files in our home directory, configure the web app, launch Python or bash consoles and even create MySQL or Postgres databases. We need to

  • transfer our code
  • build a virtual environment & install our dependencies
  • and configure the wsgi entry point to our web application.

Transferring our code

It would be best to keep our code in git, set up a github account, and use git clone to transfer our code. Feel free to do that if you already know how!

If you haven’t used git before – well one skill at a time. We’ll just zip up our code and transfer it over via the web interface to the files in our dashboard.

We want a zip archive of the entire project directory. Feel free to right-click on the project folder and select Compress from the pop-up menu to do it the easy way. Or from the console if you have the zip command available run:

(DJANGO) $ zip -r mybooks.zip mybooks
  adding: mybooks/ (stored 0%)
  adding: mybooks/books/ (stored 0%)
  adding: mybooks/books/__init__.py (stored 0%)
  adding: mybooks/books/admin.py (deflated 31%)
  ... snip ...
  adding: mybooks/templates/base.html (deflated 40%)

Now upload this file to your PythonAnywhere account. Click on the Files tab and then the Choose File button near the bottom of the page. Browse to the zip file you just made and upload it.

Setting up our environment

Now click on the Consoles tab in your PythonAnywhere account and then click on the bash link. This opens up a “bash console” in your home directory on the remote server. Of course it’s really running in your browser and is really just a very slick web app! Click on the shell and unzip the file from your remote command line:

PythonAnywhere bash shell
~ $ unzip mybooks.zip
... snip ...

Now we need to re-create our virtual environment with its dependencies. The commands are slightly different on the server (which supports multiple Python versions) but it results in the same basic environment – a virtual env with our requirements installed.

PythonAnywhere bash shell
~ $ virtualenv --python=python3.4 DJANGO
Running virtualenv with interpreter /usr/bin/python3.4
... snip ...
~ $ source DJANGO/bin/activate
(DJANGO) ~ $ pip install -r mybooks/requirements.txt
Collecting Django==1.8.3 (from -r mybooks/requirements.txt (line 1))
... snip ...

We also need a static directory with the static files collected – this looks just like it did on our development box.

PythonAnywhere bash shell
(DJANGO) ~ $ mkdir static
(DJANGO) ~ $ cd mybooks
(DJANGO) ~/mybooks $ ./manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings:
    /home/simeonfranklin/static
This will overwrite existing files!
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: yes
62 static files copied to '/home/simeonfranklin/static'.

Creating the web app

Now click on the Web tab (you can always hit the PythonAnywhere logo to get back to your dashboard) and hit the Add a new web app link on the left. The wizard will inform you that your domain name will be yourname.pythonanywhere.com. In my case my username is simeonfranklin. Click the next button and select Manual configuration link (do not click the Django option!) and select Python 3.4 on the next screen and hit the Next button one more time.

Whew – that was a lot of button clicking but now we’re almost there. We just need to configure the the virtual environment, the wsgi file, and the static files.

Click on the Virtualenv section and type the path to the virtual env. For me that looks like/home/simeonfranklin/DJANGO – you’ll need to substitute your own username.

pa-virtualenv

Be sure to click the check mark to save your entry.

We need to do the same process for the Static files section – entering /static/ for the URL and/home/simeonfranklin/static for the directory. This will allow the files we gathered with collectstatic to be served by PythonAnywhere’s HTTP server.

Finally we have to configure the wsgi file. Pronounced whiskey (at least when I pronounce it!) this is a protocol that allows us to embed an application in an HTTP server. Unfortunately we can’t just point at the wsgi file that was generated in our project.

Instead click on the WSGI configuration file link in the Code section. Delete all the code in the editor that loads and paste the following code in:

/var/www/<yourusername>_pythonanywhere_com_wsgi.py
import os, sys

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mybooks.settings")
sys.path.append(os.path.expanduser("~/mybooks"))
application = get_wsgi_application()

Hit the Save button, click back to the dashboard, click on the Web tab, and hit the big green Reload Button. A lot of button clicks I know – but hopefully you can now visit your domain and see success!

success

TIP


If you see an error page check out the error logs link under the Web console. You may be able to figure out what went wrong there.

 

Congratulations!

That’s all the development on this app I’m doing for the purposes of this tutorial – there’s only so much you can cram into one blog post.

You’re should feel proud of yourself if you’ve stuck with me this far! You’ve learned a lot about models, views, templates, urls and the admin.

There’s much more!

If you haven’t had enough and you feel ambitious try improving our application. You can learn all you need to know from the excellent Django documentation.

You might consider learning to add ratings with a ChoiceField on your Book model or add related models (like anAuthor model) or even a Reader model so you share the database with your friends. You could add thumbnails (with anImageField), add custom forms to replace or extend the admin and learn to write tests for all the code you’re writing. You could even integrate 3rdparty API’s into it (Google Reader and GoodReads come to mind).

Show me what you build! If you’re stuck you can ask me questions by tweeting @simeonfranklin. Send me your PythonAnywhere links to show off – I’d love to see your progress on your way to learning Django!