Django Forms: Best Practices, Tips, Tricks


Published on

This is a presentation for novice Django developers that reviews the basics of working with Django's Forms module.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • This is a plain vanilla form built in Django. It allows a website user to "contact" the website; hence, the name. Forms are used for data entry and modification. Django gives developers many handy tools to simplify working with forms. But many Django devs are still afraid to write and work with forms, and they lean on the Django Admin perhaps too much.
  • Add LOLcat image that sez halp!
  • Here is what a Model Form implementation looks like in practice.
  • Here are some examples of how you can override or add to a Model Form.  Django provides several ways to easily customize a Model Form, including the fields attribute, the exclude attribute, and the widgets attribute.
  • Note that the formset defaults to a table-based output. This can be modified by manually looping through the formset in the template and outputting using any of the default Django formats. TIP: Formsets do write in a "management" form in your template. If you are doing any fancy JS stuff, you might need to manipulate this management form.
  • Factories are common design patterns in many languages. Factories can and do work in Python and in Django forms.
  • The form on top is a snippet of a form made dynamic by overriding the __init__ function. For most types of form customization, this is the preferred way of handling dynamic forms. The example on bottom shows what a factory version of the same form would look like. 
  • This example of a dynamic form shows a solution to another common problem. The goal is to have additional data available for validating some part of the submitted data. (In this case, for limiting results of the search that is being performed.) This technique is also very handy for generating choice tuples based on custom queries.
  • Always try to isolate logic in places that is reusable. Forms are good places to keep reusable code elements, and the method is one of the most valuable.
  • By isolating functionality like this, it becomes possible to easily adapt to other system structures. For example, adding Message Queues to functions is very easy if you properly abstract code away from your Views.
  • Django Forms: Best Practices, Tips, Tricks

    1. 1. Django Forms Best Practices, Tips and Tricks DjangoCon 2010 Shawn Rider PBS Education
    2. 2. Basic Forms <ul><li>from django import forms  </li></ul><ul><li>class ContactForm (forms . Form):  </li></ul><ul><li>     subject = forms . CharField(max_length = 100 )  </li></ul><ul><li>     message = forms . CharField()  </li></ul><ul><li>     sender = forms . EmailField()  </li></ul><ul><li>     cc_myself = forms . BooleanField(required = False )  </li></ul><ul><li>     def save (self):  </li></ul><ul><li>         data = self.cleaned_data  </li></ul><ul><li>         contact = Contact()  </li></ul><ul><li>         contact.subject = data.get('subject', '')  </li></ul><ul><li>         contact.message = data.get('message', '')  </li></ul><ul><li>         contact.sender = data.get('sender', '')  </li></ul><ul><li>         contact.cc_myself = data.get('cc_myself', False)  </li></ul><ul><li>  </li></ul><ul><li>         return contact </li></ul>
    3. 3. But that's so much to write...
    4. 4. Model Forms <ul><ul><li>Easily create forms with just a few lines of code. </li></ul></ul><ul><ul><li>Customize and modify fields available for editing on the form. </li></ul></ul><ul><ul><li>Override default methods to support complex business logic, etc. </li></ul></ul><ul><ul><li>Customized Model Forms can easily be used in Django Admin. </li></ul></ul><ul><ul><li>Model Forms can be used with Model Formsets to edit multiple forms in a single view. </li></ul></ul>
    5. 5. Model Forms <ul><li>from django.forms import ModelForm  </li></ul><ul><li>### ... Form Definition ... ### </li></ul><ul><li>class ArticleForm (ModelForm):  </li></ul><ul><li>     class Meta :  </li></ul><ul><li>         model = Article </li></ul><ul><li>### ... Inside the View ... ### </li></ul><ul><li>article = Article.objects.get(pk=article_id) </li></ul><ul><li>if request . method == 'POST' :  </li></ul><ul><li>     form = ArticleForm(request.POST, instance=article)  </li></ul><ul><li>     if form.is_valid(): </li></ul><ul><li>         article =  </li></ul><ul><li>         return HttpResponseRedirect(redirect_url)   </li></ul><ul><li>else :  </li></ul><ul><li>     form = ArticleForm(instance=article) </li></ul>
    6. 6. Model Forms    <ul><li>A Model Form with listed editable fields and explicitly defined widgets: </li></ul><ul><li>class   ArticleForm (ModelForm):  </li></ul><ul><li>     class   Meta :  </li></ul><ul><li>         model  =  Article  </li></ul><ul><li>         fields = ('title', 'content', 'blurb') </li></ul><ul><li>         widgets = { </li></ul><ul><li>             'content': Textarea(attrs:{'cols':80, 'rows':20}) </li></ul><ul><li>         } </li></ul><ul><li>A Model Form with a custom save method: </li></ul><ul><li>class   ArticleForm (ModelForm):  </li></ul><ul><li>     class   Meta :  </li></ul><ul><li>         model  =  Article </li></ul><ul><li>     def   save( self, *args, **kwargs ) :  </li></ul><ul><li>         data  =  self.cleaned_data </li></ul><ul><li>         ### Insert complex, custom save logic here. ### </li></ul><ul><li>         return article </li></ul>
    7. 7. Formsets <ul><ul><li>Formsets allow you to produce multiple forms for a page (bulk editing). </li></ul></ul><ul><ul><li>Can be customized in many ways. </li></ul></ul><ul><ul><li>Handle basic metadata to keep forms and data properly aligned. </li></ul></ul><ul><ul><li>Formsets are used with basic Django forms. Model Formsets are used with Django Model Forms. </li></ul></ul><ul><ul><li>Allow for validation of entire set of forms as well as individual forms within the formset. </li></ul></ul>
    8. 8. Formsets <ul><li>### View code ### </li></ul><ul><li>def manage_articles (request):  </li></ul><ul><li>     ArticleFormSet = formset_factory(ArticleForm)  </li></ul><ul><li>     if request.method == 'POST': </li></ul><ul><li>         formset = ArticleFormSet(request.POST, request.FILES)  </li></ul><ul><li>         if formset.is_valid():  </li></ul><ul><li>             for form in formset:  </li></ul><ul><li>         </li></ul><ul><li>             return HttpResponseRedirect(REDIRECT_URL)  </li></ul><ul><li>   else:  </li></ul><ul><li>         formset = ArticleFormSet()  </li></ul><ul><li>     return render_to_response('manage_articles.html', { </li></ul><ul><li>         'formset': formset </li></ul><ul><li>     }) </li></ul><ul><li><!-- Template Code --> </li></ul><ul><li><form method= &quot;post&quot; action= &quot;&quot; >   </li></ul><ul><li>     <table> {{ formset }} </table>   </li></ul><ul><li></form> </li></ul>
    9. 9. Dynamic Forms <ul><li>A Dynamic Form modulates the fields and/or choices available in the form </li></ul><ul><li>Why would I ever need a Dynamic Form? </li></ul><ul><ul><li>Enforcing restricted permissions for different levels of user </li></ul></ul><ul><ul><li>Providing choices based on user or system data (custom contact lists, for example) </li></ul></ul><ul><ul><li>Enforcing other complex business logic </li></ul></ul>
    10. 10. For some developers, it seems natural to use a &quot;code factory&quot; design pattern to solve this problem. Please don't do that.
    11. 11. <ul><li>Override __init__ function </li></ul><ul><li>class ContactForm (forms.Form):  </li></ul><ul><li>     def __init__(self, user, *args, **kwargs):  </li></ul><ul><li>         super(ContactForm, self).__init__(*args, **kwargs)  </li></ul><ul><li>         if not user.is_authenticated():  </li></ul><ul><li>             self.fields['captcha'] = CaptchaField() </li></ul><ul><li>     name = forms.CharField(max_length=50) </li></ul><ul><li>     email = forms.Emailfield() </li></ul><ul><li>     message = forms.CharField(widget=forms.Textarea) </li></ul><ul><li>Code Factory </li></ul><ul><li>def make_contact_form (user):  </li></ul><ul><li>     # The basic form  </li></ul><ul><li>     class _ContactForm (forms.Form):  </li></ul><ul><li>       name = forms.CharField(max_length=50)  </li></ul><ul><li>       email = forms.EmailField()  </li></ul><ul><li>       message = forms.CharField(widget=forms.Textarea) </li></ul><ul><li>        </li></ul><ul><li>     if user.is_authenticated(): </li></ul><ul><li>       return _ContactForm  </li></ul><ul><li>     class _CaptchaContactForm (_ContactForm): </li></ul><ul><li>       captcha = CaptchaField()                  </li></ul><ul><li>     return _CaptchaContactForm  </li></ul><ul><li>(Taken from James Bennett's </li></ul>
    12. 12. Dynamic Forms <ul><li>class MemberSearchForm ( forms . Form ): </li></ul><ul><li>    def __init__ ( self , data = None , account = None , * args , ** kwargs ): </li></ul><ul><li>        self . account = account </li></ul><ul><li>        super ( MemberSearchForm , self ). __init__ ( data , * args , ** kwargs ) </li></ul><ul><li>    terms = forms . CharField ( required = False ) </li></ul><ul><li>    role = forms . ChoiceField ( choices = SEARCH_ROLE_CHOICES , required = False ) </li></ul><ul><li>    status =  forms . ChoiceField ( choices = SEARCH_STATUS_CHOICES , required = False ) </li></ul>
    13. 13. Tip:  Always Redirect After Modifying Data <ul><li>article = Article.objects.get(pk=article_id) if request . method == 'POST' : </li></ul><ul><li>     form = ArticleForm(request.POST, instance=article)  </li></ul><ul><li>     if form.is_valid(): </li></ul><ul><li>         article =  </li></ul><ul><li>         return HttpResponseRedirect(redirect_url)    </li></ul><ul><li>else :  </li></ul><ul><li>     form = ArticleForm(instance=article) </li></ul>
    14. 14. The Worst Example in the Django docs? <ul><li>def contact (request):  </li></ul><ul><li>     if request . method == 'POST' : # If the form has been submitted...   </li></ul><ul><li>       form = ContactForm(request . POST) # A form bound to the POST data   </li></ul><ul><li>       if form . is_valid(): # All validation rules pass  </li></ul><ul><li>         subject = form . cleaned_data[ 'subject' ]  </li></ul><ul><li>         message = form . cleaned_data[ 'message' ]  </li></ul><ul><li>         sender = form . cleaned_data[ 'sender' ]  </li></ul><ul><li>         cc_myself = form . cleaned_data[ 'cc_myself' ]  </li></ul><ul><li>         recipients = [ '' ]  </li></ul><ul><li>         if cc_myself:  </li></ul><ul><li>             recipients . append(sender)  </li></ul><ul><li>             from django.core.mail import send_mail  </li></ul><ul><li>             send_mail(subject, message, sender, recipients)  </li></ul><ul><li>         return HttpResponseRedirect( '/thanks/' ) # Redirect after POST  </li></ul><ul><li>     else :  </li></ul><ul><li>         form = ContactForm() # An unbound form  </li></ul><ul><li>     return render_to_response( 'contact.html' , { 'form' : form, }) </li></ul>
    15. 15. Tip: Use Forms to Isolate Logic <ul><li>class MemberSearchForm ( forms . Form ): </li></ul><ul><li>    def __init__ ( self , data = None , account = None , * args , ** kwargs ): </li></ul><ul><li>        self . account = account </li></ul><ul><li>        super ( MemberSearchForm , self ). __init__ ( data , * args , ** kwargs ) </li></ul><ul><li>    terms = forms . CharField ( required = False ) </li></ul><ul><li>    role = forms . ChoiceField ( </li></ul><ul><li>choices = SEARCH_ROLE_CHOICES , required = False ) </li></ul><ul><li>    status = forms . ChoiceField ( </li></ul><ul><li>choices = SEARCH_STATUS_CHOICES , required = False ) </li></ul><ul><li>     def search ( self ): </li></ul><ul><li>        data = self . cleaned_data         ### Complex Search Logic Here ### </li></ul><ul><li>         return results </li></ul>
    16. 16. Tip: Use django-uni-form
    17. 17. Tip: Roll Your Own Fields and Form Classes <ul><ul><li>Isolate logic at any cost </li></ul></ul><ul><ul><li>Sometimes objects or features in your project warrant a completely custom Form Field </li></ul></ul><ul><ul><li>Custom Form Classes can perform custom templatized output, eliminating repetitive HTML in your templates </li></ul></ul><ul><ul><li>Sub Tip: </li></ul></ul><ul><ul><li>Extend existing classes to save yourself headaches </li></ul></ul>
    18. 18. Thanks and Further Reading <ul><li>James Bennett's B-List: </li></ul><ul><li> </li></ul><ul><li>Danny Greenfield’s Django-Uniform: </li></ul><ul><li> </li></ul>