Entrée

We’ve seen in part I how to setup our repo, sending HTTP requests and some fundamental concepts about Django REST filtering.

In this second part we will limit our views/querysets/objects with user, more precisely with current authenticated user.

The authors list endpoint — /authors/, displays all items regardless of whether they belong to current user or not.

$ http :8000/authors

[
    {
        "first_name": "Name1",
        "full_name": "Name1 Surname1",
        "id": 1,
        "last_name": "Surname1",
        "user": 1
    },
    {
        "first_name": "Name2",
        "full_name": "Name2 Surname2",
        "id": 2,
        "last_name": "Surname2",
        "user": null
    },
    {
        "first_name": "Name3",
        "full_name": "Name3 Surname3",
        "id": 3,
        "last_name": "Surname3",
        "user": 3
    }
]

How can we change that behavior in order to return items only owned by current authenticated user?

Note

You can find the code we’re working on this tutorial series in this repository: django-rest-filtering-tutorial

You don’t need to download and setup the repository in order to follow this series, however I’d like to encorauge to do so, since while you play you learn better as always.

Current User

This is one common filter usage which is filtering the queryset to ensure only relevant results to the current authenticated user who makes the request are returned.

For this purpose we will override default get_queryset as we’ve seen in default filtering in detail.

Let’s filter authors list endpoint with the current user:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from rest_framework import generics

from src.authors.models import Author
from src.authors.serializers import AuthorSerializer


class AuthorListView(generics.ListAPIView):
    serializer_class = AuthorSerializer

    def get_queryset(self):
        """
        This view returns a list of all the authors for the currently
        authenticated user.

        Returns empyt list if user Anonymous
        """
        user = self.request.user

        if not user.is_anonymous:
            return Author.objects.filter(user=user)

        return Author.objects.none()

Note

In our repo we use ModelViewSet for authors endpoint. You can update it like above.

You can look at related AuthorSerializer and Author model in the repo, however for easy access I put them there as well. In order to examine them just expand the below section.

AuthorSerializer and Author model

AuthorSerializer from src/authors/serializers.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# src/authors/serializers.py
from rest_framework import serializers

from src.authors.models import Author


class AuthorSerializer(serializers.ModelSerializer):

    class Meta:
        model = Author
        fields = ("id", "user", "first_name", "last_name", "full_name")
        read_only_fields = ("full_name",)

Author model from src/authors/model.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# src/authors/model.py
from django.db import models
from django.conf import settings

USER = settings.AUTH_USER_MODEL


class Author(models.Model):
    """
    Author entity

    Provides first_name and last_name, since he/she can write unter a Pen Name
    """
    user = models.ForeignKey(to=USER, on_delete=models.DO_NOTHING, blank=True, null=True)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"

Now we can make a successful request by including the username and password of one of the users we created in populate_db.py:

$ http -a "User1:1234" ":8000/authors/"

[
    {
        "first_name": "Name1",
        "full_name": "Name1 Surname1",
        "id": 1,
        "last_name": "Surname1",
        "user": 1
    },
]

As you can recognize now it returns only one object instead of three.

And if you we make a request as anonymous or unauthenticated user, we will get:

$ http ":8000/authors/"

[]

User Authentication and Permission

It’s usually better idea to separate authentication and permission layer/logic. For this goal you can use authentication_classes and permission_classes on per-view or per-viewset basis;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from rest_framework import generics
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated

from src.authors.models import Author
from src.authors.serializers import AuthorSerializer


class AuthorListView(generics.ListAPIView):
    serializer_class = AuthorSerializer
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    def get_queryset(self):
        """
        This view returns a list of all the authors for the currently
        authenticated user.
        """
        user = self.request.user

        return Author.objects.filter(user=user)

Another method is setting the default authentication globally, using the DEFAULT_AUTHENTICATION_CLASSES setting:

1
2
3
4
5
6
7
# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.BasicAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ]
}

Info

In the repo these are disabled, you can set them globally if you comment out the related lines at the end of the setting.py file.

And now our view will look cleaner like this:

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

from src.authors.models import Author
from src.authors.serializers import AuthorSerializer


class AuthorListView(generics.ListAPIView):
    serializer_class = AuthorSerializer

    def get_queryset(self):
        """
        This view returns a list of all the authors for the currently
        authenticated user.
        """
        return Author.objects.filter(user=self.request.user)

For more info about authentication and permissions you can look at:

Custom FilterBackend

There is another method which is that we can also provide our own generic filtering backend.

To do so override BaseFilterBackend class, and override the .filter_queryset method. The method should return a new, filtered queryset.

As well as allowing clients to perform searches and filtering, generic filter backends can be useful to restrict which objects should be visible to any given request or user.

Let’s write IsOwnerFilterBackend for our current user logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# src/authors/filters.py
from django_filters import rest_framework as filters


class IsOwnerFilterBackend(filters.BaseFilterBackend):
    """
    Filter that only allows users to get their own objects
    """
    def filter_queryset(self, request, queryset, view):
        return queryset.filter(owner=request.user)

Add this filter to our AuthorListView:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# src/authors/views.py
from rest_framework import generics
from django_filters import rest_framework as filters

from src.authors.models import Author
from src.authors.serializers import AuthorSerializer
from src.authors.filters import IsOwnerFilterBackend


class AuthorListView(generics.ListAPIView):
    serializer_class = AuthorSerializer
    queryset = Author.objects.all()
    filter_backends = (IsOwnerFilterBackend,)
    filterset_fields = ("user",)

Now if we send request we only receive related authors to the user.

One side note to this method; as we’ve already seen above, we can achieve the same behavior by overriding get_queryset() on the view, but using a filter backend allows us to more easily add this restriction to multiple views, or to apply it across the entire API.

We can also make this filter backend as our global default backend:

1
2
3
4
# settings.py
REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": ["src.authors.filters.IsOwnerFilterBackend"],
}

Conclusion

In this tutorial we learned how to filter the queryset to ensure only relevant results to the current authenticated user who makes the request are returned.

See you in the next part of this tutorial series.

All done!