Advanced Django Form Usage by Daniel Greenfeld and Miguel Araujo
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
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
Advanced Django Form Usage @pydanny / @maraujop Tons of technical content 4
Tons of technical content • We probably won’t have time for questions Advanced Django Form Usage @pydanny / @maraujop 4
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
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
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
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
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
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
Advanced Django Form Usage @pydanny / @maraujop Good Form Patterns Fundamentals of 5
Fundamentals of Good Form Patterns • Zen of Python still applies Advanced Django Form Usage @pydanny / @maraujop 5
Fundamentals of Good Form Patterns • Zen of Python still applies • import this Advanced Django Form Usage @pydanny / @maraujop 5
Fundamentals of Good Form Patterns • Zen of Python still applies • import this • Spartan programming als0 is important Advanced Django Form Usage @pydanny / @maraujop 5
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
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
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
Advanced Django Form Usage @pydanny / @maraujop Calling forms the easy way 9
Calling forms the easy way • Smaller, cleaner code makes our lives better Advanced Django Form Usage @pydanny / @maraujop 9
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
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
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
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
Enough theory
A Basic Django Form class MyForm(forms.Form): name = forms.CharField(_('Name'), required=True) Advanced Django Form Usage @pydanny / @maraujop 11
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
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
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
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
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
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
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
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
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
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
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
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
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
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
What about file uploads?
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
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
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
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
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
What about ModelForms?
Advanced Django Form Usage @pydanny / @maraujop pydanny made up statistics 18
pydanny made up statistics • 91% of all Django projects use ModelForms Advanced Django Form Usage @pydanny / @maraujop 18
pydanny made up statistics • 91% of all Django projects use ModelForms • 80% ModelForms require trivial logic Advanced Django Form Usage @pydanny / @maraujop 18
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
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
A Basic ModelForm class MyModelForm(forms.Form): class Meta: model = MyModel fields = ['name'] Advanced Django Form Usage @pydanny / @maraujop 19
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
What about new model instances?
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
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
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
How do you test this stuff?
Advanced Django Form Usage @pydanny / @maraujop Please don’t manually test your forms 28
Please don’t manually test your forms • This isn’t a testing talk but... Advanced Django Form Usage @pydanny / @maraujop 28
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
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
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
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
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
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
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
Can we change non-required to required?
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
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
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
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
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
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
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
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
Add dynamic fields to a form
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
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
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
Can’t we just pass in a list of fields into a form from a view?
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
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
I need a formset
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
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
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
Lets do programmatic layout of forms
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
Advanced Django Form Usage @pydanny / @maraujop django-uni-form 46
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)
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)
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)
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)
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)
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)
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)
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
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
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
What about HTML5 Fields?
django-floppyforms • by Bruno Renie @brutasse • HTML5 Widgets • Fully customizable templates • Plays nice with django-uni-form Advanced Django Form Usage @pydanny / @maraujop 52
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
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
Can you combine django-uni-form and django-floppyforms?
django-uni-form + django-floppyforms = AWESOME POWER
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
Forms refactor Django 1.4
Advanced Django Form Usage @pydanny / @maraujop What we are getting in 1.4 59
What we are getting in 1.4 • Form rendering will use templates instead of HTML in Python Advanced Django Form Usage @pydanny / @maraujop 59
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
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
Advanced Django Form Usage @pydanny / @maraujop forms refactor and django-uni-form 60
forms refactor and django-uni-form • forms refactor layout “lives” in templates Advanced Django Form Usage @pydanny / @maraujop 60
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
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
Give me a way to create custom form fields
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
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
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
How the heck then do I actually add a custom form field?!?
Advanced Django Form Usage @pydanny / @maraujop validation work? How does form 65
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
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
I want a custom field that validates JSON
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
I want a custom widget that handles multiple widgets
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
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
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
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
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
Give me a MultiValue, MultiWidget field
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
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
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
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
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
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
How about custom widget output?
Advanced Django Form Usage @pydanny / @maraujop Custom Widget Output So Easy! 77
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
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
Can’t you make it easier to understand?
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
Not in Lawrence Kansas anymore
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
HTML5 tickets • Ticket #16304 • Ticket #16630 • Just use django-floppyforms Advanced Django Form Usage @pydanny / @maraujop 82
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
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
Amazing stuff • Ticket #27 reported by Adrian Holovaty • Single form field for multiple database fields Advanced Django Form Usage @pydanny / @maraujop 85
Fin
Advanced Django Form Usage @pydanny / @maraujop This has been incredible 87
This has been incredible • We’ve just scratched the surface Advanced Django Form Usage @pydanny / @maraujop 87
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
This has been incredible • We’ve just scratched the surface • Keep your code clean - or you’ll regret it • Miguel is awesome Advanced Django Form Usage @pydanny / @maraujop 87
Advanced Django Form Usage @pydanny / @maraujop This has been incredible 88
This has been incredible • We want to see this and more in the formal Django docs Advanced Django Form Usage @pydanny / @maraujop 88
This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch Advanced Django Form Usage @pydanny / @maraujop 88
This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch • There are hidden things that need to see the light of day Advanced Django Form Usage @pydanny / @maraujop 88
This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch • There are hidden things that need to see the light of day Advanced Django Form Usage • Danny holds the DjangoCon talks record @pydanny / @maraujop 88
Fin
Advanced Django Form Usage @pydanny / @maraujop Daniel Greenfeld & Miguel Araujo Questions? 90

