Opened 12 months ago

Last modified 5 months ago

#34566 assigned Bug

ModelAdmin get_field_queryset uses related admin ordering, but not related admin querysets. — at Version 1

Reported by: Mike J Owned by: nobody
Component: contrib.admin Version: 4.2
Severity: Normal Keywords:
Cc: fisadev, Ramiro Morales, Krishna2864 Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Mike J)

The ModelAdmin get_field_queryset function properly finds the related model admin and gets the ordering from that. However, it does not also get the fully annotated queryset which can lead to errors breaking the admin page.

In my case I created my own User admin page and added the user permission count to the queryset as follows:

class UserLocalAdmin(UserAdmin):

    def custom_perm_count(user):
        return user.user_permissions__count
    custom_perm_count.admin_order_field = "user_permissions__count"
    custom_perm_count.short_description = "Permission Count"

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.annotate(models.Count("user_permissions"))

    list_display = ("username", "email", "is_staff", "is_superuser", custom_perm_count, "is_active","last_login")
    sortable_by = list_display
    ordering = ("-is_active", "-user_permissions__count", "-is_superuser", "username")

Then in a different model I use user as a foreign key titled owner. I also created an admin page for that model.

The issue is the drop down list created for the "owner" foreign key field fetches the admin page ordering, but the un-altered queryset.
This causes a FieldError to be raised and django fails to load the page.

Currently the only option I can see to handle this would be override the get_field_queryset function and allow it to reference the get_queryset function or provide custom ordering.

class ReportFilterAdmin(dj_admin.ModelAdmin):
    form=AdminReportFilterForm

    def get_field_queryset(self, db, db_field, request):
        """
        An override of the original to add the required annotations.

        If the ModelAdmin specifies ordering, the queryset should respect that
        ordering.  Otherwise don't specify the queryset, let the field decide
        (return None in that case).
        """
        related_admin = self.admin_site._registry.get(db_field.remote_field.model)
        if related_admin is not None:
            ordering = related_admin.get_ordering(request)
            if ordering is not None and ordering != ():
                # return db_field.remote_field.model._default_manager.using(db).order_by( # current line in official repo
                return related_admin.get_queryset(request).order_by(
                    *ordering
                )
        return None

I don't know if there are performance issues with my approach, but I believe it achieves more universally expected behavior.

original code: https://github.com/django/django/blob/7414704e88d73dafbcfbb85f9bc54cb6111439d3/django/contrib/admin/options.py#L253-L255
Pull Request: https://github.com/django/django/pull/16859

Change History (1)

comment:1 by Mike J, 12 months ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top