Busy making things: tinycast, github, links, photos, @mc.

Django Generic Views: CRUD

Posted: August 17th, 2005 | Author: | Filed under: Django, Projects, Python | 100 Comments »

Note: I’ve not yet updated this to reflect the new model syntax. For the time being you can take a look at the new model syntax for tasks here.

There are lots of gems buried in Django that are slowly coming to light. Generic views and specifically the CRUD (create, update, delete) generic views are extremely powerful but underdocumented. This brief tutorial will show you how to make use of CRUD generic views in your Django application.

One of my first encounters with Rails was the simple todo list tutorial which managed to relate lots of useful information by creating a simple yet useful application. While I will do my best to point out interesting and useful things along the way, it is probably best that you be familiar with the official Django tutorials. Now would also probably be a good time to mention that this tutorial works for me using MySQL and revision 524. Django is under constant development, so things may change. I’ll do my best to keep up with changes.

Getting Started

As with all Django projects, the best place to start is to start with django-admin.py startproject todo. Make sure that the directory you created your project in is in your PYTHONPATH, then edit todo/settings/main.py to point to the database of your choice. Now would be a good time to set your DJANGO_SETTINGS_MODULE to "todo.settings.main". Next move to your apps/ dir and create a new application: django-admin.py startapp tasks and django-admin.py init. The initial setup process is covered in much more detail in tutorial 1.

The Model

Now that we have the project set up, let’s take a look at our rather simple model (todo/apps/tasks/models/tasks.py):

from django.core import meta

# Create your models here.

class Task(meta.Model):
  fields = (
    meta.CharField('title', maxlength=200),
    meta.TextField('description'),
    meta.DateTimeField('create_date', 'date created'),
    meta.DateTimeField('due_date', 'date due'),
    meta.BooleanField('done'),
  )

  admin = meta.Admin(
    list_display = ( 'title', 'description', 'create_date', 'due_date', 'done' ),
    search_fields = ['title', 'description'],
    date_hierarchy = 'due_date',
  )

  def __repr__(self):
    return self.title

The model is short and sweet, storing a title, description, two dates, and if the task is done or not. To play with your model in the admin, add the following to INSTALLD_APPS in todo/settings/main.py: 'todo.apps.tasks',

Feel free to play around with your model using the admin site. For details, see tutorial 2.

URL Configuration

Now let’s configure our URLs. We’ll fill in the code behind these URLs as we go. I edited todo/settings/urls/main.py directly, but you’re probably best off decoupling your URLs to your specific app as mentiond in tutorial 3.

from django.conf.urls.defaults import *

info_dict = {
  'app_label': 'tasks',
  'module_name': 'tasks',
}

urlpatterns = patterns('',
  (r'^tasks/?$', 'todo.apps.tasks.views.tasks.index'),
  (r'^tasks/create/?$', 'django.views.generic.create_update.create_object',
  dict(info_dict, post_save_redirect="/tasks/") ),
  (r'^tasks/update/(?P<object_id>\d+)/?$',
  'django.views.generic.create_update.update_object', info_dict),
  (r'^tasks/delete/(?P<object_id>\d+)/?$',
  'django.views.generic.create_update.delete_object',
  dict(info_dict, post_delete_redirect="/tasks/new/") ),
  (r'^tasks/complete/(?P<object_id>\d+)/?$',
  'todo.apps.tasks.views.tasks.complete'),
)

Note: I had to alter the formatting of the urlpatterns in order to make them fit. It looks a lot better in its original formatting.

We use the info_dict to pass information about our application and module to the generic view handlers . The CRUD generic views need only provide these two pieces of information, but some generic views need more. See the generic views documentation for an explanation.

Let’s look at each of these URLs one at a time, along with the code behind them.

Index

(r'^tasks/?$', 'todo.apps.tasks.views.tasks.index'),

This points to our index view, which is an index function in todo/apps/tasks/views/tasks.py:

from django.core import template_loader
from django.core.extensions import DjangoContext as Context
from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect
from django.models.tasks import tasks
from django.core.exceptions import Http404

def index(request):
  notdone_task_list = tasks.get_list(order_by=['-due_date'], done__exact=False)
  done_task_list = tasks.get_list(order_by=['-due_date'], done__exact=True)
  t = template_loader.get_template('tasks/index')
  c = Context(request, {
    'notdone_tasks_list': notdone_task_list,
    'done_tasks_list': notdone_task_list,
  })
  return HttpResponse(t.render(c))

