Download - Django Forms: Best Practices, Tips, Tricks
Django FormsDjango FormsBest Practices, Tips and TricksBest Practices, Tips and Tricks
DjangoCon 2010
Shawn RiderPBS Education
Basic Forms
from django import forms class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() sender = forms.EmailField() cc_myself = forms.BooleanField(required=False) def save(self): data = self.cleaned_data contact = Contact() contact.subject = data.get('subject', '') contact.message = data.get('message', '') contact.sender = data.get('sender', '') contact.cc_myself = data.get('cc_myself', False) contact.save() return contact
But that's so much to write...But that's so much to write...
Model Forms
• Easily create forms with just a few lines of code.
• Customize and modify fields available for editing on the form.
• Override default methods to support complex business logic, etc.
• Customized Model Forms can easily be used in Django Admin.
• Model Forms can be used with Model Formsets to edit multiple forms in a single view.
Model Forms
from django.forms import ModelForm
### ... Form Definition ... ### class ArticleForm(ModelForm): class Meta: model = Article ### ... Inside the View ... ### article = Article.objects.get(pk=article_id) if request.method == 'POST': form = ArticleForm(request.POST, instance=article) if form.is_valid(): article = form.save() return HttpResponseRedirect(redirect_url) else: form = ArticleForm(instance=article)
Model Forms
A Model Form with listed editable fields and explicitly defined widgets:
class ArticleForm(ModelForm): class Meta: model = Article fields = ('title', 'content', 'blurb') widgets = { 'content': Textarea(attrs:{'cols':80, 'rows':20}) }
A Model Form with a custom save method:
class ArticleForm(ModelForm): class Meta: model = Article def save(self, *args, **kwargs): data = self.cleaned_data ### Insert complex, custom save logic here. ### return article
Formsets
• Formsets allow you to produce multiple forms for a page (bulk editing).
• Can be customized in many ways.
• Handle basic metadata to keep forms and data properly aligned.
• Formsets are used with basic Django forms. Model Formsets are used with Django Model Forms.
• Allow for validation of entire set of forms as well as individual forms within the formset.
Formsets ### View code ### def manage_articles(request): ArticleFormSet = formset_factory(ArticleForm) if request.method == 'POST': formset = ArticleFormSet(request.POST, request.FILES) if formset.is_valid(): for form in formset: form.save() return HttpResponseRedirect(REDIRECT_URL) else: formset = ArticleFormSet() return render_to_response('manage_articles.html', { 'formset': formset })
<!-- Template Code --> <form method="post" action=""> <table> {{ formset }} </table> </form>
Dynamic Forms
A Dynamic Form modulates the fields and/or choices available in the form
Why would I ever need a Dynamic Form?
• Enforcing restricted permissions for different levels of user
• Providing choices based on user or system data (custom contact lists, for example)
• Enforcing other complex business logic
For some developers, For some developers, it seems natural to use it seems natural to use a "code factory" design a "code factory" design
pattern to solve this pattern to solve this problem.problem.
Please don't do that.Please don't do that.
Override __init__ function
class ContactForm(forms.Form): def __init__(self, user, *args, **kwargs): super(ContactForm, self).__init__(*args, **kwargs) if not user.is_authenticated(): self.fields['captcha'] = CaptchaField() name = forms.CharField(max_length=50) email = forms.Emailfield() message = forms.CharField(widget=forms.Textarea)
Code Factory
def make_contact_form(user): # The basic form class _ContactForm(forms.Form): name = forms.CharField(max_length=50) email = forms.EmailField() message = forms.CharField(widget=forms.Textarea) if user.is_authenticated(): return _ContactForm
class _CaptchaContactForm(_ContactForm): captcha = CaptchaField() return _CaptchaContactForm
(Taken from James Bennett's http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/)
Dynamic Forms
class MemberSearchForm(forms.Form): def __init__(self, data=None, account=None, *args, **kwargs): self.account = account super(MemberSearchForm, self).__init__(data, *args, **kwargs)
terms = forms.CharField(required=False) role = forms.ChoiceField(choices=SEARCH_ROLE_CHOICES, required=False) status = forms.ChoiceField(choices=SEARCH_STATUS_CHOICES, required=False)
Tip: Always Redirect After Modifying Data
article = Article.objects.get(pk=article_id) if request.method == 'POST': form = ArticleForm(request.POST, instance=article) if form.is_valid(): article = form.save() return HttpResponseRedirect(redirect_url) else: form = ArticleForm(instance=article)
The Worst Example in the Django docs?
def contact(request): if request.method == 'POST': # If the form has been submitted... form = ContactForm(request.POST) # A form bound to the POST data if form.is_valid(): # All validation rules pass subject = form.cleaned_data['subject'] message = form.cleaned_data['message'] sender = form.cleaned_data['sender'] cc_myself = form.cleaned_data['cc_myself'] recipients = ['[email protected]'] if cc_myself: recipients.append(sender) from django.core.mail import send_mail send_mail(subject, message, sender, recipients) return HttpResponseRedirect('/thanks/') # Redirect after POST else: form = ContactForm() # An unbound form return render_to_response('contact.html', { 'form': form, })
Tip:Use Forms to Isolate Logic class MemberSearchForm(forms.Form): def __init__(self, data=None, account=None, *args, **kwargs): self.account = account super(MemberSearchForm, self).__init__(data, *args, **kwargs) terms = forms.CharField(required=False) role = forms.ChoiceField( choices=SEARCH_ROLE_CHOICES, required=False) status = forms.ChoiceField( choices=SEARCH_STATUS_CHOICES, required=False)
def search(self): data = self.cleaned_data ### Complex Search Logic Here ### return results
Tip:Use django-uni-form
Tip:Roll Your Own Fields and Form Classes• Isolate logic at any cost
• Sometimes objects or features in your project warrant a completely custom Form Field
• Custom Form Classes can perform custom templatized output, eliminating repetitive HTML in your templates
Sub Tip:
• Extend existing classes to save yourself headaches
Thanks and Further Reading
James Bennett's B-List: http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/
Danny Greenfield’s Django-Uniform: http://github.com/pydanny/django-uni-form/