Forms, Getting Your Money's Worth


Published on

These are the slides from my talk on advanced forms topics given at EuroDjangocon 2009.

Published in: Technology, Business
  • A good guide to getting your money's worth in the form technical side.
    Are you sure you want to  Yes  No
    Your message goes here
  • Explained well on django forms.

    Mark Chang,
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Forms, Getting Your Money's Worth

  1. 1. Forms, Getting Your Money's Worth Alex Gaynor
  2. 2. What Are Forms For? •They are a way to get data from the user
  3. 3. django.forms •Encapsulate forms into objects •Know how to validate our data •In the case of ModelForms, they know how to save data •Formsets: Multiple of the same type of Form •Understand part of the idea of the HTTP request/response cycle
  4. 4. The 20 second recap from django import forms from myapp.models import MyModel class MyForm(forms.Form): my_field = forms.CharField() other_field = forms.BooleanField(widget=forms.Select) class MyModelForm(forms.ModelForm): class Meta: model = MyModel
  5. 5. Going beyond the basics •What you just saw is how 90% of people use django.forms •That's using classes to define static forms that don't change •But that's not how most applications are •What are some common tasks that aren't static, or otherwise handled by this setup?
  6. 6. Problems to Solve •Multiple forms of different types •Altering a form's options dynamically •Building entire forms dynamically
  7. 7. Multiple Forms •Formsets let us handle multiple forms of one type, for example letting a user enter multiple books •We can manually pass around multiple forms user_form = UserForm() profile_form = UserProfileForm() return render_to_response({ 'user_form': user_form, 'profile_form': profile_form, })
  8. 8. Doing a bit Better •That's really ugly, doesn't scale well •What would be easier? •One class to hold all of our forms •It should act like it's just a normal form object where possible •How do we build this? •Normal python tools of the trade, dynamically creating classes
  9. 9. def multipleform_factory(form_classes, form_order=None): if form_order: form_classes = SortedDict([ (prefix, form_classes[prefix]) for prefix in form_order]) else: form_classes = SortedDict(form_classes) return type('MultipleForm', (MultipleFormBase,), {'form_classes': form_classes})
  10. 10. How type() works type(object) -> returns the class of the object, type(1) == int type([]) == list type(name, bases, attrs) -> returns a new class named {{ name }}, inherits from {{ bases}}, and has attributes of {{ attrs}} class MyForm(forms.Form): my_field = forms.CharField() type('MyForm', (forms.Form,), {'my_field': forms.CharField()}
  11. 11. class MutlipleFormBase(object): def __init__(self, data, files): self.forms = [form_class(data, files, prefix=prefix) for form_class, prefix in self.form_classes.iteritems()] def as_table(self): return 'n'.join([form.as_table() for form in self.forms]) def save(self, commit=True): return tuple([ for form in self.forms]) def is_valid(self): return all(form.is_valid() for form in
  12. 12. How it works •Simple factory function creates a new class that subclasses MultipleFormBase, gives it an extra attribute, a dict of form classes •MultipleFormBase just emulates the API of Form and ModelForm, aggregating the methods as appropriate
  13. 13. class UserForm(forms.ModelForm): class Meta: model = User class ProfileForm(forms.ModelForm): class Meta: model = UserProfile UserProfileForm = multipleform_factory({ 'user': UserForm, 'profile': ProfileForm, }, ['user', 'profile'])
  14. 14. The Result •Now we can use UserProfileForm the same way we would a regular form. •We need to expand MultipleFormBase to implement the rest of the Form API, but these additional methods are trivial.
  15. 15. A More Dynamic Form •Not all users should see the same form •Simple example: a user should only be able to select an option from a dropdown if it belongs to them •Tools of the trade: Subclassing methods
  16. 16. class ObjectForm(forms.ModelForm): def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) super(ObjectForm, self).__init__(*args, **kwargs) self.fields['related'].queryset = self.fields['related'].queryset. filter(owner=user) class Meta: model = Object
  17. 17. Dead Simple •Now when we instantiate our form we just need to prive a user kwarg. •Form and ModelForm are just python. We overide methods to add our own behavior, then call super(). •We can apply this same technique anywhere in Python, to any method.
  18. 18. Building it Dynamically •What if we don't just want to alter our form per request, what if we want to build the whole thing dynamically? •Common example: a survey application where we keep the survey questions in the database. •Let's build it.
  19. 19. The Pieces •What do we need to build this survey application? •A Model for a Survey and another for Fields on each Form(we're keeping it simple). •A function to take a Survey object and turn it into a Form subclass.
  20. 20. class Survey(models.Model): name = models.CharField(max_length=100) FIELD_TYPE_CHOICES = ( (0, 'charfield'), (1, 'textfield'), (2, 'boolean'), ) class Question(models.Model): survey = models.ForeignKey(Survey, related_name='questions') label = models.CharField(max_length=255) field_type = models.IntegerField(choices= FIELD_TYPE_CHOICES)
  21. 21. FIELD_TYPES = { 0: forms.CharField, 1: curry(forms.CharField, widget=forms.Textarea) 2: forms.BooleanField } def form_form_survey(survey): fields = {} for question in survey.questions.all(): fields[question.label] = FIELD_TYPES[question.field_type]( label = question.label ) return type('%sForm' %, (forms.Form,), fields)
  22. 22. What do We Have •2 very simple models for storing our surveys and the questions for these surveys •a way to create actual forms.Form classes for a survey •now we can use these forms in our views the same as normal
  23. 23. What Don't We have •Handling for more complex field types(such as choices) •A way to create Surveys(other than the admin, which would actually work really well for this) •A way to save the results of a survey •Consider these an excersise for the reader
  24. 24. Recap •Use standard Python to extend what we have •Don't be afraid to build your own things on top of what we already have •It's just Python
  25. 25. Questions? P.S.: If you're someone who needs multiple datbase support in Django come talk to me