C H A P T E R   12

image

Writing Reusable Django Applications

So far, this book has mostly been concerned with covering various aspects of Django in the context of building a set of specific applications. Through the process of writing the code for those applications, you've seen Django's major components in action and learned how they can drastically reduce the amount of work needed to build useful web applications. But that's really just a small part of what Django can do to help you cut down on development time and effort. By encouraging certain best practices and by making it easier to follow them as you write, Django also helps you improve the quality, flexibility, and reusability of your code. And in the long run, that's a much larger gain.

Time and time again, you've seen how components included in Django, or applications bundled along with it, can help you kick-start the process of developing a new application by handling common tasks for you. When you're developing with Django, you don't need to worry about writing lots of code to handle your database queries. It's easy to route specific URLs to parts of your application or to generate HTML through templating. And when you use the applications bundled with Django, you can get a lot of functionality for “free.” For instance, you've seen how Django provides features such as user accounts and authentication, RSS-feed generation, user-submitted comments, and even a dynamic administrative interface for site content.

From there, the natural next step is to consider ways to write new applications that you can reuse again and again, just as you reuse Django's own components and the bundled applications in django.contrib. The applications in django.contrib provide good examples to look at, because—aside from the fact that they're included in the Django download—there's nothing special or magical about them. All of them, even the administrative interface, are simply applications that have been written with flexibility and reusability in mind, so they're no different from any other well-designed Django application.

As you gain experience with Django and start building up a library of applications you've written yourself, you'll find that developing your own reusable applications is surprisingly easy. Plus, doing so puts a powerful resource at your fingertips: instead of reimplementing a particular feature each time you need it, you can simply write it once and reuse it again and again. This gives you an impressive head start on each new project.

In this chapter, I'll take an in-depth look at some practical guidelines for developing these sorts of reusable applications, and I'll show you some specific techniques that can make the process easier.

One Thing at a Time

A popular adage in software development states that a particular program should “do one thing, and do it well.” This dates back to the early days of the UNIX operating system, which consisted, in part, of a collection of small, simple programs that users could chain together to create powerful effects. Because of this, UNIX is often contrasted with operating systems that tend to use large, complex applications packed with lots of features.

While complex applications do have their place, the philosophy of building up a system from a collection of smaller, self-contained parts opens up a lot of flexibility. Instead of making changes to a large and complicated piece of software when you need new features and keeping track of how all its features interact with one another, you can build up different arrangements of simpler applications and write new code only when you don't yet have the necessary pieces to build what you need.

Although UNIX originally applied this idea to tasks like text processing, this approach is just as powerful when applied to web development. By keeping a library of small, self-contained applications that each handle some particular feature, you gain the ability to reuse them over and over, in different combinations and configurations, as building blocks for new sites.

Staying Focused

One of the greatest dangers in software development is the process of feature creep or scope creep. Suppose you have an idea for an interesting feature that's at least somewhat related to what you're working on, so you go ahead and add it. But once that feature is in place, you start coming up with ideas for ways to build on it and enhance it with even more features and capabilities, and you start writing more and more code to support these features. Eventually, you end up with a huge tangled mess that has strayed significantly from its original purpose.

However, when you're writing code for a modular system like Django, it's often a bit easier to spot the warning signs of feature creep and get back on track. A complex site with a lot of features but only a small number of applications listed in INSTALLED_APPS often indicates that one or more of the applications it's using is trying to do too much.

Similarly, the relatively simple structure of a Django application—models, views, URLs, and maybe some custom forms or template tags—will quickly start to feel cluttered if you're trying to pack in too many features. Sometimes you'll genuinely need to maintain a large number of model classes or logical groups of views and URL patterns in a single application, but often the amount of bookkeeping work you'll need to do to keep that much code organized will hint that your application isn't as tightly focused as it could be.

As a general rule, the easiest way to stay on track is to answer a simple question: “What does this application do?” Rather than list out every feature, just try to summarize the application's purpose. For example, with the weblog application, the answer to this question would be, “Give the site staff an easy interface for posting entries and links into a weblog, and keep these entries organized through tags and topical categories.” For django.contrib.auth, the answer would be, “Provide a mechanism for storing user-account information and for authenticating users so they can interact with the site.”

If you find that your answer to this question is getting long—more than a sentence or two, in a lot of cases—it might be time to step back and evaluate if your application is trying to do too many things at once.

