Django class based views, modele i formularze
Django class based views oferują szeroki wachlarz możliwości i mogą znacząco skrócić, uprościć kod widoków. Trzeba je tylko dobrze poznać. W tym artykule przedstawię widoki powiązane z formularzami i modelami (forms.ModelForm).
Testowa aplikacja
Na potrzeby przykładów stworzyłem dwa modele:# -*- 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'
Model "Category" jest prosty, natomiast dla modelu "Entry" przewidziałem formularz, który ma edytować tylko jedno pole (user_number). Pozostałe pola są wymagane i trzeba będzie zapewnić wartości dla nich.
Widoki
Oto widoki dotyczące kategorii - listowanie i dodawanie kategorii: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()
Dokumentacja CreateView ujawnia jakie metody i atrybuty posiada dzięki swoim Mixinom. W najprostszym przypadku definiujemy get_success_url (lub success_url), na który zostaniemy przekierowaniu po dodaniu obiektu.
Teraz ciąg dalszy - widoki dla "Entry":
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()
W widoku AddEntryView zdefiniowałem metodę "get_form_kwargs", w której można operować na elementach widoku (formularzu, danych z formularza jeżeli został wysłany itd.). Można tam także podać instancję obiektu ("instance") jaki ma zostać zapisany wraz z danymi z formularza. Pozwala to ustawić wymagane wartości dla pól, których nie było w formularzu. Sam formularz jest prosty:
from django import forms
from demo.models import Entry
class CustomAddEntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['user_number']
DeleteEntryView można użyć do kasowania rekordów. Widok ten wyświetli formularz potwierdzający chęć skasowania rekordu (formularz bez pól, tylko submit). UpdateView służy do edycji już istniejących rekordów - do obu widoków musi być dopasowany jeden konkretny obiekt. Jeżeli nie definiujemy "queryset" to robione jest to automatycznie po kluczu głównym - jeżeli zastosujemy odpowiednie mapowanie zmiennych w linkach (klucz główny - 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)),
)
Dynamiczna konfiguracja formularza
Jeżeli np. walidacja formularza zależy od danych powiązanych z użytkownikiem to można przekazać do obiektu formularza potrzebne dane:
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