Django model form processing

Now that you have a solid understanding of the various model form options, it's time to take a deeper look at model form processing, which was briefly introduced in listing 9-2.

The most important factor to take into account when processing model forms is you're working with two entities: a form and a model. In the model form processing example presented in listing 9-2, this fact isn't too obvious, mainly because the form fits the backing model perfectly. However, when you modify any of the model form parts, working with a single reference that represents both a form and a model can require more forethought.

Model form initialization: initial and instance

Model forms can use two initialization parameters: initial and instance. The initial argument works just like the standard initial form argument -- described in chapter 6 -- providing the initial values for an unbound form. The instance argument is used to initialize a model form with a model instance, which in turn is also used to initialize the values of an unbound form.

In all model forms, as you learned in the previous sections, form definitions take precedence over any underlying model definitions. This means all model form values in the initial argument take precedence over values defined via the instance argument. Listing 9-7 illustrates a model form initialization sequence using both the initial and instance parameters.

Listing 9-7 Django model form initialization with initial and instance

from coffeehouse.items.models import Item

preloaded_item = Item.objects.get(id=1)

# Model form from listing 9-6, initialize with instance
form = ItemForm(instance=preloaded_item)

# Unbound form set up with instance values
form.as_p()
  <p>
   <label for="id_menu">Menu:</label>
        <select name="menu" required id="id_menu">
            <option value="">---------</option>     
            <option value="1" selected>Menu #1) Breakfast</option>
            <option value="2">Menu #2) Salads</option>
            <option value="3">Menu #3) Sandwiches</option>
            <option value="4">Menu #4) Drinks</option>
         </select>
  </p>
  <p>
    <label for="id_name">Name:</label> 
          <input type="text" name="name" 
               value="Whole-Grain Oatmeal" required maxlength="30" id="id_name" />
    </p>
    # Remaining fields committed for brevity

# Model form from listing 9-6, initialize with instance and override with initial
form2 = ItemForm(initial={'menu':3},instance=preloaded_item)

# Unbound form set up with instance values, but overridden with initial 
form2.as_p()
  <p>
   <label for="id_menu">Menu:</label>
        <select name="menu" required id="id_menu">
            <option value="">---------</option>     
            <option value="1">Menu #1) Breakfast</option>
            <option value="2">Menu #2) Salads</option>
            <option value="3" selected>Menu #3) Sandwiches</option>
            <option value="4">Menu #4) Drinks</option>
         </select>
  </p>
   # Remaining fields committed for brevity

The first step in listing 9-7 is to obtain an Item model record to populate the ItemForm model form, in this case, a query is made to get the Item model record with id=1. Next, the Item model record is used to initialize the model form with the instance values. In listing 9-7, the form is output with the standard as_p() form method, where you can confirm the form fields are pre-selected to reflect the underlying model record.

Next in listing 9-7 is an initialization sequence for the same ItemForm model form, but which also uses the initial argument in combination with the instance argument. In this case, because the initial argument provides the 'menu':3 value, the unbound form's menu field is set to a value of 3, instead of the model record's instance menu value of 1. Thus confirming the initial argument values take precedence over instance argument values.

Note that it's equally valid to only use the initial argument -- without the instance argument -- to initialize a model form as if it were a regular form. At the initialization phase of a model form, the model part of the form is unaware of any values, it's only until the model form enters its validation phase the underlying model is made of aware of any form values.

Model form validation

Similar to model form initialization, model form validation can appear to be intertwined because you're dealing with a single variable that references both a form and a model. But as long as you're aware of the fundamental steps of form validation -- described in chapter 6 -- and model validation -- described in chapter 7 -- model form validation is straightforward.

Back in listing 9-2, you learned how a model form is converted to a bound form (i.e. a form containing user data) by passing the request.POST value in a view method (e.g ContactForm(request.POST)). Once you have a bound form, the standard Django form validation workflow continues to apply for model forms: a call is made to the is_valid() method on the form reference to validate the user submitted data against form validation rules. If any of the form rules don't comply, an errors dictionary is added to the form reference with the causes, which makes its way back to the user as a re-rendered form with the errors. If the is_valid() method succeeds, the processing logic of the model form can move to the next step.

Once a model form passes the is_valid() method test, you can actually use the same standard form cleaned_data() method to gain access to a dictionary with the contents of the valid form data (e.g. form.cleaned_data() contains {'name': '...','email': '...','comment': '...', }). But since you're working with a model form, the step you're more likely take is to use the form data to further interact with a model.

To facilitate this process, Django adds the instance field to the form reference, containing the form data structured as an instance of the underlying model of the model form. The instance field is particularly important when you need or must manipulate the model data prior to attempting a model operation. And this is the most critical aspect of the model form validation process: even after the form is_valid() method passes and the data is used to structure a model instance in the instance field, this model instance data must still undergo model validation or risk being rejected by the backing model validation rules.

For straightforward model form scenarios, where a model and form map directly to one another -- like the one in listing 9-2 -- manipulating the instance field is unnecessary (e.g. Right after the form is_valid() method passes, you can call the save() method on the form reference to save the model instance in instance). But for model forms where the model and form differ in the amount of fields, you'll need to perform additional logic after the form is_valid() method passes and before a called is made to the model form's save() method.

Listing 9-8 illustrates two validation procedures for a model form where the form omits fields from the underlying model.

Listing 9-8 Django model form with reduced form that requires model update before saving

from django import forms
from django.conf import settings

class Contact(models.Model):
      user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, default=None)
      name = models.CharField(max_length=50,blank=True)
      email = models.EmailField()  
      comment = models.CharField()

class ContactForm(forms.ModelForm):
      class Meta:      
            model = Contact
            exclude = ['user']

# Option 1) Form model processing with missing value assigned with instance
         if form.is_valid():
            # Check if user is available 
            if request.user.is_authenticated():
                # Add missing user to model form
                form.instance.user = request.user
            # Insert into DB
            form.save()
 
# Option 2) Form model processing with missing value assigned after model form sequence
if form.is_valid(): # Save instance but don't commit until model instance is complete # form.save() returns a materialized model instance that has yet to be saved pending_contact = form.save(commit=False) # Check if user is available if request.user.is_authenticated(): # Add missing user to model form pending_contact.user = request.user # Insert into DB pending_contact.save()

The Contact model in listing 9-8 is similar to the model class used in previous listings, but has the additional user field to register a Django user as part of the model record. Next, in listing 9-8 is the ContactForm model form -- based on this last Contact model -- which uses the exclude option to omit the user model field from the form.

Because you're purposely omitting the user field from the model form, an end-user will have no way of providing it -- even in the unlikely case he would know his internal user. Therefore, as part of the validation process, it's necessary to update the model to contain the internal user, which is always available in the request reference of a view method.

In the first validation sequence in listing 9-8, you can see that after the model form passes the is_valid() method, a quick check is made to confirm if the user is authenticated, if so, the model's instance reference is accessed to update the user model field. Once this is done, the model form's backing instance contains a value for the omitted user field and upon calling the save() method, the model record is saved with values for all its model fields.

The second validation sequence in listing 9-8 uses the commit=False to materialize the model instance of the model form without saving it to the database. Once this is done, the model form's work is done, so you're left with a basic model record instance that needs to be updated and saved to the database. You can see in listing 9-8 an identical check is made to confirm if the user is authenticated, if so, the unsaved model record user reference is updated and a final called is made to the model's save() method to commit the record to the database.