May 12 2013

Behold the power of default – How to set the default argument of a Django model field equal to a class method using lambda

Django model fields accept an optional default argument to set the default value of the field.  This can be either a value or a callable object.  A very common use for the default argument is to set created_at and modified_at datetime fields to datetime.datetime.now.  But default can be much more powerful than that.

Continue reading


Mar 1 2012

How to add a UUID field in Django using Django Extensions and how to make it a read-only admin field

I’m using Django Extensions which is a great collection of extensions and utilities for the Django framework.  For a project I’m using it to generate and store UUID fields in several models.  I found an easy way to add that UUID field to my admin as read-only.

A sample model using Django Extensions’ UUID field (I chose to use a version 4 UUID):

 Python |  copy code |? 
1
from django.db import models
2
 
3
from django_extensions.db.fields import UUIDField
4
 
5
class SomeClass(models.Model):
6
    uuid = UUIDField(version=4)
7
    # ...other fields...

Sample admin.py class to show UUID as a read-only field:

 Python |  copy code |? 
1
from myapp.models import SomeClass
2
 
3
from django.contrib import admin
4
 
5
class SomeClassAdmin(admin.ModelAdmin):
6
    readonly_fields = ('uuid',)
7
 
8
admin.site.register(SomeClass, SomeClassAdmin)

That’s it!  Very easy to implement a UUID and make it visible as read-only in the Django admin.  Note that you need at least Django 1.2. More here.


Feb 18 2012

Tips for creating 404 ‘page not found’ and 500 ‘server error’ templates in Django, plus configuring email alerts

One of the steps that you have to take [at least] before deploying a Django project to production is to create templates for 404 (page not found) and 500 (server error) errors.   You can also setup some error and broken link reporting.  Here are a few tips:

Where the 404 and 500 templates live

The 404.html and 500.html templates live in the root of your templates directory (TEMPLATE_DIRS setting).

Creating the 404 template

The 404.html template is shown when a page is not found.  It is often a good idea to maintain the overall look and feel of the site even if a piece of content is not found.  As it is good practice to use template inheritance with Django’s templating system, create a 404.html template that extends your base template.  Here is an example:

 HTML |  copy code |? 
01
{% extends "base.html" %}
02
{% load i18n %}
03
 
04
{% block title %}Page not found{% endblock %}
05
 
06
{% block content %}
07
<h1>Page not found</h1>
08
 
09
<p>Sorry, but the requested page could not be found.</p>
10
{% endblock %}

Testing the 404 template

It is very easy to test your 404.html template.  Simply change DEBUG to False in settings.py and try visiting a non-existent URL.  Your 404.html page not found template should be served.

Creating the 500 template

The 500.html template is shown when there is some kind of catastrophic server error.  Since you cannot be sure what the root cause of the issue is, you cannot rely upon the entire Django framework being available and should therefore keep it decoupled from everything.  A simple HTML file is a good choice.  Here is an example:

 HTML |  copy code |? 
01
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
02
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
03
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
04
 
05
<head>
06
 <title>Page unavailable</title>
07
</head>
08
 
09
<body>
10
 <h1>Page unavailable</h1>
11
 
12
 <p>Sorry, but the requested page is unavailable due to a server hiccup.</p>
13
 
14
 <p>Our engineers have been notified, so check back later.</p>
15
</body>
16
 
17
</html>

Testing the 500 template

Testing the 500.html template is also easy.  If a 404 (page not found) error occurs and Django cannot find a 404.html template, it will return a 500 error and therefore the 500.html template.  Just like testing the 404.html template, be sure that DEBUG is set to False in settings.py.  Temporarily rename your 404.html file to something like 404.html.bak.  Try visiting a non-existent URL and you should be served the 500.html server error template.

Configuring error alert emails

Django can email a list of appropriate folks whenever an unhandled exception occurs, including a trace, variables, and settings.  First, in settings.py, add the appropriate names and email addresses to ADMINS, such as

 Python |  copy code |? 
1
ADMINS = (
2
    # ('Your Name', 'your_email@example.com'),
3
    ('John Doe', 'johndoe@gmail.com'),
4
    ('Jane Doe', 'janedoe@gmail.com'),
5
)

Second, configure Django to send email.  This isn’t hard if you look at the documentation.  In a nutshell, you need to configure the settings EMAIL_HOST in settings.py and possibly EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_PORT, or EMAIL_USE_TLS depending upon how you are sending your emails.  I’m running hMailServer on my Windows development machine and so I only set EMAIL_HOST to localhost and did not have to configure those other options.  I’ve configured hMailServer to relay through Google’s SMTP server using my gmail credentials.  (hMailServer offers some nice abilities to monitor SMTP queues and logging to debug email issues.)  One last gotchya: you will likely have to set SERVER_EMAIL to something other than the default root@localhost, as some SMTP servers block this, which gmail does.  It took me a little while to figure out why my emails weren’t being sent, so if you don’t see them come out the other side, make sure SERVER_EMAIL is set to something like django@myhost.com.

