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.
Related posts:
nice post, thanks! I’ve never used generic views before – kept on writing my own, but I guess I need to try.
Thanks anyway, Jan
So the generic view can only display items from the same model, or can I pass another model to the template? Let’s say I want to display list of ingredients for a dish, but I also want to have a list of related recipes listed along? If that makes sense?
of course you can. add ‘extra_context’ entry to the dictionary and include as many as you like.
Your classes:
Your ‘queryset’ dictionary:
Hope this helps
it’s also worth mentioning that with create_update functions you can set ‘login_required’ to True. if you do that, Django will only allows these URLs for people that are logged in.