May 19th, 2013

Simple CRUD App in Django

Tutorial for: Django

This tutorial will explain how to create a super quick data-driven application using Django 1.5.

I build lots of custom web applications, I use Django because it makes modeling the data I will use super simple. This tutorial assumes you already have a Django project ready to go by using django-admin.py startproject and it's all configured how you like it. I would also recommend using South, this tutorial will not go into how to use it, just read the simple tutorial provided by the project's documentation.

So first, you will need to create an app in your Django project and create a few extra files there to make it more stand-alone and plug-able into different projects. Go into the app's directory and create the following files: forms.py, and urls.py. Next create a directory called templates and inside there create a directory with the same name as your app. For this example, we will be creating a Django app which will allow us to manage a simple Name and Phone number address book, nothing too special. It will have a single relation to a Category model which will allow you to easily sort through Family, Friends and Co-workers. We will also add some other fields for the purpose of displaying what types of interesting fields you can make available. So, we'll be calling this app people for the rest of this tutorial.

First lets get the basic modeling done, which will be the database tables:

from django.db import models
from django.template.defaultfilters import slugify
from django.contrib.localflavor.us.models import PhoneNumberField

class Category(models.Model):
    title = models.CharField(max_length=80)
    slug = models.SlugField(blank=True)
    class Meta:
        verbose_name_plural = 'categories'
    def __unicode__(self):
        return u"%s" % self.title
    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        return super(Category, self).save(*args, **kwargs)
    @models.permalink
    def get_absolute_url(self):
        return ('category_detail', [self.slug])
    @models.permalink
    def get_update_url(self):
        return ('category_update', [self.slug])
    @models.permalink
    def get_delete_url(self):
        return ('category_delete', [self.slug])

class Person(models.Model):
    PROXIMITY_LIST = (
        (0, 'Not sure'),
        ('Friends', (
            (10, 'Best friends forever'),
            (20, 'Close friends'),
            (30, 'Distant friends'),
            (40, 'Enemies til the end')
        )),
        ('Family', (
            (50, 'Super close'),
            (60, 'Only on occasion'),
            (70, 'Only at funerals'),
            (80, 'Family grouch!')
        )),
    )
    first_name = models.CharField(max_length=60, help_text='Their first name')
    last_name = models.CharField(max_length=80, blank=True, help_text='Do you know it?')
    slug = models.SlugField(blank=True)
    category = models.ForeignKey(Category, blank=True, null=True, help_text='File this person where?')
    proximity = models.PositiveSmallIntegerField(choices=PROXIMITY_LIST, help_text='How much of a distance to keep?')
    home_phone = PhoneNumberField(blank=True)
    cell_phone = PhoneNumberField(blank=True)
    email = models.EmailField(blank=True, help_text='Do they have an Email address?')
    added_on = models.DateField(auto_now_add=True)
    class Meta:
        verbose_name_plural = 'people'
        ordering = ['last_name']
    @property
    def full_name(self):
        return u"%s %s" % (self.first_name, self.last_name) if self.last_name != '' else u"%s" % self.first_name
    def __unicode__(self):
        return u"%s, %s" % (self.last_name, self.first_name) if self.last_name != '' else u"%s" % self.first_name
    def save(self, *args, **kwargs):
        self.slug = slugify(self.full_name)
        return super(Person, self).save(*args, **kwargs)
    @models.permalink
    def get_absolute_url(self):
        return ('person_detail', [self.slug])
    @models.permalink
    def get_update_url(self):
        return ('person_update', [self.slug])
    @models.permalink
    def get_delete_url(self):
        return ('person_delete', [self.slug])

