Django class based views in action - forms handling
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()
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']
Comment article