Slide 1

Slide 1 text

A SURVEY OF DJANGO FORMS PRACTICES From django.forms to floppyforms 1 Friday, November 1, 13

Slide 2

Slide 2 text

@BRIANDANT [email protected] briandant.com 2 Friday, November 1, 13

Slide 3

Slide 3 text

https://docs.djangoproject.com/en/1.5/topics/forms/#overview PURPOSE 3 Friday, November 1, 13

Slide 4

Slide 4 text

https://docs.djangoproject.com/en/1.5/topics/forms/#overview PURPOSE • How do forms work? 3 Friday, November 1, 13

Slide 5

Slide 5 text

https://docs.djangoproject.com/en/1.5/topics/forms/#overview PURPOSE • How do forms work? • What are my options for dealing with forms? 3 Friday, November 1, 13

Slide 6

Slide 6 text

https://docs.djangoproject.com/en/1.5/topics/forms/#overview WHY FORMS? 4 Friday, November 1, 13

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

http://django.2scoops.org https://django.2scoops.org/ 6 GO BUY TWO SCOOPS NOW! Not

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

1 #### da_form.html #### 2 3 4 5 6 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.

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

These concepts taken with permission from Danny Greenfield: http://pydanny.com/core-concepts-django-forms.html CORE CONCEPTS OF FORMS • Forms render HTML •Forms

Slide 16

Slide 16 text

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'

Subject: Message: Cc myself: 11 FORMS RENDER HTML 2.

Slide 17

Slide 17 text

