How to use generic views in Django

This is my first “technical” post here, so bear with me on this one.

Sooner or later in your Django development career you’ll notice that you keep on repeating and implementing same views over and over again:

  • list objects
  • display details for any particular object
  • add/delete/modify the object

When I was reading about Django, I’ve noticed something mentioned that was called “generic views”, but at that time it somehow escaped me, and I wrote numerous views, just to implement those functions I mentioned above. In some extreme cases I’ve had 3-4 different views, effectivelly implementing the same for different models.

So the time came to educate myself and learn about “generic views” that are designed to do just that: implement basic and common tasks, so you don’t have to write a single line of code.

Now let’s start with the model definition. Suppose I have the following model class:

class Rule(models.Model):
    rule = models.TextField()
    description = models.CharField(max_length=400)
 
    def __unicode__(self):
        return self.description[:20]

First of all I need to import generic view functions from the Django libraries, here’s what you need to add to your urls.py file:

from django.views.generic import list_detail, create_update

list_detail provides two methods that are used to represent data:

  • object_list – to display a list of objects
  • object_detail – to display details about any given object

create_update provides three methods to manipulate data:

  • create_object – creates new object
  • update_object – modifies existing object
  • delete_object – deletes any given object

I then need to define a queryset dictionary that contains a list of objects which generic views will operate upon. Add this to your urls.py:

rule_info = {
    'queryset': Rule.objects.all(),
    'template_name': 'display_rule.html',
}

The only required field here is the queryset, others are optional. You don’t even need to define a template name as Django will automatically generate one. If you omit template_name, it will attempt to load a template <app_label>/<model_name>_form.html.

Right, now I’ll do the display bit first. Add the following URL patterns (as you might already guessed it, to ulrs.py file):

url(r'^rule/$', list_detail.object_list, rule_info, name='rule-displaytop'),
url(r'^rule/(?P<object_id>\d+)/, list_detail.object_detail, rule_info, name='rule-display'),

As you can see, I instruct urls.py to call generic views list_detail.object_list and list_detail.object_list and also pass my rule_info dictionary as an argument. The dictionary contains a list of all objects that interest me. List view would simply pass the list on to the template, and the detailed view would select an individual object and pass it on to the template. Therefore the detailed view also expects object_id (which must be a primary key), so that it can find the required object.

When views call templates to display the information, they pass object list as object_list template variable and an instance of any object as object. You can override this by adding template_object_name to the dictionary and setting it to any name you like. If you set it to foo, the names would become: foo_list and foo. For now I leave it as it is. Below is my display_rule.html template:

{% extends "base.html" %}
{% block contents %}
  {% if object %}
    <h1>Rules details</h1>
    <ul>
      <li>ID: {{ object.id }}</li>
      <li>Description: {{ object.description }}</li>
      <li>Rule text:
          {{ object.rule }}
      </li>
    </ul>
    ( <a href="{% url rule-modify object.id %}">modify</a> |
      <a href="{% url rule-delete object.id %}">delete</a> )
  {% else %}
    <h1>List of all Rules</h1>
    {% if object_list %}
      <ul>
        {% for rule in object_list %}
          <li>{{ rule.description }} ( <a href="{% url rule-display rule.id %}">details</a> |
                                       <a href="{% url rule-modify rule.id %}">modify</a> |
                                       <a href="{% url rule-delete rule.id %}">delete</a> )</li>
        {% endfor %}
      </ul>
    {% else %}
      No rules defined yet.
    {% endif %}
  <h3><a href="{% url rule-add %}">Add new rule</a></h3>
{% endif %}
{% endblock %}

As you can see the logic is somewhat simple:

  • Check if the template object object is defined
  • If it is, that means we’ve been called from the detailed object view method and so need to display object information
  • If not, then we’ve been called from the object list view, so we need to iterate through the list
  • Check if the list is not empty (very important! as the lists tend to come empty in some cases…)
  • If the list is not empty – display all objects and provide links to basic functions

Let’s start with adding new objects. You will need to define get_absolute_url method for your class (this will be used to get a URL where to redirect the user once the form has been submitted) and also define Model Form class (not required, strictly speaking, but I like to define it, so that I don’t need to if I decide to change form behaviour). This is how these two classes are going to look now:

class Rule(models.Model):
    rule = models.TextField()
    description = models.CharField(max_length=400)
 
    def __unicode__(self):
        return self.description[:20]
 
    @models.permalink
    def get_absolute_url(self):
        return ('rule-display', (), {'object_id': self.id})
 
class RuleForm(ModelForm):
    class Meta:
        model = Rule

For now ignore the “magic” with URL names, these are used to help reverse URL resolver, and I’m going to talk about these in other posts. Just note that get_absolute_url returns generated URL with object_id embedded in it.

add.html template is very simplistic and contains only few lines:

<form action="." method="POST">
{{ form.as_p }}
<input type="submit" value="Add" />
</form>

This is enough to display all fields and a submit button. If you need anything fancier than that, you’d need to alter the form to suit your needs.

Next, I’ll need to define a dictionary with settings, which will be passed to generic create and modify views, just like a queryset dictionary was used with display views:

rule_form = {
  'form_class': RuleForm,
  'template_name': 'add.html',
}

And finally some URL patterns to handle the requests:

url(r'^rule/(?P<object_id>\d+)/modify/$', create_update.update_object, rule_form, name='rule-modify'),
url(r'^rule/add/$', create_update.create_object, rule_form, name='rule-add'),

As you can see, these two are very similar with one exception, update_object requires object_id, which is embedded in the URL, so you don’t need to worry about it.
Finally let’s sort out the delete object function. This function requires three fields: model name, post delete redirect and also a confirmation template name. All defined in a dictionary form:

rule_delete = {
  'model': Rule,
  'post_delete_redirect': '../..',
  'template_name': 'delete_confirm_rule.html',
}

You also need additional URL pattern:

url(r'^rule/(?P<object_id>\d+)/delete/$, create_update.delete_object, rule_delete, name='rule-delete'),

Confirmation template just asks for a user confirmation and redirects to the same URL, however the method is POST, so the view knows it needs to delete the object rather than display the confirmation page (which happens if the request is GET):

<form method="post" action=".">
<p>Are you sure?</p>
<input type="submit" />
</form>

This is it, now you know how to perform some of the common tasks without writing any views.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Live
  • Netvibes
  • NewsVine
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  • Yahoo! Bookmarks

Related posts:

  1. Query data from Django site with jQuery
  2. Python web crawler