-
1.
Advanced Django
Form Usage
by Daniel Greenfeld and Miguel Araujo
-
2.
Daniel Greenfeld
• pydanny
• Python & Django developer for
Cartwheel Web / RevSys
• Founded django-uni-form
• Does Capoeira
• Lives in Los Angeles with his
Advanced Django Form Usage
http://www.flickr.com/photos/pydanny/4442245488/
Fiancé, Audrey Roy (audreyr)
@pydanny / @maraujop
2
-
3.
Miguel Araujo
• maraujop
• Freelance Python developer
• Co-lead of django-uni-form
• Does Muay Thai
• Lives in Madrid with his
amazing girlfiend who does
Advanced Django Form Usage
aerospace for a living
@pydanny / @maraujop
• http://maraujop.github.com/
3
-
4.
Advanced Django Form Usage
@pydanny / @maraujop
Tons of technical content
4
-
5.
Tons of technical content
• We probably won’t have time for questions
Advanced Django Form Usage
@pydanny / @maraujop
4
-
6.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
Advanced Django Form Usage
@pydanny / @maraujop
4
-
7.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
• Too much content in the abstract
Advanced Django Form Usage
@pydanny / @maraujop
4
-
8.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
• Too much content in the abstract
• Special thanks to:
Advanced Django Form Usage
@pydanny / @maraujop
4
-
9.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
• Too much content in the abstract
• Special thanks to:
• Brian Rosner
Advanced Django Form Usage
@pydanny / @maraujop
4
-
10.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
• Too much content in the abstract
• Special thanks to:
• Brian Rosner
Advanced Django Form Usage
• James Tauber
@pydanny / @maraujop
4
-
11.
Tons of technical content
• We probably won’t have time for questions
• Slides will be posted right after the talk
• Too much content in the abstract
• Special thanks to:
• Brian Rosner
Advanced Django Form Usage
• James Tauber
@pydanny / @maraujop
• Frank Wiles
4
-
12.
Advanced Django Form Usage
@pydanny / @maraujop
Good Form Patterns
Fundamentals of
5
-
13.
Fundamentals of
Good Form Patterns
• Zen of Python still applies
Advanced Django Form Usage
@pydanny / @maraujop
5
-
14.
Fundamentals of
Good Form Patterns
• Zen of Python still applies
• import this
Advanced Django Form Usage
@pydanny / @maraujop
5
-
15.
Fundamentals of
Good Form Patterns
• Zen of Python still applies
• import this
• Spartan programming als0 is important
Advanced Django Form Usage
@pydanny / @maraujop
5
-
16.
Spartan Programming
• Horizontal complexity
• Vertical complexity (line count)
• Token count
• Character count
• Variables
•
Advanced Django Form Usage
Loops
@pydanny / @maraujop
• Conditionals
http://www.codinghorror.com/blog/2008/07/spartan-programming.html
6
-
17.
Spartan Programming
• Horizontal complexity
• Vertical complexity (line count)
• Token count
• Character count
• Variables
•
Advanced Django Form Usage
Loops
@pydanny / @maraujop
• Conditionals
http://www.codinghorror.com/blog/2008/07/spartan-programming.html
7
-
18.
Spartan Programming
• Horizontal complexity
• Vertical complexity (line count)
• Token count
• Character count
• Variables
•
Advanced Django Form Usage
Loops
@pydanny / @maraujop
• Conditionals
http://www.codinghorror.com/blog/2008/07/spartan-programming.html
8
-
19.
Advanced Django Form Usage
@pydanny / @maraujop
Calling forms the easy way
9
-
20.
Calling forms the easy way
• Smaller, cleaner code makes our lives better
Advanced Django Form Usage
@pydanny / @maraujop
9
-
21.
Calling forms the easy way
• Smaller, cleaner code makes our lives better
• Line 5 of the Zen of Python
Advanced Django Form Usage
@pydanny / @maraujop
9
-
22.
Calling forms the easy way
• Smaller, cleaner code makes our lives better
• Line 5 of the Zen of Python
• Flat is better than nested.
Advanced Django Form Usage
@pydanny / @maraujop
9
-
23.
Calling forms the easy way
• Smaller, cleaner code makes our lives better
• Line 5 of the Zen of Python
• Flat is better than nested.
• Aim for minimal boilerplate
Advanced Django Form Usage
@pydanny / @maraujop
9
-
24.
Calling forms the easy way
• Smaller, cleaner code makes our lives better
• Line 5 of the Zen of Python
• Flat is better than nested.
• Aim for minimal boilerplate
Advanced Django Form Usage
• So you can focus on your business logic
@pydanny / @maraujop
9
-
25.
Enough theory
-
26.
A Basic Django Form
class MyForm(forms.Form):
name = forms.CharField(_('Name'), required=True)
Advanced Django Form Usage
@pydanny / @maraujop
11
-
27.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST':
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/')
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
12
-
28.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST': Form #1
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/')
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
12
-
29.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST': Form #1
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/') Form #2
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
12
-
30.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST': Form #1
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/') Form #2
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
Only 1 nested if, but real code
@pydanny / @maraujop
gets much more complex
12
-
31.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST': Form #1
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/') Form #2
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
Only 1 nested if, but real code
@pydanny / @maraujop
gets much more complex Custom business
goes here
12
-
32.
Standard views.py
def my_view(request, template_name='myapp/my_form.html'):
if request.method == 'POST': Form #1
form = MyForm(request.POST) # Form #1!
if form.is_valid(): # nested if!
do_x()
return redirect('/') Form #2
else:
form = MyForm() # Form #2!
return render(request, template_name, {'form': form})
Advanced Django Form Usage
Only 1 nested if, but real code
@pydanny / @maraujop
gets much more complex Custom business
goes here
12
-
33.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
Custom business
goes here
13
-
34.
Single form
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
Custom business
goes here
13
-
35.
Single form
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Advanced Django Form Usage
If no request.POST,
@pydanny / @maraujop
Custom business then instantiated with None
goes here
13
-
36.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
14
-
37.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
• 6 lines of code instead of 9
Advanced Django Form Usage
@pydanny / @maraujop
14
-
38.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
• 6 lines of code instead of 9
Advanced Django Form Usage
• 33.3333333333% less code to debug
@pydanny / @maraujop
14
-
39.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
• 6 lines of code instead of 9
Advanced Django Form Usage
• 33.3333333333% less code to debug
@pydanny / @maraujop
• One form instantiation
14
-
40.
Easy views.py
def my_view(request, template_name='myapp/my_form.html'):
# sticks in a POST or renders empty form
form = MyForm(request.POST or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
• 6 lines of code instead of 9
Advanced Django Form Usage
• 33.3333333333% less code to debug
@pydanny / @maraujop
• One form instantiation
• less conditionals == less edge case insanity
14
-
41.
What about
file uploads?
-
42.
Easy views.py + files
def my_view(request, template_name='myapp/my_form.html'):
form = MyForm(request.POST or None, request.FILES or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Advanced Django Form Usage
@pydanny / @maraujop
16
-
43.
Easy views.py + files
def my_view(request, template_name='myapp/my_form.html'):
form = MyForm(request.POST or None, request.FILES or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Request.POST
or None
Advanced Django Form Usage
@pydanny / @maraujop
16
-
44.
Easy views.py + files
def my_view(request, template_name='myapp/my_form.html'):
form = MyForm(request.POST or None, request.FILES or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Request.POST Request.FILES
or None or None
Advanced Django Form Usage
@pydanny / @maraujop
16
-
45.
Easy views.py + files
def my_view(request, template_name='myapp/my_form.html'):
form = MyForm(request.POST or None, request.FILES or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Request.POST Request.FILES
or None or None
Advanced Django Form Usage
@pydanny / @maraujop
•
6 lines of code again!
16
-
46.
Easy views.py + files
def my_view(request, template_name='myapp/my_form.html'):
form = MyForm(request.POST or None, request.FILES or None)
if form.is_valid():
do_x()
return redirect('home')
return render(request, template_name, {'form': form})
Request.POST Request.FILES
or None or None
Advanced Django Form Usage
@pydanny / @maraujop
•
6 lines of code again!
• Code donated by Audrey Roy
16
-
47.
What about
ModelForms?
-
48.
Advanced Django Form Usage
@pydanny / @maraujop
pydanny made
up statistics
18
-
49.
pydanny made
up statistics
• 91% of all Django projects use ModelForms
Advanced Django Form Usage
@pydanny / @maraujop
18
-
50.
pydanny made
up statistics
• 91% of all Django projects use ModelForms
• 80% ModelForms require trivial logic
Advanced Django Form Usage
@pydanny / @maraujop
18
-
51.
pydanny made
up statistics
• 91% of all Django projects use ModelForms
• 80% ModelForms require trivial logic
• 20% ModelForms require complicated logic
Advanced Django Form Usage
@pydanny / @maraujop
18
-
52.
pydanny made
up statistics
• 91% of all Django projects use ModelForms
• 80% ModelForms require trivial logic
• 20% ModelForms require complicated logic
Advanced Django Form Usage
@pydanny / @maraujop
Let’s try and make that easy
18
-
53.
A Basic ModelForm
class MyModelForm(forms.Form):
class Meta:
model = MyModel
fields = ['name']
Advanced Django Form Usage
@pydanny / @maraujop
19
-
54.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
20
-
55.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel) Form #1
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
20
-
56.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel) Form #1
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home') Form #2
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
20
-
57.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel) Form #1
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home') Form #2
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Only 1 nested if, but real code
Advanced Django Form Usage
gets much more complex
@pydanny / @maraujop
20
-
58.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel) Form #1
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home') Form #2
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Only 1 nested if, but real code
Advanced Django Form Usage
gets much more complex
@pydanny / @maraujop
Custom business
goes here
20
-
59.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel) Form #1
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home') Form #2
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Only 1 nested if, but real code
Advanced Django Form Usage
gets much more complex
@pydanny / @maraujop
Custom business
goes here
20
-
60.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
21
-
61.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
• 12 lines of code
Advanced Django Form Usage
@pydanny / @maraujop
21
-
62.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
• 12 lines of code
Advanced Django Form Usage
• Nested conditionals
@pydanny / @maraujop
21
-
63.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
• 12 lines of code
Advanced Django Form Usage
• Nested conditionals
@pydanny / @maraujop
• What if we have to handle 3 different submit buttons?
21
-
64.
Classic views.py for ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
# I wouldn't call the variable model, because it's an instance of a model, it's confusing
mymodel = get_object_or_404(MyModel, slug=slug)
if request.method == 'POST':
form = MyForm(request, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
mymodel.save()
return redirect('home')
else:
form = MyForm(instance=mymodel)
return render(request, template_name, {'form': form, 'model': mymodel})
• 12 lines of code
Advanced Django Form Usage
• Nested conditionals
@pydanny / @maraujop
• What if we have to handle 3 different submit buttons?
• Watch out for edge case insanity!
21
-
65.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
22
-
66.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
Single form
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
22
-
67.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
Single form
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
Advanced Django Form Usage
Custom business
@pydanny / @maraujop
goes here
22
-
68.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
Single form
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
If no request.POST,
then instantiated with None
Advanced Django Form Usage
Custom business
@pydanny / @maraujop
goes here So this will fail validation!
22
-
69.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
23
-
70.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
• 9 lines of code instead of 12
Advanced Django Form Usage
@pydanny / @maraujop
23
-
71.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
• 9 lines of code instead of 12
Advanced Django Form Usage
@pydanny / @maraujop
• One conditional
23
-
72.
easy views.py + ModelForm
def my_model_edit(request, slug=slug, template_name='myapp/
my_model_form.html'):
mymodel = get_object_or_404(MyModel, slug=slug)
form = MyModelForm(request.POST or None, instance=mymodel)
if form.is_valid():
mymodel = form.save()
mymodel.edited_at_djangocon = True
mymodel.save()
return redirect('home')
return render(request, template_name, {'form': form, 'mymodel': mymodel})
• 9 lines of code instead of 12
Advanced Django Form Usage
@pydanny / @maraujop
• One conditional
• clear and succinct code 23
-
73.
What about new
model instances?
-
74.
add views.py + ModelForm
def my_model_add(request, template_name='myapp/my_model_form.html'):
form = MyModelForm(request.POST or None)
if form.is_valid():
mymodel = form.save() No need for an
mymodel.added_at_djangocon = True
mymodel.save() instance here
return redirect('home')
return render(request,template_name,{'form': form,'mymodel':mymodel})
Advanced Django Form Usage
@pydanny / @maraujop
25
-
75.
add views.py + ModelForm
def my_model_add(request, template_name='myapp/my_model_form.html'):
form = MyModelForm(request.POST or None)
if form.is_valid():
mymodel = form.save() No need for an
mymodel.added_at_djangocon = True
mymodel.save() instance here
return redirect('home')
return render(request,template_name,{'form': form,'mymodel':mymodel})
Advanced Django Form Usage
This creates the
@pydanny / @maraujop
model instance.
25
-
76.
I can make it smaller!
def my_model_tiny_add(request,template_name='myapp/my_model_form.html'):
form = MyModelForm(request.POST or None)
if form.is_valid():
form.save() Not setting defaults here
return redirect('home')
return render(request,template_name,{'form':form,'mymodel':mymodel})
Advanced Django Form Usage
Good practice: Use model field defaults rather
@pydanny / @maraujop
than doing it in your views.
26
-
77.
How do you test
this stuff?
-
78.
Advanced Django Form Usage
@pydanny / @maraujop
Please don’t manually
test your forms
28
-
79.
Please don’t manually
test your forms
• This isn’t a testing talk but...
Advanced Django Form Usage
@pydanny / @maraujop
28
-
80.
Please don’t manually
test your forms
• This isn’t a testing talk but...
• Forms are the number one thing to test
Advanced Django Form Usage
@pydanny / @maraujop
28
-
81.
Please don’t manually
test your forms
• This isn’t a testing talk but...
• Forms are the number one thing to test
• Don’t skip on testing them
Advanced Django Form Usage
@pydanny / @maraujop
28
-
82.
Please don’t manually
test your forms
• This isn’t a testing talk but...
• Forms are the number one thing to test
• Don’t skip on testing them
• Edge case insanity is the thing to fear
Advanced Django Form Usage
@pydanny / @maraujop
28
-
83.
Django unit tests!
def test_add_package_view(self):
url = reverse('my-url')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'package/package_form.html')
for c in Category.objects.all():
self.assertContains(response, c.title)
count = Package.objects.count()
response = self.client.post(url, {
'category': Category.objects.all()[0].pk,
'repo_url': 'http://github.com/django/django',
Advanced Django Form Usage
'slug': 'test-slug',
@pydanny / @maraujop
'title': 'TEST TITLE',
}, follow=True)
self.assertEqual(Package.objects.count(), count + 1)
self.assertContains(response, "Django")
29
-
84.
Django unit tests!
def test_add_package_view(self):
url = reverse('my-url')
response = self.client.get(url) POSTing a form
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'package/package_form.html')
for c in Category.objects.all():
self.assertContains(response, c.title)
count = Package.objects.count()
response = self.client.post(url, {
'category': Category.objects.all()[0].pk,
'repo_url': 'http://github.com/django/django',
Advanced Django Form Usage
'slug': 'test-slug',
@pydanny / @maraujop
'title': 'TEST TITLE',
}, follow=True)
self.assertEqual(Package.objects.count(), count + 1)
self.assertContains(response, "Django")
29
-
85.
Django unit tests!
def test_add_package_view(self):
url = reverse('my-url')
response = self.client.get(url) POSTing a form
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'package/package_form.html')
for c in Category.objects.all():
self.assertContains(response, c.title)
count = Package.objects.count()
follow=True
response = self.client.post(url, {
'category': Category.objects.all()[0].pk,
'repo_url': 'http://github.com/django/django',
Advanced Django Form Usage
'slug': 'test-slug',
@pydanny / @maraujop
'title': 'TEST TITLE',
}, follow=True)
self.assertEqual(Package.objects.count(), count + 1)
self.assertContains(response, "Django")
29
-
86.
Django unit tests!
def test_add_package_view(self):
url = reverse('my-url')
response = self.client.get(url) POSTing a form
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'package/package_form.html')
for c in Category.objects.all():
self.assertContains(response, c.title)
count = Package.objects.count()
follow=True
response = self.client.post(url, {
'category': Category.objects.all()[0].pk,
assertContains is
'repo_url': 'http://github.com/django/django',
Advanced Django Form Usage
'slug': 'test-slug',
your best friend
@pydanny / @maraujop
'title': 'TEST TITLE',
}, follow=True)
self.assertEqual(Package.objects.count(), count + 1)
self.assertContains(response, "Django")
29
-
87.
Can we
change non-required
to required?
-
88.
non-required to required
• Your model fields are non-required
• but you want the form fields to be required
Advanced Django Form Usage
@pydanny / @maraujop
31
-
89.
A basic Django models.py
class MyModel(models.Model):
name = models.CharField(_('Name'), max_length=50, blank=True, null=True)
age = models.IntegerField(_('Age in years'), blank=True, null=True)
profession = models.CharField(_('Profession'),
max_length=100, blank=True, null=True)
bio = models.TextField(_('Bio'), blank=True, null=True)
Advanced Django Form Usage
@pydanny / @maraujop
32
-
90.
Classic forms.py overload
class MyModelTooMuchTypingForm(forms.ModelForm):
""" I've done this and it sucks
hard to debug and too much duplication
"""
name = forms.CharField(_('Name'), max_length=50, required=True)
age = forms.IntegerField(_('Age in years'), required=True)
profession = forms.CharField(_('Profession'), required=True)
bio = forms.TextField(_('Bio'), required=True)
class Meta:
model = MyModel
Advanced Django Form Usage
@pydanny / @maraujop
33
-
91.
Classic forms.py overload
class MyModelTooMuchTypingForm(forms.ModelForm):
""" I've done this and it sucks
hard to debug and too much duplication
"""
name = forms.CharField(_('Name'), max_length=50, required=True)
age = forms.IntegerField(_('Age in years'), required=True)
profession = forms.CharField(_('Profession'), required=True)
bio = forms.TextField(_('Bio'), required=True)
class Meta:
model = MyModel
Advanced Django Form Usage
@pydanny / @maraujop
class MyModel(models.Model):
name = models.CharField(_('Name'), max_length=50, blank=True, null=True)
age = models.IntegerField(_('Age in years'), blank=True, null=True)
profession = models.CharField(_('Profession'), max_length=100, blank=True, null=True)
bio = models.TextField(_('Bio'), blank=True, null=True)
33
-
92.
Classic forms.py overload
class MyModelTooMuchTypingForm(forms.ModelForm):
""" I've done this and it sucks
hard to debug and too much duplication
"""
name = forms.CharField(_('Name'), max_length=50, required=True)
age = forms.IntegerField(_('Age in years'), required=True)
profession = forms.CharField(_('Profession'), required=True)
bio = forms.TextField(_('Bio'), required=True)
class Meta: Nearly duplicated code
model = MyModel
Advanced Django Form Usage
@pydanny / @maraujop
class MyModel(models.Model):
name = models.CharField(_('Name'), max_length=50, blank=True, null=True)
age = models.IntegerField(_('Age in years'), blank=True, null=True)
profession = models.CharField(_('Profession'), max_length=100, blank=True, null=True)
bio = models.TextField(_('Bio'), blank=True, null=True)
33
-
93.
Classic forms.py overload
class MyModelTooMuchTypingForm(forms.ModelForm):
""" I've done this and it sucks
hard to debug and too much duplication
"""
name = forms.CharField(_('Name'), max_length=50, required=True)
age = forms.IntegerField(_('Age in years'), required=True)
profession = forms.CharField(_('Profession'), required=True)
bio = forms.TextField(_('Bio'), required=True)
class Meta: Nearly duplicated code
model = MyModel
Advanced Django Form Usage
@pydanny / @maraujop
class MyModel(models.Model):
name = models.CharField(_('Name'), max_length=50, blank=True, null=True)
age = models.IntegerField(_('Age in years'), blank=True, null=True)
profession = models.CharField(_('Profession'), max_length=100, blank=True, null=True)
bio = models.TextField(_('Bio'), blank=True, null=True)
33
-
94.
Better forms.py overload
class MyModelForm(forms.ModelForm):
""" Much better and you are extending, not copy/pasting """
def __init__(self):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
self.fields['age'].required = True
self.fields['profession'].required = True
self.fields['profession'].help_text = _("Hi professor")
Advanced Django Form Usage
class Meta:
Fields are in a dict-like object
@pydanny / @maraujop
model = MyModel
34
-
95.
Try it with inheritance!
class BaseEmailForm(forms.Form):
email = forms.EmailField(_('Email'))
confirm_email = forms.EmailField(_('Email 2'))
class ContactForm(BaseEmailForm):
message = forms.CharField(_('Message'))
def __init__(self):
super(ContactForm, self).__init__(*args, **kwargs)
self.fields['confirm_email'].label = _('Confirm your email')
self.fields['confirm_email'].description = _('We want to be absolutely
certain we have your correct email address.')
Advanced Django Form Usage
@pydanny / @maraujop
35
-
96.
Add dynamic fields
to a form
-
97.
Dynamically adding fields
to a form
def my_view(request, template_name='myapp/my_model_form.html'):
form = MyModelForm(request.POST or None)
# Let's add a field on the go, needs to be done before validating it
form.fields['favorite_color'] = forms.ChoiceField(
label = "Which is your favorite color from these?",
choices = (('blue', 'blue'), ('red', 'red'), ('green', 'green')),
widget = forms.RadioSelect,
required = True,
)
if form.is_valid():
# Let's get user's favorite color,
# you can do whatever you want with it
favorite_color = form.cleaned_data['favorite_color']
Advanced Django Form Usage
form.save()
@pydanny / @maraujop
return redirect('home')
return render(request, template_name, {'form': form})
37
-
98.
Dynamically adding fields
to a form
def my_view(request, template_name='myapp/my_model_form.html'): Form dictionary
form = MyModelForm(request.POST or None) of fields
# Let's add a field on the go, needs to be done before validating it
form.fields['favorite_color'] = forms.ChoiceField(
label = "Which is your favorite color from these?",
choices = (('blue', 'blue'), ('red', 'red'), ('green', 'green')),
widget = forms.RadioSelect,
required = True,
)
if form.is_valid():
# Let's get user's favorite color,
# you can do whatever you want with it
favorite_color = form.cleaned_data['favorite_color']
Advanced Django Form Usage
form.save()
@pydanny / @maraujop
return redirect('home')
return render(request, template_name, {'form': form})
37
-
99.
Dynamically adding fields
to a form
def my_view(request, template_name='myapp/my_model_form.html'): Form dictionary
form = MyModelForm(request.POST or None) of fields
# Let's add a field on the go, needs to be done before validating it
form.fields['favorite_color'] = forms.ChoiceField(
label = "Which is your favorite color from these?",
choices = (('blue', 'blue'), ('red', 'red'), ('green', 'green')),
widget = forms.RadioSelect,
required = True,
)
Fields have to be added
before the form.is_valid
if form.is_valid():
# Let's get user's favorite color,
# you can do whatever you want with it
favorite_color = form.cleaned_data['favorite_color']
method is checked.
Advanced Django Form Usage
form.save()
@pydanny / @maraujop
return redirect('home')
return render(request, template_name, {'form': form})
37
-
100.
Can’t we just pass in a
list of fields into a form
from a view?
-
101.
Constructor overrides forms.py
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
extra = kwargs.pop('extra')
super(UserCreationForm, self).__init__(*args, **kwargs)
for i, question in enumerate(extra):
self.fields['custom_%s' % i] = forms.CharField(label=question)
def extra_fields(self): looping over
"""
Returns a tuple (question, answer)
‘extra’ iterable
Advanced Django Form Usage
"""
@pydanny / @maraujop
for name, value in self.cleaned_data.items():
if name.startswith('custom_'):
yield (self.fields[name].label, value)
39
-
102.
Constructor overrides views.py
def my_view(request, template_name='myapp/my_model_form.html'):
form = MyModelForm(request.POST or None,
extra=["What's your pet's name?"])
Passing in a list
if form.is_valid():
# We can gather extra fields data doing
of form field
for (question, answer) in form.extra_fields(): titles
save_answer(request, question, answer)
form.save()
return redirect('home')
Advanced Django Form Usage
@pydanny / @maraujop
return render(request, template_name, {'form': form})
40
-
103.
I need a formset
-
104.
formsets.py
class ItemFormSet(BaseFormSet):
def __init__(self, numberItems, *args, **kwargs):
super(ItemFormSet, self).__init__(*args, **kwargs)
self.numberItems = numberItems
def clean(self):
# Don't bother validating the formset
# unless each form is valid on its own
if any(self.errors):
return
for form in self.forms:
Advanced Django Form Usage
if not form.cleaned_data['your_choice'] == 'mod_wsgi':
@pydanny / @maraujop
raise ValidationError(u'mod_wsgi is the way to go!')
42
-
105.
formsets.py
class ItemFormSet(BaseFormSet):
def __init__(self, numberItems, *args, **kwargs):
super(ItemFormSet, self).__init__(*args, **kwargs)
self.numberItems = numberItems
def clean(self):
Forms must have
# Don't bother validating the formset
‘your_choice’ field that
# unless each form is valid on its own
if any(self.errors):
return only accepts “mod_wsgi”
for form in self.forms:
Advanced Django Form Usage
if not form.cleaned_data['your_choice'] == 'mod_wsgi':
@pydanny / @maraujop
raise ValidationError(u'mod_wsgi is the way to go!')
42
-
106.
views.py
from django.forms.models import formset_factory
def wsgi_form_view(request):
WsgiFormset = formset_factory(ExampleForm, extra=5,
formset=ItemFormSet)
formset = WsgiFormset(bogus_number, request.POST,
request.FILES)
Advanced Django Form Usage
@pydanny / @maraujop
Truncated formset view
43
-
107.
Lets do
programmatic layout
of forms
-
108.
Things I want python
to describe in forms
• Different fieldsets within the same form
• Various buttons
• submit
• reset
Advanced Django Form Usage
@pydanny / @maraujop
45
-
109.
Advanced Django Form Usage
@pydanny / @maraujop
django-uni-form
46
-
110.
Programmatic layouts
class ExampleForm(forms.Form):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'first arg is the legend of the fieldset',
'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color',
'favorite_food',
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
111.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'first arg is the legend of the fieldset',
'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color',
'favorite_food',
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
112.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form): FormHelper
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'first arg is the legend of the fieldset',
'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color',
'favorite_food',
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
113.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form): FormHelper
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout( Layout
Fieldset(
'first arg is the legend of the fieldset',
'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color',
'favorite_food',
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
114.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form): FormHelper
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout( Layout
Fieldset(
'first arg is the legend of the fieldset',
Fieldset 'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color',
'favorite_food',
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
115.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form): FormHelper
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout( Layout
Fieldset(
'first arg is the legend of the fieldset',
Fieldset 'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
'favorite_color', Button
'favorite_food',
Holder
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
116.
Programmatic layouts
from uni_form.helpers import FormHelper, Submit, Reset
from uni_form.helpers import Fieldset, ButtonHolder, Layout
class ExampleForm(forms.Form): FormHelper
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout( Layout
Fieldset(
'first arg is the legend of the fieldset',
Fieldset 'like_website',
'favorite_number',
),
Fieldset(
'second arg is the legend of the fieldset',
Button 'favorite_color', Button
'favorite_food',
Holder
Advanced Django Form Usage
)
@pydanny / @maraujop
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, 47
self).__init__(*args, **kwargs)
-
117.
renders as divs
{% load uni_form_tags %}
{% uni_form my_form my_form.helper %}
Renders the HTML form
with buttons and everything
wrapped in a fieldset.
Advanced Django Form Usage
@pydanny / @maraujop
48
-
118.
Sample output
<form action="#" class="uniForm">
<fieldset class="inlineLabels">
<div class="ctrlHolder">
<label for="name">Name</label>
<input type="text" id="name" name="name" value="" size="35" class="textInput"/>
</div>
<div class="ctrlHolder">
<label for="email">Email</label>
<input type="text" id="email" name="email" value="" size="35" class="textInput"/>
</div>
<div class="ctrlHolder">
<label for="comment">Comment</label>
<textarea id="comment" name="comment" rows="25" cols="25"></textarea>
</div>
<div class="buttonHolder">
<label for="tos" class="secondaryAction"><input type="checkbox" id="tos" name="tos"/>
Advanced Django Form Usage
I agree to the <a href="#">terms of service</a></label>
<button type="submit" class="primaryAction">Post comment</button>
@pydanny / @maraujop
</div>
</fieldset>
</form>
49
-
119.
django-uni-form
• Programmatic layout
• Div based forms
• Section 508 compliant
• Fully customizable templates
Advanced Django Form Usage
• http://django-uni-form.rtfd.org
@pydanny / @maraujop
50
-
120.
What about
HTML5 Fields?
-
121.
django-floppyforms
• by Bruno Renie @brutasse
• HTML5 Widgets
• Fully customizable templates
• Plays nice with django-uni-form
Advanced Django Form Usage
@pydanny / @maraujop
52
-
122.
Easy to use!
import floppyforms as forms
class ExampleForm(forms.Form):
username = forms.CharField(
label='',
widget = forms.TextInput(
attrs={'placeholder': '@johndoe'},
),
)
• Django’s widget parameter attrs expects a dictionary
Advanced Django Form Usage
@pydanny / @maraujop
• Replaces the normal widgets with HTML5 ones
53
-
123.
Customizable widgets
import floppyforms as forms
class OtherEmailInput(forms.EmailInput):
template_name = 'path/to/other_email.html'
forms.py
<input type="email"
name="{{ name }}"
id="{{ attrs.id }}"
Advanced Django Form Usage
placeholder="john@example.com"
@pydanny / @maraujop
{% if value %}value="{{ value }}"{% endif %}>
path/to/other_email.html
54
-
124.
Can you combine
django-uni-form and
django-floppyforms?
-
125.
django-uni-form +
django-floppyforms
=
AWESOME POWER
-
126.
They get along well
class ListFriendsForm(forms.Form):
username = forms.CharField(
widget = forms.TextInput(
attrs={'placeholder': '@maraujop'}, No changes or
)
),
special code needed
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
'username',
)
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
Advanced Django Form Usage
)
@pydanny / @maraujop
)
return super(ExampleForm, self).__init__(*args, **kwargs)
57
-
127.
Forms refactor
Django 1.4
-
128.
Advanced Django Form Usage
@pydanny / @maraujop
What we are getting in 1.4
59
-
129.
What we are getting in 1.4
• Form rendering will use templates instead
of HTML in Python
Advanced Django Form Usage
@pydanny / @maraujop
59
-
130.
What we are getting in 1.4
• Form rendering will use templates instead
of HTML in Python
• Template based widgets
Advanced Django Form Usage
@pydanny / @maraujop
59
-
131.
What we are getting in 1.4
• Form rendering will use templates instead
of HTML in Python
• Template based widgets
• Template based form layout
Advanced Django Form Usage
@pydanny / @maraujop
59
-
132.
Advanced Django Form Usage
@pydanny / @maraujop
forms refactor and
django-uni-form
60
-
133.
forms refactor and
django-uni-form
• forms refactor layout “lives” in templates
Advanced Django Form Usage
@pydanny / @maraujop
60
-
134.
forms refactor and
django-uni-form
• forms refactor layout “lives” in templates
• django-uni-form layout “lives” in python
Advanced Django Form Usage
@pydanny / @maraujop
60
-
135.
forms refactor and
django-uni-form
• forms refactor layout “lives” in templates
• django-uni-form layout “lives” in python
• Different approaches, same objective
Advanced Django Form Usage
@pydanny / @maraujop
60
-
136.
Give me a way to
create custom
form fields
-
137.
The docs on custom fields
“Its only requirements are that it implement
a clean() method and that its __init__()
method accept the core arguments
mentioned above (required, label, initial,
widget, help_text).”
Advanced Django Form Usage
@pydanny / @maraujop
62
-
138.
The docs on custom fields
“Its only requirements are that it implement
a clean() method and that its __init__()
method accept the core arguments
mentioned above (required, label, initial,
widget, help_text).”
Advanced Django Form Usage
The docs seem to be wrong
@pydanny / @maraujop
62
-
139.
How it really works
You don’t need to implement “clean”
You do need to implement:
• required
• widget • error_messages
• label • show_hidden_initial
Advanced Django Form Usage
• help_text • validators
@pydanny / @maraujop
• initial • localize
63
-
140.
How the heck then do I
actually add a custom
form field?!?
-
141.
Advanced Django Form Usage
@pydanny / @maraujop
validation work?
How does form
65
-
142.
fields.py
class AMarkField(forms.Field):
widget = TextInput
default_error_messages = {
'not_an_a': _(u'you can only input A here! damn!'),
}
def __init__(self, **kwargs):
super(AMarkField, self).__init__(**kwargs)
def to_python(self, value):
if value in validators.EMPTY_VALUES:
Only accepts upper
return None
case A as input
Advanced Django Form Usage
if value != 'A':
@pydanny / @maraujop
raise ValidationError(self.error_messages['not_an_a'])
return value
66
-
143.
Not DRY
class MyModelForm(forms.ModelForm):
mark = CharField()
def clean_mark(self):
mark_value = self.cleaned_data['mark']
if mark_value is not None or mark_value.upper() != 'A':
raise ValidationError(_(u'Only input A here!'))
return mark_value
class ExampleForm(forms.Form):
mark = CharField()
def clean_mark(self):
Advanced Django Form Usage
mark_value = self.cleaned_data['mark']
@pydanny / @maraujop
if mark_value is not None or mark_value.upper() != 'A':
raise ValidationError(_(u'Only input A here!'))
return mark_value
67
-
144.
I want a custom field
that validates JSON
-
145.
The JSON field
class JSONField(forms.Field):
default_error_messages = {
'invalid': 'This is not valid JSON string'
}
def to_python(self, value):
if value in validators.EMPTY_VALUES:
return None
try:
json = simplejson.loads(value)
Advanced Django Form Usage
except ValueError:
@pydanny / @maraujop
raise ValidationError(self.error_messages['invalid'])
return json
69
-
146.
I want a custom
widget that handles
multiple widgets
-
147.
widgets.py
from django.forms.extras.widgets import MultiWidget
class AddressWidget(MultiWidget):
def __init__(self, attrs=None):
widgets = (TextInput, TextInput)
super(AddressWidget, self).__init__(widgets, attrs)
def decompress(self, value):
"""
If called, value is a string should return a list
"""
Advanced Django Form Usage
if value:
# parse stuff and return a list
@pydanny / @maraujop
return value.split()
return [None, None]
71
-
148.
widgets.py
from django.forms.extras.widgets import MultiWidget
class AddressWidget(MultiWidget): Two TextInput
def __init__(self, attrs=None): widgets!!!
widgets = (TextInput, TextInput)
super(AddressWidget, self).__init__(widgets, attrs)
def decompress(self, value):
"""
If called, value is a string should return a list
"""
Advanced Django Form Usage
if value:
# parse stuff and return a list
@pydanny / @maraujop
return value.split()
return [None, None]
71
-
149.
widgets.py
from django.forms.extras.widgets import MultiWidget
class AddressWidget(MultiWidget): Two TextInput
def __init__(self, attrs=None): widgets!!!
widgets = (TextInput, TextInput)
super(AddressWidget, self).__init__(widgets, attrs)
def decompress(self, value):
"""
If called, value is a string should return a list
"""
Advanced Django Form Usage
if value:
# parse stuff and return a list
@pydanny / @maraujop
return value.split()
Called when a form with
return [None, None] MultiWidget is passed initial
or instance values
71
-
150.
fields.py
class AddressField(forms.Field):
self.widget = AddressWidget
def to_python(self, value):
# Already gets a Python list
return value
isinstance(["921 SW Sixth Avenue", "Portland"], list)
Advanced Django Form Usage
@pydanny / @maraujop
72
-
151.
fields.py
class AddressField(forms.Field):
self.widget = AddressWidget
def to_python(self, value):
# Already gets a Python list
return value
isinstance(["921 SW Sixth Avenue", "Portland"], list)
class ExampleForm(object):
forms.CharField(widget=AddressWidget)
Advanced Django Form Usage
isinstance("921 SW Sixth Avenue, Portland", str)
@pydanny / @maraujop
72
-
152.
Give me a MultiValue,
MultiWidget field
-
153.
MultiValue, MultiWidget Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def compress(self, data_list):
return data_list
Advanced Django Form Usage
@pydanny / @maraujop
74
-
154.
MultiValue, MultiWidget Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def compress(self, data_list):
return data_list
• Clean does not work in the standard way
Advanced Django Form Usage
@pydanny / @maraujop
74
-
155.
MultiValue, MultiWidget Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def compress(self, data_list):
return data_list
• Clean does not work in the standard way
• Every field validated with its corresponding widget value
Advanced Django Form Usage
@pydanny / @maraujop
74
-
156.
MultiValue, MultiWidget Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def compress(self, data_list):
return data_list
• Clean does not work in the standard way
• Every field validated with its corresponding widget value
Advanced Django Form Usage
•
@pydanny / @maraujop
Beware! run_validator does run but validate is called
74
-
157.
MultiValue, MultiWidget Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def compress(self, data_list):
return data_list
• Clean does not work in the standard way
• Every field validated with its corresponding widget value
Advanced Django Form Usage
•
@pydanny / @maraujop
Beware! run_validator does run but validate is called
Django Ticket #14184
74
-
158.
Validators with Multi-Field
class AlternativeAddressField(forms.MultiValueField):
widget = AddressWidget
def __init__(self, *args, **kwargs):
fields = (forms.CharField(), forms.CharField())
super(AlternativeAddressField, self).__init__(fields,
*args, **kwargs)
def validate(self, value):
self._run_validators(value)
def compress(self, data_list):
Advanced Django Form Usage
return data_list
@pydanny / @maraujop
75
-
159.
How about custom
widget output?
-
160.
Advanced Django Form Usage
@pydanny / @maraujop
Custom Widget Output
So Easy!
77
-
161.
def render(self, name, value, attrs=None):
Custom Widget Output
# HTML to be added to the output
widget_labels = [
'<label for="id_%s">Address: </label>',
'<label for="id_%s">Number: </label>'
]
if self.is_localized:
for widget in self.widgets:
widget.is_localized = self.is_localized
# value is a list of values, each corresponding to a widget
So Easy!
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id', None)
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
Advanced Django Form Usage
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
# Adding labels
@pydanny / @maraujop
output.append(widget_labels[i] % ('%s_%s' % (name, i)))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return mark_safe(self.format_output(output))
77
-
162.
def render(self, name, value, attrs=None):
Custom Widget Output
# HTML to be added to the output
widget_labels = [
'<label for="id_%s">Address: </label>',
'<label for="id_%s">Number: </label>'
]
if self.is_localized:
for widget in self.widgets:
widget.is_localized = self.is_localized
# value is a list of values, each corresponding to a widget
So Easy!
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id', None)
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
Advanced Django Form Usage
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
# Adding labels
@pydanny / @maraujop
output.append(widget_labels[i] % ('%s_%s' % (name, i)))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return mark_safe(self.format_output(output))
77
-
163.
Can’t you make it
easier to understand?
-
164.
Problems with Custom
Widget Output
• Overring render means indepth knowledge
• Hard to do custom output with built-in widgets
• You can’t call super.render and customize easily
• Templates? Open Ticket #15667
Advanced Django Form Usage
@pydanny / @maraujop
79
-
165.
Not in Lawrence
Kansas anymore
-
166.
Problems with Custom
Widget Output
• Oldest ticket in Django is related to forms
(#23)
• ComboField is broken
• Validators are thought to validate simple values
Advanced Django Form Usage
because of the way run_validators is coded
@pydanny / @maraujop
81
-
167.
HTML5 tickets
• Ticket #16304
• Ticket #16630
• Just use django-floppyforms
Advanced Django Form Usage
@pydanny / @maraujop
82
-
168.
Validators
• Imagine you have an input that get a list of emails seperated
by spaces
• Your widget returns: [“a@example.com”, “b@example.com”]
• You want to run validators on all of them
•
Advanced Django Form Usage
There is an EmailValidator
@pydanny / @maraujop
83
-
169.
MultiValidator
class MultiValidator(object):
def __init__(self, validators):
self.validators = validators
def __call__(self, data_list):
errors = []
for value in data_list:
for validator in self.validators:
try:
validator(value)
except ValidationError, e:
Advanced Django Form Usage
if hasattr(e, 'code'):
errors.append("FiXED MESSAGE")
@pydanny / @maraujop
raise ValidationError(errors)
84
-
170.
Amazing stuff
• Ticket #27 reported by Adrian Holovaty
• Single form field for multiple database fields
Advanced Django Form Usage
@pydanny / @maraujop
85
-
171.
Fin
-
172.
Advanced Django Form Usage
@pydanny / @maraujop
This has been
incredible
87
-
173.
This has been
incredible
• We’ve just scratched the surface
Advanced Django Form Usage
@pydanny / @maraujop
87
-
174.
This has been
incredible
• We’ve just scratched the surface
• Keep your code clean - or you’ll regret it
Advanced Django Form Usage
@pydanny / @maraujop
87
D\n
M\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\nIf you can reduce your line count from 10 to 2 and make it more obvious, that is good\nThis is why so many people prefer things like Python over Java\n
D\nYou want to have shallow code with as little if statements as possible\nOtherwise you create lots of hard to test edge cases\n
D\n
D\n
D\n
D\n
D\n
M\nShow me the code!\n
D\n
D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
D\n
D\n
D\n
D\n
D\n
D\n
D \nMy fiancee, Audrey Roy, said my example needs to handle file uploads. How do we do that?\n
M\n
M\n
M\n
M\n
M\nHey Danny, don&#x2019;t 91% of real Django projects use ModelForms?\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\nHey Miguel, how do I make this work when adding data?\n
M\n
M\n
M\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
M\nAny other tricks?\n
D\n
D\n
D\n
D\n
D\n
D Fields are in a dictionary! We should remember this fact!\n
D\n
D\n\n
M\nDidn&#x2019;t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
M\nDidn&#x2019;t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
D\n\n
M - Let's override form constructor to wrap the logic of adding fields on the go\n\n
M - Let's override form constructor to wrap the logic of adding fields on the go\n\n
D\n\n
M\n
M\n
M\n\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n
D\n\n
M\n
M\n
M\n
D\n\n
M\n\n
M\nBecause both DUF and DFF do things in a clean, non-magical way - they just work together. \nNamespaces and clean code beat smart hackery any day.\n
M\nTell me what is coming down the road\n
D\n
D\n
D\n
D\nIt&#x2019;s okay that there are different objects\n
D\nIt&#x2019;s okay that there are different objects\n
D\nIt&#x2019;s okay that there are different objects\n
M\n\n
D\n\n
D\n
D\n\n
M\n
M Of course you could do the same with a regular CharField using form validationclean_<fieldname> method, but...\n
M - Duplicated code. So do a custom form field instead of duplicating code everywhere\nYes, you could solve this with form inheritance, but what if the inheritance chain gets too long?\n
M\n\n
D This field returns an already JSON validated Python list when accessing cleaned_data\n
D\n\n
M We are using MultiWidget.\n
M We are using MultiWidget.\n
M The difference is that you can get fancier with validation on the list than the string\n
D\n\n
M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
M\n
M\n\n
D - You are going to love how easy this is! Ready?\n
D - You are going to love how easy this is! Ready?\n
D\n\n
M\n
M\n\n
M\n
M\n
M\n
M * You can raise a ValidationError with a code. * You can raise a ValidationError with a list of messages. * You could raise a ValidationError on the first email that fails, but error will be less helpful. * Hard to customize, no error_messages, Do this in an upper layer, rewrite run_validators\n
M\n
D\n
D\n
D\n
D\n
M\n
M\n
M\n
M\n
D\n
\n