pythondjangodjango-rest-framework

Filter Related Objects in DRF Serializer Based on User's Permission


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()

Solution

  • 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 Comments 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__'