Django REST Filtering Tutorial - Filter with Current User (Part II)
Serhat Teker
Posted on April 18, 2022
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:
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
:
# 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
:
# 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;
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:
# 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:
# 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:
# 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
:
# 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:
# 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!
Posted on April 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.