I’m working with Django Rest Framework and need to filter related objects in a serializer based on custom user permissions. Specifically, I want to conditionally include or exclude certain related objects (in this case, comments) in the serialized response depending on the user's relationship to the primary object (a blog post).
For example, a user should see all comments if they have special permissions (such as being the owner or a designated collaborator on the blog post). Otherwise, they should only see comments that meet certain criteria (e.g., approved comments).
I've come up with this solution for now, but I'm unsure if it's the most efficient approach. How should I address this issue?
# models.py
from django.db import models
from django.contrib.auth.models import User
class Blog(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
owner = models.ForeignKey(User, related_name="owned_posts", on_delete=models.CASCADE)
writers = models.ManyToManyField(User, related_name="written_posts")
class Comment(models.Model):
post = models.ForeignKey(Blog, related_name="comments", on_delete=models.CASCADE)
approved = models.BooleanField(default=False)
text = models.TextField()
# serializers.py
from rest_framework import serializers
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class BlogSerializer(serializers.ModelSerializer):
comments = serializers.SerializerMethodField()
class Meta:
model = Blog
fields = '__all__'
def get_comments(self, obj):
user = self.context['request'].user
if obj.owner == user or user in obj.writers.all():
# Show all comments if the user is the owner or a writer
comments = obj.comments.all()
else:
# Otherwise, show only approved comments
comments = obj.comments.filter(approved=True)
return CommentSerializer(comments, many=True).data
# views.py
class BlogViewSet(viewsets.ModelViewSet):
serializer_class = BlogSerializer
permission_classes = (MainModelPermissions,)
pagination_class = LimitOffsetPagination
allowed_methods = ("list", "retrieve")
def get_queryset(self):
return Blog.objects.all()
You can work with a Prefetch
object [Django-doc]:
from django.db.models import Q
class BlogViewSet(viewsets.ModelViewSet):
# …
def get_queryset(self):
queryset = Blog.objects.prefetch_related(
Prefetch(
'comments',
Comment.objects.filter(
Q(post__author=self.request.user) | Q(approved=True)
),
)
)
This will thus only retain Comment
s if approved=True
or if the post__author
, the author of the post of that comment, is the logged in user (request.user
).
and that's it. The serializer does not have to worry about what comments to fetch:
class BlogSerializer(serializers.ModelSerializer):
comments = serializer.CommentSerializer(many=True)
class Meta:
model = Blog
fields = '__all__'