Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django Forms Survey

Brian Dant
October 24, 2013

Django Forms Survey

This talk looks at the core concepts of Django Forms and explores a few third party packages. Talk was given to the Django Boston Meetup on 27JUN2013 and Django NYC on 24OCT2013.

Brian Dant

October 24, 2013
Tweet

Other Decks in Programming

Transcript

  1. https://docs.djangoproject.com/en/1.5/topics/forms/#overview WHY FORMS? • All my projects have used forms

    • A few mind blowing / fun moments along the way 4 Friday, November 1, 13
  2. https://docs.djangoproject.com/en/1.5/topics/forms/#overview WHY FORMS? • All my projects have used forms

    • A few mind blowing / fun moments along the way • SO MANY OPTIONS!!! 4 Friday, November 1, 13
  3. http://learnpythonthehardway.org/book/advice.html WHY FORMS? “Programming as an intellectual activity is the

    only art form that allows you to create interactive art. You can create projects that other people can play with, and you can talk to them indirectly. No other art form is quite this interactive. Movies flow to the audience in one direction. Paintings do not move. Code goes both ways.” -- Zed Shaw, Learn Python the Hard Way; “Advice From An Old Programmer” 5 Friday, November 1, 13
  4. These concepts taken with permission from Danny Greenfield: http://pydanny.com/core-concepts-django-forms.html CORE

    CONCEPTS OF FORMS (ABSTRACT) Forms render HTML Forms are “just” Python constructs Forms validate dictionaries 7 Friday, November 1, 13
  5. 1 #### da_form.html #### 2 <form action="." method="post"> 3 <input

    type="text" name="fname"> 4 <input type="number" name="age"> 5 <input type="email" name="email_address"> 6 </form> 7 1 #### views.py #### 2 def da_form(request, template='da_form.html'): 3 4 5 if request.method == 'POST': 6 # AHHHHH! Writing data from the user directly to the database! 7 DaModel.objects.create( 8 fname=request.POST['fname'], 9 age=request.POST['age'], 10 email_address=request.POST['email_address'], 11 ) 12 13 return redirect('success/') 14 15 return render(request, template) 16 8 WHY A FORMS LIBRARY? 1.
  6. 1 #### forms.py #### 2 from django import forms 3

    4 class ContactForm(forms.Form): 5 subject = forms.CharField(max_length=100) 6 message = forms.CharField() 7 sender = forms.EmailField() 8 cc_myself = forms.BooleanField(required=False) 1 #### views.py #### 2 from django.shortcuts import render 3 from django.http import HttpResponseRedirect 4 5 def contact(request): 6 if request.method == 'POST': # If the form has been submitted... 7 form = ContactForm(request.POST) # A form bound to the POST data 8 if form.is_valid(): # All validation rules pass 9 # Process the data in form.cleaned_data 10 # ... 11 return HttpResponseRedirect('/thanks/') # Redirect after POST 12 else: 13 form = ContactForm() # An unbound form 14 15 return render(request, 'contact.html', { 16 'form': form, 17 }) 9 BASIC FORM 2.
  7. 1 # forms.py 2 3 from django import forms 4

    5 class ContactForm(forms.Form): 6 subject = forms.CharField(max_length=100) 7 message = forms.CharField() 8 sender = forms.EmailField() 9 cc_myself = forms.BooleanField(required=False) 1 >>> form = forms.ContactForm() 2 3 >>> form.as_p() 4 u'<p><label for="id_subject">Subject:</label> <input id="id ... </p> <p><label for="id_message">Message:</label> <input id="id ...</p> <p><label for="id_cc_myself">Cc myself:</label> <input ... </p> 11 FORMS RENDER HTML 2.
  8. 12 FORMS RENDER HTML 1 {# forms.html #} 2 <form

    action="/contact/" method="post">{% csrf_token %} 3 {{ form.as_p }} 4 <input type="submit" value="Submit" /> 5 </form> 6 7 {# Chrome Inspector #} 8 <form action="/contact/" method="post"> 9 <p><label for="id_subject">Subject:</label> 10 <input id="id_subject" type="text" name="subject" maxlength="100" /></p> 11 <p><label for="id_message">Message:</label> 12 <input type="text" name="message" id="id_message" /></p> 13 <p><label for="id_sender">Sender:</label> 14 <input type="email" name="sender" id="id_sender" /></p> 15 <p><label for="id_cc_myself">Cc myself:</label> 16 <input type="checkbox" name="cc_myself" id="id_cc_myself" /></p> 17 <input type="submit" value="Submit" /> 18 </form> 1.
  9. These concepts taken with permission from Danny Greenfield: http://pydanny.com/core-concepts-django-modelforms.html •

    Render Model fields as HTML • Select validators based off Model field definitions • Don’t have to display/change all available fields • Save dictionaries to SQL tables •[Model]Forms
  10. Daniel Greenfeld; Audrey Roy. Two Scoops of Django: Best Practices

    for Django 1.5 (Kindle Locations 1949-1950). Two Scoops Press. • 95% of Django projects should use ModelForms. • 91% of all Django projects use ModelForms. • 80% of ModelForms require trivial logic. • 20% of ModelForms require complicated logic. – pydanny made-up statistics 15 MODELFORMS IN REAL LIFE Friday, November 1, 13
  11. #### models.py #### 1 class Author(models.Model): 2 name = models.CharField(max_length=100)

    3 title = models.CharField(max_length=3, choices=TITLE_CHOICES) 4 birth_date = models.DateField(blank=True, null=True) 5 6 def __unicode__(self): 7 return self.name 8 9 class Book(models.Model): 10 name = models.CharField(max_length=100) 11 authors = models.ManyToManyField(Author) 12 #### forms.py #### 13 class AuthorForm(forms.ModelForm): 14 class Meta: 15 model = Author 16 fields = ['name', 'title', 'birth_date'] 17 16 BASIC MODELFORM 2.
  12. 1 #### views.py #### 2 from django.shortcuts import render 3

    from django.http import HttpResponseRedirect 4 5 def contact(request): 6 if request.method == 'POST': # If the form has been submitted... 7 form = ArticleForm(request.POST) 8 if form.is_valid(): 9 # Process the data in form.cleaned_data 10 form.save() 11 12 return HttpResponseRedirect('/success/') # Redirect after POST 13 else: 14 form = ArticleForm() # Still an unbound form 15 16 return render(request, 'article.html', { 17 'form': form, 18 }) 17 BASIC MODELFORM 1.
  13. #### forms.py #### 13 class AuthorForm(forms.ModelForm): 14 class Meta: 15

    model = Author 16 fields = ['name', 'title', 'birth_date'] 17 >>> form = forms.AuthorForm() >>> form.as_p() u'<p><label for="id_name">Name:</label> <input id="id_name" maxlength="100" name="name" type="text" /></p> <p><label for="id_title">Title:</label> <select id="id_title" name="title"> <option value="" selected="selected">---------</option> <option value="MR">Mr.</option> <option value="MRS">Mrs.</option> <option value="MS">Ms.</option> </select> </p> <p><label for="id_birth_date">Birth date:</label> <input id="id_birth_date" name="birth_date" type="text" /> </p>' 19 MODELFORMS RENDER HTML Just
  14. https://docs.djangoproject.com/en/1.5/topics/forms/#overview FORMS LIBRARY OVERVIEW Widget ”A class that corresponds to

    an HTML form widget, e.g. <input type="text"> or <textarea>. This handles rendering of the widget as HTML.” Field “A class that is responsible for doing validation, e.g. an EmailField that makes sure its data is a valid email address.” Form “A collection of fields that knows how to validate itself and display itself as HTML.” Form Media “The CSS and JavaScript resources that are required to render a form.” 21 Friday, November 1, 13
  15. 1 #### forms.py #### 2 from django import forms 3

    4 class ContactForm(forms.Form): 5 subject = forms.CharField(max_length=100) 6 message = forms.CharField() 7 sender = forms.EmailField() 8 cc_myself =forms.BooleanField(required=False) 22 FIELDS Fields Friday, November 1, 13
  16. https://docs.djangoproject.com/en/dev/ref/forms/fields/#built-in-field-classes BooleanField CharField ChoiceField TypedChoiceField DateField DateTimeField DecimalField EmailField FileField

    FilePathField FloatField ImageField IntegerField IPAddressField GenericIPAddressField MultipleChoiceField 23 OPTIONS: FIELDS TypedMultipleChoiceField NullBooleanField RegexField SlugField TimeField URLField Field
  17. https://docs.djangoproject.com/en/dev/ref/forms/fields/#decimalfield DecimalField class DecimalField(**kwargs) Default widget: NumberInput when Field.localize is

    False, else TextInput. Empty value: None Normalizes to: A Python decimal. Validates that the given value is a decimal. Leading and trailing whitespace is ignored. Error message keys: required, invalid, max_value, min_value, max_digits, max_decimal_places, max_whole_digits 24 FIELDS: DETAILED EXAMPLE What
  18. https://docs.djangoproject.com/en/dev/ref/forms/widgets/#setting-arguments-for-widgets #### forms.py #### 1 from django.forms.fields import DateField, ChoiceField,

    MultipleChoiceField 2 from django.forms.widgets import RadioSelect, CheckboxSelectMultiple 3 from django.forms.extras.widgets import SelectDateWidget 4 5 BIRTH_YEAR_CHOICES = ('1980', '1981', '1982') 6 FAVORITE_COLORS_CHOICES = (('blue', 'Blue'), 7 ('green', 'Green'), 8 ('black', 'Black')) 9 10 class SimpleForm(forms.Form): 11 birth_year = dateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES)) 12 favorite_colors = forms.MultipleChoiceField(required=False, 13 widget=CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES) 25 DEFINING A WIDGET 1.
  19. https://docs.djangoproject.com/en/dev/ref/forms/widgets/#setting-arguments-for-widgets #### forms.py #### 1 class CommentForm(forms.Form): 2 name =

    forms.CharField( 3 widget=forms.TextInput(attrs={'class':'special'})) 4 url = forms.URLField() 5 comment = forms.CharField( 6 widget=forms.TextInput(attrs={'size':'40'})) #### Chrome Inspector #### 1 <p><label for="id_name">Name:</label> 2 <input class="special" id="id_name" name="name" type="text" /> 3 </p> 4 <p><label for="id_url">Url:</label> 5 <input id="id_url" name="url" type="text" /> 6 </p> 7 <p><label for="id_comment">Comment:</label> 8 <input id="id_comment" name="comment" size="40" type="text" /></p>' 26 WIDGETS TAKE ATTRS attrs
  20. https://docs.djangoproject.com/en/dev/ref/forms/widgets/ TextInput NumberInput EmailInput URLInput PasswordInput HiddenInput DateInput DateTimeInput TimeInput

    Textarea CheckboxInput 28 OPTIONS: WIDGETS Select NullBooleanSelect SelectMultiple RadioSelect CheckboxSelectMultiple File upload widgets FileInput ClearableFileInput Composite widgets MultipleHiddenInput SplitDateTimeWidget SplitHiddenDateTimeWidget SelectDateWidget a)
  21. https://github.com/pinax/django-forms-bootstrap DJANGO-FORMS-BOOTSTRAP 1 #### form.html #### 2 <form> 3 <legend>My

    Form</legend> 4 {% csrf_token %} 5 {{ form|as_bootstrap }} 6 <div class="form-actions"> 7 <a href="#back" class="btn">Go back</a> 8 <button type="submit" class="btn btn-primary">Save changes</button> 9 </div> 10 </form> 32 Simple
  22. https://github.com/pinax/django-forms-bootstrap DFB: EXAMPLE CUSTOMIZATION 1 #### form.html #### 2 <form

    class=”form-inline”> 3 <legend>My Form</legend> 4 {% csrf_token %} 5 {{ form|as_bootstrap }} 8 <button type="submit" class="btn btn-primary">Save changes</button> 9 </div> 10 </form> 34 1.
  23. https://github.com/pinax/django-forms-bootstrap DJANGO-WIDGET-TWEAKS 37 Useful for: • Widget modifications in templates

    • Projects with devs used to modifying forms in HTML • One-off forms Friday, November 1, 13
  24. https://pypi.python.org/pypi/django-widget-tweaks DJANGO-WIDGET-TWEAKS 1 {% load widget_tweaks %} 2 3 <!--

    add/change several attributes --> 4 {% render_field form.text rows="20" cols="20" title="Hello, world!" %} 5 6 <!-- add/change several attributes --> 7 {{ form.text|attr:"rows:20"|attr:"cols:20"|attr:"title:Hello, world!" }} 8 9 <!-- add 2 extra css classes to field element --> 10 {{ form.title|append_attr:"class:css_class_1 css_class_2" }} 38 Django
  25. https://github.com/pinax/django-forms-bootstrap DJANGO-CRISPY-FORMS 42 Useful for: • Presentation in Python •

    Modifying forms “on the fly” • Reusable granular templates • Integration with Bootstrap / Uni Forms Friday, November 1, 13
  26. http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_forms.html#fundamentals CRISPY-FORMS EXAMPLE: DEFINE YOUR FORM #### forms.py #### 2

    class ExampleForm(forms.Form): 3 like_website = forms.TypedChoiceField( 4 label = "Do you like this website?", 5 choices = ((1, "Yes"), (0, "No")), 6 coerce = lambda x: bool(int(x)), 7 widget = forms.RadioSelect, 8 initial = '1', 9 required = True, 10 ) 11 12 favorite_food = forms.CharField( 13 label = "What is your favorite food?", 14 max_length = 80, 15 required = True, 16 ) 17 18 favorite_color = forms.CharField( 19 label = "What is your favorite color?", 20 max_length = 80, 21 required = True, 22 ) 43 Just
  27. http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_forms.html#fundamentals http://django-crispy-forms.readthedocs.org/en/latest/form_helper.html#helper-attributes-you-can-set CRISPY-FORMS EXAMPLE: ADD A HELPER 1 #### forms.py

    #### 2 from crispy_forms.helper import FormHelper 3 from crispy_forms.layout import Submit 4 5 class ExampleForm(forms.Form): 6 [...] 7 def __init__(self, *args, **kwargs): 8 super(ExampleForm, self).__init__(*args, **kwargs) 9 self.helper = FormHelper() 10 self.helper.form_id = 'id-exampleForm' 11 self.helper.form_class = 'blueForms' 12 self.helper.form_method = 'post' 13 self.helper.form_action = 'submit_survey' 14 15 self.helper.add_input(Submit('submit', 'Submit')) 44 Now
  28. http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_forms.html#fundamentals CRISPY-FORMS EXAMPLE: THE {% CRISPY%} TAG 1 ##### form.html

    #### 2 {% crispy form %} 3 4 #### Chrome Inspector #### 5 <form action="/submit/survey/" class="uniForm blueForms" method="post" id="id- 6 exampleForm"> 7 <div style='display:none'> 8 <input type='hidden' name='csrfmiddlewaretoken' 9 value='a643fab735d5ce6377ff456e73c4b1af' /> 10 </div> 11 <fieldset> 12 <legend></legend> 13 <div id="div_id_like_website" class="ctrlHolder"> 14 <label for="id_like_website" class="requiredField">¿Do you like 15 this website?<span class="asteriskField">*</span>< 16 /label> 17 <ul> 18 <li><label for="id_like_website_0"><input checked="checked" 19 name="like_website" value="1" 20 id="id_like_website_0" type="radio" 21 class="radioselect" /> Yes</label></li> 22 <li><label for="id_like_website_1"><input value="0" 23 type="radio" class="radioselect" 24 name="like_website" id="id_like_website_1" /> 25 No</label></li> 26 </ul> 27 </div> 28 ### snipped ### 29 ### ... ### 30 </fieldset> 31 <div class="buttonHolder"> 32 <input type="submit" name="submit" value="Submit" class="submit 33 submitButton" id="submit-id-submit" /> 34 </div> 35 </form> 45 Write
  29. http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_forms.html#fundamentals CRISPY-FORMS EXAMPLE: MANIPULATING A HELPER WITH A VIEW 1

    #### views.py #### 2 from django.core.urlresolvers import reverse 3 from forms import ExampleForm 4 5 @login_required() 6 def inbox(request, template_name): 7 example_form = ExampleForm() 8 redirect_url = request.GET.get('next') 9 10 # Form handling logic 11 [...] 12 13 if redirect_url is not None: 14 example_form.helper.form_action = reverse('submit_survey') + '?next=' 15 + redirectUrl 16 17 return render_to_response(template_name, {'example_form': example_form}, 18 context_instance=RequestContext(request)) 47 1.
  30. http://django-crispy-forms.readthedocs.org/en/latest/layouts.html#layouts CRISPY-FORMS EXAMPLE: LAYOUTS (IN PYTHON?!) 1 #### forms.py ####

    2 from crispy_forms.helper import FormHelper 3 from crispy_forms.layout import Layout, Fieldset, ButtonHolder, Submit 4 5 class ExampleForm(forms.Form): 6 [...] 7 def __init__(self, *args, **kwargs): 8 super(ExampleForm, self).__init__(*args, **kwargs) 9 self.helper = FormHelper() 10 self.helper.layout = Layout( 11 Fieldset( 12 'first arg is the legend of the fieldset', 13 'like_website', 14 'favorite_number', 15 'favorite_color', 16 'favorite_food', 17 'notes' 18 ), 19 ButtonHolder( 20 Submit('submit', 'Submit', css_class='button white') 21 ) 22 ) 48 1.
  31. http://django-crispy-forms.readthedocs.org/en/latest/layouts.html#layouts CRISPY-FORMS EXAMPLE: GET ALL THE ATTRIBUTES WE WANT ####

    forms.py #### Field('field_name', autocomplete='off') Field('field_name', data_name="whatever") Field('field_name', css_class="black-fields") Field('field_name', css_id="custom_field_id") 49 Any
  32. http://django-crispy-forms.readthedocs.org/en/latest/dynamic_layouts.html#selecting-layout- objects-with-slices CRISPY-FORMS EXAMPLE: UPDATING LAYOUTS ON THE GO WITH

    SLICES 1 #### views.py #### 2 form.helper[1:3] 3 form.helper[2] 4 form.helper[:-1] 5 6 Layout( 7 Div('email') 8 ) 9 10 form.helper[0][0] 11 50 1.
  33. https://github.com/pinax/django-forms-bootstrap DJANGO-FLOPPYFORMS 51 “an application that gives you full control

    of the output of forms rendering” Useful for: • Freeeee HTML5 widgets • Granular reusable layouts using templates Friday, November 1, 13
  34. http://django-floppyforms.readthedocs.org/en/latest/widgets-reference.html FLOPPYFORMS BRINGING US TO THE FUTURE ColorInput: <input type="color">

    RangeInput: <input type="range"> for sliders instead of spinboxes for numbers PhoneNumberInput: <input type="tel"> For phone numbers SearchInput: <input type="search">.” 52 Friday, November 1, 13
  35. http://django-floppyforms.readthedocs.org/en/latest/customization.html DFF: CUSTOM WIDGETS USING CUSTOM TEMPLATES 1 ### widgets.py

    ### 2 import floppyforms as forms 3 4 class OtherEmailInput(forms.EmailInput): 5 template_name = 'path/to/other_email.html' 6 7 #### other_email.html ### 8 <input type="email" 9 name="{{ name }}" 10 id="{{ attrs.id }}" 11 placeholder="[email protected]" 12 {% if value %}value="{{ value }}"{% endif %}> 13 14 ### forms.py ### 15 class EmailForm(forms.Form): 16 email = forms.EmailField(widget=OtherEmailInput()) 53 1.
  36. http://django-floppyforms.readthedocs.org/en/latest/customization.html DFF: REUSABLE LAYOUTS 1 ### floppyforms/layouts/p.html ### 2 3

    {% load floppyforms %}{% block formconfig %}{% formconfig row using 4 "floppyforms/rows/p.html" %}{% endblock %} 5 6 {% block forms %}{% for form in forms %} 7 8 {% block errors %}{% if form.non_field_errors or form|hidden_field_errors %}< 9 ul class="errorlist"> 10 {% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %} 11 {% for error in form|hidden_field_errors %}<li>{{ error }}</li>{% endfor %} 12 </ul>{% endif %}{% endblock %} 13 {% block rows %}{% for field in form.visible_fields %} 14 {% if forloop.last %}{% formconfig row with hidden_fields=form.hidden_fields 15 %}{% endif %} 16 {% block row %}{% formrow field %}{% endblock %} 17 {% endfor %} 18 19 {% if not form.visible_fields %}{% for field in form.hidden_fields %}{% 20 formfield field %}{% endfor %}{% endif %} 21 {% endblock %} 22 {% endfor %}{% endblock %} 54 1.
  37. http://django-floppyforms.readthedocs.org/en/latest/customization.html DFF: REUSABLE LAYOUTS PART 11 (OVERRIDES) 1 ### my_layout.html

    ### 2 {% extends "floppyforms/layouts/p.html" %} 3 4 {% block forms %} 5 {% if headline %}<h1>{{ headline }}</h1>{% endif %} 6 {{ block.super }} 7 {% endblock %} 8 {% block errors %} 9 <p>Following errors occurred that cannot be matched to a field:</p> 10 {{ block.super }} 11 {% endblock %} 12 13 ### form_page.html ### 14 <form action="/contact/" method="post"> 15 {% form contact_form using "my_layout.html" %} 16 <p><input type="submit" value="Send message" /></p> 17 </form> 55 1.
  38. http://django-floppyforms.readthedocs.org/en/latest/widgets-reference.html FLOPPY AND CRISPY: BFFS Floppy and Crispy play nice

    Both allow you to define your own templates for a field / widget (not covered) 56 Friday, November 1, 13
  39. DJANGO-PARSLEY 1 @parsleyfy 2 class GettingToKnowYouForm(forms.ModelForm): 3 4 def __init__(self,

    *args, **kwargs): 5 super(GettingToKnowYouForm, self).__init__(*args, **kwargs) 6 self.fields['applied_track'].required = False 7 8 self.helper = FormHelper(self) 9 self.helper.form_id = 'id_getting_to_know_you' 10 self.helper.form_tag = False 11 self.helper.layout = Layout( 12 Div( 13 Div( 14 Div( 15 Field('fname'), 16 Field('lname'), 17 Field('email_address'), 18 Field('applied_class'), 19 Field('applied_track'), 20 css_class="left-fieldset", 21 ), 22 [ ... ] 23 24 css_class="inner-wrapper-buttons", 25 ), 26 css_class="wrapper wrapper-buttons", 27 ), 28 ) 58 1.
  40. DJANGO-PARSLEY OUTPUT 1 <form method="post" data-validate="parsley"> 2 3 <input type='hidden'

    name='csrfmiddlewaretoken' 4 value='5HvzVf93QNYpha25h4nugUiQ67o6faJM' /> <div 5 class="wrapper-form" > 6 <div 7 class="inner-wrapper-form" ><div 8 class="left-fieldset" > 9 <div id="div_id_fname" class="control-group"> 10 <label for="id_fname" class="control-label requiredField"> 11 First Name<span class="asteriskField">*</span> 12 </label><div class="controls"> 13 <input class="textinput textInput" data-maxlength="100" data- 14 required="true" id="id_fname" maxlength="100" name="fname" 15 type="text" /> </div></div><div id="div_id_lname" 16 class="control-group"> 17 59 1.
  41. DJANGO.FORMS AND DJANGO.CONTRIB.FORMTOOLS THAT I SKIPPED Formsets Model Formsets Inline

    Formsets (Models) ModelForm Factories Form Wizard Form Preview 60 Friday, November 1, 13
  42. Text THANK YOU Nehal, Sean, and Gabe for planning Bitly

    for space You, for showing up The Django team, for Django Contributors for their contributions to third-party packages @pydanny and @audreyr for putting out Two Scoops of Django (buy it!), and a bunch of helpful form blog posts, and crispy-forms 61 Friday, November 1, 13