A major part of Django’s appeal for many developers is the fact it allows you to write Web applications using Python. Paradoxically, another major part of Django’s appeal is its generic views feature, enables you to create Web applications without writing much Python code at all. (You learned about generic views in Chapter 5, “URLs, HTTP Mechanisms, and Views.”)
Django’s generic views can be a powerful tool for rapid development and prototyping. Don’t be fooled by the name though. Generic views are not just temporary scaffolding. Newcomers to Django can be forgiven for thinking the name must refer to some sort of default template for displaying your data, but the reality is quite different. Remember, a Django view is Python code that accepts an HttpRequest
and returns an HttpResponse
. The design of the data objects passed to that view, and the template used to render that response, are completely up to you.
In this section, we walk through a simple pastebin application that relies on generic views. Here are the features our application has
A form for submitting new items with one mandatory field (the content) and two optional ones (the poster’s name and a title)
A clickable list of recent items
A detail view for each item
An administrative interface that allows us (as site owners) to edit or delete existing entries
Syntax colorizing
Periodic cleanup of outdated items
To achieve all this, we write approximately zero lines of imperative Python code. We create a model file that describes our data and its attributes and templates to display it, but nearly every other aspect of our application is handled by Django itself.
The code for this example application is functionally similar to the first version of dpaste.com, the Django community pastebin. The present-day dpaste.com no longer is a pure-generic-views application, but the heart of its simplicity and utility is present in this chapter’s code.
One note on approach before we dive in: The essence of this example is seeing how much work we can hand off to the framework. Some might call this approach lazy, but every line of code you don’t write is one you don’t have to debug. So in this example, where there’s a choice between writing a little more code to get some custom behavior or enabling Django to do it for us, we’re going to allow Django to do it.
Here’s the complete models.py
for our pastebin application. It defines a simple five-field data structure, some Meta
options, and a couple methods that are leveraged by the generic view code and our templates. It also registers our model with the admin and sets some list-related admin options.
import datetime from django.db import models from django.db.models import permalink from django.contrib import admin class Paste(models.Model): """A single pastebin item""" SYNTAX_CHOICES = ( (0, "Plain"), (1, "Python"), (2, "HTML"), (3, "SQL"), (4, "Javascript"), (5, "CSS"), ) content = models.TextField() title = models.CharField(blank=True, max_length=30) syntax = models.IntegerField(max_length=30, choices=SYNTAX_CHOICES, default=0) poster = models.CharField(blank=True, max_length=30) timestamp = models.DateTimeField(default=datetime.datetime.now, blank=True) class Meta: ordering = ('-timestamp',) def __unicode__(self): return "%s (%s)" % (self.title or "#%s" % self.id, self.get_syntax_display()) @permalink def get_absolute_url(self): return ('django.views.generic.list_detail.object_detail', None, {'object_id': self.id}) class PasteAdmin(admin.ModelAdmin): list_display = ('__unicode__', 'title', 'poster', 'syntax', 'timestamp') list_filter = ('timestamp', 'syntax') admin.site.register(Paste, PasteAdmin)
Most of these elements you have seen before. What’s unusual here is these lines constitute the bulk of the custom Python code for your pastebin application. Except for some simple rules in our application’s urls.py
, the heavy lifting is done by the framework itself.
Applications based on generic views, such as this one, really show off the power that Django’s DRY (Don’t Repeat Yourself) philosophy yields. In the example we’re about to build, the five field definitions at the core of our previous model (content
, title
, syntax
, poster
, and timestamp
) are used by:
The manage.py syncdb
command, which creates a table in the database
The admin app, which generates an editing interface for our data
The object_detail
generic view, which fetches instances of our model and sends them to the template system for display
The create_update
generic view, which generates and processes a submission form for adding new paste items
The model methods are also used in multiple places. The admin app uses the __unicode__
object when it needs to refer to the object by name (for example, in deletion-confirmation messages), and the get_absolute_url
method for generating “View on site” links. Our template uses __unicode__
implicitly anywhere it needs to display the name of an item and get_absolute_url
to generate links in the list of recent items.
Now let’s create some basic templates that are used to render our content to the user. First we need a base template, a technique you’ve seen in earlier chapters such as Chapter 7, “Photo Gallery.” Save this in pastebin/templates
.
<html> <head> <title>{% block title %}{% endblock %}</title> <style type="text/css"> body { margin: 30px; font-family: sans-serif; background: #fff; } h1 { background: #ccf; padding: 20px; } pre { padding: 20px; background: #ddd; } </style> </head> <body> <p><a href="/paste/add/">Add one</a> • <a href="/paste/">List all</a></p> {% block content %}{% endblock %} </body> </html>
Once our base template is set up, let’s create a form for users to paste their code to our application. Save the following in pastebin/templates/pastebin/paste_form.html
. (Keep reading for an explanation of the seeming redundancy in the path name.)
{% extends "base.html" %} {% block title %}Add{% endblock %} {% block content %} <h1>Paste something</h1> <form action="" method="POST"> Title: {{ form.title }}<br> Poster: {{ form.poster }}<br> Syntax: {{ form.syntax }}<br> {{ form.content }}<br> <input type="submit" name="submit" value="Paste" id="submit"> </form> {% endblock %}
Next, we make a template for our list view. This shows all the recently pasted items and enables the user to select one with a click. Save this to pastebin/templates/pastebin/paste_list.html
.
{% extends "base.html" %} {% block title %}Recently Pasted{% endblock %} {% block content %} <h1>Recently Pasted</h1> <ul> {% for object in object_list %} <li><a href="{{ object.get_absolute_url }}">{{ object }}</a></li> {% endfor %} </ul> {% endblock %}
Finally, we make a detail page template. This is the one people spend the most time looking at. Save this as pastebin/templates/pastebin/paste_detail.html
.
{% extends "base.html" %} {% block title %}{{ object }}{% endblock %} {% block content %} <h1>{{ object }}</h1> <p>Syntax: {{ object.get_syntax_display }}<br> Date: {{ object.timestamp|date:"r" }}</p> <code><pre>{{ object.content }}</pre></code> {% endblock %}
Because the structure of our application is so clear, creating our URLs is fairly easy. The only tricky part here is the apparatus needed to take advantage of generic views. We need to design three URL patterns—one for listing all items, one for showing individual items, and one for adding new items.
from django.conf.urls.defaults import * from django.views.generic.list_detail import object_list, object_detail from django.views.generic.create_update import create_object from pastebin.models import Paste display_info = {'queryset': Paste.objects.all()} create_info = {'model': Paste} urlpatterns = patterns('', url(r'^$', object_list, dict(display_info, allow_empty=True)), url(r'^(?P<object_id>d+)/$', object_detail, display_info), url(r'^add/$', create_object, create_info), )
This is really the heart of our application. We import three function objects, view functions, from django.views.generic
:
django.views.generic.list_detail.object_list
django.views.generic.list_detail.object_detail
django.views.generic.create_update.create_object
In addition to the HttpRequest
that all Django views, generic or otherwise, take as their first argument, these views also get passed a dictionary of additional values; we define two different ones in our previous URLconf. The names, display_info
and create_info
, are arbitrary (although _info
is a conventional suffix for these dictionaries), but their contents are structured specifically for the generic views we are using. The list_detail
views expect a queryset containing all eligible objects with a key of queryset
. The create_update
views expect the model class (not an instance) with a key of model
. In the case of object_list
, we add allow_empty=True
to the dictionary to tell the view we want to see the page even if there are no objects in the database.
There are many other possible values you can include in these dictionaries. Because they are the primary method for customizing the behavior of generic views they can get quite crowded. For a full list of the options in these views, see the official Django documentation. For now we’re keeping it simple.
There’s a special rule at play here that is worth knowing about when you begin expanding your use of these _info
dictionaries. The code in your URLconf is not evaluated fresh on every request. This means that, if nothing special were done, the Paste.objects.all
queryset we set up here could actually get stale as new objects were added, edited, or removed by users or site admins. Luckily, Django knows this and is explicitly instructed not to cache the data with the key queryset
.
Even though we haven’t written much in the way of imperative code, we now have a functional application. Let’s try it out. When we launch the app, we see a screen as shown in Figure 10.1—an empty pastebin.
Let’s think for a moment about all the things Django has done to get this page to us; it parsed our requested URL, determined which view needs to be called, passed an (empty) queryset derived from our model, found our template file, rendered the template using the appropriate context, and returned the resulting HTML content to the browser.
Now let’s add some content. If we click the Add One link, we should see our blank form. This form is a collaboration between Django’s view and our template. The generic create_update
view introspects our model, generates HTML form elements, and passes them to our template in the {{ form }}
template variable. Our template unpacks these elements and displays a form to the user. Note the <form>
tags and the submit button were our responsibility.
The resulting form should look something like Figure 10.2.
Our friendly pastebin doesn’t force the user to do much. Only the Code field is required, in fact. Pastebins are only useful if they are convenient, and long lists of required fields are not convenient.
By filling in the form and clicking the Paste button, we are calling Django’s create_update
generic view again. Because the data is being sent via HTTP POST instead of GET, the view knows that instead of displaying our blank form, it needs to process the user’s input and (if possible) store it in the database.
One aspect of the create_update
view we don’t illustrate here is validation. What happens if the user omits a required field? In this case, the form is simply displayed again. Our extremely simple template does not include code that looks for or displays validation error messages, but in fact Django is passing them along in the {{ form.errors }}
template variable. A more robust implementation would look for those errors and display them as helpful messages to the user.
Assuming the user has managed to fill in the one required field and click Paste Django processes the form input (again, via the create_update
generic view) and stores it in the database. It then redirects to the newly created object’s get_absolute_url
and presents us with our submission rendered via the pastebin_detail.html
template, as shown in Figure 10.3.
This is the most likely thing that a user wants after submitting an item—a view of that item and a URL that can be sent to others.
If the user wants to see what other items are in the pastebin, the List All link takes him there. Via Django’s object_list
generic view and our paste_list.html
template, a simple bulleted list of pasted items is displayed, as shown in Figure 10.4.
This list illustrates the working of the object_list
generic view nicely. The display_info
dictionary in our URLconf passes the queryset representing all our Paste objects to the object_list
view. That view passes the objects along to our template, where our concise {% for object in object_list %}
loop generates the clickable list.
In practice, a list of submitted items is not necessarily a great feature for this type of site. Pastebin users typically care only about their own submissions. Pastebin operators have been known to muse, “Why do spammers keep pasting stuff into my pastebin?” The answer, of course, is spammers “care” about any mechanism by which their content can be placed in front of unsuspecting users. A pastebin’s list of recent items serves this purpose handily, despite being an incongruous venue for commercial messages.
Finally, don’t forget on top of all the parts of our application that we more or less explicitly designed, we also get the admin app. Given the options we specified in our PasteAdmin
class, our admin looks something like Figure 10.5.
Note by using our model’s __unicode__
method as the first argument to the admin’s list_display
, rather than a particular model field, the clickable name for each item can be adjusted depending on what information is available.
What you have at this point is a useful, usable application powered entirely by Django’s generic views. Although a perfectly reasonable strategy for enhancing the application would be to begin adding your own custom view code, there are actually many more improvements you could make while sticking to generic views. We tackle three: keeping the list of recent pastes manageable, adding syntax highlighting to our entries, and scheduling cleanup of old entries.
That Recently Pasted list is cool when you have only a few items, but if you were running a busy public site, the list could quickly grow unwieldy. There are ways we could limit the number of items in this list. Given our focus on generic views, the best place to take care of it here is in the template.
This just takes one change to line 6 of our paste_list.html
template, applying a slice
filter to object_list
.
{% for object in object_list|slice:":10" %}
Here’s how this works: The URLconf passes the template a queryset representing all the pastes in the database. They’re sorted in descending order by timestamp, thanks to the ordering = ('-timestamp')
line in our models.py
. The for-loop in the template then takes the top ten items and iterates over them.
The value passed to the slice
filter is exactly what we’d pass to a normal Python object, minus the brackets. An equivalent example in Python would be something such as:
>>> number_list = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] >>> print number_list[:10] [15, 14, 13, 12, 11, 10, 9, 8, 7, 6]
If Django’s querysets weren’t “lazy”—that is, if we passed the complete list of objects just to throw away all but ten—this would be madness. If we had thousands of items, the memory consumption of our Web server processes would skyrocket. Because the queryset isn’t evaluated until the last possible moment (in this case, the for
loop in our template), there’s no penalty for specifying Paste.objects.all()
in the URLconf and then slicing in the template. Also, because the choice of how many items to display in the list is really a presentational one, the template is an excellent place to do the trimming.
A pastebin is a lot more useful (and attractive) if it knows how to properly apply colored syntax highlighting to the submitted snippets. There are various ways to do this (including the excellent Python-based library Pygments, which dpaste.com uses), but the easiest path to implementation here is to make the highlighting happen on the client side in Javascript.
A programmer named Alex Gorbatchev created an excellent and widely used highlighting utility in Javascript. Called simply Syntax Highlighter, it can be fetched from Google Code (http://code.google.com/p/syntaxhighlighter/).
A full set of instructions and examples on using Syntax Highlighter can be found on the project’s Web site, but here’s a run-through of how we’d get syntax highlighting for our Python code samples.
First, we update our paste_detail.html
template to look like this.
{% extends "base.html" %} {% block title %}{{ object }}{% endblock %} {% block content %} <h1>{{ object }}</h1> <p>Syntax: {{ object.get_syntax_display }}<br> Date: {{ object.timestamp|date:"r" }}</p> <code><pre name="code" class="{{ object.get_syntax_display|lower }}"> {{ object.content }}</pre></code> <link type="text/css" rel="stylesheet" href="/static/css/SyntaxHighlighter.css"></link> <script language="javascript" src="/static/js/shCore.js"></script> <script language="javascript" src="/static/js/shBrushPython.js"></script> <script language="javascript" src="/static/js/shBrushXml.js"></script> <script language="javascript" src="/static/js/shBrushJscript.js"></script> <script language="javascript" src="/static/js/shBrushSql.js"></script> <script language="javascript" src="/static/js/shBrushCss.js"></script> <script language="javascript"> dp.SyntaxHighlighter.HighlightAll('code'), </script> {% endblock %}
We added name
and class
attributes to our <pre>
tag. This enables our code block to be located by the Javascript when it runs.
<pre name="code" class="{{ object.get_syntax_display|lower }}">
That’s all it takes. When the browser renders the page, the syntax highlighter JavaScript code runs, transforming the bland monochromatic code sample before the user even sees it. The output should look something like Figure 10.6.
Items posted on pastebin sites tend to be ephemeral, so it’s nice to perform some periodic automated cleanup of older items. The best way to do this is with a nightly cron job running on your server.
Cron jobs and other Django scripts that are designed to run outside the Web server environment are another testament to the power of Django’s “it’s just Python” approach. Writing a script that does stuff with your Django app’s objects involves very little that is specific to Django. The following simple script depends on the following assumptions:
The environment variable DJANGO_SETTINGS_MODULE
has been set to a string containing the Python pathname of your project’s settings file (for example, “pastesite.settings”).
You have a setting named EXPIRY_DAYS
in your project’s settings
module.
Your project’s name is “pastesite.”
Assuming those are taken care of, there’s little else for you to do other than test and deploy.
#!/usr/bin/env python import datetime from django.conf import settings from pastesite.pastebin.models import Paste today = datetime.date.today() cutoff = (today - datetime.timedelta(days=settings.EXPIRY_DAYS)) Paste.objects.filter(timestamp__lt=cutoff).delete()
The last line of the script is where all the action is—it uses Django’s ORM to select all objects in the database whose timestamp is older than the calculated cutoff, and then deletes them en masse.
Depending on your database engine, you can also periodically “vacuum” or reclaim the space left by the deleted entries. A simple code snippet to perform this on a SQLite database can be found at http://djangosnippets.org.
Hopefully you are now convinced of the power of Django’s generic views. Our example pastebin site is simple in its implementation, but think of all the features it has: input validation, redirect-on-post, detail and list views, and so on. But perhaps even better than having these is knowing that with Django we have a solid base for expansion. If we want to localize our app, or fortify it against Digg-level traffic by adding caching, Django is ready for us.
18.223.195.97