Two-Faced Django Part 4: The Webapp

12/09/2007
  • You can find details about seeing a live version of this project, both web and Facebook interfaces, here.

Okay, we've been writing models, tests, and even forms, but we don't have any actual functionality yet. But, fear not, we're about to change that.

In this segment of the tutorial we are going to implement the most of our web app (just a layer between our users and the core app, where our fundamental functionality lives). Infact, we are going to implement everything except for the Ajax bits (we'll be implementing those in the next segment): soon we'll have something we can actually use in our web browser.

Writing the web/urls.py file

Now we're going to want to create a new file in our polling/web folder: urls.py.

emacs web/urls.py

And we're going to want two imports at the top of this file:

from django.conf.urls.defaults import *
from polling.core.models import *

We want to make three different url patterns for the web project: one for viewing a list of polls, one for viewing a specific poll, and one for creating polls. This is going to look like this:

polls = Poll.objects.all()
urlpatterns = patterns(
    (r'^$', 'django.views.generic.list_detail.object_list', dict(template_name='web/poll_list.html', queryset=polls)),
    (r'^poll/(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', dict(template_name='web/poll_detail.html', queryset=polls)),
    (r'^create/$', 'polling.web.views.create'),
)

Looking at the code, we are using something called generic views to avoid writing boilerplate generic code. The one thing we will need to write for these views is the template to be rendered.

Tweaking our Templates (for the generic views)

The urls.py file we are editing is in the *web application, and are being used to represent the Poll model, so the list and detail generic views are (by default, this behavior is overridable) going to be looking for templates in web/poll_list.html and web/poll_detail.html*.

Before we build those though, we'll want to put together a base template that all our web templates will end. We're going to put it at polling/templates/web/base.html and it should look something like this:

<html> <head>
<title>{% block title %}A Generic Title{% endblock %}</title>
</head>
<body>
{% block body %}
{% endblock %}
</body> </html>

Okay save that, and now we'll write a very simple template for representing a list of Poll objects. Make a file at polling/templates/web/poll_list.html. It should contain this:

{% extends "web/base.html" %}
{% block title %} Our Polls! {% endblock %}
{% block body %}
<div id="polls">
  {% for object in object_list %}
  <p id="poll{{ object.pk }}" ><a href="/poll/{{ object.pk }}/">{{ object }}</a> has a score of {{ object.score }}.</p>
  {% endfor %}
</div>
{% endblock %}

And now an equally simple template for displaying individual Poll objects:

{% extends "web/base.html" %}
{% block title %} Poll: {{ object.question }} {% endblock %}
{% block body %}
<div id="poll">
  <p>We have a question for you. {{ object.question }}
  <p> So far we have {{ object.up_votes }} upvotes and {{ object.down_votes }} downvotes, for an overall vote of {{ object.score }}! </p>
  <p> Do you <span id="upvote">agree</span> or <span id="downvote">disagree</span>? </p>
</div>
{% endblock %}

(We're going to be dealing with the up and down voting functionality (implemented via the JQuery javascript library) in the next segment of the series, for the moment we're just going to ignore the fact that we can't vote. A minor detail...)

One slightly less generic view

Now we need to put together one more view, and this one won't be generic so we'll have to write a slight bit of code for it. Its going to be the view for our "create a new poll" view. Earlier in our web/urls.py file we described where we're going to put it, so lets glance at our web/urls.py file for a moment.

(r'^create/$', 'polling.web.views.create'),

Right. Its going to be stored in a function named create in the polling/web/views.py file. Before we put the code together, lets briefly consider what we want the view to do. There are two things: allow users to create new polls, and also complain if the user attempts to create an invalid poll (in this case meaning that it asks the same question as a previously created poll).

First lets open up the views.py file in the web app.

emacs web/views.py

And now lets add some imports to the file

from django.http import HttpResponseRedirect
from django.views.generic.simple import direct_to_template
from polling.core.models import Poll, User
from polling.core.forms import PollForm

We're going to be using our *PollForm for both rendering and validating the form for creating new polls. We need the Poll model to create new instances when the form is valid, and we need direct_to_template* for rendering the template we are going to create for this view (which will live at templates/web/create.html, but don't worry about that, since we'll be writing it in a moment).

The code for our view is going to have some simple logic to it:

  1. Check if there is attached POST data.
  2. If there is POST data, attempt to create a PollForm with it.
  3. If the new PollForm is valid, then create a new Poll and redirect to the main page.
  4. If the new PollForm is not valid, return the user to the same page, but display any error messages.
  5. If there is not POST data, display an empty form for users to describe their new Poll.

In Python, it is going to look like this:

def create(request):
    if request.method == 'POST':
        form = PollForm(request.POST)
        if form.is_valid():
	   # Because explaining in detail how to use the
	   # authentication framework isn't within my
	   # energy level at this exact moment
	    u = User(name="Someone Else")
	    u.save()
            poll = Poll(creator=u, question=form.cleaned_data['question'])
            poll.save()
            return HttpResponseRedirect('/')
    else:
        form = PollForm()
    return direct_to_template(request, 'web/create.html',
                              extra_context={'form':form})

Now we just need to write a quick template to display our web.create view, and then we'll be all set.

Lets open up the template file

emacs templates/web/create.html

And this is what our template will look like:

{% extends "web/base.html" %}
{% block title %} Creating a new Poll {% endblock %}
{% block body %}
<form method="post" action="">
<table>{{ form }}</table>
<input type="submit" />
</form>
{% endblock %}

And thats a wrap.

Putting our new code through its paces

Its a bit harder to write unittests for this kind of code. You can write code that verifies that each webpage is returning what you want, you can also hook into an HTML validator and verify that the pages are returning valid HTML. You could even write some multi-step interactions. The key is to make sure that your tests can resist a bit of change in your templates.

We're not going to explicitly write those tests, but its not a bad idea to work through them yourself. We will, on the other hand, do some quick informal testing with our browser to make sure these things are coming together correctly.

First we need to synchronize our database for our project:

python manage.py syncdb

You'll be asked to create a superuser. Go ahead and do so. Once the database synchronization finishes up, next we want to run the development server:

python manage.py runserver

And now lets fire up a browser and navigate to http://127.0.0.1:8000/create/. We're going to create a poll first, because our generic views will complain if there are no polls (if this behavior really bothered us, we could change that behavior in our web/urls.py).

First go ahead and make a poll. It should redirect you to the list of polls. After that go back and try to create a poll with the same question. It should display an error message explaining that this poll has already been created. Good.

Go back to the poll listing and click on one of the polls and make sure that it behaves reasonably (displaying a few snippets of information about our poll).

A snapshot of the current development of this tutorial is available [here][polling4]. And feel free to move on to the fifth segment of this tutorial whenever you are ready.

All Rights Reserved, Will Larson 2007 - 2014.