Once you're in this mindset, you'll find that you approach new feature ideas with skepticism. Rather than thinking of features solely in terms of how cool they'd be to have on your site, you'll also start thinking in terms of how they relate to your application's purpose. This makes it a lot easier to weed out things that don't belong and either reject them or file them away to be implemented somewhere else.

Advantages of Tightly Focused Applications

Once you're developing applications with this sort of tight focus, you'll find that it's a lot easier to reuse them. For example, a well-focused application is often a lot simpler to set up and install, because you usually don't have to worry about setting up large numbers of templates or keeping track of (and possibly training your site staff to use) lots of new data models.

You'll also find that it's much easier to adapt a tightly focused application when you encounter situations where you do need to add a new feature or build in more flexibility, because you usually have less code to review and edit and it's usually well-organized. Many extremely useful Django applications consist of only three or four short files of code.

Finally, you'll notice that you suddenly have a much easier time dealing with the real, specific problems your application is trying to solve. When you're no longer maintaining large numbers of unrelated features in a single application, you're free to examine its particular problem domain in much greater detail and come up with many more thorough and flexible solutions.

A good real-world example of this would be to expand the simple user-signup system I presented in Chapter 9 to teach you about Django's form-processing system. It would be tempting to simply go from the system's basic signup form and view and start adding features that have less relevance to the user-signup process. For example, you could let the user fill out a site-specific user profile or set up preferences to control how the site is presented to him. However, that's the beginning of feature creep. Although user profiles and preference systems are important and useful features to have, they don't have a whole lot to do with the user-signup process, and just getting that process right can be complicated enough on its own.

On the other hand, a feature more relevant to the signup process might be an explicit activation step, in which you send the new user an e-mail instructing her to confirm the account. Also, if you need to have user signups on multiple sites, you'll probably need to specify different ways to collect the initial account information. For example, some sites might need new users to read and agree to terms of service or other policies, while others might have restrictions on who can sign up. Finally, many sites also want some way of preventing automated signups by spambots. Many spambots can navigate automatically through an e-mail—based activation system, so you might want to add additional wrinkles to the signup process, such as optionally generating an image with some text in it and requiring the new user to read it and type the text into a field on the form.

This is a common scenario in application development: even something that seems simple at first glance can have a lot of complexity lurking just below the surface. Keeping your applications tightly focused will help you keep your attention on dealing with that complexity, so you don't end up with only a partial solution to the problem you originally set out to solve.

Developing Multiple Applications

The idea that any given application should do one thing and do it well is only half of the process of building complex systems from small, self-contained parts. The other half is the notion that you should start with an initial idea and end up developing several applications that implement different parts of it.

To a certain extent, this is a natural consequence of developing tightly focused applications. If you don't let yourself fall into feature creep within a given application, you'll naturally end up with a list of features you'd like to have but that don't logically belong to that application. The obvious next step, then, is to develop a separate application with an appropriate focus for the features you want to implement.

Getting into the habit of “spinning off” new applications whenever you have a new set of features to implement can be tricky at first, not only because it's easy to fall victim to feature creep, but also because it's extremely tempting to view web development in a way that equates an application with a web site.

Now, sometimes this isn't a bad idea. For example, many popular off-the-shelf weblogging tools take this approach and provide not only basic features like entries and links, but also their own administrative interfaces, their own user and authentication systems, their own templating systems, and so on. Developing a single application that provides all the features of your web site can be an extremely useful way to work for certain cases—for example, when a particular application is geared toward nontechnical or only moderately technical users who simply want to download and install a single package and have their site running immediately.

However, when you're writing applications that are meant to be used and reused by other developers, or just by you as you work on different projects, this can be a disastrous method of developing an application. You'd quickly lose the ability to mix and match specific features as you build new sites, and you'd typically have to compensate by adding systems that let you develop plug-ins or other additions to a single large application. This just increases the complexity of the code and the amount of work you have to do each time you need to add or reuse a feature.

The alternative—viewing a web site as a collection of tightly focused applications, each providing some particular feature or set of features—results in far more flexibility and often encourages better code within each application, as you've already seen. Django is designed to accommodate this style of development:

  • Rather than handling everything through a single, monolithic application, Django has you specify a list of applications to use (the INSTALLED_APPS setting): You can also designate which applications are responsible for which functionality by setting up the root URL configuration.
  • Instead of forcing all the code for a particular site to exist within a single specific directory, Django uses the standard Python import path to look for the applications you list in INSTALLED_APPS: This prevents tying your code to any specific directory structure, and it lets you reuse a single copy of an application in multiple projects rather than requiring you to endlessly copy it into new project directories and keep all those copies updated as you work on the code.
  • Through abstractions such as the Site model in django.contrib.sites, Django encourages you to think in terms of reusing applications across multiple sites, even when those sites share a database and possibly even a single instance of the administrative interface: django.contrib.admin can easily provide administration for multiple sites through a setting called ADMIN_FOR, which lists the settings modules of all the sites to administer.