12 FORMS RENDER HTML 1 {# forms.html #} 2 {% csrf_token %} 3 {{ form.as_p }} 4 5 6 7 {# Chrome Inspector #} 8 9

Subject: 10

11

Message: 12

13

Sender: 14

15

Cc myself: 16

17 18 1.

Slide 18

Slide 18 text

These concepts taken with permission from Danny Greenfield: http://pydanny.com/core-concepts-django-forms.html CORE CONCEPTS OF FORMS •Forms

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

#### 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.

Slide 22

Slide 22 text

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.

Slide 23

Slide 23 text

http://pydanny.com/core-concepts-django-modelforms.html • Render Model fields as HTML •Select

Slide 24

Slide 24 text

#### 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'

Name:

Title: --------- Mr. Mrs. Ms.

Birth date:

' 19 MODELFORMS RENDER HTML Just

Slide 25

Slide 25 text

http://pydanny.com/core-concepts-django-modelforms.html •Render

Slide 26

Slide 26 text

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. or . 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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

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

Name: 2 3

4

Url: 5 6

7

Comment: 8

' 26 WIDGETS TAKE ATTRS attrs

Slide 32

Slide 32 text

27 Friday, November 1, 13

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

womp

Slide 35

Slide 35 text

https://github.com/pinax/django-forms-bootstrap http://django-crispy-forms.readthedocs.org/en/latest/ http://django-floppyforms.readthedocs.org/en/latest/ https://pypi.python.org/pypi/django-widget-tweaks • django-forms-bootstrap • django-widget-tweaks • django-crispy-forms • django-floppyforms • django-parsley 30 THIRD-PARTY PACKAGES Friday, November 1, 13

Slide 36

Slide 36 text

https://github.com/pinax/django-forms-bootstrap DJANGO-FORMS-BOOTSTRAP 31 Useful for: • Quick and easy styling Friday, November 1, 13

Slide 37

Slide 37 text

https://github.com/pinax/django-forms-bootstrap DJANGO-FORMS-BOOTSTRAP 1 #### form.html #### 2 3 My Form 4 {% csrf_token %} 5 {{ form|as_bootstrap }} 6
7 Go back 8 Save changes 9
10 32 Simple

Slide 38

Slide 38 text

PRETTY OUTPUT DFB 33 Pretty

Slide 39

Slide 39 text

https://github.com/pinax/django-forms-bootstrap DFB: EXAMPLE CUSTOMIZATION 1 #### form.html #### 2 3 My Form 4 {% csrf_token %} 5 {{ form|as_bootstrap }} 8 Save changes 9 10 34 1.

Slide 40

Slide 40 text

http://twitter.github.io/bootstrap/base-css.html#inline-form INLINE OUTPUT DFB 35 Inline

Slide 41

Slide 41 text

https://github.com/pinax/django-forms-bootstrap http://django-crispy-forms.readthedocs.org/en/latest/ http://django-floppyforms.readthedocs.org/en/latest/ https://pypi.python.org/pypi/django-widget-tweaks •django-forms-bootstrap • django-widget-tweaks • django-crispy-forms • django-floppyforms • django-parsley 36 THIRD-PARTY PACKAGES Friday, November 1, 13

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

https://pypi.python.org/pypi/django-widget-tweaks DJANGO-WIDGET-TWEAKS 1 {% load widget_tweaks %} 2 3 4 {% render_field form.text rows="20" cols="20" title="Hello, world!" %} 5 6 7 {{ form.text|attr:"rows:20"|attr:"cols:20"|attr:"title:Hello, world!" }} 8 9 10 {{ form.title|append_attr:"class:css_class_1 css_class_2" }} 38 Django

Slide 44

Slide 44 text

https://github.com/pinax/django-forms-bootstrap http://django-crispy-forms.readthedocs.org/en/latest/ http://django-floppyforms.readthedocs.org/en/latest/ https://pypi.python.org/pypi/django-widget-tweaks •django-forms-bootstrap •django-widget-tweaks • django-crispy-forms • django-floppyforms • django-parsley 39 THIRD-PARTY PACKAGES Friday, November 1, 13

Slide 45

Slide 45 text

https://twitter.com/briandant/status/275289843465089024 MY 1ST DAY W/ CRISPY 40 Friday, November 1, 13

Slide 46

Slide 46 text

http://www.troll.me/2011/10/24/success-kid/i-got-a-power/ DJANGO-CRISPY-FORMS 41 “Forms

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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 7
8 10
11 12 13
14 ¿Do you like 15 this website?*< 16 /label> 17
    18
  • Yes
  • 22
  • 25 No
  • 26
27
28 ### snipped ### 29 ### ... ### 30 31
32 34
35 45 Write

Slide 51

Slide 51 text

CRISPY + BOOTSTRAP 1 #### settings.py #### 2 CRISPY_TEMPLATE_PACK = 'bootstrap' 46 Hey

Slide 52

Slide 52 text

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.

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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.

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

http://django-floppyforms.readthedocs.org/en/latest/widgets-reference.html FLOPPYFORMS BRINGING US TO THE FUTURE ColorInput: RangeInput: for sliders instead of spinboxes for numbers PhoneNumberInput: For phone numbers SearchInput: .” 52 Friday, November 1, 13

Slide 58

Slide 58 text

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 13 14 ### forms.py ### 15 class EmailForm(forms.Form): 16 email = forms.EmailField(widget=OtherEmailInput()) 53 1.

Slide 59

Slide 59 text

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 %}
  • {{ error }}
  • {% endfor %} 11 {% for error in form|hidden_field_errors %}
  • {{ error }}
  • {% endfor %} 12 {% 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.

    Slide 60

    Slide 60 text

    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 %}

    {{ headline }}

    {% endif %} 6 {{ block.super }} 7 {% endblock %} 8 {% block errors %} 9

    Following errors occurred that cannot be matched to a field:

    10 {{ block.super }} 11 {% endblock %} 12 13 ### form_page.html ### 14 15 {% form contact_form using "my_layout.html" %} 16

    17 55 1.

    Slide 61

    Slide 61 text

    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

    Slide 62

    Slide 62 text

    https://pypi.python.org/pypi/django-parsley/0.1 DJANGO-PARSLEY 57 Useful for: • Client-side validation • Nothing else Friday, November 1, 13

    Slide 63

    Slide 63 text

    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.

    Slide 64

    Slide 64 text

    DJANGO-PARSLEY OUTPUT 1 2 3
    6
    9
    10 11 First Name* 12
    13
    17 59 1.

    Slide 65

    Slide 65 text

    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

    Slide 66

    Slide 66 text

    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