Django model forms with relationships
As you learned in the previous
two chapters, Django models can have relationships between one
another, which in turn makes models have a data type (e.g.
ForeignKey
, ManyToManyField
) that
references records in another model.
When models containing such data
types are used in the context of model forms, Django relies on two
special form fields. By default, ForeignKey
model
fields are converted to ModelChoiceField
form fields
and ManyToManyField
model fields are converted to
ModelMultipleChoiceField
form fields.
The benefit of the
ModelChoiceField
and
ModelMultipleChoiceField
form fields is they generate
a form field based on a Django model query. So instead of manually
populating a form field with model data, the
ModelChoiceField
and
ModelMultipleChoiceField
form fields generate a
friendly HTML <select>/<option>
input
field with model records.
ModelChoiceField and ModelMultipleChoiceField form field options: queryset, empty_label, to_field_name and label_from_instance
TipModelChoiceField
andModelMultipleChoiceField
are standard form fields usable on any Django form that requires model data. They are used by default on model forms with relationships, but they're not restricted to model forms (i.e. forms inherited fromforms.ModelForm
).
NoteModelChoiceField
andModelMultipleChoiceField
being standard form fields (i.e. part of the Djangoforms
package), also accept the standard form options: required, widget, label, initial, help_text and limit_choices_to -- described in chapter 6.
Since
ModelChoiceField
and
ModelMultipleChoiceField
form fields use model records
to source their data, they unequivocally require a model query. For
model forms that inherit their behavior from
forms.ModelForm
and their underlying models contain a
ForeignKey
or ManyToManyField
model
field, this model query is set automatically. For example, if an
Item
model contains a ForeignKey
to a
Menu
model, an Item
model form presents
all Menu
records in a form to allow users to select a
single Menu
record. Similarly, if a Store
model contains a ManyToManyField
to an
Amenity
model, a Store
model form
presents all Amenity
records in a form to allow users
to select multiple Amenity
records.
While this behavior is acceptable
in most circumstances, it can be necessary to provide an explicit
query to ModelChoiceField
or
ModelMultipleChoiceField
form fields, either when you
need to filter the default behavior to use all model records on a
model form field or when these form fields are used in a regular
form (i.e. that inherits forms.Form
).
Listing 9-5 illustrates the two
techniques to set a model query on either the
ModelChoiceField
and
ModelMultipleChoiceField
form fields using the
queryset
option.
Listing 9-5 Django model form and standard form with custom query for ModelChoiceField and ModelMultipleChoiceField form fields
from django import forms from coffeehouse.stores.models import Amenity class Menu(models.Model): name = models.CharField(max_length=30) def __str__(self): return "%s" % (self.name) class Item(models.Model): menu = models.ForeignKey(Menu, on_delete=models.CASCADE) name = models.CharField(max_length=30) description = models.CharField(max_length=100) class ItemForm(forms.ModelForm): menu = forms.ModelChoiceField(queryset=Menu.objects.filter(id=1)) class Meta: model = Item fields = '__all__' class StoreForm(forms.Form): name = forms.CharField() address = forms.CharField() amenities = forms.ModelMultipleChoiceField(queryset=None) def __init__(self, *args, **kwargs): super(StoreForm, self).__init__(*args, **kwargs) self.fields['amenities'].queryset = Amenity.objects.filter(name__contains='W')
The first technique in listing
9-5 defines an in-line queryset
value by overriding
the menu
field with a custom
ModelChoiceField()
on the ItemForm
model
form. In this case, instead of the ItemForm
model form
having a menu
field with all Item
records, the menu
field is restricted to only the
Item
record with id=1
.
The second technique in listing
9-5 defines an empty queryset
value on a standard form
that uses a forms.ModelMultipleChoiceField()
form
field on the amenities
field. But inside the form's
__init__
method, the amenities
field is
set to a query that restricts its records to Amenity
records that contain the letter W
.
It's worth mentioning, both
queryset
techniques illustrated in listing 9-5 are
equally valid in both model forms and regular forms, as well as
ModelChoiceField()
and
ModelMultipleChoiceField()
form fields.
By default,
ModelChoiceField()
form fields that don't define an
initial
value are generated with the empty HTML
<option>---------</option>
choice as the
default field value. It's possible to customize the value of this
empty option with the empty_label
option (e.g.
empty_label='Please select a value'
, to output
<option>Please select a value</option>
).
It's also possible to disable the inclusion of this empty option
with empty_label=None
.
By default, both
ModelChoiceField()
and
ModelMultipleChoiceField()
form fields generate their
HTML <select>/<option>
input field values
from a model record's primary key value (i.e. id)
and
model __str__
method representation. For example,
given the Menu
model definition in listing 9-5, an
HTML <select>/<option>
input field for
this model would look like the following snippet:
<select name="menu" required id="id_menu"> <option value="" selected>---------</option> <option value="1">Breakfast</option> <option value="2">Salads</option> <option value="3">Sandwiches</option> <option value="4">Drinks</option> </select>
Note each <option>
value
corresponds to a record's primary key id
value and the <option>
text corresponds to a
record's name
field returned by the model's
__str__
method.
It's possible to customize the
<option> value
used in both
ModelChoiceField()
and
ModelMultipleChoiceField()
form fields with the
to_field_name
option. For example, setting
to_field_name='name'
in the context of this last
snippet, changes the HTML
<select>/<option>
input field to the
format <option
value="Breakfast">Breakfast</option>
.
Caution Using a to_field_name value breaks the underlying model form's ability to be saved to the database, since the model's relationship value is set to a different value than the primary key expected by the model relationship.
In addition to customizing the
<option> value
, it's also possible to customize
the <option>
text in form fields to something
other than a model's __str__
method, by overriding a
label_form_instance
method in either a
ModelChoiceField()
and
ModelMultipleChoiceField()
form field. Listing 9-6
illustrate a custom form field designed for this purpose.
Listing 9-6 Django custom form field to customize <option> text for ModelChoiceField and ModelMultipleChoiceField form fields
from django import forms from django.forms import ModelChoiceField class MenuModelChoiceField(ModelChoiceField): def label_from_instance(self, obj): return "Menu #%s) %s" % (obj.id,obj.name) class ItemForm(forms.ModelForm): menu = MenuModelChoiceField(queryset=Menu.objects.all()) class Meta: model = Item fields = '__all__' # HTML menu form field output <select name="menu" id="id_menu" required> <option value="" selected>---------</option> <option value="1">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>
The first step in listing 9-6
creates the MenuModelChoiceField
custom form field
that inherits its behavior from the ModelChoiceField
form field and defines an implementation for the
label_from_instance
method. In this case, the
label_from_instance
method tells Django to generate
<option>
text values prefixed with the
Menu #
static string, followed by a model's
id
and name
. Note this same technique can
be used to customize a ModelMultipleChoiceField
, just
make sure to change the custom form field's inheriting class.
Next, the
MenuModelChoiceField
custom form field in listing 9-6
is added to the ItemForm
model form in the same
listing through the menu
field. Because
MenuModelChoiceField
is a custom
ModelChoiceField
form field, it's necessary to specify
an explicit queryset
value to populate the form field,
which in this case corresponds to all Menu
model
records.
Finally, listing 9-6 illustrates
the HTML <option>
text output for the
menu
field follows the pattern defined in the custom
MenuModelChoiceField
custom form field.