This view creates two lists for us to work with in our template, notdone_tasks_list is (not suprisingly) a list of tasks that are not done yet. Similarly, done_tasks_list contains a list of tasks that have been completed. We will use the template tasks/index.html to render this view.

Make sure that you have a template directory defined in todo.settings.main (this refers to todo/settings/main.py). Here’s mine:

TEMPLATE_DIRS = (
 "/home/mcroydon/django/todo/templates",
)

Now let’s take a look at the template that I’m using for the index:

{% if notdone_tasks_list %}
    <p>Pending Tasks:</p>
    <ul>
    {% for task in notdone_tasks_list %}
        <li>{{ task.title }}: {{ task.description }} <br/> 
          Due {{ task.due_date }} <br/> 
          <a href="/tasks/update/{{ task.id }}/">Update</a> 
          <a href="/tasks/complete/{{ task.id }}/">Complete</a>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>No tasks pending.</p>
{% endif %}
    <p>Completed Tasks:</p>
    <ul>
{% if done_tasks_list %}
    {% for task in done_tasks_list %}
        <li>{{ task.title }}: {{ task.description }} <br/> 
          <a href="/tasks/delete/{{ task.id }}/">Delete</a>
        </li>
    {% endfor %}
    </ul>
{% else %}
    <p>No completed pending.</p>
{% endif %}
<p><a href="/tasks/create/">Add a task</a></p>

Don’t let this index scare you, it’s just a little bit of logic, a little looping, and some links to other parts of the application. See the template authoring guide if you have questions. Here’s a picture to give you a better idea as to how the above barebones template renders in Firefox:

Tasks thumbnail

Create Generic View

Now let’s take a look at the following URL pattern:

(r'^tasks/create/?$', 'django.views.generic.create_update.create_object', dict(info_dict, post_save_redirect="/tasks/") ),

There’s a lot of magic going on here that’s going to make your life really easy. First off, we’re going to call the create_object generic view every time we visit /tasks/create/. If we arrive there with a GET request, the generic view displays a form. Specifically it’s looking for module_name_form.html. In our case it will be looking for tasks_form. It knows what model to look for because of the information we gave it in info_dict. If however we reach this URL via a POST, the create_object generic view will create a new object for us and then redirect us to the URL of our choice (as long as we give it a post_save_redirect).

Here’s the template that I am using for tasks_form.html:

{% block content %}

{% if object %}
<h1>Update task:</h1>
{% else %}
<h1>Create a Task</h1>
{% endif %}

{% if form.has_errors %}
<h2>Please correct the following error{{ form.errors|pluralize }}:</h2>
{% endif %}

<form method="post" action=".">
<p><label for="id_title">Title:</label> {{ form.title }}
{% if form.title.errors %}*** {{ form.title.errors|join:", " }}{% endif %}</p>
<p><label for="id_description">Description:</label> {{ form.description }}
{% if form.description.errors %}*** {{ form.description.errors|join:", " }}{% endif %}</p>
<p><label for="id_create_date_date">Create Date:</label> {{ form.create_date_date }}
{% if form.create_date_date.errors %}*** {{ form.create_date_date.errors|join:", " }}{% endif %}</p>
<p><label for="id_create_date_time">Create Time:</label> {{ form.create_date_time }}
{% if form.create_date_time.errors %}*** {{ form.create_date_time.errors|join:", " }}{% endif %}</p>
<p><label for="id_due_date_date">Due Date:</label> {{ form.due_date_date }}
{% if form.due_date_date.errors %}*** {{ form.due_date_date.errors|join:", " }}{% endif %}</p>
<p><label for="id_due_date_time">Due Time:</label> {{ form.due_date_time }}
{% if form.due_date_time.errors %}*** {{ form.due_date_time.errors|join:", " }}{% endif %}</p>
<p><label for="id_done">Done:</label> {{ form.done }}
{% if form.done.errors %}*** {{ form.done.errors|join:", " }}{% endif %}</p>
<input type="submit" />
</form>
<!--
This is a lifesaver when debugging!
<p> {{ form.error_dict }} </p>
-->

{% endblock %}

Here’s what the create template looks like rendered:

Create Tasks

If we fill out the form without the proper (or correctly formatted) information, we’ll get an error:

Create Tasks

Update

(r'^tasks/update/(?P<object_id>\d+)/?$', 'django.views.generic.create_update.update_object', info_dict),

This URL pattern handles updates. The beautiful thing is it sends requests to tasks_form.html, so with a little logic, 90% of the form can be exactly the same as the create form. If we go to /tasks/create/, we get the blank form. If we visit /tasks/update/1/ we will go to the same form but it will be prepopulated with the data from the task with the ID of 1. Here’s the logic that I used to change the header:

