Django Introduction Part9 - Working with forms
An HTML Form is a group of one or more fields/widgets on a web page, which can be used to collect information from users for submission to a server. Forms are a flexible mechanism for collecting user input because there are suitable widgets for entering many different types of data, including text boxes, checkboxes, radio buttons, date pickers, etc.
Forms are also a relatively secure way of sharing data with the server, as they allow us to send data in POST requests with cross-site request forgery(CSRF) protection.
Working with forms can be complicated! Developers need to write HTML for the form, validate and properly sanitise entered data on the server (and possibly also in the browser), repost the form with error messages to inform users of any invalid fields, handle the data when it has successfully been submitted, and finally respond to the user in some way to indicate success.
Form fields¶
The Form class is the heart of Django’s form handling system. It specifies the fields in the form, their layout, display widgets, labels, initial values, valid values, and (once validated) the error messages associated with invalid fields. The class also provides methods for rendering itself in templates using predefined formats (tables, lists, etc.) or for getting the value of any element (enabling fine-grained manual rendering).
Declaring a Form
The declaration syntax for a Form is very similar to that for declaring a Model, and shares the same field types (and some similar parameters). This makes sense because in both cases we need to ensure that each field handles the right types of data, is constrained to valid data, and has a description for display/documentation.
Form data is stored in an application’s forms.py file,to create a Form, we import the forms library, derive from the Form class, and declare the form’s fields. A very basic email form class would be like this :
1 | from django import forms |
Although the primary way you’ll use Field classes is in Form classes, you can also instantiate them and use them directly to get a better idea of how they work. Each Field instance has a clean() method, which takes a single argument and either raises a django.core.exceptions.ValidationError exception or returns the clean value:
1 | from django import forms |
Core field arguments
The arguments that are common to most fields are listed below :
-
required: If
True, the field may not be left blank or given aNonevalue. Fields are required by default, so you would setrequired=Falseto allow blank values in the form.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from django import forms
f = forms.CharField()
f.clean('foo')
# 'foo'
f.clean('')
f.clean(None)
# Traceback (most recent call last):
# ...
# ValidationError: ['This field is required.']
f.clean(' ')
# ' '
f.clean(True)
# 'True'
f2 = forms.CharField(required=False)
f2.clean('')
# ''
f2.clean(None)
# '' -
label: The label to use when rendering the field in HTML. If a label is not specified, Django will generate one from the field name by capitalizing the first letter and replacing underscores with spaces .
Here’s a full example
Formthat implementslabelfor two of its fields. We’ve specifiedauto_id=Falseto simplify the output:1
2
3
4
5
6
7
8
9
10from 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> -
label_suffix: By default a colon is displayed after the label . This argument allows you to specify a different suffix containing other character.
1
2
3
4
5
6
7
8
9class ContactForm(forms.Form):
age = forms.IntegerField()
nationality = forms.CharField()
captcha_answer = forms.IntegerField(label='2 + 2', label_suffix=' =')
f = ContactForm(label_suffix='?')
print(f.as_p())
# <p><label for="id_age">Age?</label> <input id="id_age" name="age" type="number" required></p>
# <p><label for="id_nationality">Nationality?</label> <input id="id_nationality" name="nationality" type="text" required></p>
# <p><label for="id_captcha_answer">2 + 2 =</label> <input id="id_captcha_answer" name="captcha_answer" type="number" required></p>Use the form separately in templates:
1
2
3
4
5
6
7
8{{ f.as_p }}
{% for field in f %}
<div>
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %} -
initial: The initial value for the field when the form is displayed.
The use-case for this is when you want to display an “empty” form in which a field is initialized to a particular value. For example:
1
2
3
4
5
6
7
8
9
10
11
12from django import forms
class CommentForm(forms.Form):
name = forms.CharField(initial='Your name')
url = forms.URLField(initial='http://')
comment = forms.CharField()
f = CommentForm(auto_id=False)
print(f)
# <tr><th>Name:</th><td><input type="text" name="name" value="Your name" required></td></tr>
# <tr><th>Url:</th><td><input type="url" name="url" value="http://" required></td></tr>
# <tr><th>Comment:</th><td><input type="text" name="comment" required></td></tr>
f.is_bound
# FalseYou may be thinking, why not just pass a dictionary of the initial values as data when displaying the form? Well, if you do that, you’ll trigger validation, and the HTML output will include any validation errors:
1
2
3
4
5
6
7
8
9
10
11
12class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField()
default_data = {'name': 'Your name', 'url': 'http://'}
f = CommentForm(default_data, auto_id=False)
print(f)
# <tr><th>Name:</th><td><input type="text" name="name" value="Your name" required></td></tr>
# <tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="url" # name="url" value="http://" required></td></tr>
# <tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input # type="text" name="comment" required></td></tr>
f.is_bound
# TrueThis is why
initialvalues are only displayed for unbound forms. For bound forms, the HTML output will use the bound data.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.Also note that
initialvalues are not used as “fallback” data in validation if a particular field’s value is not given.initialvalues are only intended for initial form display:1
2
3
4
5
6
7
8
9class CommentForm(forms.Form):
name = forms.CharField(initial='Your name')
url = forms.URLField(initial='http://')
comment = forms.CharField()
data = {'name': '', 'url': '', 'comment': 'Foo'}
f = CommentForm(data)
f.is_valid()
# False
## The form does *not* fall back to using the initial values.Access the errors attribute to get a dictionary of error messages
The form’s data will be validated the first time either you call is_valid() or access errors.
1
2
3
4
5
6f.errors
# {'url': ['This field is required.'], 'name': ['This field is required.']}
f.errors.as_data()
# {'name': [ValidationError(['This field is required.'])], 'url': [ValidationError(['This field is required.'])], 'comment': [ValidationError(['This field is required.'])]}
f.errors.as_json()
# '{"name": [{"message": "This field is required.", "code": "required"}], "url": [{"message": "This field is required.", "code": "required"}], "comment": [{"message": "This field is required.", "code": "required"}]}'Instead of a constant, you can also pass any callable:
1
2
3
4
5import datetime
class DateForm(forms.Form):
day = forms.DateField(initial=datetime.date.today)
print(DateForm())
# <tr><th>Day:</th><td><input type="text" name="day" value="12/23/2008" required><td></tr>The callable will be evaluated only when the unbound form is displayed, not when it is defined.
-
widget: The display widget to use.
The
widgetargument lets you specify aWidgetclass to use when rendering thisField. See Widgets for more information. -
help_text : Additional text that can be displayed in forms to explain how to use the field.
Here’s a full example
Formthat implementshelp_textfor two of its fields. We’ve specifiedauto_id=Falseto simplify the output:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30from django import forms
class HelpTextContactForm(forms.Form):
subject = forms.CharField(max_length=100, help_text='100 characters max.')
message = forms.CharField()
sender = forms.EmailField(help_text='A valid email address, please.')
cc_myself = forms.BooleanField(required=False)
f = HelpTextContactForm(auto_id=False)
print(f.as_table())
# <tr><th>Subject:</th><td><input type="text" name="subject" maxlength="100" required><br><span class="helptext">100 characters max.</span></td></tr>
# <tr><th>Message:</th><td><input type="text" name="message" required></td></tr>
# <tr><th>Sender:</th><td><input type="email" name="sender" required><br>A valid email address, please.</td></tr>
# <tr><th>Cc myself:</th><td><input type="checkbox" name="cc_myself"></td></tr>
print(f.as_ul())
# <li>Subject: <input type="text" name="subject" maxlength="100" required> <span class="helptext">100 characters max.</span></li>
# <li>Message: <input type="text" name="message" required></li>
# <li>Sender: <input type="email" name="sender" required> A valid email address, please.</li>
# <li>Cc myself: <input type="checkbox" name="cc_myself"></li>
print(f.as_p())
# <p>Subject: <input type="text" name="subject" maxlength="100" required> <span class="helptext">100 characters max.</span></p>
# <p>Message: <input type="text" name="message" required></p>
# <p>Sender: <input type="email" name="sender" required> A valid email address, please.</p>
# <p>Cc myself: <input type="checkbox" name="cc_myself"></p>
data = {'subject': 'hello',
'message': 'Hi there',
'sender': 'foo@example.com',
'cc_myself': True}
f = HelpTextContactForm(data)
f.is_valid()
# True -
error_messages: A list of error messages for the field. You can override these with your own messages if needed.
The
error_messagesargument lets you override the default messages that the field will raise. Pass in a dictionary with keys matching the error messages you want to override.And here is a custom error message:
1
2
3
4
5name = forms.CharField(error_messages={'required': 'Please enter your name'})
name.clean('')
# Traceback (most recent call last):
# ...
# ValidationError: ['Please enter your name'] -
validators: A list of functions that will be called on the field when it is validated.
See the validators documentation for more information.
-
localize: Enables the localization of form data input .
See the format localization documentation for more information.
-
disabled: The field is displayed but its value cannot be edited if this is
True. The default isFalse.
The has_changed() method is used to determine if the field value has changed from the initial value. Returns True or False.
See the Form.has_changed() documentation for more information.
Built-in Field classes
Naturally, the forms library comes with a set of Field classes that represent common validation needs. This section documents each built-in field.
For each field, we describe the default widget used if you don’t specify widget. We also specify the value returned when you provide an empty value.
BooleanField
-
Default widget:
CheckboxInputinput_type:'checkbox'template_name:'django/forms/widgets/checkbox.html'- Renders as:
<input type="checkbox" ...>
Takes one optional argument:
-
check_testA callable that takes the value of the
CheckboxInputand returnsTrueif the checkbox should be checked for that value.
-
Empty value:
False -
Normalizes to: A Python
TrueorFalsevalue. -
Validates that the value is
True(e.g. the check box is checked) if the field hasrequired=True. -
Error message keys:
required
If you want to include a boolean in your form that can be either
TrueorFalse(e.g. a checked or unchecked checkbox), you must remember to pass inrequired=Falsewhen creating theBooleanField.
CharField
- Default widget:
TextInputinput_type:'text'template_name:'django/forms/widgets/text.html'- Renders as:
<input type="text" ...>
- Empty value: Whatever you’ve given as
empty_value. - Normalizes to: A string.
- Uses
MaxLengthValidatorandMinLengthValidatorifmax_lengthandmin_lengthare provided. Otherwise, all inputs are valid. - Error message keys:
required,max_length,min_length
Has four optional arguments for validation:
-
max_length/min_lengthIf provided, these arguments ensure that the string is at most or at least the given length.
-
stripIf
True, the value will be stripped of leading and trailing whitespace. -
empty_valueThe value to use to represent “empty”. Defaults to an empty string.
ChoiceField
- Default widget:
Selecttemplate_name:'django/forms/widgets/select.html'option_template_name:'django/forms/widgets/select_option.html'- Renders as:
<select><option ...>...</select>
- Empty value:
''(an empty string) - Normalizes to: A string.
- Validates that the given value exists in the list of choices.
- Error message keys:
required,invalid_choice
The invalid_choice error message may contain %(value)s, which will be replaced with the selected choice.
Also see TypedChoiceField, MultipleChoiceField, TypedMultipleChoiceField
DateField/DateTimeField/TimeField
- Default widget:
DateInputinput_type:'text'template_name:'django/forms/widgets/date.html(datetime.html/time.html)'- Renders as:
<input type="text" ...>
- Empty value:
None - Normalizes to: A Python
datetime.date/datetime.datetimeobject. - Validates that the given value is a datetime format.
- Error message keys:
required,invalid
Takes one optional argument:
-
input_formatsA list of formats used to attempt to convert a string to a valid
datetime.dateobject.e.g. YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16), and will be rendered using the default widget.
Also see SplitDateTimeField.
DecimalField
- Default widget:
NumberInputwhenField.localizeisFalse, elseTextInput.input_type:'number'template_name:'django/forms/widgets/number.html'- Renders as:
<input type="number" ...>
- Empty value:
None - Normalizes to: A Python
decimal. - Validates that the given value is a decimal. Uses
MaxValueValidatorandMinValueValidatorifmax_valueandmin_valueare provided. Leading and trailing whitespace is ignored. - Error message keys:
required,invalid,max_value,min_value,max_digits,max_decimal_places,max_whole_digits
The max_value and min_value error messages may contain %(limit_value)s, which will be substituted by the appropriate limit. Similarly, the max_digits, max_decimal_places and max_whole_digits error messages may contain %(max)s.
Takes four optional arguments:
max_value/min_value
These control the range of values permitted in the field, and should be given as decimal.Decimal values.
-
max_digitsThe maximum number of digits (those before the decimal point plus those after the decimal point, with leading zeros stripped) permitted in the value.
-
decimal_placesThe maximum number of decimal places permitted.
Also see https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#numberinput and https://docs.djangoproject.com/en/3.1/ref/forms/fields/#integerfield
DurationField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#durationfield
EmailField
- Default widget:
EmailInputinput_type:'email'template_name:'django/forms/widgets/email.html'- Renders as:
<input type="email" ...>
- Empty value: Whatever you’ve given as
empty_value. - Normalizes to: A string.
- Uses
EmailValidatorto validate that the given value is a valid email address, using a moderately complex regular expression. - Error message keys:
required,invalid
Has three optional arguments max_length, min_length, and empty_value which work just as they do for CharField.
FileField
- Default widget:
ClearableFileInputtemplate_name:'django/forms/widgets/clearable_file_input.html'- Renders as:
<input type="file" ...>with an additional checkbox input to clear the field’s value, if the field is not required and has initial data.
- Empty value:
None - Normalizes to: An
UploadedFileobject that wraps the file content and file name into a single object. - Can validate that non-empty file data has been bound to the form.
- Error message keys:
required,invalid,missing,empty,max_length
Has two optional arguments for validation, max_length and allow_empty_file. If provided, these ensure that the file name is at most the given length, and that validation will succeed even if the file content is empty.
To learn more about the UploadedFile object, see the file uploads documentation.
When you use a FileField in a form, you must also remember to bind the file data to the form.
The max_length error refers to the length of the filename. In the error message for that key, %(max)d will be replaced with the maximum filename length and %(length)d will be replaced with the current filename length.
FilePathField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#filepathfield
ImageField
- Default widget:
ClearableFileInput - Empty value:
None - Normalizes to: An
UploadedFileobject that wraps the file content and file name into a single object. - Validates that file data has been bound to the form. Also uses
FileExtensionValidatorto validate that the file extension is supported by Pillow. - Error message keys:
required,invalid,missing,empty,invalid_image
Using an ImageField requires that Pillow is installed with support for the image formats you use. If you encounter a corrupt image error when you upload an image, it usually means that Pillow doesn’t understand its format. To fix this, install the appropriate library and reinstall Pillow.
After the field has been cleaned and validated, the UploadedFile object will have an additional image attribute containing the Pillow Image instance used to check if the file was a valid image. Pillow closes the underlying file descriptor after verifying an image, so whilst non-image data attributes, such as format, height, and width, are available, methods that access the underlying image data, such as getdata() or getpixel(), cannot be used without reopening the file. For example:
1 | from PIL import Image |
Additionally, UploadedFile.content_type will be updated with the image’s content type if Pillow can determine it, otherwise it will be set to None.
JSONField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#jsonfield
GenericIPAddressField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#genericipaddressfield
NullBooleanField
-
Default widget:
NullBooleanSelecttemplate_name:'django/forms/widgets/select.html'option_template_name:'django/forms/widgets/select_option.html'
Select widget with options ‘Unknown’, ‘Yes’ and ‘No’
-
Empty value:
None -
Normalizes to: A Python
True,FalseorNonevalue. -
Validates nothing (i.e., it never raises a
ValidationError).
NullBooleanField may be used with widgets such as Selector RadioSelectby providing the widget choices:
1 | NullBooleanField( |
RegexField
- Default widget:
TextInput - Empty value: Whatever you’ve given as
empty_value. - Normalizes to: A string.
- Uses
RegexValidatorto validate that the given value matches a certain regular expression. - Error message keys:
required,invalid
Takes one required argument:
regex: A regular expression specified either as a string or a compiled regular expression object.
Also takes max_length, min_length, strip, and empty_value which work just as they do for CharField.
strip:Defaults toFalse. If enabled, stripping will be applied before the regex validation.
SlugField
- Default widget:
TextInput - Empty value: Whatever you’ve given as
empty_value. - Normalizes to: A string.
- Uses
validate_slugorvalidate_unicode_slugto validate that the given value contains only letters, numbers, underscores, and hyphens. - Error messages:
required,invalid
This field is intended for use in representing a model SlugField in forms.
Takes two optional parameters:
-
allow_unicode: A boolean instructing the field to accept Unicode letters in addition to ASCII letters. Defaults toFalse. -
empty_value: the value to use to represent “empty”. Defaults to an empty string.
URLField
- Default widget:
URLInputinput_type:'url'template_name:'django/forms/widgets/url.html'- Renders as:
<input type="url" ...>
- Empty value: Whatever you’ve given as
empty_value. - Normalizes to: A string.
- Uses
URLValidatorto validate that the given value is a valid URL. - Error message keys:
required,invalid
Has three optional arguments max_length, min_length, and empty_value which work just as they do for CharField.
UUIDField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#uuidfield
ComboField
- Default widget:
TextInput - Empty value:
''(an empty string) - Normalizes to: A string.
- Validates the given value against each of the fields specified as an argument to the
ComboField. - Error message keys:
required,invalid
Takes one extra required argument:
-
fieldsThe list of fields that should be used to validate the field’s value (in the order in which they are provided).
1
2
3
4
5
6
7
8from django.forms import ComboField
f = ComboField(fields=[CharField(max_length=20), EmailField()])
f.clean('test@example.com')
'test@example.com'
f.clean('longemailaddress@example.com')
# Traceback (most recent call last):
# ...
# ValidationError: ['Ensure this value has at most 20 characters (it has 28).']
1 |
MultiValueField
See https://docs.djangoproject.com/en/3.1/ref/forms/fields/#multivaluefield
ModelChoiceField
- Default widget:
Select - Empty value:
None - Normalizes to: A model instance.
- Validates that the given id exists in the queryset.
- Error message keys:
required,invalid_choice
Allows the selection of a single model object, suitable for representing a foreign key.
Note that the default widget for
ModelChoiceFieldbecomes impractical when the number of entries increases. You should avoid using it for more than 100 items.
A single argument is required:
-
querysetA
QuerySetof model objects from which the choices for the field are derived and which is used to validate the user’s selection. It’s evaluated when the form is rendered.
ModelChoiceField also takes two optional arguments:
-
empty_labelBy default the
<select>widget used byModelChoiceFieldwill have an empty choice at the top of the list. You can change the text of this label (which is"---------"by default) with theempty_labelattribute, or you can disable the empty label entirely by settingempty_labeltoNone:1
2
3
4
5# A custom empty label
field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)")
# No empty label
field2 = forms.ModelChoiceField(queryset=..., empty_label=None)
1 |
|
would yield:
1 | <select id="id_field1" name="field1"> |
ModelChoiceField also has the attribute:
-
iteratorThe iterator class used to generate field choices from
queryset. By default,ModelChoiceIterator.
The __str__() method of the model will be called to generate string representations of the objects for use in the field’s choices. To provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object and should return a string suitable for representing it. For example:
1 | from django.forms import ModelChoiceField |
Also see https://docs.djangoproject.com/en/3.1/ref/forms/fields/#modelmultiplechoicefield.
Using a Form and function view¶
Validation
Django provides numerous places where you can validate your data. The easiest way to validate a single field is to override the method clean_**<fieldname>**() for the field you want to check.
The example will use a function-based view and a Form class.
Update your forms.py file so it looks like this:
1 | import datetime |
There are two important things to note. The first is that we get our data using self.cleaned_data['new_date'] and that we return this data whether or not we change it at the end of the function. This step gets us the data “cleaned” and sanitized of potentially unsafe input using the default validators, and converted into the correct standard type for the data (in this case a Python datetime.datetime object).
The second point is that if a value falls outside our range we raise a ValidationError, specifying the error text that we want to display in the form if an invalid value is entered. The example above also wraps this text in one of Django’s translation functions ugettext_lazy() (imported as _()), which is good practice if you want to translate your site later.
View
For forms that use a POST request to submit information to the server, the most common pattern is for the view to test against the POST request type (if request.method == 'POST':) to identify form validation requests and GET (using an else condition) to identify the initial form creation request.
If you want to submit your data using a GET request then a typical approach for identifying whether this is the first or subsequent view invocation is to read the form data (e.g. to read a hidden value in the form).
By convention, we use the POSTrequest approach. The code fragment below shows the (very standard) pattern for this sort of function view.
1 | from .forms import RatingModelForm |
First, we import our form (RatingModelForm) and a number of other useful objects/methods used in the body of the view function:
get_object_or_404(): Returns a specified object from a model based on its primary key value, and raises anHttp404exception (not found) if the record does not exist.HttpResponseRedirect: This creates a redirect to a specified URL (HTTP status code 302).reverse(): This generates a URL from a URL configuration name and a set of arguments. It is the Python equivalent of theurltag that we’ve been using in our templates.
Important: While you can also access the form data directly through the request (for example,
request.POST['rating_date']orrequest.GET['rating_date']if using a GET request), this is NOT recommended. The cleaned data is sanitized, validated, and converted into Python-friendly types.
That’s everything needed for the form handling itself, but we still need to restrict access to the view to MovieRatingUpdate. We should probably create a new permission in Rating("can_edit"), but, to keep things simple here, we just use the @permission_required function decorator with our existing can_edit_ratingpermission.
The final view is therefore as shown below. Please copy this into the bottom of views.py.
1 | import datetime |
The template
Create the template referenced in the view (book_renew_librarian.html) and copy the code below into it:
1 | {% extends 'movie/movie_base.html' %} |
The form code is relatively simple and we use crispy_forms to beautify the forms .
First we declare the form tags, specifying where the form is to be submitted (action) and the method for submitting the data (in this case an “HTTP POST”) .
The {% csrf_token %} added just inside the form tags is part of Django’s cross-site forgery protection. Add the {% csrf_token %} to every Django template you create that uses POST to submit data. This will reduce the chance of forms being hijacked by malicious users.
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
1
2 <!-- Use novalidate attribute to break up the default Browser Validation-->
<form action="" novalidate>
It is also possible to have complete control over the rendering of each part of the form, by indexing its properties using dot notation. So, for example, we can access a number of separate items for our renewal_date field:
{{ form.rating_date}}:The whole field.{{ form.rating_date.errors }}: The list of errors.{{ form.rating_date.id_for_label }}: The id of the label.{{ form.rating_date.help_text }}: The field help text.
ModelForms
Creating a Form class using the approach described above is very flexible, allowing you to create whatever sort of form page you like and associate it with any model or models.
However, if you just need a form to map the fields of a single model then your model will already define most of the information that you need in your form: fields, labels, help text, etc. Rather than recreating the model definitions in your form, it is easier to use the ModelForm helper class to create the form from your model.
1 | class RatingModelForm(ModelForm): |
The rest of the information comes from the model field definitions (e.g. labels, widgets, help text, error messages).
If these aren’t quite right, then we can override them in our class Meta, specifying a dictionary containing the field to change and its new value.
Generic editing views¶
The form handling algorithm we used in our function view example above represents an extremely common pattern in form editing views. Django abstracts much of this “boilerplate” for you, by creating generic editing views for creating, editing, and deleting views based on models. Not only do these handle the “view” behavior, but they automatically create the form class (a ModelForm) for you from the model.
In this section we’re going to use generic editing views to create pages to add functionality to create, edit, and delete Director records — effectively providing a basic reimplementation of parts of the Admin site (this could be useful if you need to offer admin functionality in a more flexible way that can be provided by the admin site).
Views
Open the views file views.py and append the following code block to the bottom of it:
1 | from django.views.generic.edit import CreateView, UpdateView, DeleteView |
That seems not good for DirectorCreate, because we define the director_date_edited as required in model.py:
1 | director_date_edited = models.DateTimeField('date edited',default=timezone.now()) |
Then, modify the DirectorCreate, thats it!
1 | class DirectorCreate(LoginRequiredMixin, CreateView): |
As you can see, to create, update, or delete the views you need to derive from CreateView, UpdateView, and DeleteView (respectively) and then define the associated model.
You can specify an alternative redirect location by explicitly declaring parameter success_url (as done for the DirectorDelete class).
The DirectorDelete class doesn’t need to display any of the fields, so these don’t need to be specified. You do however need to specify the success_url, because there is no obvious default value for Django to use. In this case, we use the reverse_lazy() function to redirect to our directors list after an author has been deleted — reverse_lazy() is a lazily executed version of reverse(), used here because we’re providing a URL to a class-based view attribute.
Templates
The “create” and “update” views use the same template by default, which will be named after your model: model_name**_form.html** (you can change the suffix to something other than _form using the template_name_suffix field in your view, e.g. template_name_suffix = 'movie_director_form.html' or 'template_name = 'movie/movie_director_form.html')
Create the template file movie_director_form.html and copy in the text below.
1 | {% extends 'movie/movie_base.html' %} |
This is similar to our previous forms and renders the fields using a table. Note also how again we declare the {% csrf_token %} to ensure that our forms are resistant to CSRF attacks.
The “delete” view expects to find a template named with the format model_name**_confirm_delete.html** (again, you can change the suffix using template_name_suffix in your view).
1 | {% extends 'movie/movie_base.html' %} |
URL configurations
Open your URL configuration file and add the following configuration to the bottom of the file:
1 | urlpatterns += [ |
There is nothing particularly new here! You can see that the views are classes, and must hence be called via .as_view(), and you should be able to recognize the URL patterns in each case. We must use pk as the name for our captured primary key value, as this is the parameter name expected by the view classes.
REFERENCES
- https://docs.djangoproject.com/en/3.1/intro/tutorial04/#write-a-simple-form
- https://docs.djangoproject.com/en/3.1/topics/forms/
- https://docs.djangoproject.com/en/3.1/ref/forms/widgets/
- https://docs.djangoproject.com/en/3.1/ref/forms/api/
- https://docs.djangoproject.com/en/3.1/ref/forms/fields/
- https://docs.djangoproject.com/en/3.1/ref/forms/validation/
- https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-editing/






