In this chapter we’ll create a simple app with class-based views and templates that displays a homepage and an about page. These are the building blocks for more complex web applications later on in the book. Complete source code can be found on Github.

Setup

As in Chapter 2: Hello World app, our initial set up involves the following steps:

  • create a new virtual environment
  • create a new directory for our code
  • install Django
  • create a new Django project
  • create our first app

We can accomplish all of this from our command line console as follows:

$ python3 -m venv ~/.virtualenvs/simple
$ source ~/.virtualenvs/simple/bin/activate
(simple) $ pip install django
(simple) $ mkdir ~/Desktop/simple
(simple) $ cd ~/Desktop/simple
(simple) $ django-admin.py startproject simple_project .
(simple) $ ./manage.py startapp staticpages

Going line-by-line, we just created and entered a new virtual environment called simple. We installed Django within this new virtual env. Then we made a new directory on the Desktop called simple for our code and created both a new Django project called simple_project and within it an app called staticpages.

That’s basically half of chapter 2 in just 7 lines of code!

What’s up with the last line that starts with ./manage.py? It turns out that typing python manage.py is so common in Django development that we can also shorten it to ./manage.py, so let’s do that! Both commands perform the same operation.

To make sure everything installed correctly, spin up the internal web server with the runserver command:

(simple) $ ./manage.py runserver

Then navigate to http://127.0.0.1:8000/. You should see the friendly light-blue Django welcome page:

Django welcome page

Great! We’re in business.

Views and URLConfs

To start, let’s repeat our steps from Chapter 2 to set up a simple homepage.

First, update our staticpages/views.py file with the following code:

# staticpages/views.py
from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world!")

To create a new staticpages/urls.py file, first exit out of our running server on the command line by typing Ctrl-c. Then type the following command:

(simple) $ touch staticpages/urls.py

And update it with the following code:

# staticpages/urls.py
from django.conf.urls import url

from .views import index

urlpatterns = [
    url(r'^$', index),
]

As a last step, update our project-level URLConf so that it refers to our app-level URLConf:

# simple_project/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^', include('staticpages.urls')),
]

And we’re done! Refresh the page http://127.0.0.1:8000/ and you should see the following:

Hello world homepage

Add an About page

To add additional pages, such as an about page, we need to create a new view for each and update our URLConf.

Update the staticpages/views.py file as follows:

# staticpages/views.py
from django.http import HttpResponse


def home_page_view(request):
    return HttpResponse("My staticpages homepage.")


def about_page_view(request):
    return HttpResponse("About page")

And then add new routes for each in the app-level urls.py file. We won’t need to update our project-level urls.py file because it already points to our staticpages app:

# staticpages/urls.py
from django.conf.urls import url

from .views import home_page_view, about_page_view

urlpatterns = [
    url(r'^$', home_page_view),
    url(r'^about/', about_page_view),
]

Note that we’re explicitly importing both views now, providing a regular expression for each that matches a browser URL (so r'^about/' for an /about page), and then providing the name of the view function for each right after.

Spin up our server again so we can visit our new pages:

(simple) $ ./manage.py runserver

Now you can visit your Home page at http://127.0.0.1:8000 and your About page at http://127.0.0.1:8000/about.

Homepage

About page

Templates

But wait! We’re doing something very inefficient with our current approach of hardcoding a response for each webpage into our views. Really we want to support Django’s loose coupling approach and keep different parts of the application as separate as possible.

Fortunately we can create individual HTML files for each page, called templates and simply reference them in the view.

As a first step, let’s replace the hardcoded text in our staticpages/views.py file with a link to an external template file that will contain the same code.

# staticpages/views.py
from django.shortcuts import render


def home_page_view(request):
    return render(request, 'staticpages/index.html')


def about_page_view(request):
    return render(request, 'staticpages/about.html')

We’re importing the render module and telling Django that our homepage template will be in the location staticpages/index.html and our about page template at staticpages/about.html.

Now let’s create our template files. Logically we should just be able to create the files within our staticpages directory, right? It turns out the answer is no! When loading a template file, Django under-the-hood treats all of our templates directories as a single directory. This means that a template file in the staticpages app named staticpages/index.html will be confused with a similarly named file in a different app, for example ‘other_app/index.html’.

To solve this potential issue, it is a Django best practice to create an additional templates folder within each app and then another folder with our app’s name in it.

In our case, that means creating an app structure like the below:

└── staticpages
    └── templates
        └── staticpages
            └── index.html

This redundant structure makes it explicit to Django which template we’re referring to, which is important as our Django projects grow in complexity.

To set up these new directories and the new template files, enter the following commands after first quitting our server with Ctrl-c:

(simple) $ mkdir staticpages/templates
(simple) $ mkdir staticpages/templates/staticpages
(simple) $ touch staticpages/templates/staticpages/index.html
(simple) $ touch staticpages/templates/staticpages/about.html

Then update the newly created files to look like this:

<!-- staticpages/templates/staticpages/index.html -->
<p>My staticpages homepage.</p>
<!-- staticpages/templates/staticpages/about.html -->
<p>About page.</p>

What are the <p> and </p> tags? That’s HTML and stands for opening and closing paragraph tags. Since our files are HTML files, we have to use the proper syntax now.

Lastly, we need to explicitly tell Django that we’ve added a new app by adding staticfiles to our INSTALLED_APPS in the file simple_project/settings.py. Add it at the bottom of the existing installed apps:

# simple_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # my apps
    'staticpages',
]

Now if you now start up the server again with ./manage.py runserver and open the files in the browser at http://127.0.0.1:8000/ and http://127.0.0.1:8000/about you’ll see they work exactly as before.

Templates in action