{% if object %}
<h1>Update task:</h1>
{% else %}
<h1>Create a Task</h1>
{% endif %}

So if there’s no object present, we’re creating. If there’s an object present, we’re updating. Same form. Pretty cool.

Warning: It looks like form.create_date_date and form.create_date_time have broken between the time I wrote this and wrote it up. This form will not prepopulate the form with the stored information. There’s a ticket for this, and I’ll update as neccesary when it has been fixed.

Delete

Here’s the URL pattern to delte a task:

(r'^tasks/delete/(?P<object_id>\d+)/?$', 'django.views.generic.create_update.delete_object', dict(info_dict, post_delete_redirect="/tasks/new/") ),

There’s another little Django gem in the delete function. If we end up at /tasks/delete/1 using a GET request, Django will automatically send us to the tasks_form_delete.html template. This allows us to make sure that the user really wanted to delete the task. here’s my very simple tasks_form_delete.html template:

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

Once this form is submitted, the actual delete takes place and we are redirected to the main index page (because we set post_delete_redirect.

CRUD Generic Views And the Rest of Your Application

That pretty much covers the basics of the CRUD generic views. The great thing about generic views is that you can use them along side your custom views. There’s no need to do a ton of custom programming for list/detail, date-based, or CRUD since those generic views are available to you. Because we set up our URL patterns, we can make sure that we craft URLs that look pretty and make sense.

For my sample tasks application I decided that I wanted to create links that would immediately set a task as complete and then redirect to the index. This is pretty much trivial and can be accomplished by adding the following URL pattern and backing it up with the appropriate view. Here’s the pattern:

(r'^tasks/complete/(?P<object_id>\d+)/?$', 'todo.apps.tasks.views.tasks.complete'),

And here’s the view that goes along with it (from todo/apps/tasks/models/tasks.py):

def complete(request, object_id):
  try:
    t = tasks.get_object(pk=object_id)
  except:
    # do something better than this
    raise Http404
  try:
    t.done=True
    t.save()
    return HttpResponseRedirect('/tasks/')
  except:
    # do something better than this
    raise Http404

I do plan to actually handle errors and respond accordingly, but it was late last night and I just wanted to see it work (and it does).

Conclusion

Django rocks. Generic views rock. The framework and specifically the generic views make your life easy. My little tasks app took a few hours to put together, but a significant portion of that was reading up on the documentation, trying to figure out generic views using the existing docs and reading the source, and of course pestering the DjangoMasters about generic views and other stuff on #django (thanks all).

I hope this overview of CRUD generic views helps, but if anything confuses you, don’t hesitate to comment or get in touch with me (matt at ooiio dot com). Also expect to see updates to this tutorial as APIs change and I get a little more time to clean up my code.

Feel free to download and play with my little todo app: todo-tutorial.tar.gz or todo-tutorial.zip. Consider them released under a BSD-style license. Above all, don’t sue me.


100 Comments on “Django Generic Views: CRUD”

  1. 1 plagiarism checker said at 4:56 pm on November 20th, 2010:

    May I post part of this on my site if I post a link back to this website?

  2. 2 free insurance said at 8:13 pm on November 20th, 2010:

    Thought I would comment and say neat theme, did you design it for yourself? Really looks great!

  3. 3 nurse schools said at 10:20 pm on January 5th, 2011:

    I love your writing! Keep it up 🙂

  4. 4 jimmy said at 3:19 pm on January 21st, 2011:

    qi1yXX http://chfEd38MkKsw7cXv0x3Dlc3b7.com

  5. 5 Mercedez Perna said at 4:47 pm on January 21st, 2011:

    Well I definitely liked studying it. This information offered by you is very helpful for accurate planning.

  6. 6 Jude Rosenbush said at 11:42 pm on January 22nd, 2011:

    Very well written story. It will be beneficial to anyone who employess it, as well as myself. Keep up the good work – can’r wait to read more posts.

  7. 7 cheap propecia said at 5:06 am on January 24th, 2011:

    I join. I agree with told all above. Let’s discuss this question. Here or in PM.

  8. 8 buy propecia said at 11:51 am on January 24th, 2011:

    I consider, that you commit an error. Write to me in PM.

  9. 9 Angel Perfume said at 5:26 pm on January 24th, 2011:

    The next time I read a blog, I hope that it doesnt disappoint me as much as this one. I mean, I know it was my choice to read, but I actually thought youd have something interesting to say. All I hear is a bunch of whining about something that you could fix if you werent too busy looking for attention.

  10. 10 sportowe buty said at 5:23 am on February 1st, 2011:

    It is really a great and helpful piece of info. I am glad that you shared this useful information with us. Please keep us informed like this. Thanks for sharing.

  11. 11 Call center Poland said at 7:54 am on February 1st, 2011:

    Generally I don’t read post on blogs, but I wish to say that this write-up very forced me to try and do it! Your writing style has been surprised me. Thanks, quite nice post.

  12. 12 Programy ASP said at 8:23 am on February 1st, 2011:

    I’m not sure where you are getting your information, but good topic. I needs to spend some time learning more or understanding more. Thanks for magnificent information I was looking for this information for my mission.

  13. 13 sklep spożywczy said at 10:12 am on February 1st, 2011:

    Excellent blog here! Also your website loads up fast! What host are you using? Can I get your affiliate link to your host? I wish my website loaded up as quickly as yours lol

  14. 14 nUziCBL said at 2:38 pm on February 1st, 2011:

    OnWZiJ

  15. 15 kronika sportu said at 2:51 am on February 2nd, 2011:

    I precisely wanted to thank you very much once more. I’m not certain the things I would have created without the type of concepts revealed by you over that subject matter. It was actually a very horrifying setting in my view, however , understanding your professional manner you processed that took me to jump with contentment. I’m just grateful for the service and then trust you recognize what an amazing job you have been providing educating some other people using a blog. Most likely you’ve never got to know all of us.

  16. 16 eyelashes for cars said at 4:17 pm on February 5th, 2011:

    Great news it is without doubt. My mother has been waiting for this content.

  17. 17 Serial Belinda said at 12:25 am on February 6th, 2011:

    Hallo, Dear Friend!
    I am Sofia i live in Switzerland and I am Journalist.
    You wrote a skillful passage, I am added it to my Browser rss blog reader.
    Part of your topic interesting for my site readers.
    I want place your info to my personal website.
    Can i to do that, if I add a url to your superb blog ?
    I found your skillful text via bing ..
    Looks like your very good free blog have 6 millions surfers at your excellent weblog now, excellent result for every reporter.

  18. 18 cremation jewelry said at 5:51 pm on February 6th, 2011:

    Cool story indeed. My teacher has been seeking for this info.

  19. 19 Amateur LiveCams said at 4:01 pm on February 7th, 2011:

    I don’t share your opion. Sorry, but what you’re saying here is complete nonsens!

  20. 20 Rapidshare Videos said at 3:52 pm on February 8th, 2011:

    doors.txt;5;10

  21. 21 Nunu said at 11:08 am on February 10th, 2011:

    Hello, world!

  22. 22 Resguider said at 1:25 pm on February 10th, 2011:

    keep up the great work on the site. I kinda like it! 🙂 Could use some more frequent updates, but i’m sure you have got more or better stuff to do , hehe. =)

  23. 23 dfehgiuoe said at 10:04 pm on February 10th, 2011:

    Pretty insightful post. Never thought that it was this straightforward in the end. I had spent a great deal of my time searching for anyone to explain this topic clearly and you are the only one that ever did that. Kudos to you! Continue the good work

  24. 24 Hipolito M. Wiseman said at 11:34 pm on February 10th, 2011:

    Hi there may I use some of the information here in this post if I provide a link back to your site?

  25. 25 Resorna said at 12:24 am on February 11th, 2011:

    really liked the article that you wrote . it really isn’t that easy to find great posts toactually read (you know really READ and not simply browsing through it like a zombie before going to yet another post to just ignore), so cheers man for really not wasting my time! 😉

  26. 26 discount adirondack chairs said at 6:30 pm on February 11th, 2011:

    Thanks for sharing these awesome news with me.

  27. 27 cam jobs said at 10:48 pm on February 11th, 2011:

    Great posting keep up the good work

  28. 28 brenda said at 10:41 am on February 12th, 2011:

    Lol and Lol!

  29. 29 Motorcycle Eyewear said at 6:17 pm on February 12th, 2011:

    Top-notch info it is without doubt. I have been searching for this content. I also like the design was this a free theme or a pay one?

  30. 30 yaretzi said at 3:01 am on February 13th, 2011:

    You can creat mirrow of your site on blogger. It’s really more comfortable for users

  31. 31 thailand said at 4:53 am on February 13th, 2011:

    actually appreciated the article you wrote . it really isn’t that easy to find good text to read (you know READ! and not just browsing through it like some zombie before going to yet another post to just ignore), so cheers mate for not wasting my time on the god forsaken internet. 🙂

  32. 32 Luigi Fulk said at 7:37 pm on February 13th, 2011:

    How did you make this template? I got a blog as well and my template looks kinda bad so people don’t stay on my blog very long :/.

  33. 33 Car Hire Malaga Airport said at 7:55 pm on February 13th, 2011:

    I couldn’t currently have asked for a more rewarding blog. You are ever present to supply excellent guidance, going right to the point for simple understanding of your readers. You’re truly a terrific professional in this matter. Thank you for remaining there for people like me.

  34. 34 tory-burch-sandals said at 3:56 am on February 14th, 2011:

    discount tory burch shoes…

    Tory Burch is developing faster and faster.Tory Burch Boots become more and more fashional.More and more people prefer to wear shoes like this.Tory Burch is an attainable, luxury, lifestyle brand defined by classic American sportswear with an eclectic …

  35. 35 tory-burch-heel-shoes said at 4:07 am on February 14th, 2011:

    cheap tory burch shoes …

    We specialize in selling trendy shoes online. A huge selection of women’s shoes for a very cheap price, the top name brand of cheap tory burch shoes casual shoes, tory burch boots, women’s sandals, fashion shoes.And here has cheap 2011 tory burch n…

  36. 36 tory-burch-reva-flats said at 10:32 pm on February 14th, 2011:

    cheap tory burch shoes …

    Saletoryubrchon.com provide good quality and best price for cheap tory burch shoes products, there offer tory burch boots,tory burch sandals,tory burch flats,tory burch handbangs sale, tory burch wallets for free shipping. …

  37. 37 bangkok reseguide said at 8:18 am on February 15th, 2011:

    i have visited this blog a couple of times now and i have to tell you that i find it quite exeptional actually. keep the nice work up! =)

  38. 38 sandra said at 4:40 am on February 16th, 2011:

    DiCsjV http://fhYj30Mxb55m1SpveOxt.com

  39. 39 Duane Josephson said at 11:17 am on February 16th, 2011:

    This weblog appears to get a great deal of visitors. How do you advertise it? It offers a nice unique spin on things. I guess having something useful or substantial to give info on is the most important factor.

  40. 40 Paketresor said at 5:43 pm on February 16th, 2011:

    i have begun to visit this blog a couple of times now and i have to say that i find it quite exeptional actually. keep the nice work up! 😉

  41. 41 reverse phone number lookup said at 12:38 am on February 17th, 2011:

    I had this page bookmarked some time previously but my PC crashed. I have since gotten a new one and it took me a while to locate this! I also in fact like the theme though.

  42. 42 Toshiko Chomali said at 1:40 am on February 17th, 2011:

    I think that is an interesting point, it made me think a bit. Thanks for sparking my thinking cap.

  43. 43 Cristy Lamay said at 4:20 am on February 17th, 2011:

    Dude, great article. Can you give me your email address. There is something I would like to ask you. Thanks briel Alcaide

  44. 44 Rob Rasner said at 5:39 am on February 18th, 2011:

    My spouse and I stumbled over here from a another web address and considered I might as well check it out. I like everything that We notice therefore now I’m following you. Looking forward to finding out about your blog page again:) Furthermore this Lindsey Lohan is within the media once again…

  45. 45 1 day car insurance under 21 said at 3:33 pm on February 18th, 2011:

    I love visiting your site for the reason that you often give us great posts about computers and technology. exceptional writeup… awesome Job once again. I plan to put this weblog in my favorites list. I think I shall subscribe to the website feed also…

  46. 46 Cassidy Devalk said at 3:47 pm on February 18th, 2011:

    What would we do without the marvellous strategies you talk about on this web site? Who has got the endurance to deal with essential topics in the interest of common visitors like me? I and my buddies are very delighted to have your website among the ones we regularly visit. It is hoped you know how considerably we love your efforts! Best wishes from us all.

  47. 47 Webcam Modeling said at 7:50 pm on February 18th, 2011:

    check out our webcam modeling site http://www.i-camz.com

  48. 48 Theo Woodfolk said at 8:52 pm on February 18th, 2011:

    Just wanna remark that you have a very nice internet site , I enjoy the style it actually stands out.

  49. 49 Deborah Kroeger said at 2:10 am on February 19th, 2011:

    Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Thanks, nnie Shively

  50. 50 Debi Jerome said at 10:31 am on February 21st, 2011:

    I’m happy to have found your exceptionally high quality article! I agree with some of your readers and will eagerly look forward to your coming updates. Thanks, ckole Gibas