Django 1.3, 1.4 tips&tricks

settings.py

  • Either have a global, versioned settings.py file which imports a local (non-versioned) configuration:

    import settings_local
    

    which has a versioned template settings_local.py.template, or use the reverse approach - have a common settings file, e.g. common_settings.py and then a non-versioned settings.py which imports the common stuff. The latter seems to be the preferred way.

  • Figure out project root using either:

    PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
    

    or:

    PROJECT_ROOT = os.path.realpath(os.path.dirname(__file__))
    

    both forms seems to be actively used and they are pretty much equivalent.

  • To get full file paths, use:

    os.path.join(PROJECT_ROOT, 'dir1', 'myfile.txt')
    
  • You probably always want to have the detailed information about errors in templates. This is independent of the DEBUG setting:

    TEMPLATE_DEBUG = True
    
  • You may want to use HttpOnly cookies:

    SESSION_COOKIE_PATH = '/; HttpOnly'
    SESSION_COOKIE_HTTPONLY = True
    

    Changed in version Django: 1.4

    SESSION_COOKIE_HTTPONLY is True by default in Django 1.4+

  • For multilingual sites use:

    USE_I18N = True
    

    You might also want:

    USE_L10N = True
    

    Language definitions:

    gettext = lambda s: s
    LANGUAGES = (
        ('sv', gettext('Swedish')),
        ('en', gettext('English')),
    )
    
  • If you use a global (per-project) template folder you need:

    TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, 'templates'),)
    

(De facto) standard add-ons

  • South migrations - you might want to use the following settings:

    SKIP_SOUTH_TESTS  = True,
    SOUTH_TESTS_MIGRATE  = False
    

    (SKIP_SOUTH_TESTS, SOUTH_TESTS_MIGRATE)

  • Django Debug Toolbar - make sure to configure it according to the docs

  • Django Sentry - the preferred way to catch exceptions and log messages. It has been split into Sentry and Raven so now both are needed. Note that because Sentry/Raven are meant to replace Django’s default mechanism and also to integrate deeply into the framework, some attention is needed during configuration. Also note that there were (still are?) unsolved problems like this one. But still, Sentry/Raven is probably one of the best such tools out there.

MySQL 5.x

Forms

Smart handling of forms in views (Credits go to PyDanny&Co). Instead of this:

def my_view(request):
    if request.method == 'POST':
        form = MyForm(request.POST)
        if form.is_valid():
            form.hooray()
            return HttpResponseRedirect('/success/')
    else:
        form = MyForm()
    return render_to_response('my_template.html', {'form': form})

do this:

def my_view(request):
    form = MyForm(request.POST or None)
    if form.is_valid():
        form.hooray()
        return HttpResponseRedirect('/success/')
    return render_to_response('my_template.html', {'form': form})

The catch here is that form.is_valid() returns False for unbound forms.

Rarely-known (and/or undocumented) Django features

REST, HTTP and Django

