Django Tutorial: Getting Started with Django
By Simeon Franklin for Udemy
Interested in more than a beginner’s guide? Check out a full Django course.
Note: This post is part of our “Getting Started” series of free text tutorials on some of our most popular course topics.
To jump to a specific section, click the table of contents below:
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.
>>>
$
(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 pip
will 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.
-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.
DJANGO/Scripts/activate.bat
. See here for detailsdeactivate
command we can run. While the env is activated pip install
will run pip in this venv and typing python
will 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
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 ofurls
to Djangoviews
. 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.
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
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:
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 book
table 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.
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).
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:
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:
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.
(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:
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 WHERE
clause 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:
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
- This is the actual request line –
GET
is an HTTP verb and/
is the url requested. - 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:
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.
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:
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:
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:
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.py
module 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:
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!
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 protected] 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.
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 Book
model 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:
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:
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:
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
:
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:
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:
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.
(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:
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 DIRS
directive on line 4 in the snippet below. It should look like this:
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.
<!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.html
template.
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:
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!
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
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
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.
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:
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:
{% 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:
{% 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:
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:
Django==1.8.3
django-bootstrap3==6.1.0
pytz==2015.4
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
:
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.
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:
~ $ 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.
~ $ 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.
(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.
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:
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!
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!
Recommended Articles
Top courses in Django
Django students also learn
Empower your team. Lead the industry.
Get a subscription to a library of online courses and digital learning tools for your organization with Udemy Business.