biais.org

Tuesday 16 January 2007

Django generic view and performance problem

Django generic views let the developper to quickly write common pattern to create, update, delete and show objects. There is a common problem with the create / update generic view discussed on the django developpers mail list with solutions on the django wiki. Generic view create_object generates a HttpResponse from a model name. You can then write a basic template and use the form generated by the generic view.

A basic example starting with the model.py:

class Thing(models.Model):
    name = models.CharField(maxlength=100)

views.py (note you can directly write it in your urls.py file as in the django documentation):

from myapp.models import Thing
 
def create_thing(request):
    return django.views.generic.create_update.create_object(Thing, 
             template='create.html', post_save_redirect="..")

In the urls.py file:

(r'^creatething/', "myapp.views.create_thing")

In the template create.html:

<form method="post" action="">
  <p>
    <label for="id_name">Name:</label> {{ form.name }}
  </p>
  <input type="submit" />
</form>

And, that's all ! Now, we are going to update our models:

class Element(models.Model):
    name = models.CharField(maxlength=100)
 
class Thing(models.Model):
    element = models.ForeignKey(Element)
    name = models.CharField(maxlength=100)

The default behavior of the generic_view is to generate a form with a combobox to select the foreign element from a list. The problem here, if there is a lot of Element objects, django needs to get all data from the Element table and generate an enormous combobox. We are solving this problem by using the raw_id_admin option. Note: the problem go upstream to the generic view from the AutomaticManipulator.

Supposing we are creating Thing from an element_detail page.

(r'^(?P<elementname>\w+)/creatething/$', "myapp.views.create_thing")

Add the raw_id_admin option:

class Thing(models.Model):
    element = models.ForeignKey(Element, raw_id_admin=True)
    name = models.CharField(maxlength=100)

Modify the views.py

from myapp.models import Thing, Other
 
def create_thing(request, elementname):
    element = get_object_or_404(Element, name=elementname)
    return django.views.generic.create_update.create_object(Thing, 
        template='create.html', context={'element':element}, 
        post_save_redirect="..")

Modify the template:

<form method="post" action="">
  <input type="hidden" id="id_element" name="element" value="{{element.id}}" />
  <p>
    <label for="id_name">Name:</label> {{ form.name }}
  </p>
  <input type="submit" />
</form>

Ok, the performance problem for creating a new Thing is solved, but you need to find the Element before. How ?

Thursday 11 January 2007

Django custom template filters

Django template filters are like unix pipes. In this example, we want to create a filter that take a $Date$ Subversion keyword and modify the date and time string format. Note: that also work with CVS, because CVS and Subversion shares the same keyword syntax.

Create a templatetags directory in you application folder, touch a __init_.py and a file where you are going to write your filters, for this example svn_keyword_filters.py

You should have a directory tree looking like:

myapp/
    views.py
    templatetags/
        __init__.py
        svn_keyword_filters.py

In svn_keyword_filters.py write:

from django import template
register = template.Library()
 
@register.filter("svndate")
def svndate(value):
    return value[7:26]

In the template add something like:

<p>
  {% load svn_keyword_filters  %}
  {{ "$Date: 2006-11-02 11:45:14 +0100 (Thu, 02 Nov 2006) $"|svndate }}
</p>

Produces this html output

<p>
  2006-11-02 11:45:14
</p>

Check the Django documentation about custom template filters.

Saturday 6 January 2007

Django framework and complex DB request

I'm using the Django framework for my project at work. Django is really great but sometimes I have difficulties to find the "good way". Today, I discovered how to do complex lookups. Starting with this models :
from django.db import models
class Recipe:
     name = models.CharField(unique=True)
class Ingredient:
     recipe = models.ForeignKey(Recipe)
     name = models.CharField()
     quantity = models.FloatField(max_digits=5, decimal_places=2)
You're looking for all recipes containing ingredient name begining by chick or named turkey ?
from myapp.models import Recipe
from django.db.models import Q
 
Recipe.objects.filter(Q(ingredient__name__startswith="chick")\
                     |Q(ingredient__name="turkey"))
Or maybe recipes with name containing "thanks giving" and using chicken as ingredient:
Recipe.objects.filter(Q(name__icontains="thanks giving")\
                     &Q(ingredient__name="chicken"))
Read more in the Django documentation.