When to use get_queryset vs. filter_queryset in Django

Hero image
Rocktim Saikia/3 min read

While developing APIs for a project at my current company, I noticed the use of both get_queryset and filter_queryset methods in the Serializer classes. Initially, I was puzzled, as my experience was primarily with get_queryset. However, as I delved deeper into the API creation, the distinction became clear, shedding light on when and where to use each method effectively.

The Basics

  1. get_queryset():

    • What's it for?: It helps in defining the initial group of records (or objects) we want to work with.
    • When to use?: For filters that are not dependent on the request payload. This is where you set conditions that are more static or inherent to the nature of the view, regardless of user input.
  2. filter_queryset():

    • What's it for?: Refining our initial group of records based on additional conditions.
    • When to use?: When you want to filter the data based on the request payload parameters. This is for dynamic conditions that hinge on the current request, be it user-driven filters, search parameters, or other real-time criteria.

Now, let's explore these concepts with a real-world example.

Walking Through A Practical Example

Consider a scenario where you want to showcase the unpaid invoices of a user, but only those made through specific payment modes:

class UserUnpaidInvoiceView(GenericViewSet, ListModelMixin):
    queryset = UserUnpaidInvoice.objects.all()
    serializer_class = UserUnpaidInvoiceSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(
                Q(mode_of_payment="Razorpay")
                | Q(mode_of_payment="Stripe India")
                | Q(mode_of_payment="Stripe International")
            )
            .order_by("-created_at")
        )

    def list(self, request, *args, **kwargs):
        user = request.user
        qs = self.get_queryset()
        queryset = self.filter_queryset(qs).filter(user=user)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

In this UserUnpaidInvoiceView:

  • get_queryset is overridden to set our base layer of invoices, but only those with specific modes of payment (Razorpay, Stripe India, or Stripe International). These filters remain consistent and aren't influenced by the user's current request.

  • In the list method, we then zoom into the specifics, narrowing down to the authenticated user. This is where filter_queryset would come handy if, for instance, users had the ability to further refine their view based on date ranges or amounts through the request.

To put it in human terms, it's like first grabbing a large box of assorted chocolates (get_queryset), then filtering out only the ones you love (filter_queryset) based on your current mood or craving!

Conclusion

So this is what I learned basically, the get_queryset sets the stage with an initial set of records based on broad, generally static criteria, while filter_queryset hones in on the specifics based on the user's current request. By segregating these functionalities, your Django views remain modular, clean, and highly readable. So, the next time you're crafting a view, remember to strike that balance!