[Django] #33813: Incorrect querysets for InlineModels for django.cotrib.admin

1 view
Skip to first unread message

Django

unread,
Jun 29, 2022, 5:21:05 AM6/29/22
to django-...@googlegroups.com
#33813: Incorrect querysets for InlineModels for django.cotrib.admin
-------------------------------------+-------------------------------------
Reporter: Hristo | Owner: nobody
Trendafilov |
Type: Bug | Status: new
Component: | Version:
contrib.admin | Keywords:
Severity: Normal | admin,inlines,custom model manager
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
Hello.
I have run into the following that is applicable to all Django versions.
Let's have two models and one is using a custom model manager like so

{{{

class ReviewsModelManager(models.Manager):
def get_queryset(self):
qs = super().get_queryset()
qs = qs.filter(published=True)
return qs

def get_full_queryset(self):
qs = super().get_queryset()
return qs

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)

objects = BaseModelManager()


class BookReview(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE,
related_name='reviews')
content = models.CharField(max_length=255)
published = models.BooleanField(default=False)

objects = ReviewsModelManager()

}}}

The concept is that we are having one book that could have multiple
reviews. A review could be published or not. We then apply a model manager
to show only published book reviews. We keep the original queryset to use
in the admin later in the `get_full_queryset`

Then we add a model admin in admin.py like so:

{{{
class ReviewTabularInline(admin.TabularInline):

def get_queryset(self, request):
qs = self.model._default_manager.get_full_queryset()
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs


class BookAdmin(admin.ModelAdmin):
inlines = [ReviewTabularInline]

def get_queryset(self, request):
qs = self.model._default_manager.get_full_queryset()
ordering = self.get_ordering(request)
if ordering:
qs = qs.order_by(*ordering)
return qs
}}}

Default querysets are hijacked in both admin and inline in order to
include all reviews - both published and not.

If you go to the `book admin`, the inline will display all reviews as it
should.
If you try to mark a review from `published` to `not published` in the
inline this will also work.

But if you try to mark `not-published` to `published` that is failing
with an unspecified error.

The reason for this is that the inline form does not validate.
Validation fails because the inline form receives an extra field /called
`id` by default or whatever the PK name for the model is/

That field is a `ModelChoiceField`.
But the queryset for that `ModelChoiceField` is done with the
`get_queryset` of the custom model manager - `ReviewsModelManager` that
will not include items that are not marked as `not-published`. Since you
are trying to update `non-published` to `published`, that object is going
to be missing in `ModelChoiceField` queryset which causes the field not to
validate and that drops the whole form.

This inconsistency. `ModelChoiceField` `id` field should also get the
queryset that is specified in the inline's `get_queryset`

--
Ticket URL: <https://code.djangoproject.com/ticket/33813>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Reply all
Reply to author
Forward
0 new messages