You can easily test this to make sure the emails are being sent by following the steps to test your 500.html server error template described above.  Out the other end you should receive an email with an error TemplateDoesNotExist: 404.html.

Configuring broken link alert emails

As with errors, Django can email a list of appropriate folks whenever a 404 page not found error is raised with a non-empty referrer.  If you want email sent for these broken links you need to make sure that CommonMiddleware is installed (it is by default).  Then edit your settings.py file and add the appropriate names and emails to MANAGERS.  By default, MANAGERS it set to ADMINS, which is probably fine for most of us. You then need to set SEND_BROKEN_LINK_EMAILS to True.

 Python |  copy code |? 
1
# We want to be notified of 404s via email to the MANAGERS
2
SEND_BROKEN_LINK_EMAILS = True
3
 
4
MANAGERS = ADMINS


Feb 17 2012

A better way to set your Django template directory setting — dynamically

When using Django you must specify where the framework can find your templates by setting the TEMPLATE_DIRS setting in settings.py.  Typically I just set it to a static path, but recently came across a great tip about how to set it dynamically (thanks to The Definitive Guide to Django).

In settings.py, I would usually specify something like this:

 Python |  copy code |? 
1
TEMPLATE_DIRS = (
2
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
3
    # Always use forward slashes, even on Windows.
4
    # Don't forget to use absolute paths, not relative paths.
5
    "C:/MyDjango/myproject/mytemplates"
6
)

But there is a much better way to specific this such that it is dynamic:

 Python |  copy code |? 
1
import os.path
2
 
3
TEMPLATE_DIRS = (
4
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
5
    # Always use forward slashes, even on Windows.
6
    # Don't forget to use absolute paths, not relative paths.
7
    os.path.join(os.path.dirname(__file__), 'mytemplates').replace('\\','/'),
8
)

This solution uses os.path.dirname(__file__), which gets the name of the directory that the current file resides in using __file__, which references the current Python module which the code lives in (settings.py).  The appropriate sub-directory mytemplates is appended using os.path.join. Finally, the .replace('\\','/') replaces any pesky backslashes with the required forward slashes.

A couple notes:

  1. Be sure to include the trailing comma at the end of the line, as you are able to specify more than one template directory and Django will not be happy without the comma.
  2. You must import os.path to have access to the appropriate Python functions used to assemble the path.

The flexibility of this solution should be immediately apparent, allowing for easily moving your project around, including to production, without having to update your template directory.


Dec 6 2011

How to Properly Set a Default Value for a DateTimeField in Django

It is pretty common to have database fields to track the created and updated dates for objects in your model.  But how do you make sure that every time a new object is created they receive the current date and time?  That’s easy: just set the default option for your DateTimeField, such as

created_date = models.DateTimeField('date created', default=datetime.now())

That’s exactly what I’ve done and while developing with Django’s internal development server it works great!  Then I deployed my project to production which uses Apache with mod_wsgi and I noticed that the timestamps were wrong.  They seemed to coincide with when I had restarted Apache.  I googled things like “django datetime incorrect” and found other people with the same problem.  Some people resorted to overriding the save method, while others created database triggers to update with the current date and time on insert.  I didn’t think I should have to override the save method for something like this and I certainly didn’t want to start creating database triggers (I use Django to keep all of my logic in one place and abstract away my database!).

After doing some more research, it turns out that while I had read the manual, I didn’t understand the manual.  Referring to the Django docs for the default field option for models

The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.

you can see that you can either pass a value or callable object.  I thought that I was telling Django to evaluate the current time every time a new object was created, but I wasn’t.  What instead was happening was datetime.now() was being evaluated when the class was first instantiated (when the web server was restarted!) and that value was being reused every time a new object was created.  I needed to instead pass a callable object that would return the current date and time whenever a new object was created.

I updated my code from this

created_date = models.DateTimeField('date created', default=datetime.now())

to this

created_date = models.DateTimeField('date created', default=datetime.now)

Note that datetime.now() is evaluated only once when the class is instantiated and datetime.now is a callable object that is evaluated each time a new object is created.  Including the parenthesis executes the function and passes the result while excluding the parenthesis passes the function itself.  This was a good lesson to learn and will definitely help me with my future Django coding.  Hopefully this tidbit helps someone else as well.