Okay, let me explain what some of this does. Let's begin with the Category model, it tells us that we want a title and a slug for it. A slug is used the URL of the application to make it easier to know what your visiting when sharing links with people. Although we don't need to use it for this example, I thought I'd explain how to use slugs for future applications you may create. You may notice that we have a Meta class here, verbose_name_plural is specifically used for the Django admin, if you choose to use it with this app. ordering is used globally to easily define how the data should be ordered. Since this app is focusing on a CRUD example, I'd like to avoid using the admin interface. The __unicode__ method provides a human readable format of the object. If your familiar with Django at all, you may have noticed that I overwrote the save() method, this is to handle the auto slugification of the title, it is entirely optional in Django to define a save() method, as one already exists that does all the dirty work, and this is why we also call it after slugifying the title. The final part of the class is the get_absolute_url() method, which is going to be used to make a few of the CRUD components work, and we will also call it in our templates. It provides a way to keep all URLs DRY and pointing to the correct location at all times. Something not common, but included to display how to be more DRY, is adding methods to assist in various CRUD operations, such as updating and deleting. Those last two methods in each model are used in the templates which we will get to later on in this tutorial. You'll see how much simpler and readable your template code will look when using this method. Again, it is completely optional, you can always use the url template tag for this instead. However, this will be more difficult if you choose to create generic templates as we will do in this tutorial. This tutorial is focusing on being as DRY as possible and reusing as much code as possible.

The Person has some very similar components, so I won't explain them a second time. The first thing we see here is a PROXIMITY_LIST variable. This is used for a choice field in this model. Feel free to add or remove any of them, it is more or less there for humor. Other fields used are the ForeignKey into the Category model. The proximity field shows how to use the list created in a choice list. The phone number fields show how to use the Django locale fields, and the email field shows it's purpose as well. The added_on field keeps track of when the person was added. One of the more interesting methods here has a @property decorator, a method would have worked fine as well. I choose to make this a property as I felt it is more of a property than a method. It also makes it easy to reference the same everywhere, in code and in templates.

from django import forms
from people.models import Category, Person

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = ('title',)

class PersonForm(forms.ModelForm):
    class Meta:
        model = Person
        fields = ('first_name', 'last_name', 'category', 'proximity', 'home_phone', 'cell_phone', 'email',)

The forms.py should be simple to grasp for most. It defines the forms and which fields from the database to allow the user access to. I didn't use excludes as when adding new fields later down the road, you may forget to exclude special fields you don't want visible. Forgetting to include won't lead to a security issue in special fields, but forgetting to exclude will. Keep that in mind.

from django.views.generic.list import ListView
from people.models import Category, Person
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from people.forms import CategoryForm, PersonForm
from django.views.generic.detail import DetailView
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse

class CategoryMixin(object):
    model = Category
    def get_context_data(self, **kwargs):
        kwargs.update({'object_name':'Category'})
        return kwargs

class CategoryFormMixin(CategoryMixin):
    form_class = CategoryForm
    template_name = 'people/object_form.html'

class CategoryList(CategoryMixin, ListView):
    template_name = 'people/object_list.html'

class CategoryDetail(CategoryMixin, DetailView):
    pass

class NewCategory(CategoryFormMixin, CreateView):
    pass

class EditCategory(CategoryFormMixin, UpdateView):
    pass

class DeleteCategory(CategoryMixin, DeleteView):
    template_name = 'people/object_confirm_delete.html'
    def get_success_url(self):
        return reverse('category_list')

class PersonMixin(object):
    model = Person
    def get_context_data(self, **kwargs):
        kwargs.update({'object_name':'Person'})
        return kwargs

class PersonFormMixin(PersonMixin):
    form_class = PersonForm
    template_name = 'people/object_form.html'

class PeopleList(PersonMixin, ListView):
    template_name = 'people/object_list.html'

class ViewPerson(PersonMixin, DetailView):
    pass

class NewPerson(PersonFormMixin, CreateView):
    pass

class EditPerson(PersonFormMixin, UpdateView):
    pass

class KillPerson(PersonMixin, DeleteView):
    template_name = 'people/object_confirm_delete.html'
    def get_success_url(self):
        return reverse('people_list')

