Slide 1

Slide 1 text

Advanced Django Form Usage by Daniel Greenfeld and Miguel Araujo

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Enough theory

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

What about file uploads?

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

What about ModelForms?

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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!

Slide 22

Slide 22 text

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!

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

What about new model instances?

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

How do you test this stuff?

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Can we change non-required to required?

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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)

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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.')

Slide 36

Slide 36 text

Add dynamic fields to a form

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

I need a formset

Slide 42

Slide 42 text

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”

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Lets do programmatic layout of forms

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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.

Slide 49

Slide 49 text

Advanced Django Form Usage @pydanny / @maraujop Sample output 49
Name
Email
Comment
I agree to the terms of service Post comment

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

What about HTML5 Fields?

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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'}, ), )

Slide 54

Slide 54 text

Advanced Django Form Usage @pydanny / @maraujop Customizable widgets 54 import floppyforms as forms class OtherEmailInput(forms.EmailInput): template_name = 'path/to/other_email.html' path/to/other_email.html forms.py

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Forms refactor Django 1.4

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Give me a way to create custom form fields

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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:

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

I want a custom field that validates JSON

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

I want a custom widget that handles multiple widgets

Slide 71

Slide 71 text

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!!!

Slide 72

Slide 72 text

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)

Slide 73

Slide 73 text

Give me a MultiValue, MultiWidget field

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

How about custom widget output?

Slide 77

Slide 77 text

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!

Slide 78

Slide 78 text

Can’t you make it easier to understand?

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

Not in Lawrence Kansas anymore

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

Advanced Django Form Usage @pydanny / @maraujop 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 • There is an EmailValidator 83

Slide 84

Slide 84 text

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)

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

Fin

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

Fin

Slide 90

Slide 90 text

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