SlideShare a Scribd company logo
Two Scoops of Django 
Common Patterns for Forms 
& 
More Things to Know About Forms
Who am I? 
• Vic (Shan-Ho Yang) 
• Graduate Student from NTNU 
• Love Python & Web
Outline 
• The Power of Django Forms 
• Pattern 1: Simple ModelForm With Default Validators 
• Pattern 2: Custom Form Field Validators in ModelForms 
• Pattern 3: Overriding the Clean Stage of Validation 
• Pattern 4: Hacking Form Fields (2 CBVs, 2 Forms, 1 Model) 
• Pattern 5: Reusable Search Mixin View 
• More Things to Know About Forms 
• Know How Form Validation Works
The Power of Django Forms 
• Django forms are powerful, flexible, extensible and 
robust. 
• Powerful validation features 
• Package tips: 
• django-floppyforms - http://goo.gl/kTFgu9 
• django-crispy-forms - http://goo.gl/JNmx5h 
• django-forms-bootstrap - http://goo.gl/nFpmQJ 
4
The Power of Django Forms 
• Probably using Django forms even if project 
doesn’t serve HTML. 
• The chapter goes explicitly into one of the best 
parts of Django: forms, models and CBVs 
working in concert. 
5
Pattern 1: 
Simple ModelForm With Default Validators 
(1/3) 
FlavorCreateView FlavorDetailView 
(CreateView) (DetailView) 
FlavorUpdateView FlavorDetailView 
(UpdateView) (DetailView) 
(Ch.9, subsection 9.5.1) 
6
Pattern 1: 
Simple ModelForm With Default Validators 
(2/3) 
# flavors/views.py 
from django.views.generic import CreateView, UpdateView 
from braces.views import LoginRequiredMixin 
from .models import Flavor 
class FlavorCreateView(LoginRequiredMixin, CreateView): 
model = Flavor 
fields = ('title', 'slug', 'scoops_remaining') 
class FlavorUpdateView(LoginRequiredMixin, UpdateView): 
model = Flavor 
fields = ('title', 'slug', 'scoops_remaining') 
7
Pattern 1: 
Simple ModelForm With Default Validators 
(3/3) 
• FlavorCreateView and FlavorUpdateView are 
assigned Flavor as their model. 
• Both views auto-generate a ModelForm based on 
the Flavor model. 
• Those ModelForms rely on the default field 
validation rules of the Flavor model. 
• Django gives us a lot of great defaults for data 
validation, but the defaults are never enough. 
8
Pattern 2: 
Custom Form Field Validators in 
ModelForms (1/9) 
• Target: title field across our project’s dessert 
app started with the word “Tasty”. 
• This can be solved with a simple custom field 
validation. 
9
Pattern 2: 
Custom Form Field Validators in 
ModelForms (2/9) 
# core/validators.py 
from django.core.exception import ValidationError 
def validate_tasty(value): 
"""Raise a ValidationError if the value doesn't start 
with the word 'Tasty' 
""" 
if not value.startswith(u"Tasty"): 
msg = u"Must start with Tasty" 
raise ValidationError(msg) 
10
Pattern 2: 
Custom Form Field Validators in 
ModelForms (3/9) 
# core/models.py 
from django.db import models 
from .validators import validate_tasty 
class TastyTitleAbstractModel(models.Model): 
title = models.CharField(max_length=255, validators=[validate_tasty]) 
class Meta: 
abstract = True 
11
Pattern 2: 
Custom Form Field Validators in 
ModelForms (4/9) 
# flavors/models.py 
from django.core.urlresolvers import reverse 
from django.db import models 
from core.models import TastyTitleAbstractModel 
class Flavor(TastyTitleAbstractModel): 
slug = models.SlugField() 
scoops_remaining = models.IntegerField(default=0) 
12 
def get_absolute_url(self): 
return reverse("flavor_detail", kwars={"slug": self.slug})
Pattern 2: 
Custom Form Field Validators in 
ModelForms (5/9) 
• Work with any other tasty food-based models 
such as a WaffleCone or Cake model. 
• Any model that inherits from the 
TastyTitleAbstractModel class will throw a 
validation error if anyone attempts to save a 
model with a title that doesn’t start with ‘Tasty’. 
13
Pattern 2: 
Custom Form Field Validators in 
ModelForms (6/9) 
• What if we wanted to use validate_tasty() in just 
forms? 
• What if we wanted to assign it to other fields 
besides the title? 
14
Pattern 2: 
Custom Form Field Validators in 
ModelForms (7/9) 
# flavors/forms.py 
from django import forms 
from core.validators import validate_tasty 
from .models import Flavor 
class FlavorForm(forms.ModelForm): 
def __init__(self, *args, **kwargs): 
super(FlavorForm, self).__init__(*args, **kwargs) 
self.fields["title"].validators.append(validate_tasty) 
self.fields["slug"].validators.append(validate_tasty) 
class Meta: 
model = Flavor 
15 
Not change
Pattern 2: 
Custom Form Field Validators in 
ModelForms (8/9) 
# flavors/views.py 
from django.contrib import messages 
from django.views.generic import CreateView, UpdateView, DetailView 
from braces.views import LoginRequiredMixin 
from .models import Flavor 
from .forms import FlavorForm 
class FlavorActionMixin(object): 
model = Flavor 
fields = ('title', 'slug', 'scoops_remaining') 
@property 
def success_msg(self): 
return NotImplemented 
def form_valid(self, form): 
messages.info(self.request, self.success_msg) 
return super(FlavorActionMixin, self).form_valid(form) 
16
Pattern 2: 
Custom Form Field Validators in 
ModelForms (9/9) 
class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, 
CreateView): 
success_msg = "created" 
# Explicitly attach the FlavorFrom class 
form_class = FlavorForm 
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, 
UpdateView): 
success_msg = "updated" 
# Explicitly attach the FlavorFrom class 
form_class = FlavorForm 
class FlavorDetailView(DetailView): 
model = Flavor 
17
Pattern 3: 
Overriding the Clean Stage of Validation 
(1/6) 
• Some interesting use cases: 
• Multi-field validation 
• Validation involving existing data from the 
database that has already been validated. 
• Django provides a second stage and process 
for validating incoming data. 
18
Pattern 3: 
Overriding the Clean Stage of Validation 
(2/6) 
• Why Django provides more hooks for validation? 
• The clean() method is the place to validate two 
or more fields against each other, since it’s not 
specific to any one particular field. 
• The clean validation stage is a better place to 
attach validation against persistent data. Since 
the data already has some validation, you won’t 
waste as many database cycles on needless 
queries.
Pattern 3: 
Overriding the Clean Stage of Validation 
(3/6) 
# flavors/forms.py 
from django import forms 
from flavors.models import Flavor 
class IceCreamOrderForm(forms.Form): 
slug = forms.ChoiceField("Flavor") 
toppings = forms.CharField() 
def __init__(self, *args, **kwargs): 
super(IceCreamOrderForm, self).__init__(*args, 
**kwargs) 
self.fields["slug"].choices = [ 
(x.slug, x.title) for x in Flavor.objects.all() 
] 
def clean_slug(self): 
slug = self.cleaned_data["slug"] 
if Flavor.objects.get(slug=slug).scoops_remaining <= 0: 
msg = u"Sorry we are out of that flavor." 
raise forms.ValidationError(msg) 
return slug 
20
Pattern 3: 
Overriding the Clean Stage of Validation 
(4/6) 
• For HTML-powered views, the clean_slug() 
method in our example, upon throwing an error, 
will attach a “Sorry, we are out of that flavor” 
message to the flavor HTML input field. 
• This is a great shortcut for writing HTML forms!
Pattern 3: 
Overriding the Clean Stage of Validation 
(5/6) 
# flavors/forms.py 
from django import forms 
from flavors.models import Flavor 
class IceCreamOrderForm(forms.Form): 
# ... 
def clean(self): 
cleaned_data = super(IceCreamOrderForm, self).clean() 
slug = cleaned_data.get("slug", "") 
toppings = cleaned_data.get("toppings", "") 
# Silly "too much chocolate" validation example 
if u"chocolate" in slug.lower() and  
u"chocolate" in toppings.lower(): 
msg = u"Your order has too much chocolate." 
raise forms.ValidationError(msg) 
return cleaned_data 
22
Pattern 3: 
Overriding the Clean Stage of Validation 
(6/6) 
• Tip: Common Fields Used In Multi-Field Validation 
• Strength of the submitted password. 
• If the email model field isn’t set to 
unique=True, whether or not the email is 
unique. 
23
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (1/6) 
• This pattern covers a situation where two views/ 
forms correspond to one model. 
• An example might be a list of stores, where we 
want each store entered into the system as fast 
as possible, but want to add more data such as 
phone number and description later. 
24
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (2/6) 
# stores/models.py 
from django.core.urlresolvers import reverse 
from django.db import models 
class IceCreamStore(models.Model): 
title = models.CharField(max_length=100) 
block_address = models.TextField() 
phone = models.CharField(max_length=20, blank=True) 
description = models.TextField(blank=True) 
def get_absolute_url(self): 
return reverse("store_detail", kwargs={"pk": self.pk}) 
25
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (3/6) 
# stores/forms.py 
from django import forms 
from .models import IceCreamStore 
class IceCreamStoreUpdateForm(forms.ModelForm): 
phone = forms.CharField(required=True) 
description = forms.TextField(required=True) 
class Meta: 
model = IceCreamStore 
26 
Duplicated
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (4/6) 
# stores/forms.py 
# Call phone and description from the self.fields dict-like object 
from django import forms 
from .models import IceCreamStore 
class IceCreamStoreUpdateForm(forms.ModelForm): 
class Meta: 
model = IceCreamStore 
def __init__(self, *args, **kwargs): 
# Call the original __init__ method before assigning 
# field overloads 
super(IceCreamStoreUpdateForm, self).__init__(*args, 
**kwargs) 
self.fields["phone"].required = True 
self.fields["description"].required = True 
27
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (5/6) 
# stores/forms.py 
from django import forms 
from .models import IceCreamStore 
class IceCreamStoreCreateForm(forms.ModelForm): 
class Meta: 
model = IceCreamStore 
fields = ("title", "block_address") 
class IceCreamStoreCreateForm(forms.ModelForm): 
def __init__(self, *args, **kwargs): 
super(IceCreamStoreUpdateForm, self).__init__(*args, 
**kwargs) 
self.fields["phone"].required = True 
self.fields["description"].required = True 
class Meta(IceCreamStoreCreateForm): 
fields = ("title", "block_address", "phone", 
"description") 
28
Pattern 4: 
Hacking From Fields 
(2 CBVs, 2Forms, 1 Model) (6/6) 
# stores/views.py 
from django.views.generic import CreateView, UpdateView 
from .forms import IceCreamStoreCreateForm 
from .forms import IceCreamStoreUpdateForm 
from .models import IceCreamStore 
class IceCreamCreateView(CreateView): 
model = IceCreamStore 
form_class = IceCreamStoreCreateForm 
class IceCreamUpdateView(UpdateView): 
model = IceCreamStore 
form_class = IceCreamStoreUpdateForm 
29
Pattern 5: 
Reusable Search Mixin View 
(1/5) 
• We’re going to cover how to reuse a search form 
in two views that correspond to two different 
models. 
• This example will demonstrate how a single CBV 
can be used to provide simple search 
functionality on both the Flavor and 
IceCreamStore models. 
30
Pattern 5: 
Reusable Search Mixin View 
(2/5) 
# core/views.py 
class TitleSearchMixin(object): 
def get_queryset(self): 
# Fetch the queryset from the parent's get_queryset 
queryset = super(TitleSearchMixin, self).get_queryset() 
# Get the q GET parameter 
q = self.request.GET.get("q") 
if q: 
# return a filtered queryset 
return queryset.filter(title__icontains=q) 
# No q is specified so we return queryset 
return queryset 
31
Pattern 5: 
Reusable Search Mixin View 
(3/5) 
# add to flavors/views.py 
from django.views.generic import ListView 
from core.views import TitleSearchMixin 
from .models import Flavor 
class FlavorListView(TitleSearchMixin, ListView): 
model = Flavor 
# add to stores/views.py 
from django.views.generic import ListView 
from core.views import TitleSearchMixin 
from .models import IceCreamStore 
class IceCreamStoreListView(TitleSearchMixin, ListView): 
32 
model = IceCreamStore
Pattern 5: 
Reusable Search Mixin View 
(4/5) 
{# form to go into stores/store_list.html template #} 
<form action="" method="GET"> 
<input type="text" name="q"> 
<button type="submit">search</button> 
</form> 
{# form to go into flavors/flavor_list.html template #} 
<form action="" method="GET"> 
<input type="text" name="q"> 
<button type="submit">search</button> 
33 
</form>
Pattern 5: 
Reusable Search Mixin View 
(5/5) 
• Mixin are a good way to reuse code, but using 
too many mixins in a single class makes for very 
hard-to-maintain code. 
• Try to keep our code as simple as possible. 
34
More Things to Know About 
Forms 
• Django forms are really powerful, but there are 
edge cases that can cause a bit of anguish. 
• If you understand the structure of how forms are 
composed and how to call them, most edge 
cases can be readily overcome. 
• Don’t disable Django’s CSRF protection. 
• https://docs.djangoproject.com/en/1.6/ref/contrib/csrf/ 
35
Know How Form Validation 
Works 
• Form validation is one of those areas of Django 
where knowing the inner working will drastically 
improve your code. 
• When we call form.is_valid(), a lot of things 
happen behind the scenes. 
36
Know How Form Validation 
Works 
1. If the form has bound data, form.is_valid() calls the 
form.full_clean() method. 
2. form.full_clean() iterates through the form fields and 
each field validates itself: 
A. Data coming into this field is coerced into Python via the to_python() 
method or raises a ValidationError. 
B. Data is validated against field-specific rules, including custom 
validators. Failure raises a ValidationError. 
C. If there are any custom clean_<field>() methods in the form, they 
are called at this time.
Know How Form Validation 
Works 
3. form.full_clean() executes the form.clean() method. 
4. If it’s a ModelForm instance, form._post_clean() does 
the following: 
A. Sets ModelForm data to the Model instance, 
regardless of whether form.is_valid() is True or 
False. 
B. Calls the model’s clean() method. For reference, 
saving a model instance through the ORM does 
not call the model’s clean() method.
Know How Form Validation 
Works 
# core/models.py 
from django.db import models 
class ModelFormFailureHistory(models.Model): 
form_data = models.TextField() 
model_data = models.TextField() 
39
Know How Form Validation 
Works 
# flavors/models.py 
import json 
from django.contrib import messages 
from django.core import serializers 
from core.models import ModelFormFailureHistory 
class FlavorActionMixin(object): 
@property 
def success_msg(self): 
return NotImplemented 
def form_valid(self, form): 
messages.info(self.request, self.success_msg) 
return super(FlavorActionMixin, self).form_valid(form) 
def form_invalid(self, form): 
"""Save invalid form and model data for later reference.""" 
form_data = json.dumps(form.cleaned_data) 
model_data = serializers.serialize("json", 
[form.instance])[1:-1] 
ModelFormFailureHistory.objects.create( 
form_data=form_data, 
model_data= model_data 
) 
return super(FlavorActionMixin, 
self).form_invalid(form) 
40
Know How Form Validation 
Works 
• form_invalid() is called after failed validation of a 
form with bad data. 
• When it called here in this example, both the 
cleaned form data and the final data saved to 
the database are saved as a 
ModelFormFailureHistory record. 
41
Reference 
• Two Scoops of Django - Best Practice for Django 1.6 
• By Daniel Greenfeld and Audrey Roy 
42
“Thank you.”

More Related Content

PDF
Two scoopsofdjango common patterns for forms
PDF
Two scoopsofdjango ch16 dealing with the user model
PPTX
Django: Advanced Models
PDF
Django design-patterns
PPT
Common Pitfalls Experienced in Java
PDF
Say Goodbye to Procedural Programming - Nick Sutterer
PDF
날로 먹는 Django admin 활용
KEY
Repensando o Desenvolvimento Web com Ruby on Rails
Two scoopsofdjango common patterns for forms
Two scoopsofdjango ch16 dealing with the user model
Django: Advanced Models
Django design-patterns
Common Pitfalls Experienced in Java
Say Goodbye to Procedural Programming - Nick Sutterer
날로 먹는 Django admin 활용
Repensando o Desenvolvimento Web com Ruby on Rails

What's hot (18)

KEY
Django Admin: Widgetry & Witchery
PDF
Forms, Getting Your Money's Worth
PDF
What's New in newforms-admin
PPTX
Jsf lab
DOC
Validation
PDF
Working With The Symfony Admin Generator
ZIP
Refatoração + Design Patterns em Ruby
PDF
PDF
Kotlin coroutines 톺아보기
PDF
Django Bogotá. CBV
ODP
Pruebas unitarias con django
PPTX
Django ORM - Marcin Markiewicz
PDF
Ch9 .Best Practices for Class-Based Views
PDF
Django Templates
PPT
PDF
Django class based views for beginners
PDF
Chapter 3 (validation control)
PPTX
Clean tests good tests
Django Admin: Widgetry & Witchery
Forms, Getting Your Money's Worth
What's New in newforms-admin
Jsf lab
Validation
Working With The Symfony Admin Generator
Refatoração + Design Patterns em Ruby
Kotlin coroutines 톺아보기
Django Bogotá. CBV
Pruebas unitarias con django
Django ORM - Marcin Markiewicz
Ch9 .Best Practices for Class-Based Views
Django Templates
Django class based views for beginners
Chapter 3 (validation control)
Clean tests good tests
Ad

Viewers also liked (20)

PDF
CECAFÉ - Relatório Mensal JULHO 2016
PPTX
Финансы и кредит
PPTX
Homework for ITB
DOCX
Raden ajeng kartini
PDF
Relatório sobre o mercado de café da OIC - Fevereiro 2016
ODP
Styrk dit netværk og din jobsøgning med LinkedIn
PDF
Relatório sobre o mercado de café da OIC - Novembro 2016
PDF
1° Levantamento da Safra 2016 de Café da Conab - Janeiro
PPTX
Redes de área local - Ibzan
PDF
CECAFÉ - Relatório Mensal AGOSTO 2016
PPTX
Talk at Workindenmark
PDF
LinkedIn foredrag hos Aalborg Universitet
PPTX
Бизнес-информатика
PDF
4 online community-dubai 2015
PPTX
LinkedIn-foredrag - Krifa
PDF
LinkedIn oplæg Aalborg
PPTX
Tarea 6
PDF
Kolding
PDF
LinkedIn workshop hos MA
PPTX
La petite lucette
CECAFÉ - Relatório Mensal JULHO 2016
Финансы и кредит
Homework for ITB
Raden ajeng kartini
Relatório sobre o mercado de café da OIC - Fevereiro 2016
Styrk dit netværk og din jobsøgning med LinkedIn
Relatório sobre o mercado de café da OIC - Novembro 2016
1° Levantamento da Safra 2016 de Café da Conab - Janeiro
Redes de área local - Ibzan
CECAFÉ - Relatório Mensal AGOSTO 2016
Talk at Workindenmark
LinkedIn foredrag hos Aalborg Universitet
Бизнес-информатика
4 online community-dubai 2015
LinkedIn-foredrag - Krifa
LinkedIn oplæg Aalborg
Tarea 6
Kolding
LinkedIn workshop hos MA
La petite lucette
Ad

Similar to Two Scoops of Django - Common Patterns for Forms (20)

PPTX
Tango with django
PDF
Django tricks (2)
PDF
Тестирование и Django
PDF
django_introduction20141030
KEY
Django Pro ORM
PPTX
Testing the Untestable
PPTX
Javascript for the c# developer
PDF
Testing for Pragmatic People
PDF
Defensive Apex Programming
PPTX
Query types db connector
PDF
GDG Addis - An Introduction to Django and App Engine
PDF
Data Migrations in the App Engine Datastore
PDF
Into The Box 2018 - CBT
DOC
Wheels
PPTX
chapter Two Django basics of dynamic web pages.pptx
PDF
Breaking Dependencies Legacy Code - Cork Software Crafters - September 2019
KEY
Introduction Django
PPTX
Django apps and ORM Beyond the basics [Meetup hosted by Prodeers.com]
PDF
Ctools presentation
PPT
11i10 ATFormPersonalization_HRDv2[1].ppt
Tango with django
Django tricks (2)
Тестирование и Django
django_introduction20141030
Django Pro ORM
Testing the Untestable
Javascript for the c# developer
Testing for Pragmatic People
Defensive Apex Programming
Query types db connector
GDG Addis - An Introduction to Django and App Engine
Data Migrations in the App Engine Datastore
Into The Box 2018 - CBT
Wheels
chapter Two Django basics of dynamic web pages.pptx
Breaking Dependencies Legacy Code - Cork Software Crafters - September 2019
Introduction Django
Django apps and ORM Beyond the basics [Meetup hosted by Prodeers.com]
Ctools presentation
11i10 ATFormPersonalization_HRDv2[1].ppt

Recently uploaded (20)

PDF
Arduino robotics embedded978-1-4302-3184-4.pdf
PPTX
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
PPTX
Lesson 3_Tessellation.pptx finite Mathematics
PPTX
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
PDF
Digital Logic Computer Design lecture notes
PPTX
CARTOGRAPHY AND GEOINFORMATION VISUALIZATION chapter1 NPTE (2).pptx
PPTX
bas. eng. economics group 4 presentation 1.pptx
PPTX
Internet of Things (IOT) - A guide to understanding
PDF
Embodied AI: Ushering in the Next Era of Intelligent Systems
DOCX
573137875-Attendance-Management-System-original
PPTX
web development for engineering and engineering
PDF
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
PDF
Operating System & Kernel Study Guide-1 - converted.pdf
PDF
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
PPTX
Sustainable Sites - Green Building Construction
PDF
Structs to JSON How Go Powers REST APIs.pdf
PDF
PPT on Performance Review to get promotions
PPTX
CYBER-CRIMES AND SECURITY A guide to understanding
PPTX
M Tech Sem 1 Civil Engineering Environmental Sciences.pptx
PPTX
Geodesy 1.pptx...............................................
Arduino robotics embedded978-1-4302-3184-4.pdf
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
Lesson 3_Tessellation.pptx finite Mathematics
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
Digital Logic Computer Design lecture notes
CARTOGRAPHY AND GEOINFORMATION VISUALIZATION chapter1 NPTE (2).pptx
bas. eng. economics group 4 presentation 1.pptx
Internet of Things (IOT) - A guide to understanding
Embodied AI: Ushering in the Next Era of Intelligent Systems
573137875-Attendance-Management-System-original
web development for engineering and engineering
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
Operating System & Kernel Study Guide-1 - converted.pdf
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
Sustainable Sites - Green Building Construction
Structs to JSON How Go Powers REST APIs.pdf
PPT on Performance Review to get promotions
CYBER-CRIMES AND SECURITY A guide to understanding
M Tech Sem 1 Civil Engineering Environmental Sciences.pptx
Geodesy 1.pptx...............................................

Two Scoops of Django - Common Patterns for Forms

  • 1. Two Scoops of Django Common Patterns for Forms & More Things to Know About Forms
  • 2. Who am I? • Vic (Shan-Ho Yang) • Graduate Student from NTNU • Love Python & Web
  • 3. Outline • The Power of Django Forms • Pattern 1: Simple ModelForm With Default Validators • Pattern 2: Custom Form Field Validators in ModelForms • Pattern 3: Overriding the Clean Stage of Validation • Pattern 4: Hacking Form Fields (2 CBVs, 2 Forms, 1 Model) • Pattern 5: Reusable Search Mixin View • More Things to Know About Forms • Know How Form Validation Works
  • 4. The Power of Django Forms • Django forms are powerful, flexible, extensible and robust. • Powerful validation features • Package tips: • django-floppyforms - http://goo.gl/kTFgu9 • django-crispy-forms - http://goo.gl/JNmx5h • django-forms-bootstrap - http://goo.gl/nFpmQJ 4
  • 5. The Power of Django Forms • Probably using Django forms even if project doesn’t serve HTML. • The chapter goes explicitly into one of the best parts of Django: forms, models and CBVs working in concert. 5
  • 6. Pattern 1: Simple ModelForm With Default Validators (1/3) FlavorCreateView FlavorDetailView (CreateView) (DetailView) FlavorUpdateView FlavorDetailView (UpdateView) (DetailView) (Ch.9, subsection 9.5.1) 6
  • 7. Pattern 1: Simple ModelForm With Default Validators (2/3) # flavors/views.py from django.views.generic import CreateView, UpdateView from braces.views import LoginRequiredMixin from .models import Flavor class FlavorCreateView(LoginRequiredMixin, CreateView): model = Flavor fields = ('title', 'slug', 'scoops_remaining') class FlavorUpdateView(LoginRequiredMixin, UpdateView): model = Flavor fields = ('title', 'slug', 'scoops_remaining') 7
  • 8. Pattern 1: Simple ModelForm With Default Validators (3/3) • FlavorCreateView and FlavorUpdateView are assigned Flavor as their model. • Both views auto-generate a ModelForm based on the Flavor model. • Those ModelForms rely on the default field validation rules of the Flavor model. • Django gives us a lot of great defaults for data validation, but the defaults are never enough. 8
  • 9. Pattern 2: Custom Form Field Validators in ModelForms (1/9) • Target: title field across our project’s dessert app started with the word “Tasty”. • This can be solved with a simple custom field validation. 9
  • 10. Pattern 2: Custom Form Field Validators in ModelForms (2/9) # core/validators.py from django.core.exception import ValidationError def validate_tasty(value): """Raise a ValidationError if the value doesn't start with the word 'Tasty' """ if not value.startswith(u"Tasty"): msg = u"Must start with Tasty" raise ValidationError(msg) 10
  • 11. Pattern 2: Custom Form Field Validators in ModelForms (3/9) # core/models.py from django.db import models from .validators import validate_tasty class TastyTitleAbstractModel(models.Model): title = models.CharField(max_length=255, validators=[validate_tasty]) class Meta: abstract = True 11
  • 12. Pattern 2: Custom Form Field Validators in ModelForms (4/9) # flavors/models.py from django.core.urlresolvers import reverse from django.db import models from core.models import TastyTitleAbstractModel class Flavor(TastyTitleAbstractModel): slug = models.SlugField() scoops_remaining = models.IntegerField(default=0) 12 def get_absolute_url(self): return reverse("flavor_detail", kwars={"slug": self.slug})
  • 13. Pattern 2: Custom Form Field Validators in ModelForms (5/9) • Work with any other tasty food-based models such as a WaffleCone or Cake model. • Any model that inherits from the TastyTitleAbstractModel class will throw a validation error if anyone attempts to save a model with a title that doesn’t start with ‘Tasty’. 13
  • 14. Pattern 2: Custom Form Field Validators in ModelForms (6/9) • What if we wanted to use validate_tasty() in just forms? • What if we wanted to assign it to other fields besides the title? 14
  • 15. Pattern 2: Custom Form Field Validators in ModelForms (7/9) # flavors/forms.py from django import forms from core.validators import validate_tasty from .models import Flavor class FlavorForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(FlavorForm, self).__init__(*args, **kwargs) self.fields["title"].validators.append(validate_tasty) self.fields["slug"].validators.append(validate_tasty) class Meta: model = Flavor 15 Not change
  • 16. Pattern 2: Custom Form Field Validators in ModelForms (8/9) # flavors/views.py from django.contrib import messages from django.views.generic import CreateView, UpdateView, DetailView from braces.views import LoginRequiredMixin from .models import Flavor from .forms import FlavorForm class FlavorActionMixin(object): model = Flavor fields = ('title', 'slug', 'scoops_remaining') @property def success_msg(self): return NotImplemented def form_valid(self, form): messages.info(self.request, self.success_msg) return super(FlavorActionMixin, self).form_valid(form) 16
  • 17. Pattern 2: Custom Form Field Validators in ModelForms (9/9) class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView): success_msg = "created" # Explicitly attach the FlavorFrom class form_class = FlavorForm class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, UpdateView): success_msg = "updated" # Explicitly attach the FlavorFrom class form_class = FlavorForm class FlavorDetailView(DetailView): model = Flavor 17
  • 18. Pattern 3: Overriding the Clean Stage of Validation (1/6) • Some interesting use cases: • Multi-field validation • Validation involving existing data from the database that has already been validated. • Django provides a second stage and process for validating incoming data. 18
  • 19. Pattern 3: Overriding the Clean Stage of Validation (2/6) • Why Django provides more hooks for validation? • The clean() method is the place to validate two or more fields against each other, since it’s not specific to any one particular field. • The clean validation stage is a better place to attach validation against persistent data. Since the data already has some validation, you won’t waste as many database cycles on needless queries.
  • 20. Pattern 3: Overriding the Clean Stage of Validation (3/6) # flavors/forms.py from django import forms from flavors.models import Flavor class IceCreamOrderForm(forms.Form): slug = forms.ChoiceField("Flavor") toppings = forms.CharField() def __init__(self, *args, **kwargs): super(IceCreamOrderForm, self).__init__(*args, **kwargs) self.fields["slug"].choices = [ (x.slug, x.title) for x in Flavor.objects.all() ] def clean_slug(self): slug = self.cleaned_data["slug"] if Flavor.objects.get(slug=slug).scoops_remaining <= 0: msg = u"Sorry we are out of that flavor." raise forms.ValidationError(msg) return slug 20
  • 21. Pattern 3: Overriding the Clean Stage of Validation (4/6) • For HTML-powered views, the clean_slug() method in our example, upon throwing an error, will attach a “Sorry, we are out of that flavor” message to the flavor HTML input field. • This is a great shortcut for writing HTML forms!
  • 22. Pattern 3: Overriding the Clean Stage of Validation (5/6) # flavors/forms.py from django import forms from flavors.models import Flavor class IceCreamOrderForm(forms.Form): # ... def clean(self): cleaned_data = super(IceCreamOrderForm, self).clean() slug = cleaned_data.get("slug", "") toppings = cleaned_data.get("toppings", "") # Silly "too much chocolate" validation example if u"chocolate" in slug.lower() and u"chocolate" in toppings.lower(): msg = u"Your order has too much chocolate." raise forms.ValidationError(msg) return cleaned_data 22
  • 23. Pattern 3: Overriding the Clean Stage of Validation (6/6) • Tip: Common Fields Used In Multi-Field Validation • Strength of the submitted password. • If the email model field isn’t set to unique=True, whether or not the email is unique. 23
  • 24. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (1/6) • This pattern covers a situation where two views/ forms correspond to one model. • An example might be a list of stores, where we want each store entered into the system as fast as possible, but want to add more data such as phone number and description later. 24
  • 25. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (2/6) # stores/models.py from django.core.urlresolvers import reverse from django.db import models class IceCreamStore(models.Model): title = models.CharField(max_length=100) block_address = models.TextField() phone = models.CharField(max_length=20, blank=True) description = models.TextField(blank=True) def get_absolute_url(self): return reverse("store_detail", kwargs={"pk": self.pk}) 25
  • 26. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (3/6) # stores/forms.py from django import forms from .models import IceCreamStore class IceCreamStoreUpdateForm(forms.ModelForm): phone = forms.CharField(required=True) description = forms.TextField(required=True) class Meta: model = IceCreamStore 26 Duplicated
  • 27. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (4/6) # stores/forms.py # Call phone and description from the self.fields dict-like object from django import forms from .models import IceCreamStore class IceCreamStoreUpdateForm(forms.ModelForm): class Meta: model = IceCreamStore def __init__(self, *args, **kwargs): # Call the original __init__ method before assigning # field overloads super(IceCreamStoreUpdateForm, self).__init__(*args, **kwargs) self.fields["phone"].required = True self.fields["description"].required = True 27
  • 28. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (5/6) # stores/forms.py from django import forms from .models import IceCreamStore class IceCreamStoreCreateForm(forms.ModelForm): class Meta: model = IceCreamStore fields = ("title", "block_address") class IceCreamStoreCreateForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(IceCreamStoreUpdateForm, self).__init__(*args, **kwargs) self.fields["phone"].required = True self.fields["description"].required = True class Meta(IceCreamStoreCreateForm): fields = ("title", "block_address", "phone", "description") 28
  • 29. Pattern 4: Hacking From Fields (2 CBVs, 2Forms, 1 Model) (6/6) # stores/views.py from django.views.generic import CreateView, UpdateView from .forms import IceCreamStoreCreateForm from .forms import IceCreamStoreUpdateForm from .models import IceCreamStore class IceCreamCreateView(CreateView): model = IceCreamStore form_class = IceCreamStoreCreateForm class IceCreamUpdateView(UpdateView): model = IceCreamStore form_class = IceCreamStoreUpdateForm 29
  • 30. Pattern 5: Reusable Search Mixin View (1/5) • We’re going to cover how to reuse a search form in two views that correspond to two different models. • This example will demonstrate how a single CBV can be used to provide simple search functionality on both the Flavor and IceCreamStore models. 30
  • 31. Pattern 5: Reusable Search Mixin View (2/5) # core/views.py class TitleSearchMixin(object): def get_queryset(self): # Fetch the queryset from the parent's get_queryset queryset = super(TitleSearchMixin, self).get_queryset() # Get the q GET parameter q = self.request.GET.get("q") if q: # return a filtered queryset return queryset.filter(title__icontains=q) # No q is specified so we return queryset return queryset 31
  • 32. Pattern 5: Reusable Search Mixin View (3/5) # add to flavors/views.py from django.views.generic import ListView from core.views import TitleSearchMixin from .models import Flavor class FlavorListView(TitleSearchMixin, ListView): model = Flavor # add to stores/views.py from django.views.generic import ListView from core.views import TitleSearchMixin from .models import IceCreamStore class IceCreamStoreListView(TitleSearchMixin, ListView): 32 model = IceCreamStore
  • 33. Pattern 5: Reusable Search Mixin View (4/5) {# form to go into stores/store_list.html template #} <form action="" method="GET"> <input type="text" name="q"> <button type="submit">search</button> </form> {# form to go into flavors/flavor_list.html template #} <form action="" method="GET"> <input type="text" name="q"> <button type="submit">search</button> 33 </form>
  • 34. Pattern 5: Reusable Search Mixin View (5/5) • Mixin are a good way to reuse code, but using too many mixins in a single class makes for very hard-to-maintain code. • Try to keep our code as simple as possible. 34
  • 35. More Things to Know About Forms • Django forms are really powerful, but there are edge cases that can cause a bit of anguish. • If you understand the structure of how forms are composed and how to call them, most edge cases can be readily overcome. • Don’t disable Django’s CSRF protection. • https://docs.djangoproject.com/en/1.6/ref/contrib/csrf/ 35
  • 36. Know How Form Validation Works • Form validation is one of those areas of Django where knowing the inner working will drastically improve your code. • When we call form.is_valid(), a lot of things happen behind the scenes. 36
  • 37. Know How Form Validation Works 1. If the form has bound data, form.is_valid() calls the form.full_clean() method. 2. form.full_clean() iterates through the form fields and each field validates itself: A. Data coming into this field is coerced into Python via the to_python() method or raises a ValidationError. B. Data is validated against field-specific rules, including custom validators. Failure raises a ValidationError. C. If there are any custom clean_<field>() methods in the form, they are called at this time.
  • 38. Know How Form Validation Works 3. form.full_clean() executes the form.clean() method. 4. If it’s a ModelForm instance, form._post_clean() does the following: A. Sets ModelForm data to the Model instance, regardless of whether form.is_valid() is True or False. B. Calls the model’s clean() method. For reference, saving a model instance through the ORM does not call the model’s clean() method.
  • 39. Know How Form Validation Works # core/models.py from django.db import models class ModelFormFailureHistory(models.Model): form_data = models.TextField() model_data = models.TextField() 39
  • 40. Know How Form Validation Works # flavors/models.py import json from django.contrib import messages from django.core import serializers from core.models import ModelFormFailureHistory class FlavorActionMixin(object): @property def success_msg(self): return NotImplemented def form_valid(self, form): messages.info(self.request, self.success_msg) return super(FlavorActionMixin, self).form_valid(form) def form_invalid(self, form): """Save invalid form and model data for later reference.""" form_data = json.dumps(form.cleaned_data) model_data = serializers.serialize("json", [form.instance])[1:-1] ModelFormFailureHistory.objects.create( form_data=form_data, model_data= model_data ) return super(FlavorActionMixin, self).form_invalid(form) 40
  • 41. Know How Form Validation Works • form_invalid() is called after failed validation of a form with bad data. • When it called here in this example, both the cleaned form data and the final data saved to the database are saved as a ModelFormFailureHistory record. 41
  • 42. Reference • Two Scoops of Django - Best Practice for Django 1.6 • By Daniel Greenfeld and Audrey Roy 42