class ViewCategory(PersonMixin, ListView):
    template_name = 'people/object_list.html'
    def get_queryset(self):
        self.category = get_object_or_404(Category, slug=self.kwargs['slug'])
        return super(ViewCategory, self).get_queryset().filter(category=self.category)

Here are the CRUD views, I normally aways use classes rather than placing them directly into the urls.py, I do this so that I can easily extend the features of the class in the future if I need to, for example, using permissions and limiting a queryset. I choose to use a Mixin class to contain the models to make it more DRY. I could have also did the same with the forms, but I may wish to use a separate form to create and edit. For this example, the forms will remain the same. I also created two views which do the exact same thing to show how you can view a category.

Take notice in the various Mixins which have been created here. I did this to keep the code and templates more DRY, there is a separate Mixin class for each model, a form Mixin for each model as well. Depending on how you wish to display the Category model, there are 2 methods listed here. CategoryDetail has it's own template file and it needs to do a reverse relationship in order to function. The ViewCategory view looks a little more complex, but allows us to reuse the same template for PeopleList remaining more DRY at the template layer. Depending on your projects needs, choose one which suites your requirements, you will rarely need to use both methods, if ever.

from django.conf.urls.defaults import patterns, url, include
from people.views import PeopleList, ViewPerson, NewPerson, KillPerson,\
    EditPerson, ViewCategory, EditCategory, DeleteCategory, CategoryDetail,\
    NewCategory

person_urls = patterns('',
    url(r'^$', ViewPerson.as_view(), name='person_detail'),
    url(r'^Update$', EditPerson.as_view(), name='person_update'),
    url(r'^Delete$', KillPerson.as_view(), name='person_delete'),
)

category_urls = patterns('',
    url(r'^$', ViewCategory.as_view(), name='category_detail'),
    url(r'^Alternate$', CategoryDetail.as_view(), name='category_detail_alt'),
    url(r'^Update$', EditCategory.as_view(), name='category_update'),
    url(r'^Delete$', DeleteCategory.as_view(), name='category_delete'),
)

urlpatterns = patterns('',
    url(r'^$', PeopleList.as_view(), name='people_list'),
    url(r'^(?P<slug>[\w-]+).person/', include(person_urls)),
    url(r'^NewPerson$', NewPerson.as_view(), name='person_add'),
    url(r'^Categories$', CategoryList.as_view(), name='category_list'),
    url(r'^(?P<slug>[\w-]+).cat/', include(category_urls)),
    url(r'^NewCategory$', NewCategory.as_view(), name='category_add'),
)

This describes how the URLs will route into our views. To keep things DRY, I used the include statement to include additional URLs. Without using the include, I would be repeating the slug capture numerous times for both the Person and Category views. This also shows that you can be as creative as you like with your URLs, I made these URLs descriptive and uncluttered. When using Class-based Views you should always pass the name keyword argument to the url statement, this will allow you to reference your views. Now onto the templates.

<html>
<head>
  <title>{% block title %}Untitled{% endblock %} | Phone Directory</title>
</head>
<body>
<h1>Phonebook CRUD example!</h1>
<a href="{% url 'people_list' %}">Index</a> | <a href="{% url 'category_list' %}">Categories</a> | <a href="{% url 'category_add' %}">Add Category</a> | <a href="{% url 'person_add' %}">Add Person</a>
<hr/>
{% block content %}
The end user should never ever see this!
{% endblock %}
</body>
</html>

This is the rather simple base.html for this example, it places some global links and has no styling. Notice how the global links are generated.

{% extends 'people/base.html' %}

{% block title %}{{object_name}} List{% endblock %}

{% block content %}
<table>
<thead><tr><th>{{object_name}}</th><th>Action</th></tr></thead>
<tbody>
  {% for object in object_list %}
  <tr><td><a href="{{object.get_absolute_url}}">{{object}}</a></td><td><a href="{{object.get_update_url}}">Update</a> | <a href="{{object.get_delete_url}}">Delete</a></td></tr>
  {% endfor %}