The net effect of this is that, although you can do so if you're really determined, trying to build all of your features into one large application will often give you the feeling that you're swimming against the current. As soon as you start splitting things up logically according to function, you'll find development to be a lot easier.

Drawing the Lines Between Applications

Of course, this raises the question of how to tell when you should split off a feature or set of features and start developing one or more new, separate applications. To some extent, learning how to recognize the need to spin off new applications is something that comes with experience, but you can follow some good general guidelines to help with the decision-making process.

The most obvious sign that you need to start developing a new, separate application is when you find that there's a particular feature, or some related features, that you want to have but that doesn't logically belong to the application you're working on. For example, you'd probably want to have some form of publicly accessible user-signup system to accompany the cab code-sharing application you developed in the last few chapters, but that system obviously doesn't belong in that application, so you should develop it separately.

This decision-making process gets somewhat trickier when you're considering sets of features that are at least somewhat related. The discussion in the previous section about adding user profiles and preferences to the signup system is a good example of this, because all of the features involved relate in some way to handling user accounts. You can make a case for handling them together, because they'll almost always be used together. Most of the time, a site that has users signing up through a public registration system will also have some sort of profile features or preferences that they can take advantage of.

In these cases, it's often useful to think in terms of orthogonality. Generally, in software development, two features are orthogonal if a change to one doesn't affect the other. User preferences, then, are orthogonal to user signups, because you could, for example, change the way the signup process works (say, by adding an explicit activation step or building in measures to defeat spambots) without changing the way users configure their preferences. When features are clearly orthogonal to each other like this, they almost always belong in separate applications.

Finally, reuse can be a good criterion for determining whether some particular feature deserves to be split out into its own application. If you can imagine a case where you'd want to use that feature, and just that feature, on another site, the odds are good that it ought to be in a separate application to make that reuse easier.

Splitting Up the Code-Sharing Application

For an instructive example of applying these guidelines, consider the cab code-sharing application you developed over the last few chapters. You developed it as a single application, but you might have noticed that it contains several features that could just as easily be split out into separate applications (although they'd all be necessary if you were to deploy an actual code-sharing site publicly).

For example, the rating system you developed was useful and necessary for the social features you wanted to have, but under all three of the guidelines listed previously (unrelated features, orthogonality, and reuse), it would be a strong candidate for becoming its own application:

  • Unrelated features: Providing a mechanism for users to rate code snippets isn't all that closely related to the core purpose of the application, which is providing the means for users to submit and edit the snippets in the first place.
  • Orthogonality: The rating system is largely orthogonal to the rest of the application. For example, you could change it from a simple “up” or “down” rating to a numeric score or to a system where users give ratings such as “three stars out of four,” without affecting the way people submit, edit, and bookmark snippets.
  • Reuse: It's easy to imagine other sites or projects where you'd want to have a system for users to rate content, but where you wouldn't necessarily want to have the code-snippet features along with it.

The same is true of the bookmarking system, for almost precisely the same reasons: it's not related to the core “purpose” of the application (which, again, is the code-snippet functionality). It's orthogonal to the other features. And providing the ability for users to bookmark their favorite pieces of site content is something that'd be useful on a lot of different types of sites.

Building for Flexibility

Logically splitting up functionality into multiple applications is only part of the process of making that functionality reusable. As you've already seen, it's easy to imagine a case where even a seemingly “simple” feature can vary quite a bit from one project to the next. One good example of this would be a contact form. Many different types of sites need some sort of function that lets visitors fill out a form and submit some information to site staff, but the use cases can vary wildly. For example, some sites might want a form that lets visitors send a message to the site owner(s) to provide feedback or report problems. Other sites, often business sites, will probably want to collect more information and might even want different types of forms for different situations. For example, one form might handle sales inquiries, while another could handle customer-service requests. Still other sites might want to supplement the form's validation rules with spam checks (perhaps by using Akismet or some other form of automated analysis).

At first it seems like there'd be no way to develop a single application that can handle all these cases (and this is just a small sample of the use cases for a contact form). You might suspect that you'll just have to bite the bullet and write a different version of the application each time you use it. However, with a bit of planning and a little bit of code, a Django application can become flexible enough to handle all of these variations on the underlying theme, and more.

Flexible Form Handling

If you're going to write a contact-form application, you might start out by defining a simple contact form like this:

from django import forms
from django.core.mail import mail_managers
class ContactForm(forms.Form):
    name = forms.CharField(max_length=255)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea())
    def save(self):
        message = "%s (%s) wrote: %s" % (self.cleaned_data['name'],
                                            self.cleaned_data['email'],
                                            self.cleaned_data['message'])
        mail_managers(subject="Site feedback", message=message)