Advanced Django Forms Usage

  • 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 FormUsage @pydanny / @maraujop Tons of technical content 4
  • 5.
    Tons of technicalcontent • We probably won’t have time for questions Advanced Django Form Usage @pydanny / @maraujop 4
  • 6.
    Tons of technicalcontent • 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 technicalcontent • 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 technicalcontent • 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 technicalcontent • 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 technicalcontent • 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 technicalcontent • 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 FormUsage @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 FormUsage @pydanny / @maraujop Calling forms the easy way 9
  • 20.
    Calling forms theeasy way • Smaller, cleaner code makes our lives better Advanced Django Form Usage @pydanny / @maraujop 9
  • 21.
    Calling forms theeasy 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 theeasy 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 theeasy 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 theeasy 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.
  • 26.
    A Basic DjangoForm 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.
  • 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.
  • 48.
    Advanced Django FormUsage @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 classMyModelForm(forms.Form): class Meta: model = MyModel fields = ['name'] Advanced Django Form Usage @pydanny / @maraujop 19
  • 54.
    Classic views.py forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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 forModelForm 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.
  • 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 makeit 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 youtest this stuff?
  • 78.
    Advanced Django FormUsage @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! deftest_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! deftest_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! deftest_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! deftest_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.
  • 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 Djangomodels.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 classMyModelTooMuchTypingForm(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 classMyModelTooMuchTypingForm(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 classMyModelTooMuchTypingForm(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 classMyModelTooMuchTypingForm(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 classMyModelForm(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 withinheritance! 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.
  • 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 justpass in a list of fields into a form from a view?
  • 101.
    Constructor overrides forms.py classMyModelForm(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 aformset
  • 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 importformset_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.
  • 108.
    Things I wantpython to describe in forms • Different fieldsets within the same form • Various buttons • submit • reset Advanced Django Form Usage @pydanny / @maraujop 45
  • 109.
    Advanced Django FormUsage @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.
  • 121.
    django-floppyforms • by BrunoRenie @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 floppyformsas 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-formand django-floppyforms?
  • 125.
  • 126.
    They get alongwell 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 FormUsage @pydanny / @maraujop What we are getting in 1.4 59
  • 129.
    What we aregetting in 1.4 • Form rendering will use templates instead of HTML in Python Advanced Django Form Usage @pydanny / @maraujop 59
  • 130.
    What we aregetting 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 aregetting 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 FormUsage @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 away to create custom form fields
  • 137.
    The docs oncustom 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 oncustom 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 reallyworks 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 heckthen do I actually add a custom form field?!?
  • 141.
    Advanced Django FormUsage @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 acustom field that validates JSON
  • 145.
    The JSON field classJSONField(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 acustom widget that handles multiple widgets
  • 147.
    widgets.py from django.forms.extras.widgets importMultiWidget 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 importMultiWidget 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 importMultiWidget 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 aMultiValue, 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 classAlternativeAddressField(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 FormUsage @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 makeit 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.
  • 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.
  • 172.
    Advanced Django FormUsage @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
  • 175.
    This has been incredible • We’ve just scratched the surface • Keep your code clean - or you’ll regret it • Miguel is awesome Advanced Django Form Usage @pydanny / @maraujop 87
  • 176.
    Advanced Django FormUsage @pydanny / @maraujop This has been incredible 88
  • 177.
    This has been incredible • We want to see this and more in the formal Django docs Advanced Django Form Usage @pydanny / @maraujop 88
  • 178.
    This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch Advanced Django Form Usage @pydanny / @maraujop 88
  • 179.
    This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch • There are hidden things that need to see the light of day Advanced Django Form Usage @pydanny / @maraujop 88
  • 180.
    This has been incredible • We want to see this and more in the formal Django docs • We’ve scratched our own itch • There are hidden things that need to see the light of day Advanced Django Form Usage • Danny holds the DjangoCon talks record @pydanny / @maraujop 88
  • 181.
  • 182.
    Advanced Django FormUsage @pydanny / @maraujop Daniel Greenfeld & Miguel Araujo Questions? 90

Editor's Notes

  • #2 D\n
  • #3 M\n
  • #4 D\n
  • #5 D\n
  • #6 D\n
  • #7 D\n
  • #8 D\n
  • #9 D\n
  • #10 D\n
  • #11 D\n
  • #12 D\n
  • #13 D\n
  • #14 D\n
  • #15 D\n
  • #16 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
  • #17 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
  • #18 D\n
  • #19 D\n
  • #20 D\n
  • #21 D\n
  • #22 D\n
  • #23 M\nShow me the code!\n
  • #24 D\n
  • #25 D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • #26 D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • #27 D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • #28 D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • #29 D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • #30 D\n
  • #31 D\n
  • #32 D\n
  • #33 D\n
  • #34 D\n
  • #35 D\n
  • #36 D \nMy fiancee, Audrey Roy, said my example needs to handle file uploads. How do we do that?\n
  • #37 M\n
  • #38 M\n
  • #39 M\n
  • #40 M\n
  • #41 M\nHey Danny, don&amp;#x2019;t 91% of real Django projects use ModelForms?\n
  • #42 D\n
  • #43 D\n
  • #44 D\n
  • #45 D\n
  • #46 D\n
  • #47 D\n
  • #48 D\n
  • #49 D\n
  • #50 D\n
  • #51 D\n
  • #52 D\n
  • #53 D\n
  • #54 D\n
  • #55 D\n
  • #56 D\n
  • #57 D\n
  • #58 D\n
  • #59 D\n
  • #60 D\n
  • #61 D\n
  • #62 D\nHey Miguel, how do I make this work when adding data?\n
  • #63 M\n
  • #64 M\n
  • #65 M\n
  • #66 D\n
  • #67 D\n
  • #68 D\n
  • #69 D\n
  • #70 D\n
  • #71 D\n
  • #72 D\n
  • #73 M\nAny other tricks?\n
  • #74 D\n
  • #75 D\n
  • #76 D\n
  • #77 D\n
  • #78 D\n
  • #79 D Fields are in a dictionary! We should remember this fact!\n
  • #80 D\n
  • #81 D\n\n
  • #82 M\nDidn&amp;#x2019;t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
  • #83 M\nDidn&amp;#x2019;t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
  • #84 D\n\n
  • #85 M - Let&apos;s override form constructor to wrap the logic of adding fields on the go\n\n
  • #86 M - Let&apos;s override form constructor to wrap the logic of adding fields on the go\n\n
  • #87 D\n\n
  • #88 M\n
  • #89 M\n
  • #90 M\n\n
  • #91 D\n
  • #92 D\n
  • #93 D\n
  • #94 D\n
  • #95 D\n
  • #96 D\n
  • #97 D\n
  • #98 D\n
  • #99 D\n
  • #100 D\n
  • #101 D\n
  • #102 D\n\n
  • #103 M\n
  • #104 M\n
  • #105 M\n
  • #106 D\n\n
  • #107 M\n\n
  • #108 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
  • #109 M\nTell me what is coming down the road\n
  • #110 D\n
  • #111 D\n
  • #112 D\n
  • #113 D\nIt&amp;#x2019;s okay that there are different objects\n
  • #114 D\nIt&amp;#x2019;s okay that there are different objects\n
  • #115 D\nIt&amp;#x2019;s okay that there are different objects\n
  • #116 M\n\n
  • #117 D\n\n
  • #118 D\n
  • #119 D\n\n
  • #120 M\n
  • #121 M Of course you could do the same with a regular CharField using form validationclean_&lt;fieldname&gt; method, but...\n
  • #122 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
  • #123 M\n\n
  • #124 D This field returns an already JSON validated Python list when accessing cleaned_data\n
  • #125 D\n\n
  • #126 M We are using MultiWidget.\n
  • #127 M We are using MultiWidget.\n
  • #128 M The difference is that you can get fancier with validation on the list than the string\n
  • #129 D\n\n
  • #130 M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • #131 M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • #132 M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • #133 M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • #134 M\n
  • #135 M\n\n
  • #136 D - You are going to love how easy this is! Ready?\n
  • #137 D - You are going to love how easy this is! Ready?\n
  • #138 D\n\n
  • #139 M\n
  • #140 M\n\n
  • #141 M\n
  • #142 M\n
  • #143 M\n
  • #144 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
  • #145 M\n
  • #146 D\n
  • #147 D\n
  • #148 D\n
  • #149 D\n
  • #150 M\n
  • #151 M\n
  • #152 M\n
  • #153 M\n
  • #154 D\n
  • #155 \n