Summary
We would like to set the initial selection of the select-boxes representing different kinds of model relationships on a basic "add" or "change" page in the Django admin. The initial selection should be saved to the database, if the user clicks one of the "Save" buttons.
We specifically want to do this by setting the initial
value on the form fields (for ModelAdmin
or TabularInline
).
The question is:
What is the proper "value" to assign to Field.initial
, for different kinds of relationship form-fields?
Background
Suppose we have a simple Model
with one or more relationship fields, i.e. OneToOneField
, ForeignKey
, or ManyToManyField
. By default, the latter has an implicit through
model, but it may also use an explicit through
model, as explained in the docs. An explicit through
model is a model with at least two ForeignKey
fields.
The relationship fields, represented by select-boxes, appear automatically on the standard ModelAdmin
"add" or "change" forms, except for the ManyToManyField
with explicit through
model. That one requires an inline, such as the TabularInline
, as explained here in the docs.
Note that the implicit ManyToManyField
is associated with a ModelMultipleChoiceField
on the (admin) form, and the other relationship fields are associated with a ModelChoiceField
. The latter includes the ManyToManyField
with explicit through
model, because that is actually represented by a ForeignKey
field in the TabularInline
.
Goal
Now consider a basic Django ModelAdmin
"add" (or "change") page for this model, including inlines for any explicit through
models. An example based on Django's Pizza-Topping is depicted below.
We want to achieve two goals:
- set the initial selection for one or more of the relationship fields
- the initial selection must be saved to the database if we click one of the "Save" buttons (assuming we did not change the selection manually)
Note that the second one seems trivial, but apparently isn't (see below).
Approach
As far as I know, there are several ways to achieve this:
- set the model field
default
value, e.g.ForeignKey.default
, optionally passing a callable (docs) - set the
Form.initial
value for the admin form (docs) - set the
Field.initial
value for the admin form-field (docs)
Regardless of which approach is "best" for a certain use case, this question is about the last approach: setting the value of Field.initial
.
We do this by extending ModelAdmin.formfield_for_dbfield
(or TabularInline.formfield_for_dbfield
), as follows:
def formfield_for_dbfield(self, db_field, request, **kwargs):
if db_field.name == 'some_relationship_field_name':
kwargs['initial'] = value
return super().formfield_for_dbfield(db_field, request, **kwargs)
The value
in this example could be an obj.id
, obj
, [obj.id, ...]
, or [obj, ...]
, where obj
is a Model
instance.
Problem
Depending on the type of relationship field, different types of value
may or may not work.
For the ManyToManyField
with implicit through
model, we can only assign a list of objects, so that one is easy.
For the other relationships, in some cases the initial select-box selection matches the assigned value, but is not saved (e.g. because Field.has_changed returns False
).
In other cases the initial select-box value is saved, but does not match the assigned value.
Question
So, the obvious question is:
What is the proper value
to assign to Field.initial
, for different kinds of relationship fields on an admin form?
Related
Despite having spent quite a lot of time searching, I could not find a clear answer in the documentation, nor on SO, nor anywhere else. Also tried to figure this out through the source code, but that turns out to be quite tricky.
Some similar questions: