Opened 2 years ago

Closed 2 years ago

Last modified 2 years ago

#33813 closed Bug (duplicate)

Incorrect queryset for the 'id' field in the InlineModels' forms for django.cotrib.admin when using custom model managers

Reported by: Hristo Trendafilov Owned by: nobody
Component: contrib.admin Version:
Severity: Normal Keywords: admin, inlines, custom model manager
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

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

Change History (2)

comment:1 by Hristo Trendafilov, 2 years ago

Summary: Incorrect querysets for InlineModels for django.cotrib.adminIncorrect queryset for the 'id' field in the InlineModels' forms for django.cotrib.admin when using custom model managers

comment:2 by Carlton Gibson, 2 years ago

Resolution: duplicate
Status: newclosed

This is a duplicate of #33375 — in BaseModelFormSet.add_fields() we add the id ModelChoiceField using the default manager.

You'll need to provide the inline's `form` option to provide the base form class declaring the id ModelChoiceField with the correct (for your case) QuerySet.

Last edited 2 years ago by Carlton Gibson (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top