14

I have many different filters in Django admin:

class OrderAdmin(admin.ModelAdmin):
    ...
    list_filter = ('field_1', 'field_2', 'field_3', ... , 'field_N')
    ...

I need to get filtered queryset in my overridden method changelist_view before parent changelist_view is called:

class OrderAdmin(admin.ModelAdmin):
    ...
    def changelist_view(self, request, extra_content=None):
        # here i need filtered queryset and I don`t know 
        # which filters have been applied
        return super().changelist_view(request, extra_context)
    ...

if I calling get_queryset before super in changelist_view it returns queryset without filters.

react
  • 159
  • 2
  • 9

2 Answers2

15

New version of Django admin use custom objects for ChangeList view with custom get_queryset method.

As you can see in Django source:

def changelist_view(self, request, extra_context=None):
    ...
    ChangeList = self.get_changelist(request)

    cl = ChangeList(request, self.model, list_display,
        list_display_links, list_filter, self.date_hierarchy,
        search_fields, list_select_related, self.list_per_page,
            self.list_max_show_all, self.list_editable, self)

    # Actions with no confirmation
    if (actions and request.method == 'POST' and
            'index' in request.POST and '_save' not in request.POST):
        if selected:
            response = self.response_action(request, queryset=cl.get_queryset(request))
    ...

You must override self.get_changelist(request) and return your custom ChangeList with overridden get_queryset.

ModelAdmin.get_changelist:

def get_changelist(self, request, **kwargs):
    """
    Returns the ChangeList class for use on the changelist page.
    """
    return MyChangeList  # PUT YOU OWERRIDEN CHANGE LIST HERE

MyChangeList:

from django.contrib.admin.views.main import ChangeList

class MyChangeList(ChangeList):
    def get_queryset(...):
        # if you want change get_queryset logic or add new filters
        ...
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # if you want add some context variable which can be accessed by 
        # {{ cl.some_context_varibale }} variable
        self.some_context_varibale = self.queryset.aggregate(Avg('price'))['price__avg']
pahaz
  • 837
  • 9
  • 13
  • 1
    A note to the location for this code in the source on github would be nice. Just handy for someone like myself that want to quickly look at the function signatures and how they are used. – Andre Feb 24 '20 at 17:43
4

You can tamper with the response returned by the original changelist_view call. For example, to add some extra template context data using the queryset after it's filtered by the admin's ChangeList instance:

def changelist_view(self, request, extra_context=None):
    # Obtain the original response from Django
    response = super().changelist_view(request, extra_context)

    # Extract the final queryset from the ChangeList object
    change_list = response.context_data['cl']
    queryset = change_list.queryset

    # Do stuff with the queryset
    stats = self.get_stats(queryset)

    # Inject a new key in the template context data
    response.context_data['stats'] = stats

    return response
emyller
  • 2,648
  • 1
  • 24
  • 16