Django class based views in action - forms handling

Check out the new site at https://rkblog.dev.

Django class based views offer a wide range of features - supporting forms, objects lists and many more. In this article I'll show some of the class based views in action - those related to forms and object lists.

Example application

For this example I've created two models:
# -*- coding: utf-8 -*-
from django.contrib.auth.models import User
from django.db import models

class Category(models.Model):
    title = models.CharField(max_length=150)

    class Meta:
        verbose_name = 'Category'
        verbose_name_plural = 'Categories'

    def __unicode__(self):
        return self.title


class Entry(models.Model):
    category = models.ForeignKey(Category)
    user = models.ForeignKey(User)
    user_number = models.IntegerField()
    system_number = models.IntegerField()

    class Meta:
        verbose_name = 'Entry'
        verbose_name_plural = 'Entries'

"Category" is quite plain, while for "Entry" we will need more complicated form with limited fields editable for the user - just to show the class based views in action.

Class based views

Let us start with the views for those models. At start list all categories and form for adding new categories:
from random import randint

from django.core.urlresolvers import reverse
from django.views.generic import ListView
from django.views.generic.edit import CreateView
from django.views.generic.edit import DeleteView
from django.views.generic.edit import UpdateView

from demo.forms import CustomAddEntryForm
from demo.models import *

class CategoryListView(ListView):
    model = Category
    template_name = "category_list.html"
    context_object_name="category_list"

category_list = CategoryListView.as_view()


class AddCategoryView(CreateView):
    model = Category
    template_name = "add_category.html"

    def get_success_url(self):
        return reverse('category-list')

add_category = AddCategoryView.as_view()

You should check each view type documentation to get the methods and attributes it has via Mixins it inherits. For CreateView we need the model name, optionally our own template name and get_success_url (or success_url) with a url to which form will redirect.

Entry views are bit more complicated:

class EntryListView(ListView):
    model = Entry
    template_name = "entry_list.html"
    context_object_name="entry_list"

    def get_context_data(self, **kwargs):
        context = super(EntryListView, self).get_context_data(**kwargs)
        context['category_id'] = self.kwargs['category_id']
        return context
    
    def queryset(self):
        category = Category.objects.get(pk=self.kwargs['category_id'])
        return Entry.objects.filter(category=category)

entry_list = EntryListView.as_view()


class AddEntryView(CreateView):
    model = Entry
    template_name = "add_entry.html"

    def get_form_class(self):
        return CustomAddEntryForm

    def get_form_kwargs(self, **kwargs):
        kwargs = super(AddEntryView, self).get_form_kwargs(**kwargs)
        if 'data' in kwargs:
            category = Category.objects.get(pk=self.kwargs['category_id'])
            random_int = randint(1,10)
            instance = Entry(user=self.request.user, system_number=random_int, category=category)
            kwargs.update({'instance': instance})
        return kwargs

    def get_success_url(self):
        return reverse('entry-list', kwargs={'category_id': self.kwargs['category_id']})

add_entry = AddEntryView.as_view()


class EditEntryView(UpdateView):
    model = Entry
    template_name = "edit_entry.html"

    def get_form_class(self):
        return CustomAddEntryForm

    def get_success_url(self):
        return reverse('entry-list', kwargs={'category_id': self.kwargs['category_id']})

edit_entry = EditEntryView.as_view()


class DeleteEntryView(DeleteView):
    model = Entry
    template_name = "entry_confirm_delete.html"

    def get_success_url(self):
        return reverse('entry-list', kwargs={'category_id': self.kwargs['category_id']})

delete_entry = DeleteEntryView.as_view()

AddEntryView has a "get_form_kwargs" method defined. It's a very handy method for views handling forms. Via kwargs we can set initial object, get the POSTed data or pass additional data to the form object (like for custom validation, customized field values). In this case I set "instance" with the Entry object having values for every required field that isn't in the form:

from django import forms

from demo.models import Entry

class CustomAddEntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = ['user_number']

DeleteView can be used for object deletion - it has a empty form and the template should ask for confirmation (like in admin panel delete actions).

UpdateView can be used to handle a form for editing given entry. If we won't define "queryset" it will try the default behavior - using "pk" variable from the URLs (named variable from urls are under self.kwargs, unnamed in self.args - like self.kwargs['pk']):

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^$', 'demo.views.category_list', name='category-list'),
    url(r'^category/add/$', 'demo.views.add_category', name='add-category'),
    url(r'^category/(?P<category_id>[0-9]+)/$', 'demo.views.entry_list', name='entry-list'),
    url(r'^category/(?P<category_id>[0-9]+)/add_entry/$', 'demo.views.add_entry', name='add-entry'),
    url(r'^category/(?P<category_id>[0-9]+)/edit_entry/(?P<pk>[0-9]+)/$', 'demo.views.edit_entry', name='edit-entry'),
    url(r'^category/(?P<category_id>[0-9]+)/delete_entry/(?P<pk>[0-9]+)/$', 'demo.views.delete_entry', name='delete-entry'),
    url(r'^admin/', include(admin.site.urls)),
)

Passing data to form instance

Sometimes to validate things you need the current logged in user object or other external data. You can pass them to the form through get_form_kwargs:

class AddEntryView(CreateView):
    model = Entry
    template_name = "add_entry.html"
    form_class = CustomAddEntryForm
    
    def get_form_kwargs(self, **kwargs):
        kwargs = super(AddEntryView, self).get_form_kwargs(**kwargs)
        if 'data' in kwargs:
            category = Category.objects.get(pk=self.kwargs['category_id'])
            random_int = randint(1,10)
            instance = Entry(user=self.request.user, system_number=random_int, category=category)
            kwargs.update({'instance': instance})
        # add something extra to form __init__
        kwargs.update({'current_user': self.request.user})
        return kwargs

    def get_success_url(self):
        return reverse('entry-list', kwargs={'category_id': self.kwargs['category_id']})

add_entry = AddEntryView.as_view()
And define __ini__ in the form class:
class CustomAddEntryForm(forms.ModelForm):

    def __init__(self, current_user, **kwargs):
        super(CustomAddEntryForm, self).__init__(**kwargs)
        self.current_user = current_user

    class Meta:
        model = Entry
        fields = ['user_number']
In "clean" methods or other you can access the User object via self.current_user.
RkBlog

Django web framework tutorials, 30 May 2012


Check out the new site at https://rkblog.dev.
Comment article
Comment article RkBlog main page Search RSS Contact