Let’s demonstrate why templates are so useful by adding a base template with a header section to each page. But crucially, we don’t want to repeat ourself so we should create a new template called base.html and then insert it into our existing index.html and about.html templates. Here’s how…

First from the command line quit our server with Ctrl-c and then create the new base.html file:

(simple) $ touch staticpages/templates/staticpages/base.html

And in your text editor, open the new file and add the code below:

<!-- staticpages/templates/staticpages/base.html -->
<header>
  Home | About
</header>

{% block content %}
{% endblock %}

We’ve added the code for a header and then used starting and ending blocks to indicate where future content can go thanks to the Django template engine (DTE). Wrapping something in opening and closing {% and %} tags indicates we’re using the DTE, not writing simple HTML. We’ll use the extends variable to include our base.html template file in each page.

<!-- staticpages/templates/staticpages/index.html -->
{% extends "staticpages/base.html" %}

{% block content %}
<p>My staticpages homepage.</p>
{% endblock %}
<!-- staticpages/templates/staticpages/about.html -->
{% extends "staticpages/base.html" %}

{% block content %}
<p>About page.</p>
{% endblock %}

Now if you start up the server again with ./manage.py runserver and open up our webpages again at http://127.0.0.1:8000/ and http://127.0.0.1:8000/about you’ll see the header is magically included in both locations.

Nice, right?

Homepage with header

About page with header

There’s a lot more we can use with templates and in practice you’ll typically create a base.html file and then add additional templates on top of it in a robust Django project. We’ll do this later on in the book.

Class-based views

So far we’ve created our views by making a new function in each, an approach called function-based-views (FBVs). But there is a second, more recent approach called class-based-views (CBVs). The introduction of CBVs was somewhat controversial since having both FBVs and CBVs goes against Python’s stated goal that “There should be one–and preferably only one–obvious way to do” something. Nonetheless, CBVs are incredibly useful and in most cases will solve your needs. If you truly need something custom, FBVs are still there for you.

To switch our code over to CBVs we need to update our views.py file and our urls.py file as follows:

# staticpages/views.py
from django.views.generic import TemplateView


class Home_page_view(TemplateView):
	  template_name = 'staticpages/index.html'


class About_page_view(TemplateView):
	  template_name = 'staticpages/about.html'
# staticpages/urls.py
from django.conf.urls import url

from .views import Home_page_view, About_page_view

urlpatterns = [
    url(r'^$', Home_page_view.as_view()),
    url(r'^about', About_page_view.as_view()),
]

Notice that because we’re working with classes we can subclass (or extend) the built-in TemplateView. Notice also that we’re capitalizing our view names, as is standard for classes in Python.

In our urls.py file we’re importing the updated names of our views and making a small syntax change in the urlpattern for each.

If you refresh our webpages at http://127.0.0.1:8000/ and http://127.0.0.1:8000/about you’ll see they remain the same with this new CBV approach.

Homepage with header

About page with header

In future chapters we’ll largely rely on CBVs, notably ListView and DetailView which achieve many common goals with views in Django.

Named URLs

Wouldn’t it be nice if we could click on a link to switch between our new home and about pages? We can do so easily and it’s a good example of why we should add a name to every URL.

First, go to our app-level urls.py file and add a name for our two views.

# staticpages/urls.py
from django.conf.urls import url

from .views import Home_page_view, About_page_view

urlpatterns = [
    url(r'^$', Home_page_view.as_view(), name='home'),
    url(r'^about', About_page_view.as_view(), name='about'),
]

Now in our base.html template use the built-in url tag from the Django templating engine to link to our newly named URLs.

<!-- staticpages/templates/staticpages/base.html -->
<header>
  <a href="{% url 'home' %}">Home</a> | <a href="{% url 'about' %}">About</a>
</header>

{% block content %}
{% endblock %}

If you refresh your homepage at http://127.0.0.1:8000/ you’ll see there are no links for switching back and forth between the two pages!

Home page with links

Tests

Finally we come to tests. Even if an application this simple, it’s important to add tests and get in the habit of always adding them to our Django projects. In the words of Jacob Kaplan-Moss, one of Django’s original creators, “Code without tests is broken as designed.”

Fortunately Django comes with robust, built-in testing tools that make writing and running tests straightforward.

If you look within our staticpages app, Django already provided a tests.py file we can use. Open it and add the following code:

# staticpages/tests.py
from django.test import SimpleTestCase


class SimpleTests(SimpleTestCase):
    def test_home_page_status_code(self):
        response = self.client.get('/')
        self.assertEquals(response.status_code, 200)

    def test_about_page_status_code(self):
        response = self.client.get('/about/')
        self.assertEquals(response.status_code, 200)

We’re using SimpleTestCase here since we aren’t using a database. If we were, we’d instead use TestCase. Then we perform a simple check if the status code for each page is 200, which is the standard response for a successful HTTP request.

To run the tests, type ./manage.py test on the command line:

(simple) $ ./manage.py test
Creating test database for alias 'default'...
..
----------------------------------------------------------------------
Ran 2 tests in 0.028s

OK
Destroying test database for alias 'default'...

Success! We’ll do much more with testing in the future, especially once we start working with databases. For now, it’s important to see how easy it is to add tests each and every time we add new functionality to our Django project.

Conclusion

Congratulations on building your second Django projects! This time we used templates, class-based views, explored URLConfs more fully, and added basic tests. These are fundamental parts of Django. Next up we’ll move on to our first database-backed project that leverages Django’s powerful built-in admin view.

Continue on to Chapter 4: Message board app.




Sign up for the Django For Beginners newsletter for updates when new chapters are available and special discounts for the print edition of the book.