A simple view called contact_form could process this form:

from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
def contact_form(request):
    if request.method == 'POST':
        form = ContactForm(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/contact/sent/")
    else:
        form = ContactForm()
    return render_to_response('contact_form.html',
                              { 'form': form },
                              context_instance=RequestContext(request))

For the simplest cases, this would be fine. But how could you handle a situation in which you need to use a different form—one with additional fields, for example, or additional validation rules?

The easiest solution is to remember that a Django view is simply a function and that you can define it to take any additional arguments you want to handle. You can add a new argument to the view that specifies the form class to use, and you can reference that argument whenever you need to instantiate a form from within the view:

def contact_form(request, form_class):
    if request.method == 'POST':
        form = form_class(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/contact/sent/")
    else:
        form = form_class()
    return render_to_response('contact_form.html',
                              { 'form': form },
                              context_instance=RequestContext(request))

You can improve this slightly by supplying a default value for the new argument:

def contact_form(request, form_class=ContactForm):

This is how many of the optional parameters to Django's generic views work: the view function accepts a large number of arguments and supplies sensible default values. Then, if you need to change the behavior slightly, you simply pass the appropriate argument.

If you're developing a business site that wants to handle sales inquiries through a form, you could define a form class to handle that—perhaps called SalesInquiryForm—and then set up a URL pattern like this:

url(r'^inquiries/sales/$',
    contact_form,
    { 'form_class': SalesInquiryForm },
    name='sales_inquiry_form'),

The form_class argument you pass here overrides the default in the contact_form view, and—as long as you remember to define a save() method on your SalesInquiryForm class—it simply works. If you need multiple forms of different types, you can reuse the contact_form view multiple times, passing a different form_class argument each time, in much the same way you previously reused generic views by passing different sets of arguments.

Flexible Template Handling

Of course, simply changing the form class might not help very much, because the view will always use the same template—contact_form.html—to render it. But once again, you can make a small change to the view and add some flexibility to the template handling. In this case, you can directly emulate Django's generic views, which all accept an argument called template_name to override the default template they'd use:

def contact_form(request, form_class=ContactForm,
                 template_name='contact_form.html'):
    if request.method == 'POST':
        form = form_class(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect("/contact/sent/")
    else:
        form = form_class()
    return render_to_response(template_name,
                              { 'form': form },
                              context_instance=RequestContext(request))

Then you can change the URL pattern to specify a different template:

url(r'^inquiries/sales/$',
    contact_form,
    { 'form_class': SalesInquiryForm,
      'template_name':  'sales_inquiry.html' },
    name='sales_inquiry_form'),

Being able to change both the form that the view uses and the template it uses to display that form gives you a huge amount of flexibility for reusing this view. Now you can easily set up multiple forms and customize the templates for each one with any specific presentation or instructions you want to add.

Flexible Post-Form Processing

There's one more thing missing here: no matter what arguments you pass to the view, it will always redirect to the URL /contact/sent/ after successful submission. Let's fix that by adding one final argument called success_url:

def contact_form(request, form_class=ContactForm,
                 template_name='contact_form.html',
                 success_url='/contact/sent/'):
    if request.method == 'POST':
        form = form_class(data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(success_url)
    else:
        form = form_class()
    return render_to_response(template_name,
                              { 'form': form },
                              context_instance=RequestContext(request))

Now you have full control over the entire process of displaying, validating, and processing the form:

url(r'^inquiries/sales/$',
    contact_form,
    { 'form_class': SalesInquiryForm,
      'template_name':  'sales_inquiry.html',
      'success_url': 'inquiries/sales/sent/' },
    name='sales_inquiry_form'),

You can now handle all of the cases listed previously—different combinations of forms, additional fields, and additional validation—by nothing more complicated than passing the right arguments to the contact_form view, in exactly the same way you've been passing arguments to Django's generic views. You could add even more flexibility to this view by emulating some other common arguments accepted by generic views. For example, the extra_context argument would be handy to support so that additional custom template variables could be made available.

Of course, it's important not to go overboard and add so many arguments that the view becomes too complex to use or to write, and supporting large numbers of optional arguments can be tricky. The right balance between flexibility and complexity will vary from one situation to the next, but you should try to support at least a few arguments. While you don't have to use the following names for them, picking a standard set of argument names and sticking to them will greatly improve the readability of your code. Also, when you're writing a view, it's a good idea to give your arguments the same names as similar arguments accepted by Django's generic views. In my own applications, I generally try to support at least the following arguments:

  • form_class, when I'm writing a view that handles a form
  • success_url, when I'm writing a view that redirects after successful processing (of a form, for example)
  • template_name, as in generic views
  • extra_context, also as in generic views

Also, I always make sure to use RequestContext for template rendering. This enables both the standard set of context processors, which add things to the context like the identity of the currently logged-in user, as well as any custom context processors that have been added to the site's settings.

Flexible URL Handling

In the previous examples, the default value for the success_url argument was a hard-coded URL. In the applications you've developed in this book, though, you've worked hard to stay away from ever doing that. For example, in the models, when you defined get_absolute_url(), you always used the permalink() decorator to ensure that it uses a reverse URL lookup based on the current URL configuration. And in your templates, you saw how to use the {% url %} tag to perform a similar reverse URL lookup and to ensure you always output the correct URLs for links.

You haven't encountered this issue in a view, however, and neither of the solutions you've seen so far will work in this context. But there is another function that will do what you want: django.core.urlresolvers.reverse(). This is actually the underlying mechanism for both the permalink() decorator and the {% url %} tag. Using reverse, you can easily refer to any URL pattern and have it automatically look up and generate the correct URL. So if you set up a URL pattern with a name of contact_form_sent, for example, you could rewrite the contact_form view's argument list like this (after importing reverse(), of course):

def contact_form(request, form_class=ContactForm,
                 template_name='contact_form.html',
                 success_url=reverse('contact_form_sent')):

And the proper URL would be filled in by a reverse lookup at your live URLConf module.

Whenever you need to refer to or return a URL, you should always use the reverse lookup utility that's appropriate for what you're writing:

  • django.db.models.permalink(): Use this decorator when you're writing a model's get_absolute_url() method or other methods on a model that return a URL.
  • {% url %}: Use this tag when you're writing a template.
  • django.core.urlresolvers.reverse(): Use this function in any other Python code.

To make the reverse lookups easier to use, any URLConf module included in your application should give sensible names to all of its URL patterns (preferably prefixed with the name of the application to avoid name clashes, as you've been doing previously with URL pattern names like cab_snippet_detail).

Taking Advantage of Django's APIs

It's also worth noting that many of Django's own APIs work the same way, or in extremely similar ways, with many different types of models. For example, a Django QuerySet has the same methods—all(), filter(), get(), and so on—regardless of which model it ends up querying against. This means that you can often write code that accepts a QuerySet as an argument and simply applies standard methods to it.

Similarly, you can use the ModelForm helper you saw in Chapter 9 as a way to quickly and easily generate a form for adding or editing any type of object. Because ModelForm works the same way for any model (although customizations such as the exclude feature are typically filled in on a per-model basis), you can use it with any of multiple models, even if you don't know in advance what model you'll be working with.

Staying Generic

In addition to writing views that take optional arguments to customize their behavior, you can also build flexibility into your nonview code by not tying it to specific models or specific ideas of how it ought to work. To see what I mean, think back to the weblog application: when you added the comment-moderation feature, you made some assumptions that limited its flexibility. The solution in that case was to instead use Django's built-in moderation system, which was designed to be truly generic.

And although Django's moderation system is a bit more complex than the comment-moderation feature you originally wrote for the weblog, it pays off in incredible flexibility. You can set up a different set of moderation rules for each model you allow comments on, and when you need to support custom moderation rules that aren't covered by the code in the CommentModerator class, you can subclass it, write the appropriate code for your custom moderation rules, and then use that subclass to handle your comment moderation.

This is a type of situation that recurs frequently in Django application development: a feature that starts out tied to a particular application, or even to a particular model, turns out to be useful in other contexts and gets rewritten to be generic. In fact, that's precisely how Django's comment-moderation system was developed. It began as a piece of code tightly tied to one particular third-party weblogging application, and then evolved into a generic moderation system that could work with any model in any application. At that point, it was spun off into a separate (still third-party) application, designed to enhance and extend Django's comments system. That application turned out to be quite popular, so in Django 1.1 the moderation features were incorporated directly into django.contrib.comments, which is the most logical place for them to be.

Distributing Django Applications

Once you've written an application so that you can reuse it easily, the final step is to make it easily distributable. Even if you never intend to publicly release an application you've written, going through this step can still be useful. You'll end up with a nice, packaged version of your application that you can easily copy from one computer to another, and a simple mechanism for installing it, which ensures that the application will end up in a location that's on the Python import path.

The first step in creating an easily distributed Django application is to make sure you're developing your application as a module that can live directly on the Python import path, rather than one that needs to be placed inside a project directory. Developing in this fashion makes it much easier to move a copy of an application from one computer to another, or to have multiple projects using the same application. You'll recall that the last two applications you built in this book have followed this pattern, and in general, you should always develop standalone applications in this fashion.

Python Packaging Tools

Because a Django application is just a collection of Python code, you should simply use standard Python packaging tools to distribute it. The Python standard library includes the module distutils, which provides the basic functionality you'll need: creating packages, installing them, and registering them with the Python Package Index (if you want to distribute your application to the public).

The primary way you'll use distutils is by writing a script—conventionally called setup.py—that contains some information about your package. Then you'll use that script to generate the package. In the simplest case, this is a three-step process:

  1. In a temporary directory (not one on your Python import path), create an empty setup.py file and a copy of your application's directory, containing its code.
  2. Fill out the setup.py script with the appropriate information.
  3. Run python setup.py sdist to generate the package; this creates a directory called dist that contains the package.

The other common method of distributing Python packages uses a system called setuptools. setuptools has some similarities to distutils—both use a script called setup.py, and the way you use that script to create and install packages is the same. But setuptools adds a large number of features on top of the standard distutils, including ways to specify dependencies between packages and ways to automatically download and install packages and all their dependencies. You can learn more about setuptools online at http://peak.telecommunity.com/DevCenter/setuptools. However, let's use distutils for the example here, because it's part of Python's standard library and thus doesn't require you to install any additional tools to generate packages.

Writing a setup.py Script with distutils

To see how Python's standard distutils library works, let's walk through packaging a simple application. Go to a directory that's not on your Python import path, and in it place the following:

  • An empty file named setup.py
  • An empty file named hello.py

In hello.py, add the following code:

print "Hello! I'm a packaged Python application!"

Obviously, this isn't the most useful Python application ever written, but now that you have a bit of code, you can see how to write the packaging script in setup.py:

from distutils.core import setup
setup(name="hello",
      version="0.1",
      description="A simple packaged Python application",
      author="Your name here",
      author_email="Your e-mail address here",
      url="Your website URL here",
      py_modules=["hello"],
      download_url="URL to download this package here")

Now you can run python setup.py sdist, which creates a dist directory containing a file named hello-0.1.tar.gz. This is a Python package, and you can install it on any computer that has Python available. The installation process is simple: open up the package (the file is a standard compressed archive file that most operating systems can unpack), and it will create a directory called hello-0.1 containing a setup.py script. Running python setup.py install in that directory installs the package on the Python import path.

Of course, this is a very basic example, but it shows most of what you'll need to know to create Python packages. The various arguments to the setup function in your setup.py file provide information about the package, and distutils does the rest. This only gets tricky if your application consists of several modules or submodules, or if it also includes non-Python files (such as documentation files) that need to be included in the package.

To handle multiple modules or submodules, you simply list them in the py_modules argument. For example, if you have an application named foo, which contains a submodule named foo.templatetags, you'd use this argument to tell distutils to include them:

py_modules=["foo", "foo.templatetags"],

The setup script expects the foo module to be alongside it in the same directory, so it looks inside foo to find foo.templatetags for inclusion.

Standard Files to Include in a Package

When you created the previous example package, the setup.py script probably complained about some standard files not being found. Although they're not technically required, several files are typically included with a Python package, and distutils warns you when they're absent. At a minimum, you should include two files in any package you plan to distribute:

  • A file named LICENSE or LICENSE.txt: This should contain copyright information. For many Python packages, this is simply a copy of a standard open source license with the author's name filled in appropriately.
  • A file named README or README.txt: This should provide some basic human-readable information about the package, its contents, and pointers to documentation or further information.

You might also find these other common files in many packages:

  • AUTHORS or AUTHORS.txt: For software developed by a team of contributors, this is often a list of everyone who has contributed code. For large projects, this can grow to an impressive size. Django's AUTHORS file, for example, lists everyone who has contributed code to the project and runs several hundred lines long.
  • INSTALL or INSTALL.txt: This often contains installation instructions. Even though Python packages all offer the standard setup.py install mechanism, some packages might also offer alternative installation methods or include detailed instructions for specialized cases.
  • CHANGELOG or CHANGELOG.txt: This usually includes a brief summary of the application's history, noting the changes between each released version.

Including these sorts of files in a Python package is fairly easy. While the setup.py script specifies the Python modules to be packaged, you can list additional files like these in a file named MANIFEST.in (in the same directory as setup.py). The format of this file is extremely simple and often looks something like this:

include LICENSE.txt
include README.txt
include CHANGELOG.txt

Each include statement goes on a separate line and names a file to be included in the package. For advanced use, such as packaging a directory of documentation files, you can use a recursive-include statement. For example, if documentation files reside in a directory called docs, you could use this statement to include them in the package:

recursive-include docs *

Documenting an Application

Finally, one of the most important parts of a distributable, reusable Django application is good documentation. I haven't talked much about documentation because I've mostly been focused on code, but documentation is essential whenever you're writing code that someone else might end up using (or that you might need to use again after not looking at it for a while).

One thing you can and often should do is include some documentation files in your application's package. You can generally assume that other developers will know how Python and Django work, so you don't need to document things like using setup.py install or adding the application to the INSTALLED_APPS list of a Django project. However, you should explain what your application does and how it works, and you should give at least an outline of each of the following items:

  • Any models provided by your application, their intended uses, and any custom managers or useful custom methods you've set up for them
  • A list of views in your application, along with the template names they expect and any variables they make available in the template context
  • A list of any custom template tags or filters you've provided and what they do
  • A list of any custom forms you've provided and what purposes they serve
  • A list of any third-party Python modules or Django applications your application relies on and information on how to obtain them

In addition to these outlines, or, more often, as a precursor to them, you should also include documentation directly in your code. Python makes it easy to provide documentation alongside the code you're writing by giving docstrings to your Python modules, classes, and functions. A docstring is simply a literal string of text, included as the first thing in the definition of a module, class, or function. To see an example of how this works, launch a Python interpreter and type:

>>> def add(n1, n2):
...     """
...     Add two numbers and return the result.
...
...     """
...     return n1 + n2
...

This defines a simple function and gives it a docstring. You use triple quotes (the """ at the beginning and end of the docstring) because Python allows triple-quoted strings to run over multiple lines.

Docstrings end up being useful in three primary ways:

  • Anyone who's reading your code can also see the docstrings and pick up additional information from them: This is possible because they're included directly in the code.
  • Python's automated help tool knows how to read a docstring and show you useful information: In the previous example, you could type help(add) in the interpreter, and Python would show you the function's argument signature and print its docstring.
  • Other tools can read docstrings and assemble them automatically into documentation in a variety of formats: Several standard or semistandard tools can read through an entire application, for example, and print out organized documentation from the docstrings in HTML or PDF format.

Documentation Displayed Within Django

This last point is particularly important, because Django can sift through your code for docstrings and use them to display useful documentation to users. The administrative interface usually contains a link labeled “Documentation” (in the upper right-hand corner of the main page), which takes the user to a page listing all of the documentation Django can produce (if the necessary Python documentation tools are available; see the next section for details). This includes:

  • A list of all the installed models, organized by the applications they belong to: For each model, Django shows a table listing the fields defined on the model and any custom methods, as well as the docstring of the model class.
  • A list of all the URL patterns and the views they map to: For each view, Django displays the docstring.
  • Lists of all available template tags and filters, both from Django's own built-in set and from any custom tag libraries included in your installed applications: For each tag or filter, Django shows the docstring.

Finally, giving your code good docstrings gives you a head start on producing standalone documentation for your application. It's a good practice to write useful docstrings anyway, because so many tools in Python make use of them. Once you have them, you can copy them into files to use as standalone reference documentation to distribute with your applications.

What to Document

In general, you should be liberal about writing docstrings for classes and functions in your code. It's better to have documentation when you don't need it than to need documentation when you don't have it. Generally, the only time you shouldn't worry about giving something a docstring is when you're writing something that's standard and well-known. For example, you don't need to supply a docstring for the get_absolute_url() method of a model, because that's a standard method to define on models, and you can trust that people reading your code will know why it's there and what it's doing. However, if you're providing a custom save() method, you often should document it, because an explanation of any special behavior it provides will be useful to people reading your code.

Typically, a good docstring provides a short overview of what the associated code is doing. The docstring for a class should explain what the class represents, for example, and how it's intended to be used. The docstring for a function or method should explain what it does and mention any constraints on the arguments or the return value.

Additionally, when writing docstrings you should keep in mind the following items, which are specific to Django:

  • Model classes should include information about any custom managers attached to the model: However, they don't need to include a list of fields in their docstrings, because that's generated automatically.
  • Docstrings for view functions should always mention the template name that will be used: In addition, they should provide a list of variables that are made available to the template.
  • Docstrings for custom template tags should explain the syntax and arguments the tags expect: Ideally, they should also give at least one example of how the tag works.

Within the admin interface, Django can automatically format much of this documentation for you if you have the Python docutils module installed (you can obtain it from http://docutils.sourceforge.net/ if it's not already installed on your computer). The docutils package includes a lightweight syntax called reStructuredText (commonly abbreviated as reST), and Django knows how to transform this into HTML. If you'd like, you can use this syntax in your docstrings to get nicely formatted documentation.

Django also makes use of a couple customized extensions to the reST syntax to allow you to easily refer to Django-specific elements such as model classes or view functions. To see how this works, consider a simple view that might go into your coltrane weblog application:

def latest_entries(request):
    return render_to_response('coltrane/entry_archive.html',
                              { 'latest': Entry.objects.all()[:15] })

Now, you wouldn't ever need to write this view, because Django provides a generic view that serves the same purpose, but you can use it to show off some documentation tricks. Here's the same view with a useful docstring:

def latest_entries(request):
    """
    View of the latest 15 entries published. This is similar to
    the :view:'django.views.generic.date_based.archive_index'
    generic view.
    **Template:**'
    ''coltrane/entry_archive.html''
    **Context:**
    ''latest''
        A list of :model'coltrane.Entry' objects.
    """
    return render_to_response('coltrane/entry_archive.html',
                              { 'latest': Entry.live.all()[:15] })

A lot of what's going on here is fairly simple: line breaks become paragraph breaks in the HTML-formatted documentation; double asterisks become bold text for headings; and the list of context variables becomes an HTML definition list, with the variable name latest (surrounded by backticks) in a monospaced font.

However, two specialized things are going on here: the mention of a generic view, and the mention of the Entry model. These make use of the Django-specific extensions and are transformed into a link to the generic view's documentation and a link to the Entry model's documentation, respectively.

In addition to the :view: and :model: shortcuts shown in the previous example, three others are available:

  • :tag:: This should be followed by the name of a template tag. It links to the tag's documentation.
  • :filter:: This should be followed by the name of a template filter. It links to the filter's documentation.
  • :template:: This should be followed by a template name. It links to a page that either shows locations in your project's TEMPLATE_DIRS setting where that template can be found, or shows nothing if the template can't be found.

Looking Ahead

A lot more can be said about developing Django applications to get the maximum possible use and reuse out of them, but what I've covered here is a good start.

Learning when to apply these general principles to specific applications—and, just as important, when not to apply them (there are no universal rules of software development)—is best accomplished through the experience of writing and using Django applications. Consider making a list of application ideas that interest you, and try your hand at a few of them, even if you never end up using them in any serious situation. Feel free to go back and tinker with the applications you've built in this book. There's a lot of room to expand them and add new features, or even to spin off entire new applications from them. Also, keep in mind that there's a whole ecosystem of Django applications already written and available online, providing a large base of code you can study.

Always remember that Django has a large and friendly community of developers and users who answer questions on mailing lists and in chat rooms. So whenever you get stumped (and we all get stumped once in a while), you can turn to them for help.

Above all, remember what I mentioned back in Chapter 1, when you got your first look at Django: Django's job is to make web development fun again, by relieving you of all the tedium and repetitive busy work that has traditionally been part of the process. So find an idea or two that you like, let Django take care of the heavy lifting for you, and just have fun writing your code.

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

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