forms, getting your money's worth

26
Forms, Getting Your Money's Worth Alex Gaynor

Upload: alex-gaynor

Post on 02-Dec-2014

19.462 views

Category:

Technology


0 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Forms, Getting Your Money's Worth

Forms, Getting Your Money's Worth

Alex Gaynor

Page 2: Forms, Getting Your Money's Worth

What Are Forms For?•They are a way to get data from the user

Page 3: Forms, Getting Your Money's Worth

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

Page 4: Forms, Getting Your Money's Worth

The 20 second recapfrom 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

Page 5: Forms, Getting Your Money's Worth

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?

Page 6: Forms, Getting Your Money's Worth

Problems to Solve•Multiple forms of different types•Altering a form's options dynamically•Building entire forms dynamically

Page 7: Forms, Getting Your Money's Worth

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

Page 8: Forms, Getting Your Money's Worth
Page 9: Forms, Getting Your Money's Worth

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

Page 10: Forms, Getting Your Money's Worth

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

Page 11: Forms, Getting Your Money's Worth

How type() workstype(object) -> returns the class of the object,type(1) == inttype([]) == 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()}

Page 12: Forms, Getting Your Money's Worth

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([form.save(commit) for form in self.forms]) def is_valid(self): return all(form.is_valid() for form in self.forms)

Page 13: Forms, Getting Your Money's Worth

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

Page 14: Forms, Getting Your Money's Worth

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

Page 15: Forms, Getting Your Money's Worth

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.

Page 16: Forms, Getting Your Money's Worth

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

Page 17: Forms, Getting Your Money's Worth

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

Page 18: Forms, Getting Your Money's Worth

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.

Page 19: Forms, Getting Your Money's Worth

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.

Page 20: Forms, Getting Your Money's Worth

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.

Page 21: Forms, Getting Your Money's Worth

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)

Page 22: Forms, Getting Your Money's Worth

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' % survey.name, (forms.Form,), fields)

Page 23: Forms, Getting Your Money's Worth

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

Page 24: Forms, Getting Your Money's Worth

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

Page 25: Forms, Getting Your Money's Worth

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

Page 26: Forms, Getting Your Money's Worth

Questions?

P.S.: If you're someone who needs multiple datbase support in Django

come talk to me