An Introduction to Django - Part 4


Nathan Osman's Gravatar

Nathan Osman
published Sept. 7, 2013, 8:41 a.m.


This is the fourth and final article in my Django series. We will be completing our "Track Me" web application, although I strongly encourage you not to stop here. At the end of the article, I will suggest some improvements that could be made.

Reworking the Navigation Bar

Our navigation bar doesn't really behave in the way that most users would expect. After logging in, the links on the right side do not change. Instead of displaying "Login" and "Register", we wish to display "Logout" and "New Update". In order to do this, we need to edit the template updates/templates/base.html:

<p class="nav">
    {% if user.is_authenticated %}
        <a href="{% url "updates.views.new" %}">New Update</a> |
        <a href="{% url "django.contrib.auth.views.logout" %}">Logout</a>
    {% else %}
        <a href="{% url "django.contrib.auth.views.login" %}">Login</a> |
        <a href="{% url "accounts.views.register" %}">Register</a>
    {% endif %}
</p>

What we've done here is use the {% if %} template statement to decide what to render to the page depending on a certain condition. In this case, the condition is whether or not the current user is logged in. If they are, then the first two links are displayed. If not, then the second two links are displayed.

Removing Fields

We still have a form field being incorrectly displayed when a user attempts to post a new status update. The user should not be able to choose which account the update should be posted under. Therefore the first thing we need to do is exclude the field in the form definition. Open up updates/forms.py and make the following modification:

class NewUpdateForm(forms.ModelForm):
    class Meta:
        exclude = 'user'
        model   = Update

We've added an additional attribute: exclude. This can be the name of a single field or a list of field names to exclude from display on the form. In fact, not only will the field not be displayed, but it will also not be included in the form at all. If you try to submit the form now, you will receive an error because a required field is missing.

To fix this, we need to adjust the code in the new view a little bit (updates/views.py):

if form.is_valid():
    u = Update(**form.cleaned_data)
    u.user = request.user
    u.save()
    ...

Instead of having the form save the model, we create our own instance of Update and supply the data from the form. We also specify the user that "owns" the update manually. request.user is simply the user that is currently logged in.

Static Files

If you take a look at one of our forms, you will quickly reach one conclusion: it's ugly. Error messages do not stand out well from the other text and the help text is displayed to the right of form fields. In order to fix that, we need to add some CSS styles.

At this point, it makes sense to move the three or four CSS declarations we have in updates/templates/base.html into a separate CSS file. So we will do exactly that. Create a new directory named static in updates and create a file named styles.css in that directory. Cut and paste the CSS declarations from base.html and add the following additional declarations to the end:

form .helptext {
  color: #777;
  display: block;
}
ul.errorlist {
  background-color: #fdd;
  border: 1px solid #faa;
  list-style-type: none;
  padding: 4px 10px;
}

Now how do we properly reference this file from base.html? Using the {% static %} template tag, of course! In order to do that, we replace the <style> tag with the following lines:

{% load static %}
<link href="{% static "styles.css" %}" rel="stylesheet">

We first load the static template library, which contains the tag we need to use on the next line. Then we use the {% static %} tag to obtain the correct URL for the file.

Deploying Django Projects

That's the last major modification we will make to our app. Let's assume that we are ready to actually deploy the application on a production server. I won't go into the details of setting up all of Django's dependencies here. Assuming you have everything ready to go, there are a couple of extra steps that you need to take.

  1. DEBUG needs to be set to False in trackme/settings.py
  2. you need to run ./manage.py collectstatic to gather the static files into a location where the web server can serve them

Apache with mod_wsgi is the recommended way to deploy Django projects. A sample VirtualHost for our app might look something like this:

<VirtualHost *:80>
    ServerName trackme.example.com
    ServerAdmin webmaster@example.com

    WSGIDaemonProcess trackme.example.com python-path="/opt/trackme"
    WSGIProcessGroup trackme.example.com

    WSGIScriptAlias / /opt/trackme/trackme/wsgi.py
    Alias /media/ /var/www/trackme/media/
    Alias /static/ /var/www/trackme/static/

    <Directory /opt/trackme/trackme>
        <Files wsgi.py>
            Order deny,allow
            Allow from all
        </Files>
    </Directory>
</VirtualHost>

(The example assumes you have the Track Me source code in /opt/trackme and are using /var/www/trackme for storing the static files.)

Room for Improvement

Well, believe it or not, our series has come to a close. We've covered a lot and hopefully learned the basics of writing web applications in Django. Our project could be improved in a number of different ways, and I'll give you some examples:

  • Dates are not currently displayed on the home page. This would be really easy to fix. (Hint: what is the name of the field we used to store the creation date?)

  • There is no footer on the bottom of each page. Most websites display a footer with copyright information at the bottom of each page. Try adding something to updates/templates/base.html.

  • There is no way to get to the home page. Most websites also repurpose the title at the top of every page to also act as a link pointing to the home page. This should be a relatively easy fix.

Wrapping Up

And that's it for the article. I hope it was helpful and informative. Comments are appreciated and I would also be happy to answer questions that arise from reading these articles.