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 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

# 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
            # redirect to a new URL:
            return HttpResponseRedirect('/about/contact/thankyou')
        # 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
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

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.