Advanced Django Forms Usage

  • 57,779 views
Uploaded on

An advanced forms presentation given with Miguel Araujo (marajop) at DjangoCon 2011. The transcript and slides is aimed at getting into Django Core, and Jacob Kaplan-Moss has stated this is his plan.

An advanced forms presentation given with Miguel Araujo (marajop) at DjangoCon 2011. The transcript and slides is aimed at getting into Django Core, and Jacob Kaplan-Moss has stated this is his plan.

More in: Business , Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
57,779
On Slideshare
0
From Embeds
0
Number of Embeds
19

Actions

Shares
Downloads
1,107
Comments
5
Likes
108

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide
  • D\n
  • M\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\nIf you can reduce your line count from 10 to 2 and make it more obvious, that is good\nThis is why so many people prefer things like Python over Java\n
  • D\nYou want to have shallow code with as little if statements as possible\nOtherwise you create lots of hard to test edge cases\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • M\nShow me the code!\n
  • D\n
  • D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • D\nassuming we have from forms import MyForm\nIn real life this can get big, complex, and nasty\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D \nMy fiancee, Audrey Roy, said my example needs to handle file uploads. How do we do that?\n
  • M\n
  • M\n
  • M\n
  • M\n
  • M\nHey Danny, don’t 91% of real Django projects use ModelForms?\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\nHey Miguel, how do I make this work when adding data?\n
  • M\n
  • M\n
  • M\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • M\nAny other tricks?\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D Fields are in a dictionary! We should remember this fact!\n
  • D\n
  • D\n\n
  • M\nDidn’t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
  • M\nDidn’t you just show that fields are stored in a dict in the form? Why not just add another field that way?\n
  • D\n\n
  • M - Let's override form constructor to wrap the logic of adding fields on the go\n\n
  • M - Let's override form constructor to wrap the logic of adding fields on the go\n\n
  • D\n\n
  • M\n
  • M\n
  • M\n\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n
  • D\n\n
  • M\n
  • M\n
  • M\n
  • D\n\n
  • M\n\n
  • M\nBecause both DUF and DFF do things in a clean, non-magical way - they just work together. \nNamespaces and clean code beat smart hackery any day.\n
  • M\nTell me what is coming down the road\n
  • D\n
  • D\n
  • D\n
  • D\nIt’s okay that there are different objects\n
  • D\nIt’s okay that there are different objects\n
  • D\nIt’s okay that there are different objects\n
  • M\n\n
  • D\n\n
  • D\n
  • D\n\n
  • M\n
  • M Of course you could do the same with a regular CharField using form validationclean_<fieldname> method, but...\n
  • M - Duplicated code. So do a custom form field instead of duplicating code everywhere\nYes, you could solve this with form inheritance, but what if the inheritance chain gets too long?\n
  • M\n\n
  • D This field returns an already JSON validated Python list when accessing cleaned_data\n
  • D\n\n
  • M We are using MultiWidget.\n
  • M We are using MultiWidget.\n
  • M The difference is that you can get fancier with validation on the list than the string\n
  • D\n\n
  • M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • M You need to implement compress\nIt would be nice that fields were also an attribute in the parent class.\n
  • M\n
  • M\n\n
  • D - You are going to love how easy this is! Ready?\n
  • D - You are going to love how easy this is! Ready?\n
  • D\n\n
  • M\n
  • M\n\n
  • M\n
  • M\n
  • M\n
  • M * You can raise a ValidationError with a code. * You can raise a ValidationError with a list of messages. * You could raise a ValidationError on the first email that fails, but error will be less helpful. * Hard to customize, no error_messages, Do this in an upper layer, rewrite run_validators\n
  • M\n
  • D\n
  • D\n
  • D\n
  • D\n
  • M\n
  • M\n
  • M\n
  • M\n
  • D\n
  • \n

