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.