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

Advanced Django Forms Usage

Advanced Django Forms Usage

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

Daniel Greenfeld

September 26, 2011
Tweet

More Decks by Daniel Greenfeld

Other Decks in Programming

Transcript

  1. Advanced Django
    Form Usage
    by Daniel Greenfeld and Miguel Araujo

    View full-size slide

  2. Advanced Django Form Usage
    @pydanny / @maraujop
    Daniel Greenfeld
    • pydanny
    • Python & Django developer for
    Cartwheel Web / RevSys
    • Founded django-uni-form
    • Does Capoeira
    • Lives in Los Angeles with his
    Fiancé, Audrey Roy (audreyr)
    http://www.flickr.com/photos/pydanny/4442245488/
    2

    View full-size slide

  3. Advanced Django Form Usage
    @pydanny / @maraujop
    Miguel Araujo
    • maraujop
    • Freelance Python developer
    • Co-lead of django-uni-form
    • Does Muay Thai
    • Lives in Madrid with his
    amazing girlfiend who does
    aerospace for a living
    • http://maraujop.github.com/
    3

    View full-size slide

  4. Advanced Django Form Usage
    @pydanny / @maraujop
    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
    • James Tauber
    • Frank Wiles
    4

    View full-size slide

  5. Advanced Django Form Usage
    @pydanny / @maraujop
    Fundamentals of
    Good Form Patterns
    • Zen of Python still applies
    • import this
    • Spartan programming als0 is important
    5

    View full-size slide

  6. Advanced Django Form Usage
    @pydanny / @maraujop
    Spartan Programming
    • Horizontal complexity
    • Vertical complexity (line count)
    • Token count
    • Character count
    • Variables
    • Loops
    • Conditionals
    6
    http://www.codinghorror.com/blog/2008/07/spartan-programming.html

    View full-size slide

  7. Advanced Django Form Usage
    @pydanny / @maraujop
    Spartan Programming
    • Horizontal complexity
    • Vertical complexity (line count)
    • Token count
    • Character count
    • Variables
    • Loops
    • Conditionals
    7
    http://www.codinghorror.com/blog/2008/07/spartan-programming.html

    View full-size slide

  8. Advanced Django Form Usage
    @pydanny / @maraujop
    Spartan Programming
    • Horizontal complexity
    • Vertical complexity (line count)
    • Token count
    • Character count
    • Variables
    • Loops
    • Conditionals
    8
    http://www.codinghorror.com/blog/2008/07/spartan-programming.html

    View full-size slide

  9. Advanced Django Form Usage
    @pydanny / @maraujop
    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
    • So you can focus on your business logic
    9

    View full-size slide

  10. Enough theory

    View full-size slide

  11. Advanced Django Form Usage
    @pydanny / @maraujop
    A Basic Django Form
    11
    class MyForm(forms.Form):
    name = forms.CharField(_('Name'), required=True)

    View full-size slide

  12. Advanced Django Form Usage
    @pydanny / @maraujop
    Standard views.py
    12
    def my_view(request, template_name='myapp/my_form.html'):
    if request.method == 'POST':
    form = MyForm(request.POST) # Form #1!
    if form.is_valid(): # nested if!
    do_x()
    return redirect('/')
    else:
    form = MyForm() # Form #2!
    return render(request, template_name, {'form': form})
    Form #1
    Form #2
    Only 1 nested if, but real code
    gets much more complex Custom business
    goes here

    View full-size slide

  13. Advanced Django Form Usage
    @pydanny / @maraujop
    Easy views.py
    13
    def my_view(request, template_name='myapp/my_form.html'):
    # sticks in a POST or renders empty form
    form = MyForm(request.POST or None)
    if form.is_valid():
    do_x()
    return redirect('home')
    return render(request, template_name, {'form': form})
    Single form
    Custom business
    goes here
    If no request.POST,
    then instantiated with None

    View full-size slide

  14. Advanced Django Form Usage
    @pydanny / @maraujop
    def my_view(request, template_name='myapp/my_form.html'):
    # sticks in a POST or renders empty form
    form = MyForm(request.POST or None)
    if form.is_valid():
    do_x()
    return redirect('home')
    return render(request, template_name, {'form': form})
    Easy views.py
    • 6 lines of code instead of 9
    • 33.3333333333% less code to debug
    • One form instantiation
    • less conditionals == less edge case insanity
    14

    View full-size slide

  15. What about
    file uploads?

    View full-size slide

  16. Advanced Django Form Usage
    @pydanny / @maraujop
    Easy views.py + files
    • 6 lines of code again!
    • Code donated by Audrey Roy
    16
    def my_view(request, template_name='myapp/my_form.html'):
    form = MyForm(request.POST or None, request.FILES or None)
    if form.is_valid():
    do_x()
    return redirect('home')
    return render(request, template_name, {'form': form})
    Request.POST
    or None
    Request.FILES
    or None

    View full-size slide

  17. What about
    ModelForms?

    View full-size slide

  18. Advanced Django Form Usage
    @pydanny / @maraujop
    pydanny made
    up statistics
    • 91% of all Django projects use ModelForms
    • 80% ModelForms require trivial logic
    • 20% ModelForms require complicated logic
    18
    Let’s try and make that easy

    View full-size slide

  19. Advanced Django Form Usage
    @pydanny / @maraujop
    A Basic ModelForm
    19
    class MyModelForm(forms.Form):
    class Meta:
    model = MyModel
    fields = ['name']

    View full-size slide

  20. Advanced Django Form Usage
    @pydanny / @maraujop
    Classic views.py for ModelForm
    20
    def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
    # I wouldn't call the variable model, because it's an instance of a model, it's confusing
    mymodel = get_object_or_404(MyModel, slug=slug)
    if request.method == 'POST':
    form = MyForm(request, instance=mymodel)
    if form.is_valid():
    mymodel = form.save()
    mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
    mymodel.save()
    return redirect('home')
    else:
    form = MyForm(instance=mymodel)
    return render(request, template_name, {'form': form, 'model': mymodel})
    Form #1
    Form #2
    Only 1 nested if, but real code
    gets much more complex
    Custom business
    goes here

    View full-size slide

  21. Advanced Django Form Usage
    @pydanny / @maraujop
    Classic views.py for ModelForm
    21
    def my_model_edit(request, slug=slug, template_name='myapp/my_model_form.html'):
    # I wouldn't call the variable model, because it's an instance of a model, it's confusing
    mymodel = get_object_or_404(MyModel, slug=slug)
    if request.method == 'POST':
    form = MyForm(request, instance=mymodel)
    if form.is_valid():
    mymodel = form.save()
    mymodel.day_shown = datetime.datetime.now() # Do any extra model stuff here
    mymodel.save()
    return redirect('home')
    else:
    form = MyForm(instance=mymodel)
    return render(request, template_name, {'form': form, 'model': mymodel})
    • 12 lines of code
    • Nested conditionals
    • What if we have to handle 3 different submit buttons?
    • Watch out for edge case insanity!

    View full-size slide

  22. Advanced Django Form Usage
    @pydanny / @maraujop
    easy views.py + ModelForm
    22
    def my_model_edit(request, slug=slug, template_name='myapp/
    my_model_form.html'):
    mymodel = get_object_or_404(MyModel, slug=slug)
    form = MyModelForm(request.POST or None, instance=mymodel)
    if form.is_valid():
    mymodel = form.save()
    mymodel.edited_at_djangocon = True
    mymodel.save()
    return redirect('home')
    return render(request, template_name, {'form': form, 'mymodel': mymodel})
    Single form
    Custom business
    goes here
    If no request.POST,
    then instantiated with None
    So this will fail validation!

    View full-size slide

  23. Advanced Django Form Usage
    @pydanny / @maraujop
    easy views.py + ModelForm
    • 9 lines of code instead of 12
    • One conditional
    • clear and succinct code
    23
    def my_model_edit(request, slug=slug, template_name='myapp/
    my_model_form.html'):
    mymodel = get_object_or_404(MyModel, slug=slug)
    form = MyModelForm(request.POST or None, instance=mymodel)
    if form.is_valid():
    mymodel = form.save()
    mymodel.edited_at_djangocon = True
    mymodel.save()
    return redirect('home')
    return render(request, template_name, {'form': form, 'mymodel': mymodel})

    View full-size slide

  24. What about new
    model instances?

    View full-size slide

  25. Advanced Django Form Usage
    @pydanny / @maraujop
    add views.py + ModelForm
    25
    def my_model_add(request, template_name='myapp/my_model_form.html'):
    form = MyModelForm(request.POST or None)
    if form.is_valid():
    mymodel = form.save()
    mymodel.added_at_djangocon = True
    mymodel.save()
    return redirect('home')
    return render(request,template_name,{'form': form,'mymodel':mymodel})
    No need for an
    instance here
    This creates the
    model instance.

    View full-size slide

  26. Advanced Django Form Usage
    @pydanny / @maraujop
    I can make it smaller!
    26
    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()
    return redirect('home')
    return render(request,template_name,{'form':form,'mymodel':mymodel})
    Good practice: Use model field defaults rather
    than doing it in your views.
    Not setting defaults here

    View full-size slide

  27. How do you test
    this stuff?

    View full-size slide

  28. Advanced Django Form Usage
    @pydanny / @maraujop
    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
    28

    View full-size slide

  29. Advanced Django Form Usage
    @pydanny / @maraujop
    Django unit tests!
    29
    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',
    'slug': 'test-slug',
    'title': 'TEST TITLE',
    }, follow=True)
    self.assertEqual(Package.objects.count(), count + 1)
    self.assertContains(response, "Django")
    POSTing a form
    follow=True
    assertContains is
    your best friend

    View full-size slide

  30. Can we
    change non-required
    to required?

    View full-size slide

  31. Advanced Django Form Usage
    @pydanny / @maraujop
    non-required to required
    • Your model fields are non-required
    • but you want the form fields to be required
    31

    View full-size slide

  32. Advanced Django Form Usage
    @pydanny / @maraujop
    A basic Django models.py
    32
    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)

    View full-size slide

  33. Advanced Django Form Usage
    @pydanny / @maraujop
    Classic forms.py overload
    33
    class MyModelTooMuchTypingForm(forms.ModelForm):
    """ I've done this and it sucks
    hard to debug and too much duplication
    """
    name = forms.CharField(_('Name'), max_length=50, required=True)
    age = forms.IntegerField(_('Age in years'), required=True)
    profession = forms.CharField(_('Profession'), required=True)
    bio = forms.TextField(_('Bio'), required=True)
    class Meta:
    model = MyModel
    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)
    Nearly duplicated code

    View full-size slide

  34. Advanced Django Form Usage
    @pydanny / @maraujop
    Better forms.py overload
    34
    class MyModelForm(forms.ModelForm):
    """ Much better and you are extending, not copy/pasting """
    def __init__(self):
    super(MyModelForm, self).__init__(*args, **kwargs)
    self.fields['name'].required = True
    self.fields['age'].required = True
    self.fields['profession'].required = True
    self.fields['profession'].help_text = _("Hi professor")
    class Meta:
    model = MyModel
    Fields are in a dict-like object

    View full-size slide

  35. Advanced Django Form Usage
    @pydanny / @maraujop
    Try it with inheritance!
    35
    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.')

    View full-size slide

  36. Add dynamic fields
    to a form

    View full-size slide

  37. Advanced Django Form Usage
    @pydanny / @maraujop
    Dynamically adding fields
    to a form
    37
    def my_view(request, template_name='myapp/my_model_form.html'):
    form = MyModelForm(request.POST or None)
    # Let's add a field on the go, needs to be done before validating it
    form.fields['favorite_color'] = forms.ChoiceField(
    label = "Which is your favorite color from these?",
    choices = (('blue', 'blue'), ('red', 'red'), ('green', 'green')),
    widget = forms.RadioSelect,
    required = True,
    )
    if form.is_valid():
    # Let's get user's favorite color,
    # you can do whatever you want with it
    favorite_color = form.cleaned_data['favorite_color']
    form.save()
    return redirect('home')
    return render(request, template_name, {'form': form})
    Form dictionary
    of fields
    Fields have to be added
    before the form.is_valid
    method is checked.

    View full-size slide

  38. Can’t we just pass in a
    list of fields into a form
    from a view?

    View full-size slide

  39. Advanced Django Form Usage
    @pydanny / @maraujop
    Constructor overrides forms.py
    39
    class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
    extra = kwargs.pop('extra')
    super(UserCreationForm, self).__init__(*args, **kwargs)
    for i, question in enumerate(extra):
    self.fields['custom_%s' % i] = forms.CharField(label=question)
    def extra_fields(self):
    """
    Returns a tuple (question, answer)
    """
    for name, value in self.cleaned_data.items():
    if name.startswith('custom_'):
    yield (self.fields[name].label, value)
    looping over
    ‘extra’ iterable

    View full-size slide

  40. Advanced Django Form Usage
    @pydanny / @maraujop
    Constructor overrides views.py
    40
    def my_view(request, template_name='myapp/my_model_form.html'):
    form = MyModelForm(request.POST or None,
    extra=["What's your pet's name?"])
    if form.is_valid():
    # We can gather extra fields data doing
    for (question, answer) in form.extra_fields():
    save_answer(request, question, answer)
    form.save()
    return redirect('home')
    return render(request, template_name, {'form': form})
    Passing in a list
    of form field
    titles

    View full-size slide

  41. I need a formset

    View full-size slide

  42. Advanced Django Form Usage
    @pydanny / @maraujop
    class ItemFormSet(BaseFormSet):
    def __init__(self, numberItems, *args, **kwargs):
    super(ItemFormSet, self).__init__(*args, **kwargs)
    self.numberItems = numberItems
    def clean(self):
    # Don't bother validating the formset
    # unless each form is valid on its own
    if any(self.errors):
    return
    for form in self.forms:
    if not form.cleaned_data['your_choice'] == 'mod_wsgi':
    raise ValidationError(u'mod_wsgi is the way to go!')
    formsets.py
    42
    Forms must have
    ‘your_choice’ field that
    only accepts “mod_wsgi”

    View full-size slide

  43. Advanced Django Form Usage
    @pydanny / @maraujop
    views.py
    43
    from django.forms.models import formset_factory
    def wsgi_form_view(request):
    WsgiFormset = formset_factory(ExampleForm, extra=5,
    formset=ItemFormSet)
    formset = WsgiFormset(bogus_number, request.POST,
    request.FILES)
    Truncated formset view

    View full-size slide

  44. Lets do
    programmatic layout
    of forms

    View full-size slide

  45. Advanced Django Form Usage
    @pydanny / @maraujop
    Things I want python
    to describe in forms
    • Different fieldsets within the same form
    • Various buttons
    • submit
    • reset
    45

    View full-size slide

  46. Advanced Django Form Usage
    @pydanny / @maraujop
    django-uni-form
    46

    View full-size slide

  47. Advanced Django Form Usage
    @pydanny / @maraujop
    Programmatic layouts
    47
    class ExampleForm(forms.Form):
    def __init__(self, *args, **kwargs):
    self.helper = FormHelper()
    self.helper.layout = Layout(
    Fieldset(
    'first arg is the legend of the fieldset',
    'like_website',
    'favorite_number',
    ),
    Fieldset(
    'second arg is the legend of the fieldset',
    'favorite_color',
    'favorite_food',
    )
    ButtonHolder(
    Submit('submit', 'Submit', css_class='button white')
    )
    )
    return super(ExampleForm, self).__init__(*args, **kwargs)
    from uni_form.helpers import FormHelper, Submit, Reset
    from uni_form.helpers import Fieldset, ButtonHolder, Layout
    Fieldset
    Button
    Holder
    Button
    Layout
    FormHelper

    View full-size slide

  48. Advanced Django Form Usage
    @pydanny / @maraujop
    renders as divs
    48
    {% load uni_form_tags %}
    {% uni_form my_form my_form.helper %}
    Renders the HTML form
    with buttons and everything
    wrapped in a fieldset.

    View full-size slide

  49. Advanced Django Form Usage
    @pydanny / @maraujop
    Sample output
    49



    Name



    Email



    Comment




    I agree to the terms of service
    Post comment



    View full-size slide

  50. Advanced Django Form Usage
    @pydanny / @maraujop
    django-uni-form
    • Programmatic layout
    • Div based forms
    • Section 508 compliant
    • Fully customizable templates
    • http://django-uni-form.rtfd.org
    50

    View full-size slide

  51. What about
    HTML5 Fields?

    View full-size slide

  52. Advanced Django Form Usage
    @pydanny / @maraujop
    django-floppyforms
    • by Bruno Renie @brutasse
    • HTML5 Widgets
    • Fully customizable templates
    • Plays nice with django-uni-form
    52

    View full-size slide

  53. Advanced Django Form Usage
    @pydanny / @maraujop
    Easy to use!
    • Django’s widget parameter attrs expects a dictionary
    • Replaces the normal widgets with HTML5 ones
    53
    import floppyforms as forms
    class ExampleForm(forms.Form):
    username = forms.CharField(
    label='',
    widget = forms.TextInput(
    attrs={'placeholder': '@johndoe'},
    ),
    )

    View full-size slide

  54. Advanced Django Form Usage
    @pydanny / @maraujop
    Customizable widgets
    54
    import floppyforms as forms
    class OtherEmailInput(forms.EmailInput):
    template_name = 'path/to/other_email.html'
    name="{{ name }}"
    id="{{ attrs.id }}"
    placeholder="[email protected]"
    {% if value %}value="{{ value }}"{% endif %}>
    path/to/other_email.html
    forms.py

    View full-size slide

  55. Can you combine
    django-uni-form and
    django-floppyforms?

    View full-size slide

  56. django-uni-form +
    django-floppyforms
    AWESOME POWER
    =

    View full-size slide

  57. Advanced Django Form Usage
    @pydanny / @maraujop
    They get along well
    57
    class ListFriendsForm(forms.Form):
    username = forms.CharField(
    widget = forms.TextInput(
    attrs={'placeholder': '@maraujop'},
    ),
    )
    def __init__(self, *args, **kwargs):
    self.helper = FormHelper()
    self.helper.layout = Layout(
    Div(
    'username',
    )
    ButtonHolder(
    Submit('submit', 'Submit', css_class='button white')
    )
    )
    return super(ExampleForm, self).__init__(*args, **kwargs)
    No changes or
    special code needed

    View full-size slide

  58. Forms refactor
    Django 1.4

    View full-size slide

  59. Advanced Django Form Usage
    @pydanny / @maraujop
    What we are getting in 1.4
    • Form rendering will use templates instead
    of HTML in Python
    • Template based widgets
    • Template based form layout
    59

    View full-size slide

  60. Advanced Django Form Usage
    @pydanny / @maraujop
    forms refactor and
    django-uni-form
    • forms refactor layout “lives” in templates
    • django-uni-form layout “lives” in python
    • Different approaches, same objective
    60

    View full-size slide

  61. Give me a way to
    create custom
    form fields

    View full-size slide

  62. Advanced Django Form Usage
    @pydanny / @maraujop
    The docs on custom fields
    62
    “Its only requirements are that it implement
    a clean() method and that its __init__()
    method accept the core arguments
    mentioned above (required, label, initial,
    widget, help_text).”
    The docs seem to be wrong

    View full-size slide

  63. Advanced Django Form Usage
    @pydanny / @maraujop
    How it really works
    • required
    • widget
    • label
    • help_text
    • initial
    • error_messages
    • show_hidden_initial
    • validators
    • localize
    63
    You don’t need to implement “clean”
    You do need to implement:

    View full-size slide

  64. How the heck then do I
    actually add a custom
    form field?!?

    View full-size slide

  65. Advanced Django Form Usage
    @pydanny / @maraujop
    How does form
    validation work?
    65

    View full-size slide

  66. Advanced Django Form Usage
    @pydanny / @maraujop
    fields.py
    66
    class AMarkField(forms.Field):
    widget = TextInput
    default_error_messages = {
    'not_an_a': _(u'you can only input A here! damn!'),
    }
    def __init__(self, **kwargs):
    super(AMarkField, self).__init__(**kwargs)
    def to_python(self, value):
    if value in validators.EMPTY_VALUES:
    return None
    if value != 'A':
    raise ValidationError(self.error_messages['not_an_a'])
    return value
    Only accepts upper
    case A as input

    View full-size slide

  67. Advanced Django Form Usage
    @pydanny / @maraujop
    Not DRY
    67
    class MyModelForm(forms.ModelForm):
    mark = CharField()
    def clean_mark(self):
    mark_value = self.cleaned_data['mark']
    if mark_value is not None or mark_value.upper() != 'A':
    raise ValidationError(_(u'Only input A here!'))
    return mark_value
    class ExampleForm(forms.Form):
    mark = CharField()
    def clean_mark(self):
    mark_value = self.cleaned_data['mark']
    if mark_value is not None or mark_value.upper() != 'A':
    raise ValidationError(_(u'Only input A here!'))
    return mark_value

    View full-size slide

  68. I want a custom field
    that validates JSON

    View full-size slide

  69. Advanced Django Form Usage
    @pydanny / @maraujop
    The JSON field
    69
    class JSONField(forms.Field):
    default_error_messages = {
    'invalid': 'This is not valid JSON string'
    }
    def to_python(self, value):
    if value in validators.EMPTY_VALUES:
    return None
    try:
    json = simplejson.loads(value)
    except ValueError:
    raise ValidationError(self.error_messages['invalid'])
    return json

    View full-size slide

  70. I want a custom
    widget that handles
    multiple widgets

    View full-size slide

  71. Advanced Django Form Usage
    @pydanny / @maraujop
    widgets.py
    71
    from django.forms.extras.widgets import MultiWidget
    class AddressWidget(MultiWidget):
    def __init__(self, attrs=None):
    widgets = (TextInput, TextInput)
    super(AddressWidget, self).__init__(widgets, attrs)
    def decompress(self, value):
    """
    If called, value is a string should return a list
    """
    if value:
    # parse stuff and return a list
    return value.split()
    return [None, None]
    Called when a form with
    MultiWidget is passed initial
    or instance values
    Two TextInput
    widgets!!!

    View full-size slide

  72. Advanced Django Form Usage
    @pydanny / @maraujop
    fields.py
    72
    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)
    isinstance("921 SW Sixth Avenue, Portland", str)

    View full-size slide

  73. Give me a MultiValue,
    MultiWidget field

    View full-size slide

  74. Advanced Django Form Usage
    @pydanny / @maraujop
    MultiValue, MultiWidget Field
    • Clean does not work in the standard way
    • Every field validated with its corresponding widget value
    • Beware! run_validator does run but validate is called
    74
    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
    Django Ticket #14184

    View full-size slide

  75. Advanced Django Form Usage
    @pydanny / @maraujop
    Validators with Multi-Field
    75
    class AlternativeAddressField(forms.MultiValueField):
    widget = AddressWidget
    def __init__(self, *args, **kwargs):
    fields = (forms.CharField(), forms.CharField())
    super(AlternativeAddressField, self).__init__(fields,
    *args, **kwargs)
    def validate(self, value):
    self._run_validators(value)
    def compress(self, data_list):
    return data_list

    View full-size slide

  76. How about custom
    widget output?

    View full-size slide

  77. Advanced Django Form Usage
    @pydanny / @maraujop
    def render(self, name, value, attrs=None):
    # HTML to be added to the output
    widget_labels = [
    'Address: ',
    'Number: '
    ]
    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
    # 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_:
    final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
    # Adding labels
    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))
    Custom Widget Output
    77
    So Easy!

    View full-size slide

  78. Can’t you make it
    easier to understand?

    View full-size slide

  79. Advanced Django Form Usage
    @pydanny / @maraujop
    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
    79

    View full-size slide

  80. Not in Lawrence
    Kansas anymore

    View full-size slide

  81. Advanced Django Form Usage
    @pydanny / @maraujop
    Problems with Custom
    Widget Output
    • Oldest ticket in Django is related to forms
    (#23)
    • ComboField is broken
    • Validators are thought to validate simple values
    because of the way run_validators is coded
    81

    View full-size slide

  82. Advanced Django Form Usage
    @pydanny / @maraujop
    HTML5 tickets
    • Ticket #16304
    • Ticket #16630
    • Just use django-floppyforms
    82

    View full-size slide

  83. Advanced Django Form Usage
    @pydanny / @maraujop
    Validators
    • Imagine you have an input that get a list of emails seperated
    by spaces
    • Your widget returns: [“[email protected]”, “[email protected]”]
    • You want to run validators on all of them
    • There is an EmailValidator
    83

    View full-size slide

  84. Advanced Django Form Usage
    @pydanny / @maraujop
    MultiValidator
    84
    class MultiValidator(object):
    def __init__(self, validators):
    self.validators = validators
    def __call__(self, data_list):
    errors = []
    for value in data_list:
    for validator in self.validators:
    try:
    validator(value)
    except ValidationError, e:
    if hasattr(e, 'code'):
    errors.append("FiXED MESSAGE")
    raise ValidationError(errors)

    View full-size slide

  85. Advanced Django Form Usage
    @pydanny / @maraujop
    Amazing stuff
    • Ticket #27 reported by Adrian Holovaty
    • Single form field for multiple database fields
    85

    View full-size slide

  86. Advanced Django Form Usage
    @pydanny / @maraujop
    This has been
    incredible
    • We’ve just scratched the surface
    • Keep your code clean - or you’ll regret it
    • Miguel is awesome
    87

    View full-size slide

  87. Advanced Django Form Usage
    @pydanny / @maraujop
    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
    • Danny holds the DjangoCon talks record
    88

    View full-size slide

  88. Advanced Django Form Usage
    @pydanny / @maraujop
    Questions?
    90
    Daniel Greenfeld & Miguel Araujo

    View full-size slide