Django’s role in forms
Django’s form functionality can simplify and automate vast portions of this work, and can also do it more securely than most programmers would be able to do in code they wrote themselves.
Django handles three distinct parts of the work involved in forms:
- preparing and restructuring data to make it ready for rendering
- creating HTML forms for the data
- receiving and processing submitted forms and data from the client
It is possible to write code that does all of this manually, but Django can take care of it all for you.
Demo
Demo1
The view
Form data sent back to a Django website is processed by a view, generally the same view which published the form. This allows us to reuse some of the same logic.
To handle the form we need to instantiate it in the view for the URL where we want it to be published:
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .forms import NameForm
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
If we arrive at this view with a GET
request, it will create an empty form instance and place it in the template context to be rendered. This is what we can expect to happen the first time we visit the URL.
If the form is submitted using a POST
request, the view will once again create a form instance and populate it with data from the request: form = NameForm(request.POST)
This is called “binding data to the form” (it is now a bound form).
We call the form’s is_valid()
method; if it’s not True
, we go back to the template with the form. This time the form is no longer empty (unbound) so the HTML form will be populated with the data previously submitted, where it can be edited and corrected as required.
If is_valid()
is True
, we’ll now be able to find all the validated form data in its cleaned_data
attribute. We can use this data to update the database or do other processing before sending an HTTP redirect to the browser telling it where to go next.
The template
We don’t need to do much in our name.html
template. The simplest example is:
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
All the form’s fields and their attributes will be unpacked into HTML markup from that {{ form }}
by Django’s template language.
Demo2
forms.py
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
views.py
from django.core.mail import send_mail
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info@example.com']
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect('/thanks/')
Bound and unbound forms
A Form
instance is either bound to a set of data, or unbound.
- If it’s bound to a set of data, it’s capable of validating that data and rendering the form as HTML with the data displayed in the HTML.
- If it’s unbound, it cannot do validation (because there’s no data to validate!), but it can still render the blank form as HTML.
To create an unbound Form
instance, simply instantiate the class:
>>> f = ContactForm()
To bind data to a form, pass the data as a dictionary as the first parameter to your Form
class constructor:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
In this dictionary, the keys are the field names, which correspond to the attributes in your Form
class. The values are the data you’re trying to validate. These will usually be strings, but there’s no requirement that they be strings; the type of data you pass depends on the Field
, as we’ll see in a moment.
Forms Fields
Built-in Field
classes
- BooleanField
- CharField
- ChoiceField
- DateField
- DateTimeField
- DecimalField
- IntegerField
See https://docs.djangoproject.com/en/2.2/ref/forms/fields/ for more details.
Core field arguments
required
By default, each Field
class assumes the value is required, so if you pass an empty value – either None
or the empty string (""
) – then clean()
will raise a ValidationError
exception:
>>> from django import forms
>>> f = forms.CharField()
>>> f.clean('foo')
'foo'
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: ['This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: ['This field is required.']
>>> f.clean(' ')
' '
>>> f.clean(0)
'0'
>>> f.clean(True)
'True'
>>> f.clean(False)
'False'
To specify that a field is not required, pass required=False
to the Field
constructor:
>>> f = forms.CharField(required=False)
>>> f.clean('foo')
'foo'
>>> f.clean('')
''
>>> f.clean(None)
''
>>> f.clean(0)
'0'
>>> f.clean(True)
'True'
>>> f.clean(False)
'False'
If a Field
has required=False
and you pass clean()
an empty value, then clean()
will return a normalized empty value rather than raising ValidationError
. For CharField
, this will be an empty string. For other Field
classes, it might be None
. (This varies from field to field.)
Widgets of required form fields have the required
HTML attribute. Set the Form.use_required_attribute
attribute to False
to disable it. The required
attribute isn’t included on forms of formsets because the browser validation may not be correct when adding and deleting formsets.
label
The label
argument lets you specify the “human-friendly” label for this field. This is used when the Field
is displayed in a Form
.
As explained in “Outputting forms as HTML” above, the default label for a Field
is generated from the field name by converting all underscores to spaces and upper-casing the first letter. Specify label
if that default behavior doesn’t result in an adequate label.
Here’s a full example Form
that implements label
for two of its fields. We’ve specified auto_id=False
to simplify the output:
>>> from django import forms
>>> class CommentForm(forms.Form):
... name = forms.CharField(label='Your name')
... url = forms.URLField(label='Your website', required=False)
... comment = forms.CharField()
>>> f = CommentForm(auto_id=False)
>>> print(f)
<tr><th>Your name:</th><td><input type="text" name="name" required></td></tr>
<tr><th>Your website:</th><td><input type="url" name="url"></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required></td></tr>
disabled
The disabled
boolean argument, when set to True
, disables a form field using the disabled
HTML attribute so that it won’t be editable by users. Even if a user tampers with the field’s value submitted to the server, it will be ignored in favor of the value from the form’s initial data.
Initial Values
Use initial
to declare the initial value of form fields at runtime. For example, you might want to fill in a username
field with the username of the current session.
To accomplish this, use the initial
argument to a Form
. This argument, if given, should be a dictionary mapping field names to initial values. Only include the fields for which you’re specifying an initial value; it’s not necessary to include every field in your form. For example:
>>> f = ContactForm(initial={'subject': 'Hi there!'})
These values are only displayed for unbound forms, and they’re not used as fallback values if a particular value isn’t provided.
If a Field
defines initial
and you include initial
when instantiating the Form
, then the latter initial
will have precedence. In this example, initial
is provided both at the field level and at the form instance level, and the latter gets precedence:
>>> from django import forms
>>> class CommentForm(forms.Form):
... name = forms.CharField(initial='class')
... url = forms.URLField()
... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" required></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required></td></tr>
Validation
A Form
instance has an is_valid()
method, which runs validation routines for all its fields. When this method is called, if all fields contain valid data, it will:
- return
True
- place the form’s data in its
cleaned_data
attribute.
Accessing “clean” data
Each field in a Form
class is responsible not only for validating data, but also for “cleaning” it – normalizing it to a consistent format. This is a nice feature, because it allows data for a particular field to be input in a variety of ways, always resulting in consistent output.
For example, DateField
normalizes input into a Python datetime.date
object. Regardless of whether you pass it a string in the format '1994-07-15'
, a datetime.date
object, or a number of other formats, DateField
will always normalize it to a datetime.date
object as long as it’s valid.
Once you’ve created a Form
instance with a set of data and validated it, you can access the clean data via its cleaned_data
attribute:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
Note that any text-based field – such as CharField
or EmailField
– always cleans the input into a string. We’ll cover the encoding implications later in this document.
If your data does not validate, the cleaned_data
dictionary contains only the valid fields:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid email address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}
cleaned_data
will always only contain a key for fields defined in the Form
, even if you pass extra data when you define the Form
. In this example, we pass a bunch of extra fields to the ContactForm
constructor, but cleaned_data
contains only the form’s fields:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True,
... 'extra_field_1': 'foo',
... 'extra_field_2': 'bar',
... 'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
When the Form
is valid, cleaned_data
will include a key and value for all its fields, even if the data didn’t include a value for some optional fields. In this example, the data dictionary doesn’t include a value for the nick_name
field, but cleaned_data
includes it, with an empty value:
>>> from django import forms
>>> class OptionalPersonForm(forms.Form):
... first_name = forms.CharField()
... last_name = forms.CharField()
... nick_name = forms.CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}
In this above example, the cleaned_data
value for nick_name
is set to an empty string, because nick_name
is CharField
, and CharField
s treat empty values as an empty string. Each field type knows what its “blank” value is – e.g., for DateField
, it’s None
instead of the empty string. For full details on each field’s behavior in this case, see the “Empty value” note for each field in the “Built-in Field
classes” section below.
空值问题
当我们指定这个 field 为required=False时,
- 如果该类型是一个类类型,当没有传该 key 时,通过
form.cleaned_data[”start_data“]
获取值,就是 None; - 如果是 string,通过
form.cleaned_data[”start_data“]
获取值,则为"";
举例
# forms.py
class MultipleUserForm(forms.Form):
ids = forms.CharField(required=False)
views.py
if form.is_valid():
ids_str = form.cleaned_data['ids']
如果请求的 querystring中没有 ids 这个 key,则ids_str 为“”,即
>>> ids_str == ""
True
>>> bool(ids_str == "")
True
>>> ids_str is None
False
总结
当我们指定这个 field 为required=False时,可以通过以下结构来获取这个 field 的值:
if form.is_valid():
xxx = form.cleaned_data['xxx']
if xxx:
...
Reference
- The Forms API - https://docs.djangoproject.com/en/2.2/ref/forms/api/
- Form fields - https://docs.djangoproject.com/en/2.2/ref/forms/fields/
- https://docs.djangoproject.com/en/2.2/topics/forms/