Chapter 10. Pastebin

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.

Note

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.

Defining the Model

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.

Creating the Templates

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> &bull; <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 %}

Designing the URLs

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.

Note

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.

Trying It Out

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.

The empty pastebin

Figure 10.1. The 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.

The Add One form

Figure 10.2. The Add One form

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.

The newly pasted item

Figure 10.3. The newly pasted item

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.

The list of submitted items

Figure 10.4. The list of submitted items

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.

Note

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.

The admin screen

Figure 10.5. The admin screen

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.

Limiting Number of Recent Pastes Displayed

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.

Syntax Highlighting

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.

Syntax coloring applied to a Python source snippet

Figure 10.6. Syntax coloring applied to a Python source snippet

Cleanup Via Cron Job

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.

Note

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.195.97