Transcript

  • 1. Advanced Django Form Usageby 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 Usagehttp://www.flickr.com/photos/pydanny/4442245488/ Fiancé, Audrey Roy (audreyr) @pydanny / @maraujop 2
  • 3. Miguel Araujo • maraujop • Freelance Python developer • Co-lead of django-uni-form • Does Muay Thai • Lives in Madrid with his amazing girlfiend who does Advanced Django Form Usage aerospace for a living @pydanny / @maraujop • http://maraujop.github.com/ 3
  • 4. Advanced Django Form Usage @pydanny / @maraujopTons of technical content 4
  • 5. Tons of technical content• We probably won’t have time for questions Advanced Django Form Usage @pydanny / @maraujop 4
  • 6. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk Advanced Django Form Usage @pydanny / @maraujop 4
  • 7. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk• Too much content in the abstract Advanced Django Form Usage @pydanny / @maraujop 4
  • 8. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk• Too much content in the abstract• Special thanks to: Advanced Django Form Usage @pydanny / @maraujop 4
  • 9. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk• Too much content in the abstract• Special thanks to: • Brian Rosner Advanced Django Form Usage @pydanny / @maraujop 4
  • 10. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk• Too much content in the abstract• Special thanks to: • Brian Rosner Advanced Django Form Usage • James Tauber @pydanny / @maraujop 4
  • 11. Tons of technical content• We probably won’t have time for questions• Slides will be posted right after the talk• Too much content in the abstract• Special thanks to: • Brian Rosner Advanced Django Form Usage • James Tauber @pydanny / @maraujop • Frank Wiles 4
  • 12. Advanced Django Form Usage @pydanny / @maraujopGood Form Patterns Fundamentals of 5
  • 13. Fundamentals of Good Form Patterns• Zen of Python still applies Advanced Django Form Usage @pydanny / @maraujop 5
  • 14. Fundamentals of Good Form Patterns• Zen of Python still applies • import this Advanced Django Form Usage @pydanny / @maraujop 5
  • 15. Fundamentals of Good Form Patterns• Zen of Python still applies • import this• Spartan programming als0 is important Advanced Django Form Usage @pydanny / @maraujop 5
  • 16. Spartan Programming• Horizontal complexity• Vertical complexity (line count)• Token count• Character count• Variables• Advanced Django Form Usage Loops @pydanny / @maraujop• Conditionals http://www.codinghorror.com/blog/2008/07/spartan-programming.html 6
  • 17. Spartan Programming• Horizontal complexity• Vertical complexity (line count)• Token count• Character count• Variables• Advanced Django Form Usage Loops @pydanny / @maraujop• Conditionals http://www.codinghorror.com/blog/2008/07/spartan-programming.html 7
  • 18. Spartan Programming• Horizontal complexity• Vertical complexity (line count)• Token count• Character count• Variables• Advanced Django Form Usage Loops @pydanny / @maraujop• Conditionals http://www.codinghorror.com/blog/2008/07/spartan-programming.html 8
  • 19. Advanced Django Form Usage @pydanny / @maraujopCalling forms the easy way 9
  • 20. Calling forms the easy way• Smaller, cleaner code makes our lives better Advanced Django Form Usage @pydanny / @maraujop 9
  • 21. Calling forms the easy way• Smaller, cleaner code makes our lives better• Line 5 of the Zen of Python Advanced Django Form Usage @pydanny / @maraujop 9
  • 22. Calling forms the easy way• Smaller, cleaner code makes our lives better• Line 5 of the Zen of Python • Flat is better than nested. Advanced Django Form Usage @pydanny / @maraujop 9
  • 23. Calling forms the easy way• Smaller, cleaner code makes our lives better• Line 5 of the Zen of Python • Flat is better than nested.• Aim for minimal boilerplate Advanced Django Form Usage @pydanny / @maraujop 9
  • 24. Calling forms the easy way• Smaller, cleaner code makes our lives better• Line 5 of the Zen of Python • Flat is better than nested.• Aim for minimal boilerplate Advanced Django Form Usage• So you can focus on your business logic @pydanny / @maraujop 9
  • 25. Enough theory
  • 26. A Basic Django Formclass MyForm(forms.Form): name = forms.CharField(_(Name), required=True) Advanced Django Form Usage @pydanny / @maraujop 11
  • 27. Standard views.pydef 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.pydef 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.pydef 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.pydef 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.pydef 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.pydef 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.pydef 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 / @maraujopCustom business goes here 13
  • 34. Single form Easy views.pydef 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 / @maraujopCustom business goes here 13
  • 35. Single form Easy views.pydef 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 / @maraujopCustom business then instantiated with None goes here 13
  • 36. Easy views.pydef 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.pydef 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.pydef 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.pydef 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.pydef my_view(request, template_name=myapp/my_form.html): # sticks in a POST or renders empty form form = MyForm(request.POST or None) if form.is_valid(): do_x() return redirect(home) return render(request, template_name, {form: form}) • 6 lines of code instead of 9 Advanced Django Form Usage • 33.3333333333% less code to debug @pydanny / @maraujop • One form instantiation • less conditionals == less edge case insanity 14
  • 41. What aboutfile uploads?
  • 42. Easy views.py + filesdef 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 + filesdef 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 + filesdef 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 + filesdef 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 + filesdef my_view(request, template_name=myapp/my_form.html): form = MyForm(request.POST or None, request.FILES or None) if form.is_valid(): do_x() return redirect(home) return render(request, template_name, {form: form}) Request.POST Request.FILES or None or None Advanced Django Form Usage @pydanny / @maraujop • 6 lines of code again! • Code donated by Audrey Roy 16
  • 47. What aboutModelForms?
  • 48. Advanced Django Form Usage @pydanny / @maraujoppydanny 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 ModelFormclass MyModelForm(forms.Form): class Meta: model = MyModel fields = [name] Advanced Django Form Usage @pydanny / @maraujop 19
  • 54. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) Advanced Django Form Usage @pydanny / @maraujop 20
  • 55. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) Form #1 if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) Advanced Django Form Usage @pydanny / @maraujop 20
  • 56. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) Form #1 if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) Form #2 else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) Advanced Django Form Usage @pydanny / @maraujop 20
  • 57. Classic views.py for ModelForm def my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) Form #1 if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) Form #2 else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel})Only 1 nested if, but real code Advanced Django Form Usage gets much more complex @pydanny / @maraujop 20
  • 58. Classic views.py for ModelForm def my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) Form #1 if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) Form #2 else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel})Only 1 nested if, but real code Advanced Django Form Usage gets much more complex @pydanny / @maraujop Custom business goes here 20
  • 59. Classic views.py for ModelForm def my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) Form #1 if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) Form #2 else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel})Only 1 nested if, but real code Advanced Django Form Usage gets much more complex @pydanny / @maraujop Custom business goes here 20
  • 60. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) Advanced Django Form Usage @pydanny / @maraujop 21
  • 61. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) • 12 lines of code Advanced Django Form Usage @pydanny / @maraujop 21
  • 62. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) • 12 lines of code Advanced Django Form Usage • Nested conditionals @pydanny / @maraujop 21
  • 63. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its confusing mymodel = get_object_or_404(MyModel, slug=slug) if request.method == POST: form = MyForm(request, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here mymodel.save() return redirect(home) else: form = MyForm(instance=mymodel) return render(request, template_name, {form: form, model: mymodel}) • 12 lines of code Advanced Django Form Usage • Nested conditionals @pydanny / @maraujop • What if we have to handle 3 different submit buttons? 21
  • 64. Classic views.py for ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): # I wouldnt call the variable model, because its an instance of a model, its 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 + ModelFormdef 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 + ModelFormdef 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 + ModelFormdef 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 UsageCustom business @pydanny / @maraujop goes here 22
  • 68. easy views.py + ModelFormdef 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 UsageCustom business @pydanny / @maraujop goes here So this will fail validation! 22
  • 69. easy views.py + ModelFormdef 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 + ModelFormdef 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 + ModelFormdef 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 + ModelFormdef my_model_edit(request, slug=slug, template_name=myapp/my_model_form.html): mymodel = get_object_or_404(MyModel, slug=slug) form = MyModelForm(request.POST or None, instance=mymodel) if form.is_valid(): mymodel = form.save() mymodel.edited_at_djangocon = True mymodel.save() return redirect(home) return render(request, template_name, {form: form, mymodel: mymodel}) • 9 lines of code instead of 12 Advanced Django Form Usage @pydanny / @maraujop • One conditional • clear and succinct code 23
  • 73. What about newmodel instances?
  • 74. add views.py + ModelFormdef 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 + ModelFormdef my_model_add(request, template_name=myapp/my_model_form.html): form = MyModelForm(request.POST or None) if form.is_valid(): mymodel = form.save() No need for an mymodel.added_at_djangocon = True mymodel.save() instance here return redirect(home) return render(request,template_name,{form: form,mymodel:mymodel}) Advanced Django Form Usage This creates the @pydanny / @maraujop model instance. 25
  • 76. I can make it smaller!def my_model_tiny_add(request,template_name=myapp/my_model_form.html): form = MyModelForm(request.POST or None) if form.is_valid(): form.save() Not setting defaults here return redirect(home) return render(request,template_name,{form:form,mymodel:mymodel}) Advanced Django Form Usage Good practice: Use model field defaults rather @pydanny / @maraujop than doing it in your views. 26
  • 77. How do you test this stuff?
  • 78. Advanced Django Form Usage @pydanny / @maraujopPlease don’t manually test your forms 28
  • 79. Please don’t manually test your forms• This isn’t a testing talk but... Advanced Django Form Usage @pydanny / @maraujop 28
  • 80. Please don’t manually test your forms• This isn’t a testing talk but...• Forms are the number one thing to test Advanced Django Form Usage @pydanny / @maraujop 28
  • 81. Please don’t manually test your forms• This isn’t a testing talk but...• Forms are the number one thing to test• Don’t skip on testing them Advanced Django Form Usage @pydanny / @maraujop 28
  • 82. Please don’t manually test your forms• This isn’t a testing talk but...• Forms are the number one thing to test• Don’t skip on testing them• Edge case insanity is the thing to fear Advanced Django Form Usage @pydanny / @maraujop 28
  • 83. Django unit tests!def test_add_package_view(self): url = reverse(my-url) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, package/package_form.html) for c in Category.objects.all(): self.assertContains(response, c.title) count = Package.objects.count() response = self.client.post(url, { category: Category.objects.all()[0].pk, repo_url: http://github.com/django/django, Advanced Django Form Usage slug: test-slug, @pydanny / @maraujop title: TEST TITLE, }, follow=True) self.assertEqual(Package.objects.count(), count + 1) self.assertContains(response, "Django") 29
  • 84. Django unit tests!def test_add_package_view(self): url = reverse(my-url) response = self.client.get(url) POSTing a form self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, package/package_form.html) for c in Category.objects.all(): self.assertContains(response, c.title) count = Package.objects.count() response = self.client.post(url, { category: Category.objects.all()[0].pk, repo_url: http://github.com/django/django, Advanced Django Form Usage slug: test-slug, @pydanny / @maraujop title: TEST TITLE, }, follow=True) self.assertEqual(Package.objects.count(), count + 1) self.assertContains(response, "Django") 29
  • 85. Django unit tests!def test_add_package_view(self): url = reverse(my-url) response = self.client.get(url) POSTing a form self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, package/package_form.html) for c in Category.objects.all(): self.assertContains(response, c.title) count = Package.objects.count() follow=True response = self.client.post(url, { category: Category.objects.all()[0].pk, repo_url: http://github.com/django/django, Advanced Django Form Usage slug: test-slug, @pydanny / @maraujop title: TEST TITLE, }, follow=True) self.assertEqual(Package.objects.count(), count + 1) self.assertContains(response, "Django") 29
  • 86. Django unit tests!def test_add_package_view(self): url = reverse(my-url) response = self.client.get(url) POSTing a form self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, package/package_form.html) for c in Category.objects.all(): self.assertContains(response, c.title) count = Package.objects.count() follow=True response = self.client.post(url, { category: Category.objects.all()[0].pk, assertContains is repo_url: http://github.com/django/django, Advanced Django Form Usage slug: test-slug, your best friend @pydanny / @maraujop title: TEST TITLE, }, follow=True) self.assertEqual(Package.objects.count(), count + 1) self.assertContains(response, "Django") 29
  • 87. Can wechange non-required to required?
  • 88. non-required to required• Your model fields are non-required• but you want the form fields to be required Advanced Django Form Usage @pydanny / @maraujop 31
  • 89. A basic Django models.pyclass 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 overloadclass MyModelTooMuchTypingForm(forms.ModelForm): """ Ive 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 overloadclass MyModelTooMuchTypingForm(forms.ModelForm): """ Ive 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 overloadclass MyModelTooMuchTypingForm(forms.ModelForm): """ Ive 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 overloadclass MyModelTooMuchTypingForm(forms.ModelForm): """ Ive 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 overloadclass MyModelForm(forms.ModelForm): """ Much better and you are extending, not copy/pasting """ def __init__(self): super(MyModelForm, self).__init__(*args, **kwargs) self.fields[name].required = True self.fields[age].required = True self.fields[profession].required = True self.fields[profession].help_text = _("Hi professor") Advanced Django Form Usage class Meta: Fields are in a dict-like object @pydanny / @maraujop model = MyModel 34
  • 95. Try it with inheritance!class BaseEmailForm(forms.Form): email = forms.EmailField(_(Email)) confirm_email = forms.EmailField(_(Email 2))class ContactForm(BaseEmailForm): message = forms.CharField(_(Message)) def __init__(self): super(ContactForm, self).__init__(*args, **kwargs) self.fields[confirm_email].label = _(Confirm your email) self.fields[confirm_email].description = _(We want to be absolutely certain we have your correct email address.) Advanced Django Form Usage @pydanny / @maraujop 35
  • 96. Add dynamic fields to a form
  • 97. Dynamically adding fields to a formdef my_view(request, template_name=myapp/my_model_form.html): form = MyModelForm(request.POST or None) # Lets 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(): # Lets get users 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 formdef my_view(request, template_name=myapp/my_model_form.html): Form dictionary form = MyModelForm(request.POST or None) of fields # Lets 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(): # Lets get users 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 formdef my_view(request, template_name=myapp/my_model_form.html): Form dictionary form = MyModelForm(request.POST or None) of fields # Lets 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(): # Lets get users favorite color, # you can do whatever you want with it favorite_color = form.cleaned_data[favorite_color] method is checked. Advanced Django Form Usage form.save() @pydanny / @maraujop return redirect(home) return render(request, template_name, {form: form}) 37
  • 100. Can’t we just pass in alist of fields into a form from a view?
  • 101. Constructor overrides forms.pyclass MyModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): extra = kwargs.pop(extra) super(UserCreationForm, self).__init__(*args, **kwargs) for i, question in enumerate(extra): self.fields[custom_%s % i] = forms.CharField(label=question) def extra_fields(self): looping over """ Returns a tuple (question, answer) ‘extra’ iterable Advanced Django Form Usage """ @pydanny / @maraujop for name, value in self.cleaned_data.items(): if name.startswith(custom_): yield (self.fields[name].label, value) 39
  • 102. Constructor overrides views.py def my_view(request, template_name=myapp/my_model_form.html): form = MyModelForm(request.POST or None, extra=["Whats your pets name?"]) Passing in a list if form.is_valid(): # We can gather extra fields data doing of form field for (question, answer) in form.extra_fields(): titles save_answer(request, question, answer) form.save() return redirect(home) Advanced Django Form Usage @pydanny / @maraujop return render(request, template_name, {form: form}) 40
  • 103. I need a formset
  • 104. formsets.pyclass ItemFormSet(BaseFormSet): def __init__(self, numberItems, *args, **kwargs): super(ItemFormSet, self).__init__(*args, **kwargs) self.numberItems = numberItems def clean(self): # Dont 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(umod_wsgi is the way to go!) 42
  • 105. formsets.pyclass ItemFormSet(BaseFormSet): def __init__(self, numberItems, *args, **kwargs): super(ItemFormSet, self).__init__(*args, **kwargs) self.numberItems = numberItems def clean(self): Forms must have # Dont 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(umod_wsgi is the way to go!) 42
  • 106. views.pyfrom django.forms.models import formset_factorydef wsgi_form_view(request): WsgiFormset = formset_factory(ExampleForm, extra=5, formset=ItemFormSet) formset = WsgiFormset(bogus_number, request.POST, request.FILES) Advanced Django Form Usage @pydanny / @maraujop Truncated formset view 43
  • 107. Lets doprogrammatic layout of forms
  • 108. Things I want python to describe in forms• Different fieldsets within the same form• Various buttons • submit • reset Advanced Django Form Usage @pydanny / @maraujop 45
  • 109. Advanced Django Form Usage @pydanny / @maraujopdjango-uni-form 46
  • 110. Programmatic layoutsclass 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, Layoutclass 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, Layoutclass 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, Layoutclass 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, Layoutclass 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, Layoutclass 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, Layoutclass 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 UsageI agree to the <a href="#">terms of service</a></label> <button type="submit" class="primaryAction">Post comment</button> @pydanny / @maraujop </div> </fieldset></form> 49
  • 119. django-uni-form• Programmatic layout• Div based forms• Section 508 compliant• Fully customizable templates Advanced Django Form Usage• http://django-uni-form.rtfd.org @pydanny / @maraujop 50
  • 120. What aboutHTML5 Fields?
  • 121. django-floppyforms• by Bruno Renie @brutasse• HTML5 Widgets• Fully customizable templates• Plays nice with django-uni-form Advanced Django Form Usage @pydanny / @maraujop 52
  • 122. Easy to use! import floppyforms as forms class ExampleForm(forms.Form): username = forms.CharField( label=, widget = forms.TextInput( attrs={placeholder: @johndoe}, ), )• Django’s widget parameter attrs expects a dictionary Advanced Django Form Usage @pydanny / @maraujop• Replaces the normal widgets with HTML5 ones 53
  • 123. Customizable widgetsimport floppyforms as formsclass 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 combinedjango-uni-form anddjango-floppyforms?
  • 125. django-uni-form +django-floppyforms =AWESOME POWER
  • 126. They get along wellclass ListFriendsForm(forms.Form): username = forms.CharField( widget = forms.TextInput( attrs={placeholder: @maraujop}, No changes or ) ), special code needed def __init__(self, *args, **kwargs): self.helper = FormHelper() self.helper.layout = Layout( Div( username, ) ButtonHolder( Submit(submit, Submit, css_class=button white) Advanced Django Form Usage ) @pydanny / @maraujop ) return super(ExampleForm, self).__init__(*args, **kwargs) 57
  • 127. Forms refactor Django 1.4
  • 128. Advanced Django Form Usage @pydanny / @maraujopWhat we are getting in 1.4 59
  • 129. What we are getting in 1.4• Form rendering will use templates instead of HTML in Python Advanced Django Form Usage @pydanny / @maraujop 59
  • 130. What we are getting in 1.4• Form rendering will use templates instead of HTML in Python • Template based widgets Advanced Django Form Usage @pydanny / @maraujop 59
  • 131. What we are getting in 1.4• Form rendering will use templates instead of HTML in Python • Template based widgets • Template based form layout Advanced Django Form Usage @pydanny / @maraujop 59
  • 132. Advanced Django Form Usage @pydanny / @maraujopforms refactor and django-uni-form 60
  • 133. forms refactor and django-uni-form• forms refactor layout “lives” in templates Advanced Django Form Usage @pydanny / @maraujop 60
  • 134. forms refactor and django-uni-form• forms refactor layout “lives” in templates• django-uni-form layout “lives” in python Advanced Django Form Usage @pydanny / @maraujop 60
  • 135. forms refactor and django-uni-form• forms refactor layout “lives” in templates• django-uni-form layout “lives” in python• Different approaches, same objective Advanced Django Form Usage @pydanny / @maraujop 60
  • 136. Give me a way to create custom form fields
  • 137. The docs on custom fields“Its only requirements are that it implementa clean() method and that its __init__()method accept the core argumentsmentioned above (required, label, initial,widget, help_text).” Advanced Django Form Usage @pydanny / @maraujop 62
  • 138. The docs on custom fields“Its only requirements are that it implementa clean() method and that its __init__()method accept the core argumentsmentioned above (required, label, initial,widget, help_text).” Advanced Django Form Usage The docs seem to be wrong @pydanny / @maraujop 62
  • 139. How it really worksYou don’t need to implement “clean” You do need to implement:• required• widget • error_messages• label • show_hidden_initial Advanced Django Form Usage• help_text • validators @pydanny / @maraujop• initial • localize 63
  • 140. How the heck then do I actually add a custom form field?!?
  • 141. Advanced Django Form Usage @pydanny / @maraujopvalidation work?How does form 65
  • 142. fields.pyclass AMarkField(forms.Field): widget = TextInput default_error_messages = { not_an_a: _(uyou 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 DRYclass 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(_(uOnly input A here!)) return mark_valueclass 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(_(uOnly input A here!)) return mark_value 67
  • 144. I want a custom field that validates JSON
  • 145. The JSON fieldclass JSONField(forms.Field): default_error_messages = { invalid: This is not valid JSON string } def to_python(self, value): if value in validators.EMPTY_VALUES: return None try: json = simplejson.loads(value) Advanced Django Form Usage except ValueError: @pydanny / @maraujop raise ValidationError(self.error_messages[invalid]) return json 69
  • 146. I want a customwidget that handles multiple widgets
  • 147. widgets.pyfrom django.forms.extras.widgets import MultiWidgetclass 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.pyfrom django.forms.extras.widgets import MultiWidgetclass 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.pyfrom django.forms.extras.widgets import MultiWidgetclass 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 valueisinstance(["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 Usageisinstance("921 SW Sixth Avenue, Portland", str) @pydanny / @maraujop 72
  • 152. Give me a MultiValue, MultiWidget field
  • 153. MultiValue, MultiWidget Field class AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): return data_list Advanced Django Form Usage @pydanny / @maraujop 74
  • 154. MultiValue, MultiWidget Field class AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): return data_list• Clean does not work in the standard way Advanced Django Form Usage @pydanny / @maraujop 74
  • 155. MultiValue, MultiWidget Field class AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): return data_list• Clean does not work in the standard way• Every field validated with its corresponding widget value Advanced Django Form Usage @pydanny / @maraujop 74
  • 156. MultiValue, MultiWidget Field class AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): return data_list• Clean does not work in the standard way• Every field validated with its corresponding widget value Advanced Django Form Usage• @pydanny / @maraujop Beware! run_validator does run but validate is called 74
  • 157. MultiValue, MultiWidget Field class AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): return data_list• Clean does not work in the standard way• Every field validated with its corresponding widget value Advanced Django Form Usage• @pydanny / @maraujop Beware! run_validator does run but validate is called Django Ticket #14184 74
  • 158. Validators with Multi-Fieldclass AlternativeAddressField(forms.MultiValueField): widget = AddressWidget def __init__(self, *args, **kwargs): fields = (forms.CharField(), forms.CharField()) super(AlternativeAddressField, self).__init__(fields, *args, **kwargs) def validate(self, value): self._run_validators(value) def compress(self, data_list): Advanced Django Form Usage return data_list @pydanny / @maraujop 75
  • 159. How about custom widget output?
  • 160. Advanced Django Form Usage @pydanny / @maraujopCustom Widget Output So Easy! 77
  • 161. def render(self, name, value, attrs=None): Custom Widget Output # HTML to be added to the output widget_labels = [ <label for="id_%s">Address: </label>, <label for="id_%s">Number: </label> ] if self.is_localized: for widget in self.widgets: widget.is_localized = self.is_localized # value is a list of values, each corresponding to a widget So Easy! # in self.widgets. if not isinstance(value, list): value = self.decompress(value) output = [] final_attrs = self.build_attrs(attrs) id_ = final_attrs.get(id, None) for i, widget in enumerate(self.widgets): try: widget_value = value[i] except IndexError: widget_value = None if id_: Advanced Django Form Usage final_attrs = dict(final_attrs, id=%s_%s % (id_, i)) # Adding labels @pydanny / @maraujop output.append(widget_labels[i] % (%s_%s % (name, i))) output.append(widget.render(name + _%s % i, widget_value, final_attrs)) return mark_safe(self.format_output(output)) 77
  • 162. def render(self, name, value, attrs=None): Custom Widget Output # HTML to be added to the output widget_labels = [ <label for="id_%s">Address: </label>, <label for="id_%s">Number: </label> ] if self.is_localized: for widget in self.widgets: widget.is_localized = self.is_localized # value is a list of values, each corresponding to a widget So Easy! # in self.widgets. if not isinstance(value, list): value = self.decompress(value) output = [] final_attrs = self.build_attrs(attrs) id_ = final_attrs.get(id, None) for i, widget in enumerate(self.widgets): try: widget_value = value[i] except IndexError: widget_value = None if id_: Advanced Django Form Usage final_attrs = dict(final_attrs, id=%s_%s % (id_, i)) # Adding labels @pydanny / @maraujop output.append(widget_labels[i] % (%s_%s % (name, i))) output.append(widget.render(name + _%s % i, widget_value, final_attrs)) return mark_safe(self.format_output(output)) 77
  • 163. Can’t you make iteasier to understand?
  • 164. Problems with Custom Widget Output• Overring render means indepth knowledge• Hard to do custom output with built-in widgets• You can’t call super.render and customize easily• Templates? Open Ticket #15667 Advanced Django Form Usage @pydanny / @maraujop 79
  • 165. Not in LawrenceKansas anymore
  • 166. Problems with Custom Widget Output• Oldest ticket in Django is related to forms (#23)• ComboField is broken• Validators are thought to validate simple values Advanced Django Form Usage because of the way run_validators is coded @pydanny / @maraujop 81
  • 167. HTML5 tickets• Ticket #16304• Ticket #16630• Just use django-floppyforms Advanced Django Form Usage @pydanny / @maraujop 82
  • 168. Validators• Imagine you have an input that get a list of emails seperated by spaces• Your widget returns: [“a@example.com”, “b@example.com”]• You want to run validators on all of them • Advanced Django Form Usage There is an EmailValidator @pydanny / @maraujop 83
  • 169. MultiValidatorclass MultiValidator(object): def __init__(self, validators): self.validators = validators def __call__(self, data_list): errors = [] for value in data_list: for validator in self.validators: try: validator(value) except ValidationError, e: Advanced Django Form Usage if hasattr(e, code): errors.append("FiXED MESSAGE") @pydanny / @maraujop raise ValidationError(errors) 84
  • 170. Amazing stuff• Ticket #27 reported by Adrian Holovaty• Single form field for multiple database fields Advanced Django Form Usage @pydanny / @maraujop 85
  • 171. Fin
  • 172. Advanced Django Form Usage @pydanny / @maraujopThis 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 Form Usage @pydanny / @maraujopThis 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. Fin
  • 182. Advanced Django Form Usage @pydanny / @maraujop Daniel Greenfeld & Miguel AraujoQuestions? 90