Error with inline on junction model: The inline value did not match the parent instance.
SOLVED while writing this so, let's keep it for reference…
TL;DR: As mentioned in the documentation:
my_orderis the first field in the ordering tuple of the model’s Meta class.
The doc could be more explicit, for example:
my_orderMUST ABSOLUTELY be the first field in theorderingtuple. It's retrieved first from theModelAdmin, otherwise from theModel.Metaclass.
FEATURE REQUEST: (already requested in #155, #198)
Please allow to specify the order field in the Model.
It makes sense to sort first on another field in junction models. Especially as that ordering is used elsewhere.
In inlines, the drag&drop works but there's just a global error message Please correct the errors below. but no specific errors in the form/formset… so it seems the error is on a missing or well hidden field.
Tracing down the problem, there's an error on a field in the inline's formset: The inline value did not match the parent instance.
Investigating in the HTML, apparently there's no order value and it's the album_id that is changed with the new sorting order (which explains the error message).
Investigating in the JS, it looks for a default_order_field.
Investigating in the admin.py, there's a _get_default_ordering function that finds it from the first item in the model_admin.ordering or model._meta.ordering… OK 🤬😕🙄
"Conceptual" code…
class Media(Model): …
class Album(Model):
medias = models.ManyToManyField(Media, through="AlbumMedias")
class AlbumMedias(Model):
album, media, order
class Meta:
ordering = ["album", "order"] # /!\ WRONG! `order` must be the first item.
class AlbumAdminInline(SortableAdminInlineMixin, TabularInline):
model = Album.medias.through
# Try 1: No fields defined
# Try 2: fields = ["media"]
# Try 3: fields = ["order", "media"]
class AlbumAdmin(SortableAdminMixin, ModelAdmin):
inlines = [AlbumAdminInline]
def save_form(self, request, form, change):
obj = super().save_form(request, form, change)
breakpoint()
return obj
Debug:
> /…/my-app/admin.py(131)save_form()
-> return obj
(Pdb) n
--Return--
> /…/my-app/admin.py(131)save_form()-><Album: Foo>
-> return obj
(Pdb) n
> /…/django/contrib/admin/options.py(1578)_changeform_view()
-> formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
(Pdb) n
> /…/django/contrib/admin/options.py(1579)_changeform_view()
-> if all_valid(formsets) and form_validated:
(Pdb) formsets[0].errors
[{'album': ['The inline value did not match the parent instance.']}, …(same for all inline rows, of course)…]
Admin page before drag&drop:
<td class="original">
…
<input type="hidden" name="album_medias-0-id" value="123" id="id_album_medias-0-id">
<input type="hidden" name="album_medias-0-album" value="987" id="id_album_medias-0-album">
</td>
<td class="original">
…
<input type="hidden" name="album_medias-1-id" value="124" id="id_album_medias-1-id">
<input type="hidden" name="album_medias-1-album" value="987" id="id_album_medias-1-album">
</td>
Admin page after drag&drop:
<td class="original">
…
<input type="hidden" name="album_medias-1-id" value="124" id="id_album_medias-1-id">
<input type="hidden" name="album_medias-1-album" value="1" id="id_album_medias-1-album">
</td>
<td class="original">
…
<input type="hidden" name="album_medias-0-id" value="123" id="id_album_medias-0-id">
<input type="hidden" name="album_medias-0-album" value="2" id="id_album_medias-0-album">
</td>