Intro

We saw in previous article how to use query params. Now it’s time to improve it.

Improvement I

Our overwritten get_queryset method will create multiple INNER JOINS in our SQL query. We can prevent that by using prefetch_related:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# articles/views.py
from rest_framework import generics

from src.articles.models import Article
from src.articles.serializer import ArticleSerializer


class ArticleListView(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    region_separator = ","

    def get_queryset(self):
        """
        Optionally restricts the returned articles to given regions,
        by filtering against a `regions` query parameter in the URL.
        """
        regions = self.request.query_params.get("regions", None)
        if regions:
            qs = Article.objects.prefetch_related("regions")
            for region in regions.split(self.region_separator):
                qs = qs.filter(regions__code=region)

            return qs

        # return default Article.objects.all()
        return super().get_queryset()

Improvement II

Writing business logic or query decisions in Views is usually considered anti-pattern for Django. Query methods should be in models or better in models.Manager;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# articles/models.py
from django.db import models

from src.articles.models import Article
from src.articles.serializer import ArticleSerializer


class ArticleManager(models.Manager):
    def by_regions(self, regions, region_separator=","):
        """
        Restricts the returned articles to given regions
        """
        if regions:
            qs = self.prefetch_related("regions")
            for region in regions.split(region_separator):
                qs = qs.filter(regions__code=region)

            return qs

        return self.all()


class Article(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField(blank=True)
    regions = models.ManyToManyField(
        "Region", related_name="articles", blank=True
    )

    objects = ArticleManager()

    def __str__(self):
        return self.title

    ...

Now our get_queryset simply looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# articles/views.py
from rest_framework import generics

from src.articles.models import Article
from src.articles.serializer import ArticleSerializer


class ArticleListView(generics.ListCreateAPIView):
    serializer_class = ArticleSerializer

    def get_queryset(self):
        """
        Optionally restricts the returned articles to given regions,
        by filtering against a `regions` query parameter in the URL.
        """
        regions = self.request.query_params.get("regions", None)

        return Article.objects.by_regions(regions=regions)

This is definitely cleaner and better.

If you have more complex queries, then you can consider using third party packages.

All done!