URLs, application structure

  • A good practice is to design your URL structure so that it more or less follows the de facto standard convention. Note that this is mostly about “ordnung”, not about being RESTful. It’s very hard, if not impossible, to write a RESTful service - and if you violate any of the REST principles, you’re not RESTful anymore. So just accept that and follow whatever is reasonable.

  • Still not convinced that REST is not what it appears to be (i.e. a way of naming URLs)? Check these resources (in random order): S.O. thread #1, Roy Fielding’s article, S.O. thread #2, Example of RESTful web service design.

  • Specifically, Django sessions are not RESTful so to speak (check these: [1], [2], [3]). But they are great otherwise, so why not use them? Web development is not a purity contest!

  • Still, adopting parts of the REST philosophy is a good idea. Some readings: [1], [2], [3], [4].

  • Get lost, my website is RESTful!!!!! collapses if only it uses HTML forms. For illustration - let’s imagine that we want to add books to a catalog. To create a new book resource you POST data to /books/ collection. If there is any error, you can get one of the HTTP error codes. If the new book resource is created, you get #201 response.

    Now, that’s not how it works in Django (or any other web framework)! In Django, if there is any form validation error, a normal (i.e. #200) response is returned, just with some additional HTML markup for presenting errors to the user. And even if the new book resource is created, a #302 redirect is returned. Moreover, you POST to the very same URL which you get the form from - and not to the /books/ collection!

    Why do we have here such a big deviation from how it should look like in a RESTful case?

    The answer is simple - the HTML form is kind of a separate application, a user interface to the server-side service - in the old days it would just be a standalone program. It’s simply a coincidence (or signum temporis) that now it’s a part of the same web application.

    The moment we abandon the POST-REDIRECT-GET paradigm, and start POSTing forms to the backend using AJAX requests, we have a much cleaner separation of the user interface part and the underlying RESTful (or pseudo-RESTful) service. Only that the application is hooked to an URL in the same URL space..

    So what to do about that? Just treat forms as non-RESTful parts, separate applications that happen to live in the same house. Use a consistent URL naming for them, like /books/1/edit, and don’t think about them more.

  • Some back up for what I’ve written above: [1], [2], [3], [4], [5].

  • Some more reading about “RESTful” URLs: [1], [2].

  • Which HTTP error codes to use? Here’s the answer. Ok ok, I know :-)

  • But seriously, there are some rules that are worth following.

  • HttpResponseBadRequest [400] seems to be a good choice when Django view is reached but request parameters are invalid. Here are some good discussions on that.

  • HttpResponseForbidden [403] seems like a good choice to indicate that authentication is needed in a situation when redirection to the login page doesn’t make sense - e.g. for AJAX requests. Note that there is also 401 code, but it is meant to be used for the purposes of HTTP authentication, and not a custom one. (A nice discussion)

Django and HTTP caching for static assets

  • Introduction to HTTP caching

  • Use an asset manager. There is one shipped with Django 1.3+ (django.contrib.staticfiles) but it’s not too powerful

    • Pick your favourite one from django-pluggables

    • A pretty great one is (was?) django-mediagenerator (Hopefully someone will maintain it)

    • Your picked assed manager should be able to:

      • Combine & minimize CSS and JS scripts, preferably using YUI Compressor and/or Google Closure Compiler

      • Version the assets, i.e. give them unique names like sitescripts.1fhdysjnry46.js - this is required to efficiently cache them

      • Now, you want your web server to serve the assets with one of these headers:

        Expires: (now + 1 year)
        Cache-Control: public, max-age=31536000

        plus this one:

        Last-Modified: {{ date }}
      • Thanks to the above headers, the browser caches the assets for up to one year - and in case it wants to check if an asset has changed, it sends a conditional request (using If-Modified-Since header) that makes it possible for the web server to reply with 304 Not Modified status code.

      • Perfect caching headers

      • Even more, from Yahoo

      • In Apache one need to add something like this to the virtual host definition (after making sure that the relevant modules are loaded):

        <Directory /my/project/dir/_generated_media>
            ExpiresActive On
            ExpiresDefault "access plus 1 year"
            Header merge Cache-Control "public"
            Header unset Etag
            FileETag None
        </Directory>
      • That’s basically all - for static assets there is no need to worry about things like proxy caches storing sensitive data etc.

      • Ah, one more thing - you probably want to have Keep-Alive on for static assets, but it’s not that good for your Django application. So better think about some nginx. Useful link

      • Btw do not get frustrated if the caching doesn’t work when you refresh the page using F5. That’s a known issue.

HTTP caching for Django views

  • There’s probably no single setup suitable for all your views (pages)

  • So let me just give you a few links:

  • Because of all these things to consider, if you don’t have enough manpower to handle it properly, I think that it’s not that unreasonable to just disable HTTP caching using something like this (idea borrowed from Google Docs):

    response['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
    response['Expires'] = 'Fri, 01 Jan 2010 00:00:00 GMT'
    
  • Otherwise you would have to make sure that there’s no leak of sensitive data, no old content is presented to users etc. (Btw using must-revalidate causes the back button in the browser to refresh (reload) the page when pressed.)

Other HTTP performance tips

Non-HTTP caching and Django

Avoid Apache :)