ExTexan
ExTexan

Reputation: 453

Django: How to add query params to queryset as is done in Pagination?

Our site is DRF backend, with Vue frontend. It incorporates extensive filter options, so any endpoint could have many query parameters - like example.com?name__icontains=bob&...etc.

We have a situation on one view where we need to return a sum of one column. Instead of creating a separate endpoint for the frontend to hit, we want to add it to the results of the original view. Like so...

{
    "pagination": {
        ...pagination stuff...
    },
    "results": [
        {
            "id": 1,
            "task": "DMSD-50",
            ...other fields...,
            "duration": 204
        },
        {
            "id": 2,
            "task": "DMSD-53",
            ...other fields...,
            "duration": 121
        },
        ...more rows...
    ],
    "totals": {
        "duration": 955
    }
}

My first attempt was to add this to the get() method of the view:

    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)
        queryset = self.get_queryset()
        totals = queryset.aggregate(total_duration=Sum('duration'))
        response['totals'] = {
            'duration': totals['total_duration'] or 0
        }
        return response

...which worked when I did the full (unfiltered) list. But when I added filters in the URL, the total was always the same. That made me realize that get_queryset() doesn't add query parameters from the URL. I then realized that the pagination class must be incorporating query parameters, so my second solution was to add this code to our custom pagination class. That works, but it seems like a hack.

What's the best way to get the queryset with query parameters already added, or to add them myself? I know those parameters are available in kwargs, so if (for example) my view only filtered on "name", I could access the filter via kwargs['name'] - simple. But, as I said, there can be many filter args, and they certainly vary from one view to another.

If I loop through kwargs, I'm thinking I might be getting other things besides filter args - sort order comes to mind - but I'm guessing there may be others. I need a fool-proof way of getting ALL filter args and ONLY filter args.

Upvotes: 3

Views: 1632

Answers (2)

Abdul Aziz Barkat
Abdul Aziz Barkat

Reputation: 21812

I assume you are using some generic view or are atleast inheriting from ListModelMixin. The get_queryset method does not do the filtering of the queryset, that task is done by the filter_queryset method of the view, hence when you override the get method and don't call that method there is no filtering done. Change your code to use it like so:

class YourView(generics.ListAPIView):  # Just an assumption here
    ...
    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)
        queryset = self.filter_queryset(self.get_queryset())
        totals = queryset.aggregate(total_duration=Sum('duration'))
        response['totals'] = {
            'duration': totals['total_duration'] or 0
        }
        return response

For understanding better exactly how the builtin view works you might want to look at it's source code [GitHub].

Upvotes: 1

Yousef Alm
Yousef Alm

Reputation: 238

  1. Create empty context

    Context = {}
    
  2. Update context with each existing filter in request.GET

    If request.GET.get('filter'):
    
       Context['filter] = request.GET.get('filter')
    
  3. Add filters to queryset

    Query.objects.filter(**Context )
    

Upvotes: 0

Related Questions