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
defaultvalue, e.g.ForeignKey.default, optionally passing a callable (docs) - set the
Form.initialvalue for the admin form (docs) - set the
Field.initialvalue 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:
