django-admin-sortable2 icon indicating copy to clipboard operation
django-admin-sortable2 copied to clipboard

Error with inline on junction model: The inline value did not match the parent instance.

Open ppo opened this issue 4 years ago • 0 comments

SOLVED while writing this so, let's keep it for reference…

TL;DR: As mentioned in the documentation:

my_order is the first field in the ordering tuple of the model’s Meta class.

The doc could be more explicit, for example:

my_order MUST ABSOLUTELY be the first field in the ordering tuple. It's retrieved first from the ModelAdmin, otherwise from the Model.Meta class.

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>

ppo avatar Jan 20 '21 03:01 ppo