Tangowithdjango2 Sample
Tangowithdjango2 Sample
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.
Find out what other people are saying about the book by clicking on this link to
search for this hashtag on Twitter:
#tangowithdjango
Also By These Authors
Books by Leif Azzopardi
How to Tango with Django 1.9/1.10/1.11
1. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Why Work with this Book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 What you will Learn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Technologies and Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Rango: Initial Design and Specification . . . . . . . . . . . . . . . . . . . . 5
1.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3. Django Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.1 Testing Your Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Creating Your Django Project . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.3 Creating a Django App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Creating a View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.5 Mapping URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.6 Basic Workflows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7. Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
7.1 Basic Workflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
7.2 Page and Category Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
This book seeks to complement the official Django Tutorials¹ and many of the other
excellent tutorials available online. By putting everything together in one place,
this book fills in many of the gaps in the official Django documentation by providing
an example-based, design-driven approach to learning the Django framework.
Furthermore, this book provides an introduction to many of the aspects required
to master web application development (such as HTML, CSS and JavaScript).
This book will save you time. On many occasions we’ve seen clever students
get stuck, spending hours trying to fight with Django and other aspects of web
development. More often than not, the problem was usually because a key piece
of information was not provided, or something was not made clear. While the
occasional blip might set you back 10-15 minutes, sometimes they can take hours
to resolve. We’ve tried to remove as many of these hurdles as possible. This will
mean you can get on with developing your application instead of getting stuck.
This book will lower the learning curve. Web application frameworks can save
you a lot of hassle and a lot of time. But that is only true if you know how to use
them in the first place! Often the learning curve is steep. This book tries to get you
going – and going fast – by explaining how all the pieces fit together and how to
build your web app logically.
¹https://docs.djangoproject.com/en/2.1/intro/tutorial01/
Overview 2
This book will improve your workflow. Using web application frameworks re-
quires you to pick up and run with particular design patterns – so you only have to
fill in certain pieces in certain places. After working with many students, we heard
lots of complaints about using web application frameworks – specifically about
how they take control away from the software engineer (i.e. inversion of control²).
To help you, we’ve created several workflows to focus your development process
so that you can regain that sense of control and build your web application in a
disciplined manner.
This book is not designed to be read. Whatever you do, do not read this book! It
is a hands-on guide to building web applications in Django. Reading is not doing.
To increase the value you gain from this experience, go through and develop the
application. When you code up the application, do not just cut and paste the code.
Type it in, think about what it does, then read the explanations we have provided. If
you still do not understand, then check out the Django documentation, go to Stack
Overflow³ or other helpful websites and fill in this gap in your knowledge. If you
are stuck, get in touch with us, so that we can improve the book – we’ve already
had contributions from numerous other readers!
At the end of each chapter, we have also included several exercises designed to
push you to apply what you have learnt during the chapter. To push you harder,
we’ve also included several open development challenges, which require you to
use many of the lessons from the previous chapters – but don’t worry, as we’ve also
included solutions and explanations on these, too!
Exercises
In each chapter, we have added several exercises to test your knowledge and
skill. Such exercises are denoted like this.
⁴https://en.wikipedia.org/wiki/Object-relational_mapping
⁵https://github.com/maxwelld90/tango_with_django_2_code
Overview 4
Through the course of this book, we will use various technologies and external
services including:
We’ve selected all of these technologies and services as they are either fundamental
to web development, and/or enable us to provide examples on how to integrate your
web application with CSS toolkits like Twitter Bootstrap, external services like those
provided by the Microsoft Bing Search API and deploy your application quickly and
easily with PythonAnywhere. Let’s get started!
⁶https://www.python.org
⁷https://pip.pypa.io/en/stable/
⁸https://www.djangoproject.com
⁹https://en.wikipedia.org/wiki/Unit_testing
¹⁰https://git-scm.com
¹¹https://github.com
¹²https://www.w3.org/html/
¹³https://www.w3.org/Style/CSS/
¹⁴https://www.javascript.com/
¹⁵https://jquery.com
¹⁶https://getbootstrap.com/
¹⁷https://docs.microsoft.com/en-gb/rest/api/cognitiveservices/bing-web-api-v7-reference
¹⁸https://www.pythonanywhere.com
Overview 5
The focus of this book will be to develop an application called Rango. As we develop
this application, it will cover the core components that need to be developed when
building any web application. To see a fully-functional version of the application,
you can visit our How to Tango with Django website¹⁹.
Design Brief
Let’s imagine that we would like to create a website called Rango that lets users
browse through user-defined categories to access various web pages. In Spanish,
the word rango²⁰ is used to mean “a league ranked by quality” or “a position in a social
hierarchy” – so we can imagine that at some point, we will want to rank the web
pages in Rango.
• For the main page of the Rango website, your client would like visitors to be
able to see:
– the five most viewed pages;
– the five most viewed (or rango’ed) categories; and
– some way for visitors to browse and/or search through categories.
• When a user views a category page, your client would like Rango to display:
– the category name, the number of visits, the number of likes, along with the list
of associated pages in that category (showing the page’s title, and linking
to its URL); and
– some search functionality (via the search API) to find other pages that can be
linked to this category.
• For a particular category, the client would like: the name of the category to be
recorded; the number of times each category page has been visited; and how many
users have clicked a “like” button (i.e. the page gets rango’ed, and voted up the
social hierarchy).
• Each category should be accessible via a readable URL – for example, /rango/books-
about-django/.
¹⁹http://www.tangowithdjango.com/
²⁰https://www.vocabulary.com/dictionary/es/rango
Overview 6
• Only registered users will be able to search and add pages to categories. Therefore,
visitors to the site should be able to register for an account.
Exercises
Before going any further, think about these specifications and draw up the
following design artefacts.
Try these exercises out before moving on – even if you aren’t familiar with
system architecture diagrams, wireframes or ER diagrams, how would you
explain and describe, formally, what you are going to build so that someone
else can understand it.
²¹https://en.wikipedia.org/wiki/Entity–relationship_model
Overview 7
N-Tier Architecture
The high-level architecture for most web applications is based around a 3-Tier
architecture. Rango will be a variant on this architecture as also interfaces with an
external service.
Given the different boxes within the high-level architecture, we need to start
making some decisions about the technologies that will be going into each box.
Since we are building a web application with Django, we will use the following
technologies for the following tiers.
• The client will be a web browser (such as Chrome, Firefox, and Safari) which will
render HTML/CSS pages, and any interpret JavaScript code.
• The middleware will be a Django application and will be dispatched through
Django’s built-in development web server while we develop (and then later a
web server like Nginx or Apache web server).
• The database will be the Python-based SQLite3 Database engine.
• The search API will be the Bing Search API.
For the most part, this book will focus on developing middleware. However, it
should be evident from the system architecture diagram that we will have to
interface with all the other components.
Overview 8
Wireframes
Wireframes are a great way to provide clients with some idea of what the appli-
cation is going to look like, and what features it will provide. They can vary from
hand-drawn sketches to exact mockups depending on the tools that you have at
your disposal. For our Rango application, we’d like to make the index page of the
site look like the screenshot below. Our category page is also shown below.
The index page with a categories search bar on the left, also showing the top five pages and top five
categories.
Overview 9
The category page showing the pages in the category (along with the number of views for the
category and each page).
From the specification, we have already identified two pages that our application
will present to the user at different points in time. To access each page, we will need
to describe URL mappings. Think of a URL mapping as the text a user would have to
enter into a browser’s address bar to access a given page. The basic URL mappings
for Rango are shown below.
As we build our application, we will probably need to create other URL mappings.
However, the mappings listed above are enough for us to get started. As we progress
through the book, we will flesh out how to construct all of these pages using the
Django framework and use its Model-View-Template²² design pattern.
Entity-Relationship Diagram
Now that we have a gist of the URL mappings and what the pages are going to
look like, we need to define the data model that will house the data for our web
application. Given the specification, it should be clear that we have at least two
entities: a category and a page. It should also be clear that a category can be associated
with many pages. We can formulate the following ER Diagram to describe this
simple data model.
Note that this specification is rather vague. A single page could, in theory, ex-
ist in one or more categories. Working with this assumption, we could model
the relationship between categories and pages as a many-to-many relationship²³.
However, this approach introduces several complexities.
We will make the simplifying assumption that one category contains many pages,
but one page is assigned to one category. This does not preclude that the same
page can be assigned to different categories – but the page would have to be entered
twice. While this is not ideal, it does reduce the complexity of the models.
²²https://docs.djangoproject.com/en/2.1/
²³https://en.wikipedia.org/wiki/Many-to-many_(data_model)
Overview 11
Take Note!
Get into the habit of noting down any working assumptions that you make,
just like the one-to-many relationship assumption that we assume above.
You never know when they may come back to bite you later on! By noting
them down, this means you can communicate it with your development
team and make sure that the assumption is sensible, and that they are happy
to proceed under such an assumption.
With this assumption, we can produce a series of tables that describe each entity
in more detail. The tables contain information on what fields are contained within
each entity. We use Django ModelField types to define the type of each field (i.e.
IntegerField, CharField, URLField or ForeignKey). Note that in Django primary keys are
implicit such that Django adds an id to each Model, but we will talk more about that
later in the Models and Databases chapter.
We will also have a model for the User so that they can register and login. We have
not shown it here but shall introduce it later in the book when we discuss user
authentication. In subsequent chapters, we will see how to instantiate these models
in Django, and how we can use the built-in ORM to interact with the database.
Overview 12
1.5 Summary
These high-level design and specifications will serve as a useful reference point
when building our web application. While we will be focusing on using specific
technologies, these steps are common to most database-driven websites. It’s a
good idea to become familiar with reading and producing such specifications and
designs so that you can communicate your designs and ideas with others. Here, we
will be focusing on using Django and the related technologies to implement this
specification.
Furthermore, cutting and pasting Python code is asking for trouble. Whites-
pace can end up being interpreted as spaces, tabs or a mixture of spaces and
tabs. This will lead to all sorts of weird errors, and not necessarily indent
errors. If you do cut and paste code be wary of this. Pay particular attention
to this with regards to tabs and spaces – mixing these up will likely lead to a
TabError.
Most code editors will show the ‘hidden characters’, which in turn will show
whether whitespace is either a tab or a space. If you have this option, turn it
on. You will likely save yourself a lot of confusion.
Overview 13
Representing Commands
As you work through this book, you’ll encounter lots of text that will be en-
tered into your computer’s terminal or Command Prompt. Snippets starting
with a dollar sign ($) denotes a command that must be entered – the remain-
der of the line is the command. In a UNIX terminal, the dollar represents a
separator between the prompt and the command that you enter.
david@seram:~ $ exit
Whenever you see >>>, the following is a command that should be entered
into the interactive Python interpreter. This is launched by issuing $ python.
See what we did there? Once inside the Python interpreter, you can exit it by
typing quit() or exit().
2. Getting Ready to Tango
Before we start coding, it’s really important that we set your development environ-
ment up correctly so that you can Tango with Django with ease. You’ll need to make
sure that you have all of the necessary components installed on your computer, and
that they are configured correctly. This chapter outlines the six key components
you’ll need to be aware of, setup and use. These are:
We also touch on the merits of unit testing, discussing how being able to test your
implementation at different points can help keep you on track.
If you already have Python 3 and Django 2 installed on your computer and are
familiar with the technologies listed above, you can skip straight ahead to the
Django Basics chapter. If you are not familiar with some or all of the technologies
listed, we provide an overview of each below. These go hand in hand with later
supplementary chapter that provides a series of pointers on how to set the different
components up, if you need help doing so.
¹https://en.wikipedia.org/wiki/Terminal_emulator
²https://en.wikipedia.org/wiki/Cmd.exe
Getting Ready to Tango 15
From experience, we can also say with confidence that as you set your
environment up, it’s a good idea to note down the steps that you took. You
will probably need that workflow again one day – maybe you will purchase
a new computer, or be asked to help a friend set their environment up, too.
Don’t think short-term, think long-term!
2.1 Python 3
To work with Tango with Django, we require you to have installed on your computer
a copy of the Python 3 programming language. A Python version of 3.5 or greater
should work fine with Django 2.0, 2.1 and 2.2 – although the official Django website
recommends that you have the most recent version of Python installed. As such, we
recommend you install Python 3.7. At the time of writing, the most recent release is
Python 3.7.2. If you’re not sure how to install Python and would like some assistance,
have a look at our quick guide on how to install Python.
You must however make sure you are using at least Python version 3.5.
Version 3.4 and below are incompatible with these releases of Django.
These guides will help you familiarise yourself with the basics of Python so
you can start developing with Django. Note you don’t need to be an expert in
Python to work with Django – Python is straightforward to use, and you can
pick it up as you go, especially if you already know the ins and outs of at least
one other programming language.
³https://twitter.com/tangowithdjango
⁴https://github.com/leifos/tango_with_django_2/issues
⁵https://www.tangowithdjango.com
⁶https://docs.python.org/3/tutorial/
⁷https://greenteapress.com/wp/think-python-2e/
⁸https://www.stavros.io/tutorials/python/
⁹https://www.coursera.org/course/programming1
Getting Ready to Tango 17
You’ll want to create a virtual environment using Python 3 for your Rango develop-
ment environment. Call the environment rangoenv. If you are unsure as to how to do
this, go to the supplementary chapter detailing how to set up virtual environments
before continuing. If you do choose to use a virtual environment, remember to
activate the virtual environment by issuing the following command.
$ workon rangoenv
From then on, all of your prompts with the terminal or Command Prompt will
precede with the name of your virtual environment to remind you that it is switched
on. Check out the following example to know what we are discussing.
Getting Ready to Tango 18
$ workon rangoenv
(rangoenv) $ pip install django==2.1.5
...
(rangoenv) $ deactivate
$
The penultimate line of the example above demonstrates how to switch your virtual
environment off after you have finished with it – note the lack of (rangoenv) before
the prompt. Again, refer to the system setup chapter in the appendices of this book
for more information on how to setup and use virtual environments.
Going hand in hand with virtual environments, we’ll also be making use of the
Python package manager, pip, to install several different Python software packages
– including Django – to our development environment. Specifically, we’ll need to
install two packages: Django 2 and Pillow. Pillow is a Python package providing
support for handling image files (e.g. .jpg and .png files), something we’ll be doing
later in this tutorial.
A package manager, whether for Python, your operating system¹⁰ or some other
environment¹¹, is a software tool that automates the process of installing, upgrad-
ing, configuring and removing packages – that is, a package of software which you
can use on your computer that provides some functionality. This is opposed to
downloading, installing and maintaining software manually.
Maintaining Python packages is pretty painful. Most packages often have dependen-
cies – additional packages that are required for your package to work! This can get
very complex very quickly. A package manager handles all of this for you, along
with issues such as conflicts regarding different versions of a package. Luckily, pip
handles all this for you.
Try and run the command $ pip to execute the package manager. Make sure you
do this with your virtual environment acivated. Globally, you may have to use the
¹⁰https://en.wikipedia.org/wiki/Advanced_Packaging_Tool
¹¹https://docs.npmjs.com/cli/install
Getting Ready to Tango 19
command pip3. If these don’t work, you have a setup issue – refer to our pip setup
guide for help.
With your virtual environment switched on, execute the following two commands
to install Django and Pillow.
Installing these two packages will be sufficient to get you started. As you work
through the tutorial, there will be a couple more packages that we will require. We’ll
tell you to install them as we require them. For now, you’re good to go.
If you receive this error, try installing Pillow without JPEG support enabled,
with the following command.
While you obviously will have a lack of support for handling JPEG im-
ages, Pillow should then install without problem. Getting Pillow installed
is enough for you to get started with this tutorial. For further information,
check out the Pillow documentation¹².
¹²https://pillow.readthedocs.io/en/stable/installation.html
Getting Ready to Tango 20
While not necessary, a good Python-based IDE can be very helpful to you during
the development process. Several exist, with perhaps PyCharm¹³ by JetBrains and
PyDev (a plugin of the Eclipse IDE¹⁴) standing out as popular choices. The Python
Wiki¹⁵ provides an up-to-date list of Python IDEs.
Research which one is right for you, and be aware that some may require you to
purchase a licence. Ideally, you’ll want to select an IDE that supports integration
with Django. Of course, if you prefer not to use an IDE, using a simple text editor
like Sublime Text¹⁶, TextMate¹⁷ or Atom¹⁸ will do just fine. Many modern text editors
support Python syntax highlighting, which makes things much easier!
We should also point out that when you develop code, you should always house
your code within a version-controlled repository such as SVN²⁰ or Git²¹. We won’t
be explaining this right now so that we can get stuck into developing an application
in Django. We have however written a chapter providing a crash course on Git for
your reference that you can refer to later on. We highly recommend that you set
up a Git repository for your projects.
¹³http://www.jetbrains.com/pycharm/
¹⁴http://www.eclipse.org/downloads/
¹⁵http://wiki.python.org/moin/IntegratedDevelopmentEnvironments
¹⁶https://www.sublimetext.com/
¹⁷https://macromates.com/
¹⁸https://atom.io/
¹⁹https://www.jetbrains.com/help/pycharm/2016.1/creating-and-running-your-first-django-project.html
²⁰http://subversion.tigris.org/
²¹http://git-scm.com/
Getting Ready to Tango 21
Exercises
To get comfortable with your environment, try out the following exercises.
• Get up to speed with Python if you’re new to the language. Try out one
or more of the tutorials we listed earlier.
• Install Python 3.7. Make sure pip3 (or pip within your virtual environ-
ment) is also installed and works on your computer.
• Play around with your command line interface (CLI), whether it be the
Command Prompt (Windows) or a terminal (macOS, Linux, UNIX, etc.).
• Create a new virtual environment using Python 3.7. This is optional, but
we strongly encourage you to use virtual environments.
• Within your environment, install Django 2 and Pillow 5.4.1.
• Set up an account on a Git repository site like GitHub²² or BitBucket²³ if
you haven’t already done so.
• Download and set up an IDE like PyCharm²⁴, or set up your favourite
text editor for working with Python files.
As previously stated, we’ve made the code for the application available on
our GitHub repository²⁵.
• If you spot any errors or problems in the book, please let us know by
making an issue on GitHub²⁶.
• If you have any problems with the exercises, you can check out the
repository to see how we completed them.
• If you spot any errors or problems with the unit tests and/or sample
solutions we provide, you can also let us know by raising an issue in the
relevant repository²⁷.
²²https://github.com/
²³https://bitbucket.org/
²⁴https://www.jetbrains.com/pycharm/
²⁵https://github.com/maxwelld90/tango_with_django_2_code
²⁶https://github.com/leifos/tango_with_django_2/issues
²⁷https://github.com/maxwelld90/tango_with_django_2_code/issues
Getting Ready to Tango 22
As you work through your implementation of the requirements for the Rango app,
we want you to have the confidence to know that what you are coding up is correct.
We can’t physically sit next to you, so we’ve gone and done the next best thing –
we’ve implemented a series of different tests that you can run against your
codebase to see what’s correct, and what can be improved. We’ve got tests for
the core chapters (up to Chapter 10).
These are available from our sample codebase repository, available on GitHub²⁸.
The progress_tests directory on this repository contains a number of different
Python modules, each containing a series of different test modules you can run
against your Rango implementation. Note that they are for individual chapters
– for example, you should run the module tests_chapter3.py against your imple-
mentation after completion of Chapter 3, but before starting Chapter 4. Note that
not every chapter will have tests at the end of it – some portions of the book are
very difficult to write automated tests for without resorting to having to download
sizable support libraries.
We check the basic functionality that should be working up to the point you are
testing at. We also check what is returned from the server when a particular URL is
accessed – and if the response doesn’t match exactly what we requested in the book,
the test will fail. This might seem overly harsh, but we want to drill into your head
that you must satisfy requirements exactly as they are laid out – no deviation is acceptable.
This also drills into your head the idea of test-driven development, something that we
outline at the start of the testing chapter.
²⁸https://github.com/maxwelld90/tango_with_django_2_code/tree/master/progress_tests
Getting Ready to Tango 23
How do you run the tests, though? This step-by-step process demonstrates the
basic process on what you have to do. We will assume that you want to run the tests
for Chapter 3, Django Basics.
You will also need to ensure that when these tests run, your rangoenv virtual
environment is active.
Once the tests all complete, you should see OK. This means they all passed! If you
don’t see OK, something failed – look through the output of the tests to see what
test failed, and why. Sometimes, you might have missed something which causes
an exception to be raised before the test can be carried out. In instances like this,
you’ll need to look at what is expected, and go back and fill it in. You can tweak your
code and re-run the tests to see if they then pass.
Test Updates
Over time, we may release updates to the unit test files if an issue is raised,
or we decide to add extra tests if there is sufficent demand for us to do so.
You should keep an eye on the repository for any changes – if you cloned it
to your computer, you can simply git pull to retrieve updates.
3. Django Basics
Let’s get started with Django! In this chapter, we’ll be giving you an overview of the
creation process. You’ll be setting up a new project and a new web application. By
the end of this chapter, you will have a simple Django powered website running!
Let’s start by checking that your Python and Django installations are correct for this
tutorial. To do this, open a new terminal/Command Prompt window, and activate
your rangoenv virtual environment.
Once activated, issue the following command. The output will tell what Python
version you have.
$ python --version
The response should be something like 3.7.2, but any 3.5+ versions of Python
should work fine. If you need to upgrade or install Python, go to the chapter on
setting up your system.
If you are using a virtual environment, then ensure that you have activated it – if
you don’t remember how then have a look at our chapter on virtual environments.
After verifying your Python installation, check your Django installation. In your
terminal window, run the Python interpreter by issuing the following command.
Django Basics 26
$ python
Python 3.7.2 (default, Mar 30 2019, 05:40:15)
[Clang 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
All going well you should see the correct version of Django, and then can use exit()
to leave the Python interpreter. If import django fails to import, then check that you
are in your virtual environment, and check what packages are installed with pip
list at the terminal window.
If you have problems with installing the packages or have a different version
installed, go to System Setup chapter or consult the Django Documentation on
Installing Django¹.
To create a new Django Project, go to your workspace directory, and issue the
following command:
If you don’t have a workspace directory, we recommend that you create one. This
means that you can house your Django projects (and other code projects) within
this directory. It keeps things organised, without you placing directories contain-
ing code in random places, such as your Desktop directory!
We will refer to your workspace directory throughout this book as <workspace>. You
will have to substitute this with the path to your workspace directory. For example,
¹https://docs.djangoproject.com/en/2.1/topics/install/
Django Basics 27
we recommend that you create a workspace directory in your home folder. The path
/Users/maxwelld90/Workspace/ would then constitute as a valid directory for the user
maxwelld90 on a Mac.
python c:\Users\maxwelld90\.virtualenvs\rangoenv\bin\django-admin.py
startproject tango_with_django_project
as suggested on StackOverflow². Note that the path will likely vary on your
own computer.
This command will invoke the django-admin.py script, which will set up a new
Django project called tango_with_django_project for you. Naming conventions dic-
tate that we would typically append _project to the end of our Django project
directories so we know exactly what they contain – but naming this is really entirely
up to you.
You’ll now notice within your workspace is a directory set to the name of your new
project, tango_with_django_project. Within this newly created directory, you should
see two items:
In the project directory, you will see there is a file called manage.py. We will be
calling this script time and time again as we develop our project. It provides you
with a series of commands you can run to maintain your Django project. For
example, manage.py allows you to run the built-in Django development server, test
your application, and run various database commands. We will be using the script
for virtually every Django that command we want to run.
Note that if you run python manage.py help you can see the list of commands
available.
You can try using the manage.py script now, by issuing the following command.
Executing this command will launch Python, and instruct Django to initiate its
lightweight development server. You should see the output in your terminal win-
dow similar to the example shown below:
³https://docs.djangoproject.com/en/2.1/ref/django-admin/#django-admin-py-and-manage-py
Django Basics 29
In the output, you can see several things. First, there are no issues that stop the
application from running. However, you will notice that a warning is raised –
unapplied migration(s). We will talk about this in more detail when we set up our
database, but for now we can ignore it. Third, and most importantly, you can see
that a URL has been specified: http://127.0.0.1:8000/, which is the address that
the Django development server is running at.
Now open up your web browser and enter the URL mentioned above into your
browser’s address bar – http://127.0.0.1:8000/⁴. You should see a webpage similar
to the one shown below.
⁴http://127.0.0.1:8000/
Django Basics 30
A screenshot of the initial Django page you will see when running the development server for the
first time.
You can stop the development server at any time by pushing CTRL + C in your
terminal or Command Prompt window. This applies to both Macs and PCs! If you
wish to run the development server on a different port or allow users from other
machines to access it, you can do so by supplying optional arguments. Consider
the following command.
Executing this command will force the development server to respond to incoming
requests on TCP port 5555. You will need to replace <your_machines_ip_address> with
your computer’s IP address or 127.0.0.1.
When setting ports, it is unlikely that you will be able to use TCP port 80 or 8080
as these are traditionally reserved for HTTP traffic. Also, any port below 1024 is
considered to be privileged⁵ by your operating system.
While you won’t be using the lightweight development server to deploy your ap-
plication, it’s nice to be able to demo your application on another machine in your
network. Running the server with your machine’s IP address will enable others to
enter in http://<your_machines_ip_address>:<port>/ and view your web application.
Of course, this will depend on how your network is configured. There may be
proxy servers or firewalls in the way that would need to be configured before
this would work. Check with the administrator of the network you are using if
you can’t view the development server remotely.
A Django application exists to perform a particular task. You need to create specific
apps that are responsible for providing your site with particular kinds of function-
ality. For example, we could imagine that a project might consist of several apps
including a polling app, a registration app, and a specific content related app. In
another project, we may wish to re-use the polling and registration apps, and so
can include them in other projects. We will talk about this later. For now, we are
going to create the app for the Rango app.
To achieve this, you need to make sure you’re in your Django project’s directory (e.g.
<workspace>/tango_with_django_project). From there, run the following command.
The startapp command creates a new directory within your project’s root. Unsur-
prisingly, this directory is called rango – and contained within it are several Python
scripts:
views.py and models.py are the two files you will use for any given app and form
part of the main architectural design pattern employed by Django, i.e. the Model-
View-Template pattern. You can check out the official Django documentation⁶ to see
how models, views and templates relate to each other in more detail.
Before you can get started with creating your models and views, you must first tell
your Django project about your new app’s existence. To do this, you need to modify
the settings.py file, contained within your project’s configuration directory. Open
the file and find the INSTALLED_APPS list. Add the rango app to the end of the tuple,
which should then look like the following example.
⁶https://docs.djangoproject.com/en/2.1/intro/overview/
Django Basics 33
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rango',
]
Verify that Django picked up your new app by running the development server
again. If you can start the server without errors, your app was picked up and you
will be ready to proceed to the next step.
With our Rango app created, let’s now create a simple view. Views handle a request
that comes from the client, executes some code, and provides a response to the client.
To fulfil the request, it may contact other services or query for data from other
sources. The job of a view is to collate and package the data required to handle
the request, as we outlined above. For our first view, given a request, the view will
simply send some text back to the client. For the time being, we won’t concern
ourselves about using models (i.e. getting data from other sources) or templates
(i.e. which help us package our responses nicely).
In your editor, open the file views.py, located within your newly created rango app
directory. Remove the comment # Create your views here., but keep the following
line line – you’ll use it later on!
You can now add in the following code underneath the aforementioned import
statement.
Django Basics 34
def index(request):
return HttpResponse("Rango says hey there partner!")
Breaking down the three lines of code, we observe the following points about
creating this simple view.
With the view created, you’re only part of the way to allowing a user to access it. For
a user to see your view, you must map a Uniform Resource Locator (URL)⁹ to the
view.
Next, change the urlpatterns list to look like the example below.
⁷https://docs.djangoproject.com/en/2.1/ref/request-response/#django.http.HttpResponse
⁸https://docs.djangoproject.com/en/2.1/ref/request-response/#django.http.HttpRequest
⁹http://en.wikipedia.org/wiki/Uniform_resource_locator
Django Basics 35
urlpatterns = [
path('', views.index, name='index'),
path('admin/', admin.site.urls),
]
This maps the basic URL to the index view in the rango app. Run the development
server (e.g. python manage.py runserver) and visit http://127.0.0.1:8000 or whatever
address your development server is running on. You’ll then see the rendered
output of the index view.
Rather than directly mapping URLs from the project to the app, we can make our
app more modular (and thus re-usable) by changing how we route the incoming
URL to a view. To do this, we first need to modify the project’s urls.py and have it
point to the app to handle any specific Rango app requests. We then need to specify
how Rango deals with such requests.
First, open the project’s urls.py file which is located inside your project configura-
tion directory. As a relative path from your workspace directory, this would be the
file <workspace>/tango_with_django_project/tango_with_django_project/urls.py. Up-
date module to look like the example below.
urlpatterns = [
path('', views.index, name='index'),
path('rango/', include('rango.urls')),
# The above maps any URLs starting with rango/ to be handled by rango.
path('admin/', admin.site.urls),
]
You will see that the urlpatterns is a Python list, which is expected by the Django
framework. The added mapping looks for URL strings that match the patterns
Django Basics 36
rango/. When a match is made, the remainder of the URL string is then passed onto
and handled by rango.urls through the use of the include() function from within
the django.urls package.
An illustration of a URL, represented as a chain, showing how different parts of the URL following
the domain are the responsibility of different url.py files.
Think of this as a chain that processes the URL string – as illustrated in the URL
chain figure. In this chain, the domain is stripped out and the remainder of the URL
string (rango/) is passed on to tango_with_django project, where it finds a match and
strips away rango/, leaving an empty string to be passed on to the app rango for it to
handle.
Consequently, we need to create a new file called urls.py in the rango app directory,
to handle the remaining URL string (and map the empty string to the index view):
Django Basics 37
app_name = 'rango'
urlpatterns = [
path('', views.index, name='index'),
]
This code imports the relevant Django machinery for URL mappings and the views
module from rango. This allows us to call the function url and point to the index
view for the mapping in urlpatterns.
When we talk about URL strings, we assume that the host portion of a given URL
has already been stripped away. The host portion of a URL denotes the host address
or domain name that maps to the webserver, such as http://127.0.0.1:8000 or
http://www.tangowithdjango.com. Stripping the host portion away means that the
Django machinery needs to only handle the remainder of the URL string. For
example, given the URL http://127.0.0.1:8000/rango/about/, Django will handle the
/rango/about/ part of the URL string.
The URL mapping we have created above calls Django’s path() function, where the
first parameter is the string to match. As we have used an empty string '', Django
will then only find a match if there is nothing after http://127.0.0.1:8000/. The
second parameter tells Django what view to call if the pattern '' is matched. In this
case, views.index() will be called. The third and optional parameter is called name. It
provides a convenient way to reference the view, and by naming our URL mappings
we can employ reverse URL matching. That is we can reference the URL mapping by
name rather than by the URL. Later, we will explain and show why this is incredibly
useful. It can save you time and hassle as your application becomes more complex.
This will go hand-in-hand with the app_name variable we’ve also placed in the new
urls.py module.
A screenshot of a web browser displaying our first Django powered webpage. Hello, Rango!
Within each app, you will create several URL mappings. The initial mapping is quite
simple, but as we progress through the book we will create more sophisticated and
parameterised URL mappings.
It’s also important to have a good understanding of how URLs are handled in
Django. It may seem a bit confusing right now, but as we progress through the book,
we will be creating more and more URL mappings, so you’ll soon be a pro. To find
out more about them, check out the official Django documentation on URLs¹⁰ for
further details and further examples.
If you are using version control, now is a good time to commit the changes you have
made to your workspace. Refer to the chapter providing a crash course on Git if you
can’t remember the commands and steps involved in doing this.
¹⁰https://docs.djangoproject.com/en/2.1/topics/http/urls/
Django Basics 39
What you’ve just learnt in this chapter can be succinctly summarised into a list of
actions. Here, we provide these lists for the two distinct tasks you have performed.
You can use this section for a quick reference if you need to remind yourself about
particular actions later on.
Exercises
Now that you have got Django and your new app up and running, try out the
following exercises to reinforce what you’ve learnt. Getting to this stage is a
significant landmark in working with Django. Creating views and mapping
URLs to views is the first step towards developing more complex and usable
web applications.
• Revise the procedure and make sure you follow how the URLs are
mapped to views.
• Create a new view method called about which returns the following
HttpResponse: 'Rango says here is the about page.'
• Map this view to /rango/about/. For this step, you’ll only need to edit the
urls.py of the Rango app. Remember the /rango/ part is handled by the
projects urls.py. This new mapping will have a name of about.
• Revise the HttpResponse in the index() view to include a hyperlink (or
anchor) to the about page. This is part of the response – meaning that
it also lives within the same string that says Rango says hey there
partner!.
• Include a link back to the index page in the about view’s response.
• Now that you have started the book, you can follow us on Twitter if you
have it – our handle is @tangowithdjango¹¹. Let us know how you are
getting on!
¹¹https://twitter.com/tangowithdjango
Django Basics 41
Hints
If you’re struggling to get the exercises done, the following hints will provide
you with some inspiration on how to progress.
¹²https://en.wikipedia.org/wiki/Hyperlink
¹³https://docs.djangoproject.com/en/2.1/intro/tutorial01/
4. Templates and Media Files
In this chapter, we’ll be introducing the Django template engine, as well as showing
you how to serve both static files and media files. Rather than crafting each page
by returning strings as our response, we can use templates to provide the skeleton
structure of the page from a separate file. From the view that generates the re-
sponse, we can provide the template with the necessary data to render that page in
its entirety. To incorporate JavaScript and CSS (along with images and other media
content) we will use the machinery provided by Django to include and dispatch
such files to clients, which will in turn allow us to provide added functionality (in
the case of JavaScript), or to provide styling to our pages.
Up until this point, we have only connected a URL mapping to a view. However,
the Django framework is based around the Model-View-Template architecture. In
this section, we will go through the mechanics of how Templates work with Views.
In subsequent chapters, we will put these together with Models.
Why templates? The layout from page to page within a website is often the same.
Whether you see a common header or footer on a website’s pages, the repetition
of page layouts¹ aids users with navigation and reinforces a sense of continuity.
Django provides templates² to make it easier for developers to achieve this design
goal, as well as separating application logic (code within your views) from presen-
tational concerns (look and feel of your app).
In this chapter, you’ll create a basic template that will be used to generate an HTML
page. This will then be dispatched via a Django view. In the chapter concerning
databases and models, we will take this a step further by using templates in
conjunction with models to dispatch dynamically generated data.
¹http://www.techrepublic.com/blog/web-designer/effective-design-principles-for-web-designers-repetition/
²https://docs.djangoproject.com/en/2.1/ref/templates/
Templates and Media Files 43
To get templates up and running with your Django app, you’ll need to create two
directories in which template files are stored.
To tell the Django project where templates will be stored, open your project’s
settings.py file. Next, locate the TEMPLATES data structure. By default, when you
create a new Django project, it will look like the following.
Templates and Media Files 44
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
What we need to do is tell Django where our templates will be stored by modifying
the DIRS list, which is set to an empty list by default. Change the dictionary key/value
pair to look like the following.
'DIRS': ['<workspace>/tango_with_django_project/templates']
Note that you are required to use absolute paths to locate the templates directory. If
you are collaborating with team members or working on different computers, then
this will become a problem. You’ll have different usernames and different drive
structures, meaning the paths to the <workspace> directory will be different. One
solution would be to add the path for each different configuration. For example:
'DIRS': [ '/Users/leifos/templates',
'/Users/maxwelld90/templates',
'/Users/davidm/templates', ]
However, there are several problems with this. First, you have to add in the path for
each setting, each time. Second, if you are running the app on different operating
systems the backslashes have to be constructed differently.
Templates and Media Files 45
Dynamic Paths
A better solution is to make use of built-in Python functions to work out the path of
your templates directory automatically. This way, an absolute path can be obtained
regardless of where you place your Django project’s code. This, in turn, means that
your project becomes more portable.
At the top of your settings.py file, there is a variable called BASE_DIR. This vari-
able stores the path to the directory in which your project’s settings.py module
is contained. This is obtained by using the special Python __file__ attribute,
which is set to the path of your settings module⁶. Using this as a parameter to
os.path.abspath() guarantees the absolute path to the settings.py module. The call
to os.path.dirname() then provides the reference to the absolute path of the direc-
tory containing the settings.py module. Calling os.path.dirname() again removes
another directory layer, so that BASE_DIR then points to your project directory, or
<workspace>/tango_with_django_project/. If you are curious, you can see how this
works by adding the following lines to your settings.py file.
print(__file__)
print(os.path.dirname(__file__))
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
Having access to the value of BASE_DIR makes it easy for you to reference other
aspects of your Django project. Using the BASE_DIR variable, we can now create a
new variable called TEMPLATE_DIR that will reference your new templates directory.
We can make use of the os.path.join() function to join up multiple paths, leading
³http://en.wikipedia.org/wiki/Hard_coding
⁴http://sourcemaking.com/antipatterns
⁵http://en.wikipedia.org/wiki/Software_portability
⁶http://stackoverflow.com/a/9271479
Templates and Media Files 46
to a variable definition like the example below. Make sure you put this underneath
the definition of BASE_DIR!
Here, we make use of the os.path.join() function to join together (or concatenate)
the value of the BASE_DIR variable, and the string 'templates'. Upon completion,
this concatenation yields <workspace>/tango_with_django_project/templates/. From
here, we can then use our new TEMPLATE_DIR variable to replace the hard-coded path
we defined earlier in TEMPLATES. Update the DIRS key/value pairing to look like the
following code snippet.
'DIRS': [TEMPLATE_DIR, ]
Why TEMPLATE_DIR?
You’ve created a new variable called TEMPLATE_DIR at the top of your
settings.py file because it’s easier to access should you ever need to change
it. For more complex Django projects, the DIRS list allows you to specify more
than one template directory to draw templates from. For this book however,
one location is sufficient to get everything working.
Concatenating Paths
When concatenating system paths together, always use os.path.join().
Using this built-in function ensures that the correct path separators are
used. On a UNIX operating system (or derivative of), forward slashes (/)
would be used to separate directories, whereas a Windows operating system
would use backward slashes (\). If you manually append slashes to paths,
you may end up with path errors when attempting to run your code on a
different operating system, thus reducing your project’s portability.
Templates and Media Files 47
Adding a Template
With your template directory and path now set up, create a file called index.html
and place it in the templates/rango/ directory. Within this new file, add the following
HTML markup and Django template code.
<!DOCTYPE html>
<html>
<head>
<title>Rango</title>
</head>
<body>
<h1>Rango says...</h1>
<div>
hey there partner! <br />
<strong>{{ boldmessage }}</strong><br />
</div>
<div>
<a href="/rango/about/">About</a><br />
</div>
</body>
</html>
From this HTML code, it should be clear that a simple HTML page is going to be
generated that greets a user with a hello world message. You might also notice some
non-HTML in the form of {{ boldmessage }}. This is a Django template variable. We
can set values to these variables so they are replaced with whatever we want when
the template is rendered. We’ll get to that in a moment.
To use this template, we need to reconfigure the index() view that we created
earlier. Instead of dispatching a simple response, we will change the view to
dispatch our template.
In rango/views.py, check to see if the following import statement exists at the top of
the file. Django should have added it for you when you created the Rango app. If it
is not present, add it, but you should have kept it from earlier.
Templates and Media Files 48
You can then update the index() view function as follows. Check out the inline
commentary to see what each line does.
def index(request):
# Construct a dictionary to pass to the template engine as its context.
# Note the key boldmessage matches to {{ boldmessage }} in the template!
context_dict = {'boldmessage': 'Crunchy, creamy, cookie, candy, cupcake!'}
First, we construct a dictionary of key/value pairs that we want to use within the
template. Then, we call the render() helper function. This function takes as input
the user’s request, the template filename, and the context dictionary. The render()
function will take this data and mash it together with the template to produce a
complete HTML page that is returned with a HttpResponse. This response is then
returned and dispatched to the user’s web browser.
Now that you have updated the view to employ the use of your template, start the
Django development server and visit http://127.0.0.1:8000/rango/. You should see
your simple HTML template rendered in your browser – and it should look just like
the example screenshot shown below.
Templates and Media Files 49
If you don’t, read the error message presented to see what the problem is, and
then double-check all the changes that you have made. One of the most common
issues people have with templates is that the path is set incorrectly in settings.py.
Sometimes it’s worth adding a print statement to settings.py to report the BASE_DIR
and TEMPLATE_DIR to make sure everything is correct.
This example demonstrates how to use templates within your views. However, we
have only touched on a fraction of the functionality provided by the Django tem-
plating engine. We will use templates in more sophisticated ways as you progress
through this book. In the meantime, you can find out more about templates from
the official Django documentation⁷.
What you should see when your first template is working correctly. Note the bold text – Crunchy,
creamy, cookie, candy, cupcake! – which originates from the view, but is rendered in the template.
⁷https://docs.djangoproject.com/en/2.1/ref/templates/
Templates and Media Files 50
While you’ve got templates working, your Rango app is admittedly looking a bit
plain right now – there’s no styling or imagery. We can add references to other
files in our HTML template such as Cascading Style Sheets (CSS)⁸, JavaScript⁹ and
images to improve the presentation. These are called static files, because they are
not generated dynamically by a web server; they are simply sent as is to a client’s
web browser. This section shows you how to set Django up to serve static files, and
shows you how to include an image within your simple template.
To start, you will need to set up a directory in which static media files are stored. In
your project directory (e.g. <workspace>/tango_with_django_project/), create a new
directory called static and a new directory called images inside static. Check that
the new static directory is at the same level as the templates directory you created
earlier in this chapter.
Next, place an image inside the images directory. As shown in below, we chose a
picture of the chameleon Rango¹⁰ – a fitting mascot, if ever there was one.
⁸http://en.wikipedia.org/wiki/Cascading_Style_Sheets
⁹https://en.wikipedia.org/wiki/JavaScript
¹⁰http://www.imdb.com/title/tt1192628/
Templates and Media Files 51
Just like the templates directory we created earlier, we need to tell Django about
our new static directory. To do this, we once again need to edit our project’s
settings.py module. Within this file, we need to add a new variable pointing to our
static directory, and a data structure that Django can parse to work out where our
new directory is.
First of all, create a variable called STATIC_DIR at the top of settings.py, preferably
underneath BASE_DIR and TEMPLATES_DIR to keep your paths all in the same place.
STATIC_DIR should make use of the same os.path.join trick – but point to static
this time around, just as shown below.
For this book, we’re only going to be using a single location to store our project’s
static files – the path defined in STATIC_DIR. As such, we can simply set up the list
STATICFILES_DIRS with the following.
STATICFILES_DIRS = [STATIC_DIR, ]
Finally, check that the STATIC_URL variable is defined within your settings.py mod-
ule. If it has not been defined, then add it in, as shown below. Note that this variable
by default appears close to the end of the module, so you may have to scroll down
to the bottom of settings.py to find it (if it’s already there).
STATIC_URL = '/static/'
With everything required now entered, what does it all mean? Put simply, the
first two variables STATIC_DIR and STATICFILES_DIRS refers to the locations on your
computer where static files are stored. The final variable STATIC_URL then allows us
to specify the URL with which static files can be accessed when we run our Django
development server. For example, with STATIC_URL set to /static/, we would be
able to access static content at http://127.0.0.1:8000/static/. Think of the first two
variables as server-side locations, with the third variable as the location with which clients
can access static content.
Templates and Media Files 53
Try to figure this out before you move on! It’ll help you understand how
to interpret static URLs. The answer is coming up if you are struggling to
figure it out.
If you haven’t managed to figure out where the image should be accessible from,
point your web browser to http://127.0.0.1:8000/static/images/rango.jpg.
Now that you have your Django project set up to handle static files, you can now
make use of these files within your templates to improve their appearance and add
additional functionality.
¹¹https://docs.djangoproject.com/en/2.1/ref/settings/#std:setting-STATIC_URL
¹²https://docs.djangoproject.com/en/2.1/howto/static-files/deployment/
Templates and Media Files 54
To demonstrate how to include static files, open up the index.html templates you
created earlier, located in the <workspace>/templates/rango/ directory. Modify the
HTML source code as follows. The two lines that we add are shown with an HTML
comment next to them for easy identification.
<!DOCTYPE html>
<html>
<head>
<title>Rango</title>
</head>
<body>
<h1>Rango says...</h1>
<div>
hey there partner! <br />
<strong>{{ boldmessage }}</strong><br />
</div>
<div>
<a href="/rango/about/">About</a><br />
<img src="{% static 'images/rango.jpg' %}"
alt="Picture of Rango" /> <!-- New line -->
</div>
</body>
</html>
The first new line added ({% load staticfiles %}) informs Django’s template engine
that we will be using static files within the template. This then enables us to access
the media in the static directories via the use of the static template tag¹³. This
indicates to Django that we wish to show the image located in the static media
directory called images/rango.jpg. Template tags are denoted by curly brackets
(e.g. {% %}), and calling static will combine the URL specified in STATIC_URL with
images/rango.jpg to yield /static/images/rango.jpg. The HTML generated by the
Django template engine would be:
¹³https://docs.djangoproject.com/en/2.1/ref/templates/builtins/
Templates and Media Files 55
If for some reason the image cannot be loaded, it is always a good idea to specify
an alternative text tagline. This is what the alt attribute provides inside the img tag.
You can see what happens in the image below.
The image of Rango couldn’t be found, and is instead replaced with a placeholder containing the
text from the img alt attribute.
With these minor changes in place, start the Django development server once more
and navigate to http://127.0.0.1:8000/rango. If everything has been done correctly,
you will see a webpage that looks similar to the screenshot shown below.
Templates and Media Files 56
Our first Rango template, complete with a picture of Rango the chameleon.
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<title>Rango</title>
<!-- CSS -->
<link rel="stylesheet" href="{% static "css/base.css" %}" />
<!-- JavaScript -->
<script src="{% static "js/jquery.js" %}"></script>
</head>
<body>
<!-- Image -->
<img src="{% static "images/rango.jpg" %}" alt="Picture of Rango" />
</body>
</html>
Static files you reference will obviously need to be present within your
static directory. If a requested file is not present or you have referenced
it incorrectly, the console output provided by Django’s development server
will show a HTTP 404 error¹⁶. Try referencing a non-existent file and see what
happens. Looking at the output snippet below, notice how the last entry’s
HTTP status code is 404.
For further information about including static media you can read through
the official Django documentation on working with static files in templates¹⁷.
¹⁶https://en.wikipedia.org/wiki/HTTP_404
Templates and Media Files 58
Static media files can be considered files that don’t change and are essential to your
application. However, often you will have to store media files which are dynamic.
These files can be uploaded by your users or administrators, and so they may
change. As an example, a media file would be a user’s profile picture. If you run
an e-commerce website, a series of media files would be used as images for the
different products that your online shop has.
To serve media files successfully, we need to update the Django project’s settings.
This section details what you need to add – but we won’t be fully testing it out until
later where we implement the functionality for users to upload profile pictures.
Modifying settings.py
First, open your Django project’s settings.py module. In here, we’ll be adding
a couple more things. Like static files, media files are uploaded to a specified
directory on your filesystem. We need to tell Django where to store these files.
At the top of your settings.py module, locate your existing BASE_DIR, TEMPLATE_DIR
and STATIC_DIR variables – they should be close to the top. Underneath, add a
further variable, MEDIA_DIR.
¹⁷https://docs.djangoproject.com/en/2.1/howto/static-files/#staticfiles-in-templates
Templates and Media Files 59
This line instructs Django that media files will be uploaded to your Django project’s
root, plus ‘/media’ – or <workspace>/tango_with_django_project/media/. As we pre-
viously mentioned, keeping these path variables at the top of your settings.py
module makes it easy to change paths later on if necessary.
Now find a blank spot in settings.py, and add two more variables. The variables
MEDIA_ROOT and MEDIA_URL will be picked up and used by Django to set up media file
hosting¹⁸.
MEDIA_ROOT = MEDIA_DIR
MEDIA_URL = '/media/'
The two variables tell Django where to look in your filesystem for media files
(MEDIA_ROOT) that have been uploaded and stored, and what URL to serve them
from (MEDIA_URL). With the configuration we defined above, a user uploading the file
sample.pdf will, for example, be then made available on your Django development
server through the URL http://localhost:8000/media/sample.pdf.
When we come to working with templates later on in this book, it’ll be handy for
us to obtain a reference to the MEDIA_URL path when we need to reference uploaded
content. Django provides a template context processor¹⁹ that’ll make it easy for us to
do. While we don’t strictly need this set up now, it’s a good time to add it in.
To do this, find the TEMPLATES list that resides within your project’s settings.py mod-
ule. The list contains a dictionary; look for the context_processors list within the
nested dictionary. Within the context_processors list, add a new string to include
¹⁸https://docs.djangoproject.com/en/2.1/howto/static-files/#serving-files-uploaded-by-a-user-during-
development
¹⁹https://docs.djangoproject.com/en/2.1/ref/templates/api/#django-template-context-processors-media
Templates and Media Files 60
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.media', # Check/add this line!
],
The final step for setting up the serving of media in a development environment is
to tell Django to serve static content from MEDIA_URL. This can be achieved by open-
ing your project’s urls.py module. Remember, your project’s urls.py module is
the one that lives within the tango_with_django_project/tango_with_django_project
directory – not the tango_with_django_project/rango/urls.py module!
In the urls.py module, begin by adding the following import statements at the top.
If you find that one of these lines already exists, you don’t need to enter it again.
Once you have the imports, you need to modify the urlpatterns list underneath.
Modify it by appending a call to the static() function that you just imported,
complete with the settings telling the function where the files are stored on your
filesystem (MEDIA_URL), and what URL they should be served from (MEDIA_URL).
urlpatterns = [
...
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Once these changes have been made, you should be able to serve content from the
media directory of your project from the /media/ URL.
Templates and Media Files 61
With the chapter complete, you should now know how to set up and create tem-
plates, use templates within your views, setup and use the Django development
server to serve static media files, and include images within your templates. We’ve
covered quite a lot!
Creating a template and integrating it within a Django view is a key concept for you
to understand. It takes several steps but will become second nature to you after a
few attempts.
1. First, create the template you wish to use and save it within the templates
directory you specified in your project’s settings.py module. You may wish
to use Django template variables (e.g. {{ variable_name }}) or template tags²⁰
within your template. You’ll be able to replace these with whatever you like
within the corresponding view.
2. Find or create a new view within an application’s views.py file.
3. Add your view specific logic (if you have any) to the view. For example, this may
involve extracting data from a database and storing it within a list.
4. Within the view, construct a dictionary object which you can pass to the
template engine as part of the template’s context.
5. Make use of the render() helper function to generate the rendered response.
Ensure you reference the request, then the template file, followed by the
context dictionary.
6. Finally, map the view to a URL by modifying your project’s urls.py file (or the
application-specific urls.py file if you have one). This step is only required if
you’re creating a new view, or you are using an existing view that hasn’t yet
been mapped!
²⁰https://docs.djangoproject.com/en/2.1/ref/templates/builtins/
Templates and Media Files 62
The steps involved in getting a static media file onto one of your pages are part of
another important process that you should be familiar with. Check out the steps
below on how to do this.
1. Take the static media file you wish to use and place it within your project’s
static directory. This directory is defined in STATICFILES_DIRS – one of the
variables that you set up in settings.py.
2. Add a reference to the static media file to a template. For example, an image
would be inserted into an HTML page through the use of the <img /> tag.
3. Remember to use the {% load staticfiles %} and {% static "<filename>" %}
commands within the template to access the static files. Replace <filename>
with the path to the image or resource you wish to reference. Whenever you
wish to refer to a static file, use the static template tag!
The steps for serving media files are similar to those for serving static media.
1. Place a file within your project’s media directory. The media directory is speci-
fied by your project’s MEDIA_ROOT variable.
2. Link to the media file in a template through the use of the {{ MEDIA_URL }}
context variable. For example, referencing an uploaded image cat.jpg would
have an <img /> tag like <img src="{{ MEDIA_URL }}cat.jpg" alt="Picture of a
Cat."/>.
Templates and Media Files 63
Exercises
Give the following exercises a go to reinforce what you’ve learnt from this
chapter.
• Convert the about page to use a template too. Use a template called
about.html for this purpose. Base the contents of this file on index.html.
In the new template’s <h1> element, keep Rango says... – but on the line
underneath, have the text 'here is the about page.'.
• Remember to update the about() view in views.py! Do you think you
need a context dictionary for this view?
• Your new about.html must include a link back to the index page. If you
copied your existing index.html template, a small change is required to
achieve this.
• Within the new about.html template, add a picture stored within your
project’s static files. Plese reuse the rango.jpg image you used in the
index() view. Make sure you keep the same alt text as before!
• On the about page, include a line that says This tutorial has been put
together by <your-name>. If you copied over from index.html, replacing
{{ boldmessage }} would be the perfect place for this.
• In your Django project directory, create a new directory called media (if
you have not done so already). Download a JPEG image of a cat, and save
it to the media directory as cat.jpg.
• In your about.html template, add in an <img> tag to display the picture
of the cat to ensure that your media is being served correctly. Keep the
static image of Rango in your index page so that your about page has
working examples of both static and media files. The cat image should
have alternative text of Picture of a Cat. This means you should have
an image of both Rango (from static) and a cat (from media) in your
rendered about page. You don’t need to touch the about() view – only
about.html needs to be modified for this final task.
Templates and Media Files 64
This chapter walks you through the basics of data management with Django and
its ORM. You’ll find it’s incredibly easy to add, modify and delete data within your
app’s underlying database, and see how straightforward it is to get data from the
database to the web browsers of your users.
Before we get started, let’s go over the data requirements for the Rango app that we
are developing. Full requirements for the application are provided in detail earlier
on. Let’s look at the database requirements again here.
• There are several different webpage categories with each category housing none,
one or many links. We assumed in the overview chapter that this is a one-to-
many relationship. Check out the Entity Relationship diagram below.
• A category has a name, several visits, and several likes.
• A page belongs to a particular category, has a title, a URL, and several views.
Before we can create any models, we need to set up our database to work with
Django. In Django, a DATABASES variable is automatically created in your settings.py
module when you set up a new project. Unless you changed it, it should look like
the following example.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
We can pretty much leave this as-is for our Rango app. You can see a default
database that is powered by a lightweight database engine, SQLite² (see the ENGINE
option). The NAME entry for this database is the path to the database file, which is by
default <workspace>/tango_with_django_project/db.sqlite3 – or, in other words, the
db.sqlite3 file that lives in the root of your Django project.
²https://www.sqlite.org/
Models and Databases 67
Instead, add db.sqlite3 to your .gitignore file so that it won’t be added when
you git commit and git push. You can also do this for other files like *.pyc and
machine specific files. For more information on how to set your .gitignore
file up, you can refer to our Git familiarisation chapter in the appendices.
While we don’t cover how to use other database engines in this book, there
are guides online which show you how to do this. A good starting point is the
official Django documentation⁶.
With your database configured in settings.py, let’s create the two initial data
models for the Rango application. Models for a Django app are stored in the
³http://www.postgresql.org/
⁴https://www.mysql.com/
⁵https://en.wikipedia.org/wiki/Microsoft_SQL_Server
⁶https://docs.djangoproject.com/en/2.1/ref/databases/#storage-engines
⁷http://www.sqlite.org/whentouse.html
Models and Databases 68
respective models.py module. This means that for Rango, models are stored within
<workspace>/tango_with_django_project/rango/models.py.
For the models themselves, we will create two classes – one class representing each
model. Both must inherit⁸ from the Model base class, django.db.models.Model. The
two Python classes will be the definitions for models representing categories and
pages. Define the Category and Page model as follows.
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
def __str__(self):
return self.name
class Page(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
title = models.CharField(max_length=128)
url = models.URLField()
views = models.IntegerField(default=0)
def __str__(self):
return self.title
When you define a model, you need to specify the list of fields and their associated
types, along with any required or optional parameters. By default, all models have
an auto-increment integer field called id which is automatically assigned and acts
a primary key.
Django provides a comprehensive series of built-in field types⁹. Some of the most
commonly used are detailed below.
• CharField, a field for storing character data (e.g. strings). Specify max_length to
provide a maximum number of characters that a CharField field can store.
⁸https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)
⁹https://docs.djangoproject.com/es/2.1/ref/models/fields/#model-field-types
Models and Databases 69
• URLField, much like a CharField, but designed for storing resource URLs. You
may also specify a max_length parameter.
• IntegerField, which stores integers.
• DateField, which stores a Python datetime.date object.
For each field, you can specify the unique attribute. If set to True, the given field’s
value must be unique throughout the underlying database table that is mapped to
the associated model. For example, take a look at our Category model defined above.
The field name has been set to unique, meaning that every category name must be
unique. This means that you can use the field as a primary key.
You can also specify additional attributes for each field, such as stating a default
value with the syntax default='value', and whether the value for a field can be blank
(or NULL¹¹) (null=True) or not (null=False).
Django provides three types of fields for forging relationships between models in
your database. These are:
From our model examples above, the field category in model Page is a ForeignKey.
This allows us to create a one-to-many relationship with model/table Category,
¹⁰https://docs.djangoproject.com/es/2.1/ref/models/fields/#model-field-types
¹¹https://en.wikipedia.org/wiki/Nullable_type
¹²https://en.wikipedia.org/wiki/One-to-many_(data_model)
¹³https://en.wikipedia.org/wiki/One-to-one_(data_model)
¹⁴https://en.wikipedia.org/wiki/Many-to-many_(data_model)
Models and Databases 70
Finally, it is good practice to implement the __str__() method. Without this method
implemented it will show as <Category: Category object> if you were to print()
the object (perhaps in the Django shell, as we discuss later in this chapter). This
isn’t very useful when debugging or accessing the object. How do you know what
category is being shown? When including __str__() as defined above, you will
see <Category: Python> (as an example) for the Python category. It is also helpful
when we go to use the admin interface later because Django will display the string
representation of the object, derived from __str__().
With our models defined in models.py, we can now let Django work its magic and
create the tables in the underlying database. Django provides what is called a
migration tool¹⁶ to help us set up and update the database to reflect any changes
to your models. For example, if you were to add a new field, then you can use the
migration tools to update the database.
¹⁵https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.ForeignKey
¹⁶https://en.wikipedia.org/wiki/Data_migration
Models and Databases 71
Setting up
First of all, the database must be initialised. This means that creating the database
and all the associated tables so that data can then be stored within it/them. To do
this, you must open a terminal or Command Prompt, and navigate to your project’s
root directory – where the manage.py module is stored. Run the following command,
bearing in mind that the output may vary slightly from what you see below.
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... 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 auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying sessions.0001_initial... OK
All apps installed in your Django project (check INSTALLED_APPS in settings.py) will
be called to update their database representations when this command is issued.
After this command is issued, you should then see a db.sqlite3 file in your Django
project’s root.
Next, create a superuser to manage the database. Run the following command.
The superuser account will be used to access the Django admin interface, used
later on in this chapter. Enter a username for the account, e-mail address and
Models and Databases 72
provide a password when prompted. Once completed, the script should finish
successfully. Make sure you take note of the username and password for your
superuser account.
Whenever you make changes to your app’s models, you need to register the changes
via the makemigrations command in manage.py. Specifying the rango app as our tar-
get, we then issue the following command from our Django project’s root directory.
Upon the completion of this command, check the rango/migrations directory to see
that a Python script has been created. It’s called 0001_initial.py, which contains all
the necessary details to create your database schema for that particular migration.
In this example, rango is the name of your app, and 0001 is the migration
you wish to view the SQL code for. Doing this allows you to get a better
understanding of what exactly is going on at the database layer, such as what
tables are created. You will find for complex database schemas including a
many-to-many relationship that additional tables are created for you.
After you have created migrations for your app, you need to commit them to the
database. Do so by once again issuing the migrate command.
Models and Databases 73
Operations to perform:
Apply all migrations: admin, auth, contenttypes, rango, sessions
Running migrations:
Applying rango.0001_initial... OK
This output confirms that the database tables have been created in your database,
and you are then ready to start using the new models and tables.
However, you may have noticed that our Category model is currently lacking some
fields that were specified in Rango’s requirements. Don’t worry about this, as
these will be added in later, allowing you to work through the migration pro-
cess once more.
Before we turn our attention to demonstrating the Django admin interface, it’s
worth noting that you can interact with Django models directly from the Django
shell – a very useful tool for debugging purposes. We’ll demonstrate how to create
a Category instance using this method.
To access the shell, we need to call manage.py from within your Django project’s root
directory once more. Run the following command.
This will start an instance of the Python interpreter and load in your project’s
settings for you. You can then interact with the models, with the following terminal
session demonstrating this functionality. Check out the inline commentary that we
added to see what each command achieves.
Models and Databases 74
In the example, we first import the model that we want to manipulate. We then
print out all the existing categories. As our underlying Category table is empty, an
empty list is returned. Then we create and save a Category, before printing out all
the categories again. This second print then shows the new Category just added.
Note the name Test appears in the second print – this is the output of the __str__(),
and neatly demonstrates why including these methods is important!
¹⁷https://docs.djangoproject.com/en/2.1/intro/tutorial02/
¹⁸https://docs.djangoproject.com/en/2.1/ref/django-admin/#available-commands
Models and Databases 75
By default, things are pretty much ready to go. Start the Django development server
in the usual way with the following command.
While this looks good, we are missing the Category and Page models that were
defined for the Rango app. To include these models, we need to let Django know
that we want to include them.
To do this, open the file rango/admin.py. With an include statement already present,
modify the module so that you register each class you want to include. The exam-
ple below registers both the Category and Page class to the admin interface.
admin.site.register(Category)
admin.site.register(Page)
Adding further classes which may be created in the future is as simple as adding
another call to the admin.site.register() method, making sure that the model is
imported at the top of the module.
Models and Databases 77
With these changes saved, restart the Django development server and revisit the
admin interface at http://127.0.0.1:8000/admin/. You will now see the Category and
Page models, as shown below.
Try clicking the Categorys link within the Rango section. From here, you should see
the Test category that we created earlier via the Django shell.
Delete the Test category that was previously created. We’ll be populating the
database shortly with example data. You can delete the Test category from
the admin interface by clicking the checkbox beside it, and selecting Delete
selected categorys from the dropdown menu at the top of the page. Confirm
your intentions by clicking the big red button that appears!
Models and Databases 78
User Management
The Django admin interface is also your port of call for user management
through the Authentication and Authorisation section. Here, you can create,
modify and delete user accounts, and vary privilege levels. More on this later.
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
Expanding admin.py
It should be noted that the example admin.py module for your Rango app is
the most simple, functional example available. However, you can customise
the Admin interface in several ways. Check out the official Django documen-
tation on the admin interface²⁰ for more information if you’re interested.
We’ll be working towards manipulating the admin.py module later on in the
tutorial.
¹⁹https://docs.djangoproject.com/en/2.1/topics/db/models/#meta-options
²⁰https://docs.djangoproject.com/en/2.1/ref/contrib/admin/
Models and Databases 79
Entering test data into your database tends to be a hassle. Many developers will
add in some bogus test data by randomly hitting keys, like wTFzmN00bz7. Rather
than do this, it is better to write a script to automatically populate the database
with realistic and credible data. This is because when you go to demo or test your
app, you’ll need to be able to see some credible examples in the database. If you’re
working in a team, an automated script will mean each collaborator can simply run
that script to initialise the database on their computer with the same sample data
as you. It’s therefore good practice to create what we call a population script.
1 import os
2 os.environ.setdefault('DJANGO_SETTINGS_MODULE',
3 'tango_with_django_project.settings')
4
5 import django
6 django.setup()
7 from rango.models import Category, Page
8
9 def populate():
10 # First, we will create lists of dictionaries containing the pages
11 # we want to add into each category.
12 # Then we will create a dictionary of dictionaries for our categories.
13 # This might seem a little bit confusing, but it allows us to iterate
14 # through each data structure, and add the data to our models.
15
16 python_pages = [
17 {'title': 'Official Python Tutorial',
18 'url':'http://docs.python.org/3/tutorial/'},
19 {'title':'How to Think like a Computer Scientist',
20 'url':'http://www.greenteapress.com/thinkpython/'},
21 {'title':'Learn Python in 10 Minutes',
22 'url':'http://www.korokithakis.net/tutorials/python/'} ]
23
24 django_pages = [
Models and Databases 80
68
69 # Start execution here!
70 if __name__ == '__main__':
71 print('Starting Rango population script...')
72 populate()
We’ve provided explanations below to help you learn from our code!
You should also note that when you see line numbers alongside the code.
We’ve included these to make copying and pasting a laborious chore – why
not just type it out yourself and think about each line instead?
While this looks like a lot of code, what is going on is essentially a series of function
calls to two small functions, add_page() and add_cat(), both defined towards the end
of the module. Reading through the code, we find that execution starts at the bottom
of the module – look at lines 71 and 72. This is because, above this point, we define
functions; these are not executed unless we call them. When the interpreter hits if
__name__ == '__main__'²¹, we call and begin execution of the populate() function.
Importing Models
When importing Django models, make sure you have imported your
project’s settings by importing django and setting the environment variable
DJANGO_SETTINGS_MODULE to be your project’s setting file, as demonstrated
in lines 1 to 6 above. You then call django.setup() to import your Django
project’s settings.
If you don’t perform this crucial step, you’ll get an exception when at-
tempting to import your models. This is because the necessary Django
infrastructure has not yet been initialised. This is why we import Category
and Page after the settings have been loaded on the seventh line.
The for loop occupying lines 47-50 is responsible for the calling the add_cat() and
add_page() functions repeatedly. These functions are in turn responsible for the
creation of new categories and pages. populate() keeps tabs on categories that are
created. As an example, a reference to a new category is stored in local variable c –
check line 48 above. This is stored because a Page requires a Category reference.
After add_cat() and add_page() are called in populate(), the function concludes
by looping through all-new Category and associated Page objects, displaying their
names on the terminal.
Models and Databases 83
This helper method can remove a lot of repetitive code for us. Rather than
doing this laborious check ourselves, we can make use of code that does
exactly this for us.
This explanation means that the [0] after get_or_create() returns the object
reference only. Like most other programming language data structures,
Python tuples use zero-based numbering²².
You can check out the official Django documentation²³ for more informa-
tion on the handy get_or_create() method. We’ll be using this extensively
throughout the rest of the tutorial.
When saved, you can then run your new populations script by changing the present
working directory in a terminal to the Django project’s root. It’s then a simple case
of executing the command $ python populate_rango.py. You should then see an
output to the command similar to that shown below. The order in which categories
are added may vary; don’t worry about that – as long as they are all listed!
²²http://en.wikipedia.org/wiki/Zero-based_numbering
²³https://docs.djangoproject.com/en/2.1/ref/models/querysets/#get-or-create
Models and Databases 84
$ python populate_rango.py
Next, verify that the population script worked as it should have and populated the
database. To do this, restart the Django development server, and navigate to the
admin interface (at http://127.0.0.1:8000/admin/). Once there, check that you have
some new categories and pages. Do you see all the pages if you click Pages, like in
the figure shown below?
The Django admin interface, showing the Page model populated with the new population script.
Success!
Models and Databases 85
While creating a population script may take time initially, you will save yourself
heaps of time in the long run. When deploying your app elsewhere, running the
population script after setting everything up means you can start demonstrating
your app straight away. You’ll also find it very handy when it comes to unit testing
your code.
Now that we’ve covered the core principles of dealing with Django’s ORM, now is
a good time to summarise the processes involved in setting everything up. We’ve
split the core tasks into separate sections for you. Check this section out when you
need to quickly refresh your mind of the different steps.
With a new Django project, you should first tell Django about the database you
intend to use (i.e. configure DATABASES in settings.py). You can also register any
models in the admin.py module of your app to then make them accessible via the
web-based admin interface.
Adding a Model
The workflow for adding models can be broken down into five steps.
1. First, create your new model(s) in your Django application’s models.py file.
2. Update admin.py to include and register your new model(s) if you want to make
them accessible to the admin interface.
3. Perform the migration $ python manage.py makemigrations <app_name>.
4. Apply the changes $ python manage.py migrate. This will create the necessary
infrastructure (tables) within the database for your new model(s).
5. Create/edit your population script for your new model(s).
Models and Databases 86
There will be times when you will have to delete your database. Sometimes it’s
easier to just start afresh. Perhaps you might end up caught in a loop when trying
to make further migrations, and something goes wrong.
When you encounter the need to refresh the database, you can go through the
following steps. Note that for this tutorial, you are using an SQLite database –
Django does support a variety of other database engines²⁴.
Exercises
Now that you’ve completed this chapter, try out these exercises to reinforce
and practice what you have learnt. Once again, note that the following
chapters will have expected you to have completed these exercises!
• Update the Category model to include the additional attributes views and
likes where the default values for each are both zero (0).
• As you have changed your models, make the migrations for your Rango
app. After making migrations, commit the changes to the database.
• Next update your population script so that the Python category has 128
views and 64 likes, the Django category has 64 views and 32 likes, and the
Other Frameworks category has 32 views and 16 likes.
• Delete and recreate your database, populating it with your updated
population script.
• Complete parts two²⁵ and seven²⁶ of the official Django tutorial. The
two sections will reinforce what you’ve learnt on handling databases
in Django, and will also provide you with additional techniques to
customising the Django admin interface. This knowledge will help you
complete the final exercise below.
• Customise the admin interface. Change it in such a way so that when
you view the Page model, the table displays the category, the title of the
page, and the url – just like in the screenshot shown below. Make sure
the three fields appear in that order, too. Complete the official Django
tutorial or look at the tip below to complete this particular exercise.
²⁵https://docs.djangoproject.com/en/2.1/intro/tutorial02/
²⁶https://docs.djangoproject.com/en/2.1/intro/tutorial07/
Models and Databases 88
The updated admin interface Page view, complete with columns for category and URL.
Models and Databases 89
Exercise Hints
If you require some help or inspiration to complete these exercises done,
here are some hints.
• Modify the Category model by adding two IntegerFields: views and likes.
Both should have a parameter default set to 0.
• In your population script, you can then modify the add_cat() function
to take the values of the views and likes.
– You’ll need to add two parameters to the definition of add_cat() so
that views and likes values can be passed to the function, as well as
a name for the category. Give them default values of 0 (i.e. views=0).
– You can then use these parameters to set the views and likes
fields within the new Category model instance you create within
the add_cat() function. The model instance is assigned to variable
c in the population script, as defined earlier in this chapter. As an
example, you can access the likes field using the notation c.likes.
Don’t forget to save() the instance!
– You then need to update the cats dictionary in the populate()
function of your population script. Look at the dictionary. Each
key/value pairing²⁷ represents the name of the category as the key,
and an additional dictionary containing additional information re-
lating to the category as the value. You’ll want to modify this second,
nested dictionary to include views and likes for each category.
– The final step involves you modifying how you call the add_cat()
function. You now have three parameters to pass (name, views and
likes); your code currently provides only the name. You need to add
the additional two parameters to the function call. If you aren’t
sure how the for loop works over dictionaries, check out this online
Python tutorial²⁸. From here, you can figure out how to access the
views and likes values from your dictionary (hint: cat_data).
• After your population script has been updated, you can move on to
customising the admin interface. You will need to edit rango/admin.py
and create a PageAdmin class that inherits from admin.ModelAdmin.
– Within your new PageAdmin class, add list_display = ('title',
'category', 'url').
²⁷https://www.tutorialspoint.com/python/python_dictionary.htm
Models and Databases 90
²⁸https://www.tutorialspoint.com/python/python_dictionary.htm
6. Models, Templates and Views
Now that we have the models set up and populated the database with some sample
data, we can now start connecting the models, views and templates to serve up dy-
namic content. In this chapter, we will go through the process of showing categories
on the main page, and then create dedicated category pages which will show the
associated list of links.
To do this, there are five main steps that you must undertake to create a data-driven
webpage in Django.
These steps highlight how we need to work within Django’s framework to bind
models, views and templates together.
One of the requirements regarding the main page was to show the top five cate-
gories present within your app’s database. To fulfil this requirement, we will go
through each of the above steps.
Models, Templates and Views 92
First, we need to complete step one. Open rango/views.py and at the top of the file,
after the other imports, import the Category model from Rango’s models.py file.
Here we will complete steps two and step three, where we need to modify the view
index() function. Remember that the index() function is responsible for the main
page view. Modify index() as follows:
def index(request):
# Query the database for a list of ALL categories currently stored.
# Order the categories by the number of likes in descending order.
# Retrieve the top 5 only -- or all if less than 5.
# Place the list in our context_dict dictionary (with our boldmessage!)
# that will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
context_dict = {}
context_dict['boldmessage'] = 'Crunchy, creamy, cookie, candy, cupcake!'
context_dict['categories'] = category_list
With the query complete, we passed a reference to the list (stored as variable
category_list) to the dictionary, context_dict. This dictionary is then passed as
part of the context for the template engine in the render() call. Note that above,
we still include our boldmessage in the context_dict – this is still required for the
existing template to work! This means our context dictionary now contains two
key/value pairs: boldmessage, representing our Crunchy, creamy, cookie, candy,
cupcake! message, and categories, representing our top five categories that have
been extracted from the database.
Warning
For this to work, you will have had to complete the exercises in the previous
chapter where you need to add the field likes to the Category model. Like we
have said already, we assume that you complete all exercises as you progress
through this book.
With the view updated, we can complete the fourth step and update the template
rango/index.html, located within your project’s templates directory. Change the
HTML and Django template code so that it looks like the example shown below.
Note that the major changes start at line 15.
1 <!DOCTYPE html>
2
3 {% load staticfiles %}
4
5 <html>
6
7 <head>
8 <title>Rango</title>
9 </head>
10
11 <body>
12 <h1>Rango says...</h1>
13 <div>
14 hey there partner! <br/>
15 <strong>{{ boldmessage }}</strong>
Models, Templates and Views 94
16 </div>
17
18 <div>
19 {% if categories %}
20 <ul>
21 {% for category in categories %}
22 <li>{{ category.name }}</li>
23 {% endfor %}
24 </ul>
25 {% else %}
26 <strong>There are no categories present.</strong>
27 {% endif %}
28 </div>
29
30 <div>
31 <a href="/rango/about/">About Rango</a><br />
32 <img src="{% static "images/rango.jpg" %}"
33 alt="Picture of Rango" />
34 </div>
35 </body>
36
37 </html>
Here, we make use of Django’s template language to present the data using if and
for control statements. Within the <body> of the page, we test to see if categories
– the name of the context variable containing our list of (a maximum of) five
categories – actually contains any categories ({% if categories %}).
If so, we proceed to construct an unordered HTML list (within the <ul> tags). The
for loop ({% for category in categories %}) then iterates through the list of results,
and outputs each category’s name ({{ category.name }}) within an <li> element to
denote that it is a list element.
As the example also demonstrates Django’s template language, all template com-
mands are enclosed within the tags {% and %}, while variables whose values are to be
placed on the page are referenced within {{ and }} brackets. Everything within these
Models, Templates and Views 95
tags and brackets is interpreted by the Django templating engine before sending a
completed response back to the client.
Now, save the template file and head over to your web browser. Refresh Rango’s
homepage at http://127.0.0.1:8000/rango/, and you show then see a list of cat-
egories underneath the page title and your bold message, just like in the figure
below. Well done, this is your first data-driven webpage!
According to the specifications for Rango, we also need to show a list of pages that
are associated with each category. To accomplish this, there are several different
challenges that we need to overcome. First, a new view must be created. This new
view will have to be parameterised. We also need to create URL patterns and URL
strings that encode category names.
Models, Templates and Views 96
Let’s start by considering the URL problem. One way we could handle this problem
is to use the unique ID for each category within the URL. For example, we could
create URLs like /rango/category/1/ or /rango/category/2/, where the numbers
correspond to the categories with unique IDs 1 and 2 respectively. However, it is
not possible to infer what the category is about just from the ID.
Instead, we could use the category name as part of the URL. For example, we
can imagine that the URL /rango/category/python/ would lead us to a list of pages
related to Python. This is a simple, readable and meaningful URL. If we go with this
approach, we’ll also have to handle categories that have multiple words, like Other
Frameworks, for example. Putting spaces in URLs is generally regarded as bad
practice (as we describe below). Spaces need to be percent-encoded². For example,
the percent-encoded string for Other Frameworks would read Other%20Frameworks.
This looks messy and confusing!
To solve this problem, we will make use of the so-called slugify() function provided
by Django.
To make readable URLs, we need to include a slug⁴ field in the Category model. First,
we need to import the function slugify from Django that will replace whitespace
with hyphens, circumnavigating the percent-encoded problem. For example, "how
do i create a slug in django" turns into "how-do-i-create-a-slug-in-django".
²https://www.w3schools.com/tags/ref_urlencode.asp
³http://en.wikipedia.org/wiki/Clean_URL
⁴https://prettylinks.com/2018/03/url-slugs/
Models, Templates and Views 97
Unsafe URLs
While you can use spaces in URLs, it is considered to be unsafe to use them.
Check out the Internet Engineering Task Force Memo on URLs⁵ to read more.
Next, we need to override the save() method of the Category model. This overridden
function will call the slugify() function and update the new slug field with it. Note
that every time the category name changes, the slug will also change – the save()
method is always called when creating or updating an instance of a Django model.
Update your Category model as shown below.
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField()
class Meta:
verbose_name_plural = 'categories'
def __str__(self):
return self.name
Don’t forget to add in the following import at the top of the module, either.
Now that the model has been updated, the changes must now be propagated to
the database. However, since data already exists within the database from previous
chapters, we need to consider the implications of the change. Essentially, for all the
existing category names, we want to turn them into slugs (which is performed when
the record is initially saved). When we update the models via the migration tool, it
will add the slug field and provide the option of populating the field with a default
value. Of course, we want a specific value for each entry – so we will first need to
perform the migration, and then re-run the population script. This is because the
population script will explicitly call the save() method on each entry, triggering the
save() as implemented above, and thus update the slug accordingly for each entry.
To perform the migration, issue the following commands (as detailed in the Models
and Databases Workflow).
Since we did not provide a default value for the slug and we already have existing
data in the model, the makemigrations command will give you two options. Select
the option to provide a default (option 1), and enter an empty string – denoted by
two quote marks (i.e. '').
You should then see output that confirms that the migrations have been created.
You are trying to add a non-nullable field 'slug' to category without a default;
we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null
value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do
e.g. timezone.now
Type 'exit' to exit this prompt
>>> ''
Migrations for 'rango':
rango/migrations/0003_category_slug.py
- Add field slug to category
Models, Templates and Views 99
From there, you can then migrate the changes, and run the population script again
to update the new slug fields.
Now run the development server with the command $ python manage.py runserver,
and inspect the data in the models with the admin interface. Remember that the ad-
min interface is reached by pointing your browser to http://127.0.0.1:8000/admin/.
If you go to add in a new category via the admin interface you may encounter a
problem – or two!
1. Let’s say we added in the category Python User Groups. If you try to save the
record, Django will not let you save it unless you also fill in the slug field too.
While we could type in python-user-groups, relying on users to fill out fields will
always be error-prone, and wouldn’t provide a good user experience. It would
be better to have the slug automatically generated.
2. The next problem arises if we have one category called Django and one called
django. Since the slugify() makes the slugs lower case it will not be possible to
identify which category corresponds to the django slug.
To solve the first problem, there are two possible solutions. The easiest solution
would be to update our model so that the slug field allows blank entries, i.e.:
slug = models.SlugField(blank=True)
However, this is less than desirable. A blank slug would probably not make any
sense, and at that, you could define multiple blank slugs! How could we then
identify what category a user is referring to? A much better solution would be
to customise the admin interface so that it automatically pre-populates the slug
field as you type in the category name. To do this, update rango/admin.py with the
following code.
Models, Templates and Views 100
Note that with the above admin.site.register() call, you should ensure that you
replace the existing call to admin.site.register(Category) with the one provided
above. Once completed, try out the admin interface and add in a new category. Note
that the slug field automatically populates as you type into the name field. Try adding
a space and see what happens!
Now that we have addressed the first problem, we can ensure that the slug field
is also unique by adding the constraint to the slug field. Update your models.py
module to reflect this change with the inclusion of unique=True.
slug = models.SlugField(unique=True)
Now that we have added in the slug field, we can now use the slugs to guarantee
a unique match to an associated category. We could have added the unique con-
straint earlier. However, if we had done that and then performed the migration
(and thus setting everything to be an empty string by default), the migration would
have failed. This is because the unique constraint would have been violated. We
could have deleted the database and then recreated everything – but that is not
always desirable.
Migration Woes
It’s always best to plan out your database in advance and avoid changing it.
Making a population script means that you easily recreate your database if
you find that you need to delete it and start again.
Now we need to figure out how to create a page for individual categories. This
will allow us to then be able to see the pages associated with a category. To
implement the category pages so that they can be accessed using the URL pattern
/rango/category/<category-name-slug>/, we need to undertake the following steps.
We’ll also need to update the index() view and index.html template to provide links
to the category page view.
Category View
In rango/views.py, we first need to import the Page model. This means we must add
the following import statement at the top of the file.
try:
# Can we find a category name slug with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# The .get() method returns one model instance or raises an exception.
category = Category.objects.get(slug=category_name_slug)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from
# the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything -
# the template will display the "no category" message for us.
context_dict['category'] = None
context_dict['pages'] = None
Our new view follows the same basic steps as our index() view. We first define a
context dictionary. Then, we attempt to extract the data from the models and add
the relevant data to the context dictionary. We determine which category has been
requested by using the value passed category_name_slug to the show_category() view
function (in addition to the request parameter).
If the category slug is found in the Category model, we can then pull out the asso-
ciated pages, and add this to the context dictionary, context_dict. If the category
requested was not found, we set the associated context dictionary values to None.
Finally, we render() everything together, using a new category.html template.
Models, Templates and Views 103
Category Template
1 <!DOCTYPE html>
2
3 <html>
4
5 <head>
6 <title>Rango</title>
7 </head>
8
9 <body>
10 <div>
11 {% if category %}
12 <h1>{{ category.name }}</h1>
13 {% if pages %}
14 <ul>
15 {% for page in pages %}
16 <li><a href="{{ page.url }}">{{ page.title }}</a></li>
17 {% endfor %}
18 </ul>
19 {% else %}
20 <strong>No pages currently in category.</strong>
21 {% endif %}
22 {% else %}
23 The specified category does not exist.
24 {% endif %}
25 </div>
26 </body>
27
28 </html>
The HTML code example again demonstrates how we utilise the data passed to the
template via its context through the tags {{ }}. We access the category and pages
objects and their fields – such as category.name and page.url, for example.
If the category exists, then we check to see if there are any pages in the category. If
so, we iterate through the returned pages using the {% for page in pages %} template
tags. For each page in the pages list, we present their title and url attributes as listed
Models, Templates and Views 104
hyperlink (e.g. within <li> and <a> elements). These are displayed in an unordered
HTML list (denoted by the <ul> tags). If you are not too familiar with HTML, then
have a look at the HTML Tutorial by W3Schools.com⁶ to learn more about the
different tags.
Now let’s have a look at how we pass the value of the category_name_url parameter to
our function show_category() function. To do so, we need to modify Rango’s urls.py
file (remember, the file located at <workspace>/tango_with_django_project/rango/),
and update the urlpatterns tuple as follows.
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('category/<slug:category_name_slug>/',
views.show_category, name='show_category'),
]
name is what we pass through to the view show_category(). If these two names do
not match exactly, Django will get confused and raise an error. Instead of slugs,
you can also extract out other variables like strings and integers. Refer to the
Django documentation on URL paths⁷ for more details. If you need to parse more
complicated expressions, you can use re_path() instead of path(). This will allow
you to match all sorts of regular (and irregular) expressions. Luckily for us, Django
provides matches for the most common patterns.
All view functions defined as part of a Django app must take at least one parameter.
This is typically called request – and provides access to information related to the
given HTTP request made by the user. When parameterising URLs, you supply
additional named parameters to the signature for the given view. That’s why
show_category() was defined as def show_category(request, category_name_slug).
Regex Hell
“Some people, when confronted with a problem, think ‘I know, I’ll use regular
expressions.’ Now they have two problems.” Jamie Zawinski⁸
Django’s path() method means you can generally avoid Regex Hell – but
if you need to use a regular expression (with the re_path() function, for
instance), this cheat sheet⁹ is really useful.
Our new view is set up and ready to go – but we need to do one more thing. Our index
page template needs to be updated so that it links to the category pages that are
listed. We can update the index.html template to now include a link to the category
page via the slug.
Locate the template code {% for category in categories %}, and adjust the <li>
element to match the example below.
⁷https://docs.djangoproject.com/en/2.1/ref/urls/
⁸http://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/
⁹http://cheatography.com/davechild/cheat-sheets/regular-expressions/
Models, Templates and Views 106
<ul>
{% for category in categories %}
<li>
<!-- The following line is changed to add an HTML hyperlink -->
<a href="/rango/category/{{ category.slug }}/">{{ category.name }}</a>
</li>
{% endfor %}
</ul>
Again, we used the HTML tag <ul> to define an unordered list. Within the list, we
create a series of list elements (<li>), each of which in turn contains an HTML hy-
perlink (<a>). The hyperlink has an href attribute, which we use to specify the target
URL defined by /rango/category/{{ category.slug }}/ which, for example, would
turn into /rango/category/other-frameworks/ for the category Other Frameworks.
Demo
Let’s try everything out now by visiting the Rango homepage. You should see up
to five categories on the index page (although the population script only creates
three – you can test this by adding up to six categories in the admin interface).
The categories should now be links. Clicking on Django should then take you to the
Django category page, as shown in the figure below. If you see a list of links like
Official Django Tutorial, then you’ve successfully set up the new page.
What happens when you visit a category that does not exist? Try navigating a
category which doesn’t exist, like /rango/category/computers/. Do this by typing
the address manually into your browser’s address bar. You should see a message
telling you that the specified category does not exist. Look at your template’s logic
and work through it to understand what is going on.
Models, Templates and Views 107
The links to Django pages. Note the mouse is hovering over the first link – you can see the
corresponding URL for that link at the bottom left of the Google Chrome window.
Models, Templates and Views 108
Exercises
Reinforce what you’ve learnt in this chapter!
• Update the population script to add some value to the views count for
each page. Pick whatever integers you want – as long as each page
receives a whole (integer) number greater than zero.
• Modify the index page to also include the top five most viewed pages.
When no pages are present, you should include a friendly message in
place of a list, stating: There are no pages present. This message should
be bolded – wrap it around <strong>...</strong> tags. The entire top
five pages block should then be wrapped in its own <div>...</div> tag.
• Leading on from the exercise above, include a heading for the Most
Liked Categories and Most Viewed Pages. These must be placed as
second-level headers, using the <h2> tag. Place each of the headers
above their respective list.
• Include a link back to the index page from the category page.
• Undertake part three of official Django tutorial¹⁰ if you have not done so
already to reinforce what you’ve learnt here.
¹⁰https://docs.djangoproject.com/en/2.1/intro/tutorial03/
Models, Templates and Views 109
The index page after you complete the exercises. Your output may vary slightly.
Models, Templates and Views 110
Hints
• When updating the population script, you’ll essentially follow the same
process as you went through in the previous chapter’s exercises. You
will need to update the data structures for each page, and also update
the code that makes use of them.
– Update the python_pages, django_pages and other_pages data struc-
tures. Each page has a title and url – they all now need a count of
how many views they see, too.
– Look at how the add_page() function is defined in your population
script. Does it allow for you to pass in a views count? Do you need to
change anything in this function?
– Finally, update the line where the add_page() function is called. If
you called the views count in the data structures views, and the
dictionary that represents a page is called p in the context of where
add_page() is called, how can you pass the views count into the
function?
– Remember to re-run the population script so that the database is
updated with your new counts.
• You will need to edit both the index view and the index.html template to
put the most viewed (i.e. popular pages) on the index page.
– Instead of accessing the Category model, you will have to ask the
Page model for the most viewed pages.
Model Tips
For more tips on working with models you can take a look through the
following blog posts:
Some of these tests may seem overly harsh – but remember, this book is a
specification for the product we want you to develop. If you don’t develop
software exactly as specified, it can produce undesirable results when your
bit of code is plugged into a larger framework. By following specifications to
the letter, you’ll be learning a valuable lesson that you can take forward in a
future development career.
¹¹http://steelkiwi.com/blog/best-practices-working-django-models-python/
¹²https://medium.com/@raiderrobert/make-your-django-models-dryer-4b8d0f3453dd#.ozrdt3rsm
7. Forms
As part of the Rango application, we will want to capture new categories and new
pages from users. In this chapter, we will run through how to capture data through
web forms. Django comes with some excellent form handling functionality, making
it a pretty straightforward process to collect information from users and save it to
the database via the models. According to Django’s documentation on forms¹, the
form handling functionality allows you to:
1. display an HTML form with automatically generated form widgets (like a text
field or date picker);
2. check submitted data against a set of validation rules;
3. redisplay a form in case of validation errors; and
4. convert submitted form data to the relevant Python data types.
One of the major advantages of using Django’s forms functionality is that it can save
you a lot of time and hassle creating the HTML forms.
The basic steps involved in creating a form and handling user input is as follows.
1. If you haven’t already got one, create a forms.py module within your Django
app’s directory (rango) to store form-related classes.
2. Create a ModelForm class for each model that you wish to represent as a form.
3. Customise the forms as you desire.
4. Create or update a view to handle the form…
• including displaying the form,
• saving the form data, and
¹https://docs.djangoproject.com/en/2.1/topics/forms/
Forms 113
• flagging up errors which may occur when the user enters incorrect data (or
no data at all) in the form.
5. Create or update a template to display the form.
6. Add a urlpattern to map to the new view (if you created a new one).
This workflow is a bit more complicated than those we have previously seen, and
the views that we have to construct are lot more complex, too. However, once you
undertake the process a few times, it will become clearer how everything pieces
together. Trust us.
Here, we will implement the necessary infrastructure that will allow users to add
categories and pages to the database via forms.
First, create a file called forms.py within the rango application directory. While this
step is not necessary (you could put the forms in the models.py), this makes your
codebase tidier and easier to work with.
Within Rango’s forms.py module, we will be creating several classes that inherit
from Django’s ModelForm. In essence, a ModelForm² is a helper class that allows you to
create a Django Form from a pre-existing model. As we’ve already got two models
defined for Rango (Category and Page), we’ll create ModelForms for both.
²https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/#modelform
Forms 114
We need to specify which fields are included on the form, via fields, or specify
which fields are to be excluded, via exclude.
Django provides us with several ways to customise the forms that are created on
our behalf. In the code sample above, we’ve specified the widgets that we wish
to use for each field to be displayed. For example, in our PageForm class, we’ve
Forms 115
defined forms.CharField for the title field, and forms.URLField for url field. Both
fields provide text entry for users. Note the max_length parameters we supply to
our fields – the lengths that we specify are identical to the maximum length of each
field we specified in the underlying data models. Go back to the chapter on models
to check for yourself, or have a look at Rango’s models.py module.
You will also notice that we have included several IntegerField entries for the views
and likes fields in each form. Note that we have set the widget to be hidden with the
parameter setting widget=forms.HiddenInput(), and then set the value to zero with
initial=0. This is one way to set the field to zero by default. Since the fields will be
hidden, the user won’t be able to enter a value for these fields.
However, even though we have a hidden field in the PageForm, we still need to include
the field in the form. If in fields we excluded views, then the form would not contain
that field (despite it being specified). This would mean that the form would not
return the value zero for that field. This may raise an error depending on how
the model has been set up. If in the model we specified that the default=0 for
these fields, then we can rely on the model to automatically populate the field with
the default value – and thus avoid a not null error. In this case, it would not be
necessary to have these hidden fields. We have also included the field slug in the
CategoryForm, and set it to use the widget=forms.HiddenInput().
However, rather than specifying an initial or default value, we have said the field
is not required by the form. This is because our model will be responsible for
populating the field when the form is eventually saved. Essentially, you need to
be careful when you define your models and forms to make sure that the form is
going to contain and pass on all the data that is required to populate your model
correctly.
Besides the CharField and IntegerField widgets, many more are available for use. As
an example, Django provides EmailField (for e-mail address entry), ChoiceField (for
radio input buttons), and DateField (for date/time entry). There are many other field
types you can use, which perform error checking for you (e.g. is the value provided a
valid integer?).
Perhaps the most important aspect of a class inheriting from ModelForm is the need
to define which model we’re wanting to provide a form for. We take care of this through
Forms 116
our nested Meta class. Set the model attribute of the nested Meta class to the model
you wish to use. For example, our CategoryForm class has a reference to the Category
model. This is a crucial step enabling Django to take care of creating a form in the
image of the specified model. It will also help in handling the flagging up of any
errors, along with saving and displaying the data in the form.
We also use the Meta class to specify which fields we wish to include in our form
through the fields tuple. Use a tuple of field names to specify the fields you wish to
include.
With our CategoryForm class now defined, we’re now ready to create a new view to
display the form and handle the posting of form data. To do this, add the following
code to rango/views.py.
def add_category(request):
form = CategoryForm()
# A HTTP POST?
if request.method == 'POST':
form = CategoryForm(request.POST)
# Will handle the bad form, new form, or no form supplied cases.
# Render the form with error messages (if any).
return render(request, 'rango/add_category.html', {'form': form})
You’ll need to add the following two import statements at the top of the module, too.
The new add_category() view adds several key pieces of functionality for handling
forms. First, we create a CategoryForm(), then we check if the HTTP request was a
POST (did the user submit data via the form?). We can then handle the POST request
through the same URL. The add_category() view function can handle three different
scenarios:
⁴http://www.w3schools.com/tags/ref_httpmethods.asp
Forms 118
Django’s form handling machinery processes the data returned from a user’s
browser via an HTTP POST request. It not only handles the saving of form data into
the chosen model but will also automatically generate any error messages for each
form field (if any are required). This means that Django will not store any submitted
forms with missing information that could potentially cause problems for your
database’s referential integrity⁵. For example, supplying no value in the category
name field will return an error, as the field cannot be blank.
From the render() call, you’ll see that we refer to add_category.html – a new template
(we define this below!). This will contain the relevant Django template code and
HTML for the form and page.
Create the file templates/rango/add_category.html. Within the file, add the following
HTML markup and Django template code.
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Rango</title>
5 </head>
6
7 <body>
8 <h1>Add a Category</h1>
9 <div>
10 <form id="category_form" method="post" action="/rango/add_category/">
11 {% csrf_token %}
12 {% for hidden in form.hidden_fields %}
13 {{ hidden }}
14 {% endfor %}
15 {% for field in form.visible_fields %}
16 {{ field.errors }}
17 {{ field.help_text }}
18 {{ field }}
19 {% endfor %}
20 <input type="submit" name="submit" value="Create Category" />
21 </form>
⁵https://en.wikipedia.org/wiki/Referential_integrity
Forms 119
22 </div>
23 </body>
24 </html>
You can see that within the <body> of the HTML page, we placed a <form> element.
Looking at the attributes for the <form> element, you can see that all data captured
within this form is sent to the URL /rango/add_category/ as an HTTP POST request
(the method attribute is case insensitive, so you can do POST or post – both provide
the same functionality). Within the form, we have two for loops,
The visible fields (those that will be displayed to the user) are controlled by the
fields attribute within your ModelForm Meta class. These template loops produce the
necessary HTML markup for each form element. For visible form fields, we also
add in any errors that may be present with a particular field and help text that can
be used to explain to the user what he or she needs to enter.
Hidden Fields
The need for hidden as well as visible form fields are necessitated by the
fact that HTTP is a stateless protocol. You can’t persist state between different
HTTP requests that can make certain parts of web applications difficult
to implement. To overcome this limitation, hidden HTML form fields were
created which allow web applications to pass important information to a
client (which cannot be seen on the rendered page) in an HTML form, only
to be sent back to the originating server when the user submits the form.
Forms 120
Now we need to map the add_category() view to a URL. In the template, we have
used the URL /rango/add_category/ in the form’s action attribute. We now need to
create a mapping from the URL to the view. In rango/urls.py modify the urlpatterns
list to make it look like the following.
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('category/<slug:category_name_slug>/', views.show_category,
name='show_category'),
path('add_category/', views.add_category, name='add_category'),
]
As a final step, we can put a link on the index page so that users can then eas-
ily navigate to the page that allows them to add categories. Edit the template
rango/index.html, and add the following HTML hyperlink in the <div> element with
the About link.
⁶https://docs.djangoproject.com/en/2.1/ref/csrf/
⁷https://docs.djangoproject.com/en/2.1/topics/http/urls/#how-django-processes-a-request
Forms 121
Demo
Now let’s try it out! Start or restart your Django development server, and then point
your web browser to Rango at http://127.0.0.1:8000/rango/. Use your new link to
jump to the Add Category page, and try adding a category. The figure below shows
screenshots of the Add Category and Index pages. In the screenshots, we add the
category Assembly.
Forms 122
Missing Categories?
If you play around with this new functionality and add several different
categories, remember that they will not always appear on the index page.
This is because we coded up our index view to only show the top five categories
in terms of the number of likes they have received. If you log into the admin
interface, you should be able to view all the categories that you have entered.
For confirmation that the category is being added, you can update
the add_category() method in rango/views.py and change the line
form.save(commit=True) to be cat = form.save(commit=True). This will
give you a reference to an instance of the created Category object. You can
then print the category(e.g. print(cat, cat.slug) ).
Cleaner Forms
Recall that our Page model has a url attribute set to an instance of the URLField type.
In a corresponding HTML form, Django would reasonably expect any text entered
into a url field to be a correctly formatted, complete URL. However, users can find
entering something like http://www.url.com to be cumbersome – indeed, users may
not even know what forms a correct URL⁸!
URL Checking
Most modern browsers will now check to make sure that the URL is well-
formed for you, so this example will only work on older browsers. However, it
does show you how to clean the data before you try to save it to the database.
If you don’t have an old browser to try this example (in case you don’t believe
it), try changing the URLField to a CharField. The rendered HTML will then not
instruct the browser to perform the checks on your behalf, and the code you
implemented will be executed.
In scenarios where user input may not be entirely correct, we can override the
clean() method implemented in ModelForm. This method is called upon before
saving form data to a new model instance, and thus provides us with a logical place
⁸https://support.google.com/webmasters/answer/76329?hl=en
Forms 124
to insert code which can verify – and even fix – any form data the user inputs. We
can check if the value of url field entered by the user starts with http:// – and if it
doesn’t, we can prepend http:// to the user’s input.
class PageForm(forms.ModelForm):
...
def clean(self):
cleaned_data = self.cleaned_data
url = cleaned_data.get('url')
return cleaned_data
Within the clean() method, a simple pattern is observed which you can replicate
in your own Django form handling code.
This trivial example shows how we can clean the data being passed through the
form before being stored. This is pretty handy, especially when particular fields
Forms 125
need to have default values – or data within the form is missing, and we need to
handle such data entry problems.
Clean Overrides
Overriding methods implemented as part of the Django framework can
provide you with an elegant way to add that extra bit of functionality for your
application. There are many methods which you can safely override for your
benefit, just like the clean() method in ModelForm as shown above. Check out
the Official Django Documentation on Models⁹ for more examples on how
you can override default functionality to slot your own in.
⁹https://docs.djangoproject.com/en/2.1/topics/db/models/#overriding-predefined-model-methods
Forms 126
Exercises
Now that you’ve worked through the chapter, consider the following ques-
tions, and how you could solve them.
• What would happen if you don’t enter in a category name on the add
category form?
• What happens when you try to add a category that already exists?
• In the section above where we implemented our ModelForm classes, we
repeated the max_length values for fields that we had previously defined
in the models chapter. This is bad practice as we are repeating ourselves!
How can you refactor your code so that you are not repeating the same
max_length values?
• If you have not done so already undertake part four of the official Django
Tutorial¹⁰ to reinforce what you have learnt here.
• Now, implement functionality to let users add pages to each category.
See below for the instructions (and hints!).
A next logical step would be to allow users to add pages to a given category. To do
this, repeat the same workflow above, but for adding pages.
To get you started, here is the code for the add_page() view function.
¹⁰https://docs.djangoproject.com/en/2.1/intro/tutorial04/
Forms 127
form = PageForm()
if request.method == 'POST':
form = PageForm(request.POST)
if form.is_valid():
if category:
page = form.save(commit=False)
page.category = category
page.views = 0
page.save()
return redirect(reverse('rango:show_category',
kwargs={'category_name_slug':
category_name_slug}))
else:
print(form.errors)
Note that in the example above, we need to redirect the user to the show_category()
view once the page has been created. This involves the use of the redirect() and
reverse() helper functions to redirect the user and to lookup the appropriate URL,
respectively. The following imports at the top of Rango’s views.py module will
therefore be required for this code to work – but you should have redirect from
earlier.
Forms 128
Here, the redirect() function is called which in turn calls the reverse() func-
tion. reverse() looks up URL names in your urls.py modules – in this instance,
rango:show_category. If a match is found against the name provided, the complete
URL is returned. The added complication here is that the show_category() view
takes an additional parameter category_name_slug. By providing this value in a
dictionary as kwargs to the reverse() function, it has all of the information it needs
to formulate a complete URL. This completed URL is then used as the parameter to
the redirect() method, and the response is complete!
Note also from the new view that when attempting to add a new page to a
category that does not exist, we simply redirect the user back to the index page.
This removes the need for you to implement something to cover this scenario in
your template. Your template needs to only focus on displaying a form.
More on reverse()
This is our first exposure to the reverse() helper function. It’s an incredibly
useful and powerful function, and we’ll be using it extensively in the next
chapter of the book to make our views more maintainable.
Forms 129
Hints
To help you with the exercises above, the following hints may be of use.
If all else fails and you still find yourself stuck, you can check out our model
solution on GitHub¹³.
¹¹https://github.com/maxwelld90/tango_with_django_2_code/blob/b90fc0b52abda784e09fd8ee99c9f7d356e54470/
tango_with_django_project/templates/rango/add_page.html
¹²https://github.com/maxwelld90/tango_with_django_2_code/commit/a8c71c01d2fb34f900ac13433c7d7a3c3f17082b
¹³https://github.com/maxwelld90/tango_with_django_2_code/tree/b90fc0b52abda784e09fd8ee99c9f7d356e54470
Forms 130
We have only painted the broad brush strokes of a web application in this tutorial.
As you have probably noticed, there are lots of improvements that could be made to
Rango – and these finer details often take a lot more time to complete as you polish
the application. By developing your application with solid foundations, you will be
able to construct up to 80% of your site very rapidly and get a working demo online.
In future versions of this book, we intend to provide some more details on various
aspects of the framework – along with covering the basics of some of the other
fundamental technologies associated with web development. If you have any sug-
gestions or comments about how to improve the book please get in touch.
Please report any typos, bugs, or other issues online via Twitter¹, or submit change
requests via GitHub². Thank you!
¹https://twitter.com/tangowithdjango
²https://github.com/leifos/tango_with_django_2
Final Thoughts 132
8.1 Acknowledgements
This book was written to help teach web application development to computing
science students. In writing the book and the tutorial, we have had to rely upon the
awesome Django community and the Django Documentation for the answers and
solutions. This book is really the combination of that knowledge pieced together in
the context of building Rango.
We would also like to thank all the people who have helped to improve this resource
by sending us comments, suggestions, Git issues and pull requests. If you’ve sent
in changes over the years, please do remind us if you are not on the list!
Adam Kikowski, Adam Mertz³, Alessio Oxilia, Ally Weir, bernieyangmh⁴, Break-
erfall⁵, Brian⁶, Burak K.⁷, Burak Karaboga, Can Ibanoglu⁸, Charlotte, Claus Con-
rad⁹, Codenius¹⁰, cspollar¹¹, Dan C, Darius¹², David Manlove, David Skillman¹³,
Deep Sukhwani¹⁴ Devin Fitzsimons¹⁵, Dhiraj Thakur¹⁶, Duncan Drizy, Gerardo A-
C¹⁷, Giles T.¹⁸, Grigoriy M¹⁹, James Yeo, Jan Felix Trettow²⁰, Joe Maskell, Jonathan
Sundqvist²¹, Karen Little, Kartik Singhal²², koviusesGitHub²³, Krace Kumar²⁴,
ma-152478²⁵, Manoel Maria, marshwiggle²⁶ Martin de G.²⁷, Matevz P.²⁸, mHulb²⁹,
³https://github.com/Amertz08
⁴https://github.com/bernieyangmh
⁵https://github.com/breakerfall
⁶https://github.com/flycal6
⁷https://github.com/McMutton
⁸https://github.com/canibanoglu
⁹https://github.com/cconrad
¹⁰https://twitter.com/Codenius
¹¹https://github.com/cspollar
¹²https://github.com/dariushazimi
¹³https://github.com/reggaedit
¹⁴https://github.com/ProProgrammer
¹⁵https://github.com/aisflat439
¹⁶https://github.com/dhirajt
¹⁷https://github.com/gerac83
¹⁸https://github.com/gpjt
¹⁹https://github.com/GriMel
²⁰https://twitter.com/JanFelixTrettow
²¹https://github.com/jonathan-s
²²https://github.com/k4rtik
²³https://github.com/koviusesGitHub
²⁴https://github.com/kracekumar
²⁵https://github.com/ma-152478
²⁶https://github.com/marshwiggle
²⁷https://github.com/martindegroot
²⁸https://github.com/matonsjojc
²⁹https://github.com/mHulb
Final Thoughts 133
Michael Herman³⁰, Michael Ho Chum³¹, Mickey P.³², Mike Gleen, Muhammad Rad-
ifar, nCrazed³³, Nitin Tulswani, nolan-m³⁴, Oleg Belausov, Olivia Foulds, pawon-
fire³⁵, pdehaye³⁶, Peter Mash, Pierre-Yves Mathieu³⁷, Piotr Synowiec³⁸, Praest-
gias, pzkpfwVI³⁹, Ramdog⁴⁰, Rezha Julio⁴¹, rnevius⁴², Sadegh Kh, Saex⁴³, Saurabh
Tandon⁴⁴, Serede Sixty Six, Svante Kvarnstrom, Tanmay Kansara, Thomas Mur-
phy, Thomas Whyyou⁴⁵, William Vincent, and Zhou⁴⁶.
Common Guides
This chapter provides instructions on how to set up the various technologies
that you’ll be using throughout Tango with Django that we believe will work on
the largest number of systems. However, every computer setup is different.
Different versions of software exist, complete with subtle differences. These
differences make providing universal setup guides a very difficult thing to
do.
If you are using this book as part of a course, you may be provided with
setup instructions unique to your lab computers. Follow these instructions
instead – a majority of the setup work will likely be taken care of for you
already.
However if you are working solo on your computer and you follow the
instructions provided in this chapter without success, we recommend head-
ing to your favourite search engine and entering the problem you’re having.
Typically, this will involve copying and pasting the error message you see
at whatever step you’re struggling at. By pasting in the message verbatim,
chances are you’ll find someone who suffered the same issue as you – and
from that point, you’ll hopefully find a solution to resolve your problem.
How do you go about installing Python 3.7 on your computer? This section an-
swers that question. As we discussed previously, you may find that you already
Setting up your System 135
have Python installed on your computer. If you are using a Linux distribution or
macOS, you will have it installed. Some of your operating system’s functionality is
implemented in Python¹, hence the need for an interpreter! Unfortunately, most
modern operating systems that come preloaded with Python use a version that is
much older than what we require. Exceptions include Ubuntu, coming with version
3.6.5 which should be sufficient for your needs. If you do need to install 3.7, we must
install this version of Python side-by-side with the old one.
There are many different ways in which you can install Python. We demonstrate
here the most common approaches that you can use on Apple’s macOS, various
Linux distributions and Windows 10. Pick the section associated with your oper-
ating system to proceed. Note that we favour the use of package managers² where
appropriate to ensure that you have the means of maintaining and updating the
software easily (when required).
Apple macOS
The simplest way to acquire Python 3 for macOS is to download a .dmg image
file from the official Python website³. This will provide you with a step-by-step
installation interface that makes setting everything up straightforward. If your
development environment will be kept lightweight (i.e. Python only), this option
makes sense. Simply download the installer, and Python 3 should then be available
on your Mac’s terminal!
However, package managers make life easier⁴ when development steps up and
involves a greater number of software tools. Installing a package manager makes
it easy to maintain and update the software on your computer – and even to install
new software, too. macOS does not come preinstalled with a package manager, so
you need to download and install one yourself. If you want to go down this route,
we’ll introduce you to MacPorts, a superb package manager offering a large host
of tools⁵ for you to download and use. We recommend that you follow this route.
Although more complex, the result will be a complete development environment,
¹http://en.wikipedia.org/wiki/Yellowdog_Updater,_Modified
²https://en.wikipedia.org/wiki/Package_manager
³https://www.python.org/downloads/mac-osx/
⁴https://softwareengineering.stackexchange.com/questions/372444/why-prefer-a-package-manager-over-a-
library-folder
⁵https://www.macports.org/ports.php
Setting up your System 136
ready for you to get coding and working on whatever project you (or your future
self!) will be entrusted with.
A prerequisite for using MacPorts is that you have Apple’s Xcode environment
installed. This download is several gigabytes in size. The easiest way to acquire
this is through the App Store on your macOS installation. You’ll need your Apple
account to download that software. Once XCode has been installed, follow the
following steps to setup MacPorts.
1. Verify that XCode is installed by launching it. You should see a welcome screen.
If you see this, everything is ready, so you can then quit the app.
2. Open a Terminal window. Install the XCode command line tools by entering
the command
$ xcode-select --install
This will download additional software tools that will be required by XCode and
additional development software that you later install.
3. Agree to the XCode license, if you have not already. You can do this by entering
the command
$ xcode-build license
Read the terms to the bottom of the page, and type Y to complete – but only if
you agree to the terms!
4. From the MacPorts installation page⁶, download the MacPorts installer for your
correct macOS version.
5. On your Mac’s Finder, open the directory where you downloaded the installer
to, and double-click the file to launch the installation process.
6. Follow the steps, and provide your password to install the software.
7. Once complete, delete the installer file – you no longer require it. Close down
any Terminal windows that are still open.
Once the MacPorts installation has been completed, installing Python is straight-
forward.
⁶https://www.macports.org/install.php
Setting up your System 137
1. Open a new Terminal window. It is important that you launch a new window
after MacPorts installation to ensure that all the changes the installer made
come into effect!
2. Enter the following command.
After entering your password, this will take a few minutes. Several dependen-
cies will be required – agree to these being installed by responding with Y.
3. Once installation completes, activate your new Python installation. Enter the
command
4. Test that the command succeeds by issuing the command $ python. You should
then see the interpreter for Python 3.7.2 (or whatever version you just installed
– as long as it starts with 3.7, this will be fine).
Once this has been completed, Python has been successfully installed and is ready
to use. However, we still need to set up virtual environments to work with your
installation.
1. Enter the following commands to install virtualenv and helper functions pro-
vided in the user-friendly wrapper.
3. Edit your ∼/.profile file. This can be done with the TextEdit app in macOS by
issuing the command $ open ∼/.profile. Add the following four lines at the
end of the file.
Setting up your System 138
export VIRTUALENVWRAPPER_PYTHON='/opt/local/bin/python3.7'
export VIRTUALENVWRAPPER_VIRTUALENV='/opt/local/bin/virtualenv-3.7'
export VIRTUALENVWRAPPER_VIRTUALENV_CLONE='/opt/local/bin/virtualenv-clone-3.7'
source /opt/local/bin/virtualenvwrapper.sh-3.7
4. Save the file and close all open Terminals. After doing this, open a new Termi-
nal. Everything should now be working and ready for you to use. Test with the
following command. You should not see the command not found error.
$ mkvirtualenv
To view the packages MacPorts has already installed on your system, issue
the command $ port list installed. You will see python37 listed!
Linux Distributions
There are many different ways in which you can download, install and run an up-
dated version of Python on your Linux distribution. Unfortunately, methodologies
vary from distribution to distribution. To compound this, almost all distributions
of Linux don’t have a precompiled version of Python 3.7 ready for you to download
and start using (at the time of writing), although the latest release of Ubuntu uses
Python 3.6 (which is sufficient).
If you do choose to install a new version of Python, we’ve put together a series of
steps that you can follow. These will allow you to install Python 3.7.2 from scratch.
⁷https://www.macports.org/ports.php
Setting up your System 139
The steps have been tested thoroughly in Ubuntu 18.04 LTS; other distributions
should also work with minor tweaks, especially concerning the package manager
being used in step 1. A cursory search on your favourite search engine should
reveal the correct command to enter. For example, on a Red Hat Enterprise Linux
installation, the system package manager is yum instead of apt.
Assumption of Knowledge
To complete these steps, we assume you know the basics for Bash interac-
tion, including what the tilde (∼) means, for example. Be careful with the
sudo command, and do not execute it except for the steps we list requiring it
below.
1. Install the required packages for Python to be built successfully. These are
listed below, line by line. These can be entered into an Ubuntu Terminal as-
is; slight modifications will be required for other Linux distributions.
2. Once the above packages are installed, download the source for Python 3.7.2.
We create a pytemp directory to download the file to. We’ll delete this once
everything has completed.
$ mkdir ~/pytemp
$ cd ~/pytemp
$ wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz
4. Configure the Python source code for your computer, and build it. altinstall
tells the installer to install the new version of Python to a different directory
from the pre-existing version of Python on your computer. You’ll need to enter
your password for the system to make the necessary changes. This process will
take a few minutes, and you’ll see a lot of output. Don’t panic. This is normal. If
the build fails, you haven’t installed all of the necessary prerequisites. Check
you have installed everything correctly from step 1, and try again.
$ sudo ./configure --enable-optimizations
$ sudo make altinstall
5. Once complete, delete the source files. You don’t need them anymore.
$ cd ~
$ rm -rf ~/pytemp
6. Attempt to run the new installation of Python. You should see the interpreter
prompt for version 3.7.2, as expected.
$ python3.7
Python 3.7.2 (default, Jan. 8 2019, 20:05:08)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information
>>> quit()
$
9. Modify your ∼/.bashrc file, and include the following lines at the very bottom of
the file. Note that if you are not using Ubuntu, you might need to edit ∼/.profile
instead. Check the documentation of your distribution for more information.
A simple text editor will allow you to do this, like nano.
Setting up your System 141
EXPORT VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3.7
source /usr/local/bin/virtualenvwrapper.sh
10. Restart your Terminal. Python 3.7.2 will now be set up and ready for you to use,
along with the pip and virtualenv tools.
Windows
1. Download the appropriate installer from the official Python website¹⁰. At the
time of writing, the latest release was version 3.7.2.
2. Run the installer. You’ll want to make sure that you check the box saying that
Python 3.7 is added to PATH. You’ll want to install for all users, too. Choose the
Customize option.
3. Proceed with the currently selected checkboxes, and choose Next.
4. Make sure that the checkbox for installing Python for all users is checked. The
installation location will change. Refer to the figure below for an example.
5. Click Next to install Python. You will need to give the installer elevated privi-
leges to install the software.
6. Close the installer when completed, and delete the file you downloaded.
Once the installer is complete, you should have a working version of Python 3.7 in-
stalled and ready to go. Following the instructions above, Python 3.7 is installed to
the directory C:\Program Files\Python37. If you checked all of the options correctly,
the PATH environment variable used by Windows should also have been updated
to incorporate the new installation of Python. To test this, launch a Command
Prompt window and type $ python. Execute the command. You should see the
⁸http://www.python.org/download/
⁹https://support.microsoft.com/en-gb/help/13443/windows-which-operating-system
¹⁰http://www.python.org/download/
Setting up your System 142
Python interpreter launch. If this fails, check your PATH environment variable is set
correctly by following an online guide¹¹.
Once you are sure that Python is installed correctly, you need to install virtual
environment support. Issue the following two commands, and then restart all open
Command Prompt windows.
Once completed, everything should be set up and ready for you to use.
Configuring Python 3.7.2 on Windows 10 x64 – allowing the installation to be run by all users.
By default, when you install software for Python, it is installed system-wide. All
Python applications can see the new software and make use of it. However, issues
¹¹https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/
Setting up your System 143
can occur with this setup. Earlier in the book, we discussed a scenario of two pieces
of software requiring two different versions of the same dependency. This presents a
headache; you cannot typically install two different versions of the same software
into your Python environment!
The solution to this is to use a virtual environment. Using a virtual environment, each
different piece of software that you wish to run can be given its environment, and by
definition, its set of installed dependencies. If you have ProjectA requiring Django
1.11 and ProjectB requiring Django 2.1, you could create a virtual environment for
each with their packages installed.
The five basic commands one would use to manipulate virtual environments are
listed below.
Following the examples above, we can then create an environment for each, in-
stalling the required software in the relevant environment. For ProjectA, the en-
vironment is called projAENV, with projBENV used for ProjectB. Note that to install
software to the respective environments we use pip. The commands used for pip
are discussed below.
Setting up your System 144
$ mkvirtualenv projAENV
(projAENV) $ pip install Django==1.11
(projAENV) $ pip freeze
Django==1.11
(projAENV) $ deactive
$ pip
<Command not found>
$ workon projAENV
(projAENV) $ pip freeze
Django=1.11
(projAENV) $ cd ProjectA/
(projAENV) $ python manage.py runserver
...
(projAENV) $ deactivate
$
The code blocks above create the new virtual environment, projAENV. We then
install Django 1.11 to that virtual environment, before issuing pip freeze to list
the installed packages – confirming that Django was installed. We then deactivate
the virtual environment. pip then cannot be found as we are no longer in the
virtual environment! By switching the virtual environment back on with workon,
our Django package can once again be found. The final two commands launch the
Django development server for ProjectA.
$ mkvirtualenv projBENV
(projBENV) $ pip install Django==2.1
(projBENV) $ pip freeze
Django==2.1
(projBENV) $ cd ProjectA/
(projBENV) $ python manage.py runserver
<INCORRECT PYTHON VERSION!>
We create our new environment with the mkvirtualenv command. This creates and
activates projBENV. However, when trying to launch the code for ProjectA, we get an
error! We are using the wrong virtual environment. By switching to projAENV with
workon projAENV, we can then launch the software correctly. This demonstrates the
power of virtual environments, and the advantages that they can bring. Further
tutorials can be found online.¹²
You can tell if a virtual environment is active by the brackets before your
prompt, like (envname) $. This means that virtual environment envname is
currently switched on, with its settings loaded. Turn it off with deactivate
and the brackets will disappear.
¹²https://realpython.com/python-virtual-environments-a-primer/
Setting up your System 146
Note that when you enable your virtual environment, the command you
enter to start Python is simply python. The same is applied for pip – if you
launch pip3 outside a virtual environment, pip will be the command you use
inside the virtual environment.
The Python package manager is very straightforward to use and allows you to
keep track of the various Python packages (software) that you have installed. We
highly recommend that you use pip alongside a virtual environment, as packages
installed using pip appear only within the said virtual environment.
When you find a package that you want to install, the command required is $ pip
install <packagename>==<version>. Note that the version component is optional;
omitting the version of a particular package will mean that the latest available
version is installed.
You can find the name of a package by examining the PyPi package index¹³, from
which pip downloads software. Simply search or browse the index to find what you
are looking for. Once you know the package’s name, you can issue the installation
command in your Terminal or Command Prompt.
pip is also super useful for listing packages that are installed in your environment.
¹³https://pypi.org/
Setting up your System 147
This can be achieved through the pip freeze command. Sample output is issued
below.
This shows that three packages are installed in a given environment: Django,
Pillow and pytz. The output from this command is typically saved to a file called
requirements.txt, stored in the root directory of a Python project. If somebody
wants to use your software, they can then download your software – complete
with the requirements.txt file. Using this file, they can then create a new virtual
environment set up with the required packages to make the software work.
The recommended way to create your own requirements.txt file is to pipe the
output of the pip freeze command to the file. Navigate to the directory where you
want to create the file, and issue the following command.
$ cd awesome_python_project
$ pip freeze > requirements.txt
This then creates your requirements.txt file. You can then add this to your project’s
version control. A Python developer who sees a requirements.txt file should know
exactly what to do with it!
If you find yourself in a situation like this, you run pip with the -r switch. Given a
requirements.txt in a directory downloaded_project with only pytz==2018.9 listed, an
example CLI session would involve something like the following.
Setting up your System 148
$ cd downloaded_project
$ mkvirtualenv someenv
(someenv) $ pip install -r requirements.txt
...
(someenv) $ pip freeze
pytz==2018.9
pip install installs packages from requirements.txt, and pip freeze, once ev-
erything has been installed, demonstrates that the packages have been installed
correctly.
When developing code, it’s highly recommended that you house your codebase
within a version-controlled repository such as SVN¹⁴ or Git¹⁵. We have provided
a chapter on how to use Git if you haven’t used Git and GitHub before. We highly
recommend that you set up a Git repository for your projects. Doing so could save
you from disaster.
To use Git, we recommend that you use the command-line tool to interact with
your repositories. This is done through the git command. On Windows, you’ll need
to download Git from the Git website¹⁶. If using macOS or Linux, the Git website
also has downloadable installers for you to use¹⁷. However, why not get into the
habit of using a package manager to install the software? This is generally the
recommended way for downloading and using software developed on the UNIX
design principles (including macOS).
For example, installing Git is as simple as typing $ sudo apt install git. Let
the software download, and the apt package manager takes care of the rest. If you
installed MacPorts on your macOS installation as described above, Git will already
be present for you as it is part of the Apple XCode Command Line Developer Tools.
Once installed, typing git will show the commands you can use, as shown in the
example below.
¹⁴http://subversion.tigris.org/
¹⁵http://git-scm.com/
¹⁶https://git-scm.com/download/win
¹⁷https://git-scm.com/downloads
Setting up your System 149
$ git
usage: git [--version] [--help] [-C <path>] [-c name=value]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
10. A Crash Course in UNIX-based
Commands
Depending on your computing background, you may or may not have encountered
a UNIX-based system – or a derivative of. This small crash course focuses on getting
you up to speed with the terminal, an application in which you issue commands for
the computer to execute. This differs from a point-and-click Graphical User Interface
(GUI), the kind of interface that has made computing so much more accessible. A
terminal-based interface may be more complex to use, but the benefits of using
such an interface include getting things done quicker – and more accurately, too.
UNIX based operating systems and derivatives – such as macOS and Linux distri-
butions – all use a similar-looking terminal application, typically using the Bash
shell⁵. All possess a core set of commands that allow you to navigate through
¹http://www.ai.uga.edu/mc/winforunix.html
²https://msdn.microsoft.com/en-us/powershell/mt173057.aspx
³https://docs.microsoft.com/en-us/windows/wsl/install-win10
⁴https://www.cygwin.com/
⁵https://en.wikipedia.org/wiki/Bash_(Unix_shell)
A Crash Course in UNIX-based Commands 151
your computer’s filesystem and launch programs – all without the need for any
graphical interface.
Upon launching a new terminal instance, you’ll be typically presented with some-
thing resembling the following.
sibu:~ david$
What you see is the prompt, and indicates when the system is waiting to execute
your every command. The prompt you see varies depending on the operating
system you are using, but all look generally very similar. In the example above,
there are three key pieces of information to observe:
• your username and computer name (username of david and computer name
of sibu);
• your present working directory (the tilde, or ∼); and
• the privilege of your user account (the dollar sign, or $).
What is a Directory?
In the text above, we refer to your present working directory. But what ex-
actly is a directory? If you have used a Windows computer up until now, you’ll
probably know a directory as a folder. The concept of a folder is analogous to
a directory – it is a cataloguing structure that contains references to other
files and directories.
The dollar sign ($) typically indicates that the user is a standard user account.
Conversely, a hash symbol (#) may be used to signify the user logged in has root
privileges⁶. Whatever symbol is present is used to signify that the computer is
awaiting your input.
When you are using the terminal, it is important to know where you are in the file
system. To find out where you are, you can issue the command pwd. This will display
your Present Working Directory (hence pwd). For example, check the example terminal
interactions below.
You can see that the present working directory in this example is /users/grad/david.
You’ll also note that the prompt indicates that the present working directory is a
tilde ∼. The tilde is used as a special symbol which represents your home directory.
The base directory in any UNIX based file system is the root directory. The path of
the root directory is denoted by a single forward-slash (/). As folders (or directories)
are separated in UNIX paths with a /, a single / denotes the root!
If you are not in your home directory, you can Change Directory (cd) by issuing the
following command:
sibu:/ david$ cd ~
sibu:~ david$
Note how the present working directory switches from / to ∼ upon issuing the cd ∼
command.
A Crash Course in UNIX-based Commands 153
Path Shortcuts
UNIX shells have several different shorthand ways for you to move around
your computer’s filesystem. You’ve already seen that a forward slash (/) rep-
resents the root directory⁷, and the tilde (∼) represents your home directory
in which you store all your files. However, there are a few more special
characters you can use to move around your filesystem in conjunction with
the cd command.
Now, let’s create a directory within the home directory called code. To do this, you
can use the Make Directory command, called mkdir.
There’s no confirmation that the command succeeded. We can change the present
working directory with the cd command to change to code. If this succeeds, we will
know the directory has been successfully created.
⁷https://en.wikipedia.org/wiki/Root_directory
A Crash Course in UNIX-based Commands 154
Issuing another pwd command to confirm our present working directory returns
the output /users/grad/david/code. This is our home directory with code appended
to the end. You can also see from the prompt in the example above that the present
working directory changes from ∼ to code.
Change Back
Now issue the command to change back to your home directory. What
command do you enter?
From your home directory, let’s now try out another command to see what files
and directories exist. This new command is called ls, shorthand for list. Issuing ls
in your home directory will yield something similar to the following.
sibu:~ david$ ls
code
This shows us that there is something present in our home directory called code,
as we would expect. We can obtain more detailed information by adding a l switch
to the end of the ls command – with l standing for list.
sibu:~ david$ ls -l
drwxr-xr-x 2 david grad 68 21 Jul 15:00 code
This provides us with additional information, such as the modification date (2 Apr
11:07), whom the file belongs to (user david of group grad), the size of the entry (68
bytes), and the file permissions (drwxr-xr-x). While we don’t go into file permissions
here, the key thing to note is the d at the start of the string that denotes the entry
is a directory. If we then add some files to our home directory and reissue the ls
-l command, we then can observe differences in the way files are displayed as
opposed to directories.
A Crash Course in UNIX-based Commands 155
sibu:~ david$ ls -l
drwxr-xr-x 2 david grad 68 21 Jul 15:00 code
-rw-r--r--@ 1 david grad 303844 2 Apr 16:16 document.pdf
-rw-r--r-- 1 david grad 14 2 Apr 11:14 readme.md
One final useful switch to the ls command is the a switch, which displays all files
and directories. This is useful because some directories and files can be hidden by
the operating system to keep things looking tidy. Issuing the command yields more
files and directories!
This command shows a hidden directory .virtualenvs and a hidden file .profile.
Note that hidden files on a UNIX based computer (or derivative) start with a period
(.). There’s no special hidden file attribute you can apply, unlike on Windows
computers.
Combining ls Switches
You may have noticed that we combined the l and a switches in the above
ls example to force the command to output a list displaying all hidden files.
This is a valid command – and there are even more switches you can use⁸ to
customise the output of ls.
Creating files is also easy to do, straight from the terminal. The touch command
creates a new, blank file. If we wish to create a file called new.txt, issue touch
new.txt. If we then list our directory, we then see the file added.
⁸http://man7.org/linux/man-pages/man1/ls.1.html
A Crash Course in UNIX-based Commands 156
sibu:~ david$ ls -l
drwxr-xr-x 2 david grad 68 21 Jul 15:00 code
-rw-r--r--@ 1 david grad 303844 1 Apr 16:16 document.pdf
-rw-r--r-- 1 david grad 0 2 Apr 11:35 new.txt
-rw-r--r-- 1 david grad 14 2 Apr 11:14 readme.md
Note the filesize of new.txt – it is zero bytes, indicating an empty file. We can start
editing the file using one of the many available text editors that are available for use
directly from a terminal, such as nano⁹ or vi¹⁰. While we don’t cover how to use these
editors here, you can have a look online for a simple how-to tutorial¹¹. We suggest
starting with nano – while there are not as many features available compared to
other editors, using nano is much simpler.
In the short tutorial above, you’ve covered a few of the core commands such as
pwd, ls and cd. There are however a few more standard UNIX commands that you
should familiarise yourself with before you start working for real. These are listed
below for your reference, with most of them focusing upon file management. The
list comes with an explanation of each and an example of how to use them.
• pwd: As explained previously, this command displays your present working direc-
tory to the terminal. The full path of where you are presently is displayed.
• ls: Displays a list of files in the present working directory to the terminal.
• cd: In conjunction with a path, cd allows you to change your present working
directory. For example, the command cd /users/grad/david/ changes the
present working directory to /users/grad/david/. You can also move up a
directory level without having to provide the absolute path¹² by using two dots,
e.g. cd ..
• cp: Copies files and/or directories. You must provide the source and the target.
For example, to make a copy of the file input.py in the same directory, you could
issue the command cp input.py input_backup.py.
⁹http://www.nano-editor.org/
¹⁰http://en.wikipedia.org/wiki/Vi
¹¹http://www.howtogeek.com/howto/42980/the-beginners-guide-to-nano-the-linux-command-line-text-editor/
¹²http://www.coffeecup.com/help/articles/absolute-vs-relative-pathslinks/
A Crash Course in UNIX-based Commands 157
• mv: Moves files/directories. Like cp, you must provide the source and target. This
command is also used to rename files. For example, to rename numbers.txt to
letters.txt, issue the command mv numbers.txt letters.txt. To move a file to
a different directory, you would supply either an absolute or relative path as
part of the target – like mv numbers.txt /home/david/numbers.txt.
• mkdir: Creates a directory in your present working directory. You need to
supply a name for the new directory after the mkdir command. For example,
if your present working directory was /home/david/ and you ran mkdir music,
you would then have a directory /home/david/music/. You will need to then cd
into the newly created directory to access it.
• rm: Shorthand for remove, this command removes or deletes files from your
filesystem. You must supply the filename(s) you wish to remove. Upon issuing
a rm command, you will be prompted if you wish to delete the file(s) selected.
You can also remove directories using the recursive switch¹³. Be careful with
this command – recovering deleted files is very difficult, if not impossible!
• rmdir: An alternative command to remove directories from your filesystem.
Provide a directory that you wish to remove. Again, be careful: you will not be
prompted to confirm your intentions.
• sudo: A program which allows you to run commands with the security privi-
leges of another user. Typically, the program is used to run other programs as
root – the superuser¹⁴ of any UNIX-based or UNIX-derived operating system.
There’s More!
This is only a brief list of commands. Check out Ubuntu’s documentation on
Using the Terminal¹⁵ for a more detailed overview, or the Cheat Sheet¹⁶ by
FOSSwire for a quick, handy reference guide. Like anything else, the more
you practice, the more comfortable you will feel working with the terminal.
¹³http://www.computerhope.com/issues/ch000798.htm
¹⁴http://en.wikipedia.org/wiki/Superuser
¹⁵https://help.ubuntu.com/community/UsingTheTerminal
¹⁶http://fosswire.com/post/2007/08/unixlinux-command-cheat-sheet/
11. A Git Crash Course
We strongly recommend that you spend some time familiarising yourself with a
version control¹ system for your application’s codebase. This chapter provides you
with a crash course in how to use Git², one of the many version control systems
available. Originally developed by Linus Torvalds³, Git is today one of the most
popular version control systems in use⁴, and is used by open-source and closed-
source projects alike.
This tutorial demonstrates at a high level how Git works, explains the basic com-
mands that you can use, and explains Git’s workflow. By the end of this chapter,
you’ll be able to make contributions to a Git repository, enabling you to work solo,
or in a team.
As your software engineering skills develop, you will find that you can plan and
implement solutions to ever more complex problems. As a rule of thumb, the larger
the problem specification, the more code you have to write. The more code you
write, the greater the emphasis you should put on software engineering practices.
Such practices include the use of design patterns and the DRY (Don’t Repeat Yourself)⁵
principle.
Think about your experiences with programming thus far. Have you ever found
yourself in any of these scenarios?
Using a version control system makes your life easier in all of the above cases. While
using version control systems at the beginning may seem like a hassle it will pay
off later – so it’s good to get into the habit now!
We missed one final (and important) argument for using version control. With ever
more complex problems to solve, your software projects will undoubtedly contain
a large number of files containing source code. It’ll also be likely that you aren’t
working alone on the project; your project will probably have more than one contributor. In
this scenario, it can become difficult to avoid conflicts when working on files.
What is a Repository?
We keep repeating the word repository, but what do we mean by that? When
considering version control, a repository is a data structure which contains
metadata (a set of data that describes other data, hence meta) concerning
the files which you are storing within the version control system. The kind
of metadata that is stored can include aspects such as the historical changes
that have taken place within a given file so that you have a record of all
changes that take place.
If you want to learn more about the metadata stored by Git, there is a
technical tutorial available⁶ for you to read through.
For now, though, let’s provide an overview of each of the different aspects of the Git
system. We’ll recap some of the things we’ve already mentioned just to make sure
it makes sense to you.
• The local index is technically part of the local repository. The local index
stores a list of files that you want to be managed with version control. This is
explained in more detail later in this chapter. You can have a look here¹⁰ to see
a discussion on what exactly a Git index contains.
• The final aspect of Git is your workspace. Think of this folder or directory
as the place on your computer where you make changes to your version-
controlled files. From within your workspace, you can add new files or modify
or remove previously existing ones. From there, you then instruct Git to update
the repositories to reflect the changes you make in your workspace. This is
important – don’t modify the code inside the local repository – you only ever edit files
in your workspace.
Next, we’ll be looking at how to get your Git workspace set up and ready to go. We’ll
also discuss the basic workflow you should use when using Git.
We assume that you’ve got Git installed with the software to go. One easy way to test
the software out is to simply issue git to your terminal or Command Prompt. If you
don’t see a command not found error, you’re good to go. Otherwise, have a look at how
to install Git to your system.
¹⁰http://stackoverflow.com/questions/4084921/what-does-the-git-index-exactly-contains
A Git Crash Course 162
We recommend however that you stick to the command line program. We’ll
be using the commands in this crash course. Furthermore, if you switch to
a UNIX/Linux development environment at a later stage, you’ll be glad you
know the commands!
Cloning your repository is a straightforward process with the git clone command.
Supplement this command with the URL of your remote repository – and if re-
quired, authentication details, too. The URL of your repository varies depending on
the provider you use. If you are unsure of the URL to enter, it may be worth querying
it with your search engine or asking someone in the know.
For GitHub, try the following command, replacing the parts below as appropriate:
• <OWNER> with the username of the person who owns the repository;
• <REPO_NAME> with the name of your project’s repository; and
• <workspace> with the name for your workspace directory. This is optional;
leaving this option out will simply create a directory with the same name as
the repository.
If all is successful, you’ll see some text like the example shown below.
If the output lines end with done, everything should have worked. Check your
filesystem to see if the directory has been created by running $ ls – is the directory
now listed?
A Git Crash Course 164
Once you have cloned your remote repository onto your local computer, navigate
into the directory with your terminal, Command Prompt or GUI file browser. If you
have cloned an empty repository the workspace directory should appear empty.
This directory is your blank workspace with which you can begin to add your
project’s files.
However, the directory isn’t blank at all! On closer inspection, you will notice
a hidden directory called .git. Stored within this directory are both the local
repository and local index. Do not alter the contents of the .git directory. Doing
so could damage your Git setup and break version control functionality. Your newly
created workspace therefore actually contains within it the local repository and index.
Final Tweaks
With your workspace setup, now would be a good time to make some final tweaks.
Here, we discuss two useful features you can try which could make your life (and
your team members’) a little bit easier.
When using your Git repository as part of a team, any changes you make will
be associated with the username you use to access your remote Git repository.
However, you can also specify your full name and e-mail address to be included
with changes that are made by you on the remote repository. Simply open a
Command Prompt or terminal and navigate to your workspace. From there, issue
two commands: one to tell Git your full name, and the other to tell Git your e-mail
address.
¹⁶https://bitbucket.org/
¹⁷https://unfuddle.com/
A Git Crash Course 165
Replace the example name and e-mail address with your own – unless your name
is John Doe!
Git also provides you with the capability to stop – or ignore – particular files from
being added to version control. For example, you may not wish a file containing
unique keys to access web services from being added to version control. If the file
were to be added to the remote repository, anyone could theoretically access the
file by cloning the repository. With Git, files can be ignored by including them in
the .gitignore file, which resides in the root of <workspace>. When adding files to
version control, Git parses this file. If a file that is being added to version control
is listed within .gitignore, the file is ignored. Each line of .gitignore should be a
separate file entry.
.pyc
.DS_Store
__pycache__/
db.sqlite3
*.key
thumbs.db
In this example .gitignore file, there are six entries – one on each line. We detail
each of the entries below.
• The first entry prompts Git to ignore any file with an extension of .pyc –
the wildcard * denoting anything. A .pyc file is a bytecode representation of
a Python module¹⁸, something that Python creates for modules to speed up
execution. These can be safely ignored when committing to version control.
¹⁸https://stackoverflow.com/a/2998228
A Git Crash Course 166
• The second entry, .DS_Store, is a hidden file created by macOS systems. These
files contain custom attributes that you set when browsing through your
filesystem using the Finder. Custom attributes may, for example, represent the
position of icons, or the view that you use to show your files in a given directory.
• The third entry represents any directory of the name __pycache__. This direc-
tory is new to Python 3, and is where the bytecode representation¹⁹ of your
modules live. Again, these directories can be safely ignored.
• db.sqlite3 is your database. Read the note below for a detailed explanation on
why this should always be excluded from your repository.
• *.key is the fifth entry. This represents any file with the extension key, a con-
vention we use where files with this extension contain sensitive information –
like keys for API authentication. These should never be committed – doing so
would be the equivalent of publicly sharing your password!
• The final entry is thumbs.db. Like the .DS_Store file created by macOS, this is
the Windows equivalent.
Remember, wildcards are a neat feature to use here. If you ever have a type of file
you do not wish to commit, then just use a *.abc to denote any filename, with the
extension .abc.
There are many kinds of files you could safely ignore from being committed
and pushed to your Git repositories. Examples include temporary files,
databases (that can easily be recreated) and operating system-specific files.
Operating system-specific files include configurations for the appearance
of the directory when viewed in a given file browser. Windows computers
create thumbs.db files, while macOS creates .DS_Store files.
¹⁹https://stackoverflow.com/a/16869074
A Git Crash Course 167
With your repository cloned and ready to go on your local computer, you’re ready
to get to grips with the Git workflow. This section shows you the basic Git workflow,
and the associated Git commands you can issue.
A Git Crash Course 168
We have provided a representation of the basic Git workflow as shown above. Match
each of the numbers in the black circles to the numbered descriptions below to
read more about each stage. Refer to this diagram whenever you’re unsure about
the next step you should take – it’s very useful!
1. Starting Off
Before you can start work on your project, you must prepare Git. If you haven’t yet
sorted out your project’s Git workspace, you’ll need to clone your repository to set
it up.
If you’ve already cloned your repository, it’s good practice to get into the habit
of updating your local copy by using the git pull command. This pulls the latest
changes from the remote repository onto your computer. By doing this, you’ll be
working with the most up-to-date version of the repository. This will reduce the
possibility of conflicting versions of files, which really can make your life a bit of a
nightmare.
A Git Crash Course 169
To perform a git pull, first navigate to your repository directory within your
Command Prompt or terminal, then issue the command git pull. Check out the
snippet below from a Bash terminal to see exactly what you need to do, and what
output you should expect to see.
$ cd somerepository/
$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/someuser/somerepository
86a0b3b..a7cec3d master -> origin/master
Updating 86a0b3b..a7cec3d
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 README.md
This example shows that a README.md file has been updated or created from the
latest pull.
Getting an Error?
If you receive fatal: Not a git repository (or any of the parent
directories): .git, you’re not in the correct directory. You need cd to your
local repository directory – the one in which you cloned your repository to.
The majority of Git commands only work when you’re in a Git repository.
²⁰https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
A Git Crash Course 170
Once your workspace has been cloned or updated with the latest changes, it’s time
for you to get some work done! Within your workspace directory, you can take
existing files and modify them. You can delete them too, or add new files to be
version controlled.
When you modify your repository in any way, you need to keep Git up-to-date of
any changes you make – this does not take place automatically. Telling Git what has
changed allows you to keep your local index updated. The list of files stored within
the local index is then used to perform your next commit, which we’ll be discussing
in the next step. To keep Git informed, several Git commands let you update the
local index. Three of the commands are near identical to those that were discussed
in the Unix Crash Course (e.g. cp, mv), save for the addition of a git prefix.
• The first command git add allows you to request Git to add a particular file
to staging for the next commit. For any file that you wish to include in version
control – whether it is new or updated, you must git add it. The command
is invoked by typing git add <filename>, where <filename> is the name of the
file you wish to add to your next commit. Multiple files and directories can be
added with the command git add . – but be careful with this²¹.
• git mv performs the same function as the Unix mv command – it moves files.
The only difference between the two is that git mv updates the local index
for you before moving the file. Specify the filename with the syntax git mv
<current_filename> <new_filename>. For example, with this command, you can
move files to a different directory within your repository. This will be reflected
in your next commit. The command is also used to rename files – from the
current filename to the new.
• git cp allows you to make a copy of a file or directory while adding references
to the new files into the local index for you. The syntax is the same as git
mv above where the filename or directory name is specified thus: git cp
<current_filename> <copied_filename>.
• The command git rm adds a file or directory delete request into the local index.
While the git rm command does not delete the file straight away, the requested
²¹http://stackoverflow.com/a/16969786
A Git Crash Course 171
file or directory is removed from your filesystem and the Git repository upon
the next commit. The syntax is similar to the git add command, where a
filename can be specified thus: git rm <filename>. Note that you can add a
large number of requests to your local index in one go, rather than removing
each file manually. For example, git rm -rf media/ creates delete requests in
your local index for the media/ directory. The r switch enables Git to recursively
remove each file within the media/ directory, while f allows Git to forcibly
remove the files. Check out the Wikipedia page²² on the rm command for more
information.
Lots of changes between commits can make things pretty confusing. You may
easily forget what files you’ve already instructed Git to remove, for example. For-
tunately, you can run the git status command to see a list of files which have been
modified from your current working directory but haven’t been added to the local
index for processing. Have a look at the typical output from the command below to
get a taste of what you can see.
²²http://en.wikipedia.org/wiki/Rm_(Unix)
A Git Crash Course 172
On branch master
Your branch is up-to-date with 'origin/master'.
modified: chapter-git.md
modified: chapter-system-setup.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
images/screenshot_template.ai
no changes added to commit (use "git add" and/or "git commit -a")
Reading the output of the git status command carefully, we can see that the
files chapter-git.md and chapter-system-setup.md are already known to your Git
repository, but have been modified from the previous commit. The output of the
command is telling you that for these changes to be reflected in the next commit,
you must git add these files. Files that have been deleted or moved concerning the
previous commit will also appear here.
There is also a section for untracked files. These files have never been committed to
version control – and as such will not show up on any Git commits. Files that you
have just created will appear in this section. For them to be committed, you simply
git add each file in turn. If you then run git status again, you will see them move
up to the first section.
Checking Status
For further information on the git status command, check out the official
Git documentation²³.
²³https://git-scm.com/docs/git-status
A Git Crash Course 173
We’ve mentioned committing several times in the previous step – but what does it
mean? Committing is when you save changes – which are listed in the local index –
that you have made within your workspace. The more often you commit, the greater
the number of opportunities you’ll have to revert to an older version of your code
if things go wrong. It’s like a checkpoint in a game – reach the checkpoint and you
can save your status, perhaps giving you the ability to roll back in the future if you
need to.
Make sure you commit often, but don’t commit an incomplete or broken version
of a particular module or function. Your colleagues will hate you for it. There’s
a lot of discussion as to when the ideal time to commit is. Have a look at this
Stack Overflow page²⁴ for the opinions of several developers. However, it does make
sense to commit only when everything is working. To reiterate, put yourself in the
shoes of a developer who has found their colleague has committed something that
is broken. How would you feel?
To commit, you issue the git commit command. Any changes to existing files that
you have indexed will be saved to version control at this point. Additionally, any
files that you’ve requested to be copied, removed, moved or added to version
control via the local index will be undertaken at this point. When you commit, you
are updating the HEAD of your local repository²⁵.
Commit Requirements
To successfully commit, you need to modify at least one file in your repos-
itory and instruct Git to commit it, through the git add command. See the
previous step for more information on how to do this.
As part of a commit, it’s incredibly useful to your future self and others to explain
why you committed when you did. You can supply an optional message with your
commit if you wish to do so. Instead of simply issuing git commit, run the following
amended command.
²⁴http://stackoverflow.com/questions/1480723/dvcs-how-often-and-when-to-commit-changes
²⁵http://stackoverflow.com/questions/2304087/what-is-git-head-exactly
A Git Crash Course 174
From the example above, you can see that using the -m switch followed by a string
provides you with the opportunity to append a message to your commit. Be as
explicit as you can, but don’t write too much. People want to see at a glance what
you did and do not want to be bored or confused with a long essay. At the same time,
don’t be too vague. Simply specifying Updated models.py may tell a developer what
file you modified, but they will require further investigation to see exactly what you
changed.
Sensible Commits
Although frequent commits may be a good thing, you will want to ensure
that what you have written works before you commit. This may sound silly,
but it’s an incredibly easy thing to not think about. To reiterate (for the third
time), committing code which doesn’troll back work can be infuriating
to your team members if they then rollback to a version of your project’s
codebase which is broken!
After you’ve committed your local repository and committed your changes, you’re
just about ready to send your commit(s) to the remote repository by pushing your
changes. However, what if someone within your group pushes their changes before
you do? This means your local repository will be out of sync with the remote
repository, meaning that any git push command that you issue will fail.
It’s therefore always a good idea to check whether changes have been made on the
remote repository before updating it. Running a git pull command will pull down
A Git Crash Course 175
any changes from the remote repository, and attempt to place them within your
local repository. If no changes have been made, you’re clear to push your changes.
If changes have been made and cannot be easily rectified, you’ll need to do a little
bit more work.
In scenarios such as this, you have the option to merge changes from the remote
repository. After running the git pull command, a text editor will appear in which
you can add a comment explaining why the merge is necessary. Upon saving the
text document, Git will merge the changes from the remote repository to your local
repository. The simplest kind of merge is for a file that someone else has changed,
but you haven’t touched. This is to be expected – and a simple merge of this kind
simply merges the other person’s edits with your edits to produce a merged version
of the repository.
The horrible kind of merge conflict happens when you and someone else work
on the same file at the same time. To fix this, you’ll need to carefully examine
the file and figure out what changes stay, and what changes go. This can be very
time consuming, so communication between team members is the key to avoid
this scenario.
Pushing is the phrase used by Git to describe the sending of any changes in your
local repository to the remote repository. This is how your changes become avail-
able to your other team members, who can then retrieve them by running the git
²⁶http://en.wikipedia.org/wiki/Vi
²⁷http://www.cs.colostate.edu/helpdocs/vi.html
²⁸http://git-scm.com/book/en/Customizing-Git-Git-Configuration#Basic-Client-Configuration
A Git Crash Course 176
pull command in their respective local workspaces. The git push command isn’t
invoked as often as committing – you require one or more commits to perform a push.
You could aim for one push per day, when a particular feature is completed, or at
the request of a team member who is after your updated code.
As explained in this Stack Overflow question and answer page²⁹ this command
instructs the git push command to push your local master branch (where your
changes are saved) to the origin (the remote server from which you originally
cloned). If you are using a more complex setup involving branching and merging³⁰,
alter master to the name of the branch you wish to push.
Important Push?
If your git push is particularly important, you can also alert other team
members to the fact they should update their local repositories by pulling
your changes. You can do this through a pull request. Issue one after pushing
your latest changes by invoking the command git request-pull master,
where master is your branch name (this is the default value). If you are
using a service such as GitHub, the web interface allows you to generate
requests without the need to enter the command. Check out the official
GitHub website’s tutorial³¹ for more information.
This section presents a solution to a coder’s worst nightmare: what if you find that
your code no longer works? Perhaps a refactoring went wrong, or another team
member without discussion changed something. Whatever the reason, using a
form of version control always gives you a last resort: rolling back to a previous
²⁹http://stackoverflow.com/questions/7311995/what-is-git-push-origin-master-help-with-gits-refs-heads-and-
remotes
³⁰http://git-scm.com/book/en/Git-Branching-Basic-Branching-and-Merging
³¹https://help.github.com/articles/using-pull-requests
A Git Crash Course 177
commit. This section details how to do just that. We follow the information given
from this Stack Overflow³² question and answer page.
Rolling back your workspace to a previous commit involves two steps: determining
which commit to rollback to, and performing the rollback. To determine what
commit to rollback to, you can make use of the git log command. Issuing this
command within your workspace directory will provide a list of recent commits
that you made, your name and the date at which you made the commit. Addition-
ally, the message that is stored with each commit is displayed. This is why it is
highly beneficial to supply commit messages that provide enough information
to explain what you do at each commit! Check out the following output from a git
log invocation below to see for yourself.
From this list, you can choose a commit to rollback to. For the commit you want to
rollback to, you must take the commit hash – the long string of letters and numbers.
To demonstrate, the top (or HEAD) commit hash in the example output above is
88f41317640a2b62c2c63ca8d755feb9f17cf16e. You can select this in your terminal and
copy it to your computer’s clipboard.
With your commit hash selected, you can now rollback your workspace to the
previous revision. You can do this with the git checkout command. The following
example command would rollback to the commit with the aforementioned hash.
Make sure that you run this command from the root of your workspace, and do
not forget to include the dot at the end of the command! The dot indicates that
you want to apply the changes to the entire workspace directory tree. After this
has completed, you should then immediately commit with a message indicating
that you performed a rollback. Push your changes and alert your collaborators –
perhaps with a pull request. From there, you can start to recover from the mistake
by putting your head down and getting on with your project.
Exercises
If you haven’t undertaken what we’ve been discussing in this chapter al-
ready, you should go through everything now to ensure your Git repository
is ready to go. To try everything out, you can create a new file README.md in
the root of your <workspace> directory. The file will be used by GitHub³⁴ to
provide information on your project’s GitHub homepage.
• Create the file and write some introductory text to your project.
• Add the file to the local index upon completion of writing and commit
your changes.
• Push the new file to the remote repository and observe the changes on
the GitHub website.
Once you have completed these basic steps, you can then go back and edit
the readme file some more. Add, commit and push – and then try to revert
to the initial version to see if it all works as expected.
³⁴https://help.github.com/articles/github-flavored-markdown
A Git Crash Course 179
There’s More!
There are other more advanced features of Git that we have not covered in
this chapter. Examples include branching and merging, which are useful
for projects with different release versions, for example. There are many
fantastic tutorials available online if you are interested in taking you super-
awesome version control skills a step further. For more details about such
features take a look at this tutorial on getting started with Git³⁵, the Git
Guide³⁶ or Learning about Git Branching³⁷.
However, if you’re only using this chapter as a simple guide to getting to grips
with Git, everything that we’ve covered should be enough. Good luck!
³⁵https://veerasundar.com/blog/2011/06/git-tutorial-getting-started/
³⁶https://rogerdudler.github.io/git-guide/
³⁷https://pcottle.github.io/learnGitBranching/