Views | Convert Request to Response |
Forms | Convert input to Python objects |
Models | Data and business logic |
Forms are composed of fields, which have a widget.
from django.utils.translation import gettext_lazy as _
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(label=_("Your Name"),
max_length=255,
widget=forms.TextInput,
)
email = forms.EmailField(label=_("Email address"))
Unbound forms don't have data associated with them, but they can be rendered:
form = ContactForm()
Bound forms have specific data associated, which can be validated:
form = ContactForm(data=request.POST, files=request.FILES)
Two ways to access fields on a Form instance
form.fields['name']
returns the Field
objectform['name']
returns a BoundField
BoundField
wraps a field and value for HTML outputform = ContactForm(
initial={
'name': 'First and Last Name',
},
)
>>> form['name'].value()
'First and Last Name'
form.is_valid()
triggers validation if neededform.cleaned_data
form.full_clean()
performs the full cycleValidationError
.clean_fieldname()
method is called after validatorsValidationErrors
.clean_email()
§class ContactForm(forms.Form):
name = forms.CharField(
label=_("Name"),
max_length=255,
)
email = forms.EmailField(
label=_("Email address"),
)
def clean_email(self):
if (self.cleaned_data.get('email', '')
.endswith('hotmail.com')):
raise ValidationError("Invalid email address.")
return self.cleaned_data.get('email', '')
.clean()
performs cross-field validationValidationErrors
raised by .clean()
will be grouped in form.non_field_errors()
by default..clean()
example§class ContactForm(forms.Form):
name = forms.CharField(
label=_("Name"),
max_length=255,
)
email = forms.EmailField(label=_("Email address"))
confirm_email = forms.EmailField(label=_("Confirm"))
def clean(self):
if (self.cleaned_data.get('email') !=
self.cleaned_data.get('confirm_email')):
raise ValidationError("Email addresses do not match.")
return self.cleaned_data
Initial data is used as a starting point
It does not automatically propagate to cleaned_data
Defaults for non-required fields should be specified when accessing the dict:
self.cleaned_data.get('name', 'default')
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self._user = kwargs.pop('user')
super(MyForm, self).__init__(*args, **kwargs)
Forms use initial data to track changed fields
form.has_changed()
form.changed_data
Fields can render a hidden input with the initial value, as well:
>>> changed_date = forms.DateField(show_hidden_initial=True)
>>> print form['changed_date']
'<input type="text" name="changed_date" id="id_changed_date" /><input type="hidden" name="initial-changed_date" id="initial-id_changed_date" />'
- Initial states
- Field Validation
- Final state of
cleaned_data
import unittest
class FormTests(unittest.TestCase):
def test_validation(self):
form_data = {
'name': 'X' * 300,
}
form = ContactForm(data=form_data)
self.assertFalse(form.is_valid())
from rebar.testing import flatten_to_dict
form_data = flatten_to_dict(ContactForm())
form_data.update({
'name': 'X' * 300,
})
form = ContactForm(data=form_data)
assert(not form.is_valid())
from django.views.generic.edit import FormMixin, ProcessFormView
class ContactView(FormMixin, ProcessFormView):
form_class = ContactForm
success_url = '/contact/sent'
def form_valid(self, form):
# do something -- save, send, etc
pass
def form_invalid(self, form):
# do something -- log the error, etc -- if needed
pass
Three primary "whole-form" output modes:
form.as_p()
, form.as_ul()
, form.as_table()
<tr><th><label for="id_name">Name:</label></th>
<td><input id="id_name" type="text" name="name" maxlength="255" /></td></tr>
<tr><th><label for="id_email">Email:</label></th>
<td><input id="id_email" type="text" name="email" maxlength="Email address" /></td></tr>
<tr><th><label for="id_confirm_email">Confirm email:</label></th>
<td><input id="id_confirm_email" type="text" name="confirm_email" maxlength="Confirm" /></td></tr>
{% for field in form %}
{{ field.label_tag }}: {{ field }}
{{ field.errors }}
{% endfor %}
{{ field.non_field_errors }}
Additional rendering properties:
field.label
field.label_tag
field.html_name
field.help_text
You can specify additional attributes for widgets as part of the form definition.
class ContactForm(forms.Form):
name = forms.CharField(
max_length=255,
widget=forms.Textarea(
attrs={'class': 'custom'},
),
)
You can also specify form-wide CSS classes to add for error and required states.
class ContactForm(forms.Form):
error_css_class = 'error'
required_css_class = 'required'
Built in validators have default error messages
>>> generic = forms.CharField()
>>> generic.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
error_messages
lets you customize those messages
>>> name = forms.CharField(
... error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: [u'Please enter your name']
ValidationErrors
raised are wrapped in a classErrorList
is used: outputs as <ul>
error_class
kwarg when constructing the form to overridefrom django.forms.util import ErrorList
class ParagraphErrorList(ErrorList):
def __unicode__(self):
return self.as_paragraphs()
def as_paragraphs(self):
return "<p>%s</p>" % (
",".join(e for e in self.errors)
)
form = ContactForm(data=form_data, error_class=ParagraphErrorList)
Avoid potential name collisions with prefix
:
contact_form = ContactForm(prefix='contact')
Adds the prefix to HTML name and ID:
<tr><th><label for="id_contact-name">Name:</label></th>
<td><input id="id_contact-name" type="text" name="contact-name"
maxlength="255" /></td></tr>
<tr><th><label for="id_contact-email">Email:</label></th>
<td><input id="id_contact-email" type="text" name="contact-email"
maxlength="Email address" /></td></tr>
<tr><th><label for="id_contact-confirm_email">Confirm
email:</label></th>
<td><input id="id_contact-confirm_email" type="text"
name="contact-confirm_email" maxlength="Confirm" /></td></tr>
id
).save()
method.instance
propertyfrom django.db import models
from django import forms
class Contact(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
notes = models.TextField()
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ('name', 'email',)
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
exclude = ('notes',)
class ContactForm(forms.ModelForm):
name = forms.CharField(widget=forms.TextInput)
class Meta:
model = Contact
model_form = ContactForm()
model_form = ContactForm(
instance=Contact.objects.get(id=2)
)
_post_clean()
class ModelFormTests(unittest.TestCase):
def test_validation(self):
form_data = {
'name': 'Test Name',
}
form = ContactForm(data=form_data)
self.assert_(form.is_valid())
self.assertEqual(form.instance.name, 'Test Name')
form.save()
self.assertEqual(
Contact.objects.get(id=form.instance.id).name,
'Test Name'
)
Handles multiple copies of the same form
Adds a unique prefix to each form:
form-1-name
Support for insertion, deletion, and ordering
from django.forms import formsets
ContactFormSet = formsets.formset_factory(
ContactForm,
)
formset = ContactFormSet(data=request.POST)
Factory kwargs:
can_delete
extra
max_num
<form action=”” method=”POST”>
{% formset %}
</form>
Or more control over output:
<form action=”.” method=”POST”>
{% formset.management_form %}
{% for form in formset %}
{% form %}
{% endfor %}
</form>
formset.management_form
provides fields for tracking the member forms
TOTAL_FORMS
INITIAL_FORMS
MAX_NUM_FORMS
.clean()
method on the FormSetformset.clean()
can be overridden to validate across Formsformset.non_form_errors()
from django.forms import formsets
class BaseContactFormSet(formsets.BaseFormSet):
def clean(self):
names = []
for form in self.forms:
if form.cleaned_data.get('name') in names:
raise ValidationError()
names.append(form.cleaned_data.get('name'))
ContactFormSet = formsets.formset_factory(
ContactForm,
formset=BaseContactFormSet
)
management_form
to determine how many forms to buildTOTAL_FORM_COUNT
formset.empty_form
provides an empty copy of the form with __prefix__
as the indexDELETE
field is added to each form.deleted_forms
propertyContactFormSet = formsets.formset_factory(
ContactForm, can_delete=True,
)
ORDER
field is added to each form.ordered_forms
propertyContactFormSet = formsets.formset_factory(
ContactForm,
can_order=True,
)
flatten_to_dict
works with FormSets just like Formsempty_form_data
takes a FormSet and index, returns a dict of data for an empty form:from rebar.testing import flatten_to_dict, empty_form_data
formset = ContactFormSet()
form_data = flatten_to_dict(formset)
form_data.update(
empty_form_data(formset, len(formset))
)
queryset
argument specifies initial set of objects.save()
returns the list of saved instancescan_delete
is True
, .save()
also deletes the models flagged for deletionEnable in settings.py
:
USE_L10N = True
USE_THOUSAND_SEPARATOR = True # optional
And then use the localize
kwarg
>>> from django import forms
>>> class DateForm(forms.Form):
... pycon_ends = forms.DateField(localize=True)
>>> DateForm({'pycon_ends': '3/15/2012'}).is_valid()
True
>>> DateForm({'pycon_ends': '15/3/2012'}).is_valid()
False
>>> from django.utils import translation
>>> translation.activate('en_GB')
>>> DateForm({'pycon_ends':'15/3/2012'}).is_valid()
True
form.fields
__init__
finishes, you can manipulate form.fields
without impacting other instancesfrom django import forms
from rebar.validators import StateValidator, StateValidatorFormMixin
class PublishValidator(StateValidator):
validators = {
'title': lambda x: bool(x),
}
class EventForm(StateValidatorFormMixin, forms.Form):
state_validators = {
'publish': PublishValidator,
}
title = forms.CharField(required=False)
>>> form = EventForm(data={})
>>> form.is_valid()
True
>>> form.is_valid('publish')
False
>>> form.errors('publish')
{'title': 'This field is required'}