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:
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:
If we fill out the form without the proper (or correctly formatted) information, we’ll get an error:
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.
Comments
100 responses to “Django Generic Views: CRUD”
[…] Create, Read, Update, Delete – die Standardfunktionen klassischer Interfaces – kann man mit Django sehr einfach zusammenbauen. Dazu gibt es die Generic Views. Auf Postneo gibts jetzt ein CRUD Tutorial, welches zeigt wie simpel solche Oberflächen mit Django zusammengestellt werden können. […]
What would you think about making a screencast, a la Rails? That was a lot of Python to read, and I ended up skipping over it pretty quickly.
Cool stuff, but I noticed that the pluralize filter doesn’t work (see screenshot)
Dagur,
Yeah I noticed that but forgot to investigate it. I’ll let you know what I find out.
Steve,
Yeah, that would rock. I didn’t realize how much code I was going to have to escape and format before this post was done!
This is some cool stuff! It would be nice to have it as part of official Django documentation, if some small typos are corrected.
Unfortunately this example doesn’t work out of box.
1) Getting Started — make sure to create your database _before_ you run django-admin.py init. It is not going to be created for you. Official Tutorial 1 (which is mentioned after “init”) explains it in details.
2) The Model — before going to play with the model make sure to install it using django-admin.py install tasks. It is explained in Official Tutorial 1.
3) URL Configuration — probably urls should be decoupled as in Official Tutorial 3. It is better for novices to provide ready-made code instead of reference to Official Tutorial 3.
4) URL Configuration — update should have the same parameters as create. Otherwise it doesn’t know where to forward to after updating.
5) URL Configuration — most probably delete should forward to ‘/tasks/’. ‘/tasks/new/’ is not defined in your tutorial.
6) Index — the same list is passed as ‘notdone_tasks_list’ and ‘done_tasks_list’. The latter should be done_task_list.
7) Index — it should be noted that all template files are to be created in subdirectory ‘templates/tasks/’, not directly in ‘templates/’.
8) Create Generic View — by some reasons tasks_form template has all ‘”‘ prepended with ‘\\’. They should be removed, otherwise it doesn’t work.
9) Delete — tasks_form_delete template should be renamed to tasks_confirm_delete.
I’m pretty sure that these tiny bugs are due to ongoing changes in Django and naturally occurring typos. Thank you for great tutorial!
Eugene,
Hey thanks for the feedback, I’ll do my best to update the tutorial this weekend. I did my best to get from scratch to my working situation, but I sure missed a couple of things along the way.
Thanks again for taking the time to go throught the tutorial and pointing out the little things.
Be aware that there are some significant and backwards-incompatible syntax changes for the models that might be showing up shortly, but I’ll definitely update this tutorial when/if that happens.
Great article.
Few minor additions that will help others (newb->newb). (I’m running django revision 525, FYI.)
1. A little reminder to “django-admin.py init tasks” to create the database table in the initialized database would help just before the screen pic of the index.html.
2. (r’^tasks/delete/(?P\d+)/?$’, […] post_delete_redirect=”/tasks/new/”) ),
seems to work as
(r’^tasks/delete/(?P\d+)/?$’, […] post_delete_redirect=”/tasks/create/”) ),
instead.
3. tasks_form_delete.html template seems to work as tasks_confirm_delete.hmtl instead
-=-
Awesome bite-sized article. Thanks for sharing.
I forgot to mention django-admin.py createsuperuser.
Matt, thank you the tutorial. I tried it but stopped with
/tasks/create/
because in my MS Explorer there is no submit button shown. Only input field.
Is it a problem on my side( django installation or MS Explorer or similar)?
Thank you for your reply
@Lada
You need to edit the template “tasks_form.html” and change all \” to “
Spam Link
Matt Croydon::Postneo …
[…] Be sure to check out the full documentation as well as a brief screencast highlighting the changes. I will do my best to update my CRUD Generic Views tutorial as soon as possible to reflect the syntax change. […]
First of all , thank you Matt for the tutorial. I learnt a lot.
But I think that UPDATE should also have post_save_redirect like Create Generic View. For me it did not work without that.
Best regards,
Lad.
Django Generic Views: CRUD…
TrackBack From:http://www.blogjava.net/martinx/archive/2005/12/30/26029.html…
W A R N I N G ! ! ! :: This Tutorial is VERY MUCH OUT OF DATE!!!
So what is out of day ? can we have an updated tutorial ?
This article may be slightly out-of-date, but it had far more useful examples on generic views to extrapolate from then the “official documentation” on the topic.
Matt, many thanks!
lesbiens…
Matt Croydon::Postneo ……
hi
Prompt how to get rid of advertising?
Cool gay butts are online now. Gay Butts shows its http://www.gay-butts.be butts collection just in Febrary!
American arthritis association…
This weekend in philadelphia Community colleges in south carolina Pet insurance usa Money to australia Black label label kills Cipro shelf life Nj s corporation Perfect credit score College professor ratings Credit union chelmsford President on money I…
jennifer-aniston-topless-photo…
Matt Croydon::Postneo ……
adult product catalogs…
anime sfx…
adult jobs in la…
best lesbian fiction on fictionpress…
paris hilton getting out of car pic…
popular used cars for girls…
strap on babes domination…
california swingers clubs,resorts…
php insertion…
rich girl video gwen…
chase platinum visa card…
chore:Weldwood sandwiches bites digestive …
Hello! Good Site! Thanks you! hyocbvfahpdzmp
I just wanted to say WOW!
Hi all!
Very interesting information! Thanks!
Bye
dollar car rental orlando international
dollar car rental orlando international
8
8
cdee678621dc…
cdee678621dcd1a42ee4…
Thanks for articles, I have searched blog same this since long time
kimjlouytvgqw
kimjlouytvgqw
kimjlouytvgqw
kimjlouytvgqw
дарова хороший блог у Ñ‚ÐµÐ±Ñ Ð½Ð¾ вÑÑ‘ веÑÑŒ равно подобно то разброÑано конечно и выделÑй заголовки)))
Вот поÑмотри на моём блоге про Ñтарые машины и поймёшь как нужно выделÑÑ‚ÑŒ 😉
Привет! Почитай выше блог про бетонные работы, почему надо низко их , Ñтих трудÑг которые пашут и не жалеют ÑÐµÐ±Ñ , Ð²Ñ‹Ð¿ÐµÐºÐ°Ñ Ð¿Ð¾ 10 чаÑов в день без оÑтановки, реÑпект им!
а Ñ‚Ñ‹ нравитÑÑ Ð¿Ð¾Ñтите ролики впроекте?
I’ll be traveling late in July and the info on car rentals has been very helpful !
Очень понравилÑÑ Ð²Ð°Ñˆ блог! ПодпиÑалÑÑ Ð½Ð° rss. Буду регулÑрно читать.
Your blog is so interesting! I have subscribed on rss and I will read it regullary/
Amazing blog! Very interesting aspects. I will allways read it. Also e-mailed on rss.
Ðарод в таких ÑлучаÑÑ… говорит – БаÑнÑми ÑÑ‹Ñ‚ не будешь.
Ðвтор, а Ñкажите а куда напиÑать по поводу обмена ÑÑылок (на какое мыло)?
Да таков наш Ñовременный мир и боюÑÑŒ Ñ Ñтим ни чего невозможно поделать:)
http://imgwebsearch.com/35357/img0/buy%20risperdal%20consta/15_buy_risperdal_consta.png
43pO0X http://djb3jDdmjckow30cnjcmd61l0dy.com