In the previous two chapters you learned how Django models are used to move data between a relational database and a Django project. Although this is the main purpose of Django models, there's another important set of functionalities fulfilled by Django models that isn't tied directly to a database.
In this chapter you'll learn how to create Django forms parting from Django models, a process that further extends Django's DRY (Don't Repeat Yourself) principle. You'll learn how a Django model can produce a Django form, including its fields, validations and also save its data to a database, all without writing many of the form scaffolding logic described in chapter 6.
Next, you'll learn about Django class-based views with models. Although you can continue to use the Django view techniques covered in chapter 2 -- just like the form techniques in chapter 6 -- with a firm grasp of Django models, you can further apply the Django DRY principle to views. You'll learn how to create class-based views to execute model CRUD operations, in turn, reducing the amount of logic required to incorporate model CRUD operations in views.
Django model form structure, options and field mapping
Django models represent the
standard way to move data into and out of a database. But as you've
learned throughout the previous two chapters, the phase of moving
data into and out of a database requires you programmatically
manipulate model records (e.g. inside view methods in
views.py
files) to execute the needed CRUD operation
on a model.
While this is a perfectly valid workflow for any web framework, you can improve this process of moving data into and out of a database by linking Django models to a more natural input/output mechanism: forms.
Once you programmatically create
enough Django model records in real-life projects, you'll see a
pattern emerge: the logic behind most Django model operations is
dictated by user interactions. Either an end user creates an
Order
model record, an end user reads a
Store
model record, an administrator updates a
Menu
model record or an administrator deletes an
Item
model record. And what do these end users and
administrators use to communicate these model operations ? Exactly,
forms in a user interface (UI).
Now let's take a look at the flip side of linking forms to models. In chapter 6 you learned about Django forms, but did you realize what's the most likely operation you're going to do with the form data after its processed ? You're most likely to save it to a database, which involves Django models.
So in the spirit of Django's DRY principle, model forms offer a way to use a Django model as the foundation to produce a Django form to execute CRUD operations on a Django model. In other words, instead of creating a standalone Django form and then creating the necessary 'glue' code to create a Django model instance, or viceversa, creating a standalone Django model and then creating the necessary form to do CRUD operations on a model record, Django model forms allow you to not repeat yourself.
Create Django model forms
Back in Chapter 6 you created a Django form to capture a name, email and comment. Next, we'll re-design this form as a model form to be able to quickly save the data to a database.
The first step to create a model form is to create a model as the foundation for the data. Listing 9-1 illustrates a Django model class and immediately after Django model form created from the model.
Tip Consult the book's accompanying source code to run the exercises, in order to reduce typing and automatically access test data.
Listing 9-1. Django model class and model form
from django import forms class Contact(models.Model): name = models.CharField(max_length=50,blank=True) email = models.EmailField() comment = models.CharField(max_length=1000) class ContactForm(forms.ModelForm): class Meta: model = Contact fields = '__all__'
The first important aspect of listing 9-1 is the Django model follows the standard model syntax, with three fields that use model fields to restrict the type of data stored by the model. The model in listing 9-1 is kept simple to better illustrate model forms, but it's possible to add any other model functionality you learned in the previous two chapters (e.g. validators, clean methods, Meta options).
Next in listing 9-1 is the
ContactForm
class that represents the form and which
inherits its behavior from the django.forms.ModelForm
class, the last of which is what makes it a model form. Notice the
ContactForm
class lacks any form fields like those you
learned in chapter 6 in table 6-2, instead it declares a Meta
class
section like the one used in models.
The Meta
class
section for the ContactForm
specifies two options:
model
and fields
. The model
option indicates which model to use to generate the form, in this
case, the Contact
model also in listing 9-1. The
fields
option indicates which model fields to use to
generate the form, in the case, '__all__'
tells Django
to use all the model fields
in the model.
The powerful aspect of
ContactForm
in listing 9-1 is it uses two statements
to create a form that reflects the same field types as the
Contact
model. Not only does this avoid repeating
yourself (e.g. typing in explicit form fields), the form fields
also inherits the validation behaviors of the model (e.g.
models.EmailField()
get translated into
forms.EmailField
). But I'll describe more details and
options about this model-to-form inheritance behavior shortly, once
I finish describing the basics of model forms.
Once you have a model form class, you might wondering how does its processing differ from a standard Django form? Very little actually, the same concepts you learned in chapter 6 to process, validate and layout forms are just as valid for model forms, as shown in listing 9-2.
Listing 9-2 Django model form processing
# views.py method to process model form def contact(request): if request.method == 'POST': # POST, generate bound form with data from the request form = ContactForm(request.POST) # check if it's valid: if form.is_valid(): # Insert into DB form.save() # redirect to a new URL: return HttpResponseRedirect('/about/contact/thankyou') else: # GET, generate unbound (blank) form form = ContactForm() return render(request,'about/contact.html',{'form':form}) # See chapter 6 for form layout template syntax in about/contact.html
In listing 9-2 you can see the
view method sequence follows the same pattern as a standard Django
form. When a user makes a GET request on the view method, an
unbound form instance is created which is sent to the user
and rendered via the about/contact.html
template.
Next, when a user submits the form via a POST request, a
bound form is created using the request.POST
argument which is then validated using the is_valid()
method. If the form values are invalid, the bound form
with errors is returned to the user so he can correct the mistakes,
if the form values are valid, in the specific case of listing 9-2,
the user is redirected to the /about/contact/thankyou
page.
However, there's one important
processing difference in model forms that's bolded out in listing
9-2. After a form's values are determined to be valid, a call is
made to the save()
on the model form instance. This
save()
method is tied to a form's backing model
save()
method, which means the form data is structured
as a model record and saved to the database.
As you can realize, this process to create and process a model form is a real time saver vs. having to a create and process a standalone form and a standalone model.
Django model form options and field mapping
Now that you understand the basic operation of model forms, let's take a look at its various options. Most model form options are declared in the Meta class statement, as you saw in listing 9-1. However, it's also possible to declare regular form fields, to either override the default model field behavior or include new form fields altogether.
Model form required options: model and fields or exclude
Model forms inherit their
behavior from the forms.ModelForm
class -- instead of
the standard forms.Form
class -- therefore Django
always expects a model on which to base the form, which is the
purpose of the meta model
option. Therefore a
model
option value is always a requirement of model
forms.
Django doesn't expect the
structure of a model to fit perfectly with a form, to the point
Django also expects you to explicitly tell it which fields of the
backing model should or shouldn't become part of the model form.
This is achieved with either the fields
option -- to
indicate which model fields become part of the model form -- of the
exclude
option -- to indicate which model fields
shouldn't become part of the model form. The fields
or
exclude
option is always required, even when a model
form will contain all fields of the backing model. Notice how the
model form example in listing 9-1 declares the option
fields='__all__'
to create a model form that captures
the same set of fields as its backing model.
When you declare a model form
with something other than fields='__all__'
(e.g. a
shortened list of model fields) or the exclude
option
(e.g. a list of model fields to omit in the form), be aware that
you're willfully and potentially breaking a model's rules. For
example, by default all model fields are required, so if you create
a model form that omits certain fields -- either with
fields
or exclude
-- the form itself can
appear normal, but the model form will never successfully finish
its standard workflow, unless you manually add the omitted fields.
Under such circumstances, end users will see an 'invalid form
error' because the model-part of the form is broken due to a
required model field value. The upcoming section on model form
validation and initialization describes how to manually add omitted
field values to model forms.
As you can see, you can create a model form with more or less fields than its backing model. In addition, it's also possible to add new fields to a model form -- that aren't part of a backing model -- as well as, customize the default form field produced by a model field.
In order to describe a solution to these last two scenarios, the next section describes the different form fields produced by each model field -- so you can determine if you need to customize the default behavior - and the subsequent section describes how to customize and add new fields to a model form.
Model form default field mapping
Models forms follow certain rules
to transform model field data types -- described in table 7-1 --
into form field data types -- described in table 6-2. In most
cases, model field data types get transformed into mirror-like
equivalent form field data types. For example, if a model field
uses the models.CharField
data type, a model form
converts this field to a forms.CharField
data
type.
Table 9-1 illustrates the model form mapping used between model data types and form data types. Note that data types with mirror-like data type mappings between models and forms are enclosed in the first line in table 9-1.
Table 9-1 Model form data type mapping between models and forms
Model field | Form field |
---|---|
models.BooleanField models.DateField models.DateTimeField models.DecimalField models.EmailField models.FileField models.FilePathField models.FloatField models.ImageField models.IntegerField models.IPAddressField models.GenericIPAddressField models.NullBooleanField models.SlugField models.TimeField models.URLField |
forms.BooleanField forms.DateField forms.DateTimeField forms.DecimalField forms.EmailField forms.FileField forms.FilePathField forms.FloatField forms.ImageField forms.IntegerField forms.IPAddressField forms.GenericIPAddressField forms.NullBooleanField forms.SlugField forms.TimeField forms.URLField |
models.AutoField models.BigAutoField |
Not represented in the form, because Auto model fields are generated by the database |
models.BigIntegerField | forms.IntegerField, with min_value set to -9223372036854775808 and max_value set to 9223372036854775807) |
models.CharField | forms.CharField, with max_length set to the model field's max_length and empty_value set to None if null=True |
models.CommaSeparatedIntegerField | forms.CharField |
models.ForeignKey | forms.ModelChoiceField |
models.ManyToManyField | forms.ModelMultipleChoiceField |
models.PositiveIntegerField | forms.IntegerField, with min_value set to 0 |
models.PositiveSmallIntegerField | forms.IntegerField, with min_value set to 0 |
models.SmallIntegerField | forms.IntegerField |
models.TextField | forms.CharField, with widget=forms.Textarea |
As you can see in table 9-1, over
50% of Django model data types map directly to equivalent form data
types. Most of the remaining model data types map to slightly
adjusted form data types to better fit the backing model type (e.g.
models.PositiveIntegerField
maps to a
forms.IntegerField
but with a form
min_value
value of 0).
It's only four model data types
in table 9-1 that don't map directly to form data types described
in chapter 6 in table 6-2. The models.AutoField
and
models.BigAutoField
model data types are never
represented in model forms, for the simple reason their values are
auto-assigned by a database, so they have no place to be input in
forms. The models.ForeignKey
and
models.ManyToManyField
model data types represent
model relationships, which means their data comes from separate
models. In turn, the models.ForeignKey
and
models.ManyToManyField
model data types don't map to
regular form field for strings or numbers, but rather form fields
that represent other model data, which is the purpose of the
special form data types: forms.ModelChoiceField
and
forms.ModelMultipleChoiceField
. These two last form
fields are described in the later sub-section on model forms with
relationships.
Tip To consult the HTML produced by a form field data type (e.g. <input type="text" ...> consult table 6-2 which contains the mapping between form fields and form widgets, the last of which produces the actual form HTML markup.
Model form new and custom fields: widgets, labels, help_texts, error_messages, field_classes and localize_fields
Now that you know how all model fields are transformed into form fields in a model form, let's address how to add and customize form fields in a model form.
Adding a new form field to a model form is as simple as declaring a form field as if it were a regular form. It's also possible to customize the default form field data type used by a model field data type (i.e. the mappings in table 9-1), by declaring a new form field with the same name as a model field, to take precedence over the default model-form field mapping.
Listing 9-3 illustrates the Django model class and model form from listing 9-1, updated to include a new form field and a form field that overrides a default model-form field mapping.
Listing 9-3 Django model form with new and custom field
from django import forms def faq_suggestions(value): # Validate value and raise forms.ValidationError for invalid values pass class Contact(models.Model): name = models.CharField(max_length=50,blank=True) email = models.EmailField() comment = models.CharField() class ContactForm(forms.ModelForm): age = forms.IntegerField() comment = forms.CharField(widget=forms.Textarea,validators=[faq_suggestions]) class Meta: model = Contact fields = '__all__'
Listing 9-3 first adds the new
age
form field to capture an integer value in the
form. Although the underlying Contact
model is never
aware of the age
field or value, with this
modification the model form will require this field to be provided
as part of the form workflow.
Next in listing 9-3 is the
comment
form field, which overrides the underlying
model field by the same name. In this case, overriding the
comment
form field has the purpose of adding a custom
widget
, as well as adding a custom
validators
method to verify the comment
value before the form is deemed valid -- note that both the
widget
option and validators
option are
standard form options described in chapter 6.
The form field overriding
mechanism in listing 9-3 has both an advantage and disadvantage.
The advantage is you get full control of the form field to define
any options. The disadvantage is a model field option's (e.g.
max_length
) -- that would be passed to the form field
-- are lost and need to be re-declared as part of the new form
field statement.
To preserve a model field's
underlying behavior and still be able to customize certain form
field options, model forms support additional meta class options
besides the model
, fields
and
exclude
options. Listing 9-4 illustrates a model
form's additional meta options to override the default model-form
field mapping, while keeping the underlying model field
behavior.
Listing 9-4 Django model form with meta options to override default form field behavior
from django import forms class Contact(models.Model): name = models.CharField(max_length=50,blank=True) email = models.EmailField() comment = models.CharField() class ContactForm(forms.ModelForm): class Meta: model = Contact fields = '__all__' widgets = { 'name': models.CharField(max_length=25), 'comment': form.Textarea(attrs={'cols': 100, 'rows': 40}) } labels = { 'name': 'Full name', 'comment': 'Issue' } help_texts = { 'comment': 'Provide a detailed account of the issue to receive a quick answer' } error_messages = { 'name': { 'max_length': "Name can only be 25 characters in length" } } field_classes = { 'email': EmailCoffeehouseFormField }, localized_fields = '__all__'
The most important aspect of the meta model form options in listing 9-4 is they're pluralized names of the form field options described in chapter 6. The highlighted model form meta options in listing 9-4 are pluralized because they can declare options for multiple form fields as a dictionary, where each key represents the form field name and its value the option value.
For example, the
widgets
and labels
meta options in
listing 9-4 define custom widgets and labels for both the
name
and comment
model form fields. The
help_texts
meta option defines the
help_text
option for the comment
model
form field, while the error_messages
meta option
declares a custom form error message for the
max_length
key error on the name
model
form field.
Next, the
field_classes
meta option in listing 9-4 is used to
declare a custom form field for the email
model form
field. Finally, the localized_field
meta option in
listing 9-4 is set to __all__
to tell Django to
localize (i.e. convert into a different language) all model form
fields. If the localized_field
option is omitted, then
model form fields are not localized. It's worth mentioning you can
selectively localize certain model form fields by passing a list of
model form fields to the localized_field
option, just
like it's done with the fields
and
exclude
options.