</tbody>
</table>
{% endblock %}

This is the object_list.html template file, it provides a generic means of displaying a list object. For most projects you may wish to not use a generic list, as you may wish to provide additional information from the model. I did this in the example to keep it short and simple. You should also notice the methods from the model are being used here, and it works with all models that have these methods created. If these methods were not created, creating the action links would have been more difficult, and most definitely leading to more code. Model methods can do a lot, consider using it over a templatetag if you can.

{% extends 'people/base.html' %}

{% block title %}{{object_name}} Form{% endblock %}

{% block content %}
<h3>{% if object %}Updating {{object}}{% else %}Add a new {{object_name}}{% endif %}</h3>
<form action="" method="post">{% csrf_token %}
<table>
{{form}}
</table>
<input type="submit" value="Save {{object_name}}"/></form>
{% endblock %}

Another example of using a generic template, this is the object_form.html template and should work for almost any ModelForm you give it. It allows you to quickly prototype the application's data components.

{% extends 'people/base.html' %}

{% block title %}{{object}}{% endblock %}

{% block content %}
<table>
<tr><th>Full name:</th><td>{{object.full_name}}</td></tr>
<tr><th>Filed under:</th><td><a href="{{object.category.get_absolute_url}}">{{object.category}}</a></td></tr>
<tr><th>Proximity:</th><td>{{object.get_proximity_display}}</td></tr>
{% if object.home_phone %}<tr><th>Home Phone:</th><td>{{object.home_phone}}</td></tr>{% endif %}
{% if object.cell_phone %}<tr><th>Cell Phone:</th><td>{{object.cell_phone}}</td></tr>{% endif %}
{% if object.email %}<tr><th>Email:</th><td>{{object.email}}</td></tr>{% endif %}
</table>
<a href="{{object.get_update_url}}">Update</a> | <a href="{{object.get_delete_url}}">Delete</a>
{% endblock %}

This is the person_detail.html template for displaying a single person in the database. It also has a cross-reference here to the Category it is part of.

{% extends 'people/base.html' %}

{% block title %}{{object_name}} Confirm delete?{% endblock %}

{% block content %}
<h3>Are you sure you want to delete {{object}}?</h3>
<form action="" method="post">{% csrf_token %}
<input type="submit" value="Yes!"/>  <input type="button" value="No..." onclick="window.history.go(-1);"/>
</form>
{% endblock %}

This is the object_confirm_delete.html template which is displayed to the user when they would like to delete an object, again it is generic and should work for most models.

{% extends 'people/base.html' %}

{% block title %}{{object}}{% endblock %}

{% block content %}
<table>
<thead><tr><th>Person</th><th>Action</th></tr></thead>
<tbody>
  {% for object in object.person_set.all %}
  <tr><td><a href="{{object.get_absolute_url}}">{{object}}</a></td><td><a href="{{object.get_update_url}}">Update</a> | <a href="{{object.get_delete_url}}">Delete</a></td></tr>
  {% endfor %}
</tbody>
</table>
{% endblock %}

This is the alternate method of displaying items in a Category, it uses a reverse relationship mechanic of Django to work.

There you have it, a complete Django CRUD example. This should show you how quick and simple it is to create data-driven applications in Django. This code could be much more simpler, but I wanted to be thorough and extend the classes to show what you can do.

May 23, 2013, 10:39 a.m. - TempppUser

Hi, thank you for this tutorial. It is very useful for newbie like me :) May I ask you to attach project as archive, it will be better for using this code in own editor without entering all source code.

About Me

My Photo
Names Kevin, hugely into UNIX technologies, not just Linux. I've dabbled with the demons, played with the Sun, and now with the Penguins.




Kevin Veroneau Consulting Services
Do you require the services of a Django contractor? Do you need both a website and hosting services? Perhaps I can help.

This Month

If you like what you read, please consider donating to help with hosting costs, and to fund future books to review.

Python Powered | © 2012-2013 Kevin Veroneau