emft
emft

Reputation: 416

Django Rest Framework: Filtering in Read/Write Related Field

I have Users and Groups, where Groups are a PrimaryKeyRelatedField on Users. When writing a User, I only want to assign it to Groups the current user is in. When retrieving a User, I only want to show the Groups that it has in common with the current user.

For example, suppose user_1 belongs to group_1, and user_2 belongs to group_2. If user_1 pings my LIST USERS endpoint I want to get:

[{'groups': [1], 'username': 'user_1'}, {'groups': [], 'username': 'user_2'}]

Note that although group_2 exists, it is not listed under user_2's groups, because user_1 is not in it.

Thoughts so far:

I don't see any guides on how have a single read/write RelatedField filtered by the current user. Any ideas?


My code:

# serializers.py, vanilla
from django.contrib.auth.models import User, Group
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'groups')


# views.py
from django.contrib.auth.models import User
from rest_framework.authentication import BasicAuthentication
from rest_framework import viewsets
from app import serializers

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = serializers.UserSerializer
    queryset = User.objects.all()
    authentication_classes = (BasicAuthentication,)

# serializers.py, with write-choice filter modification
class FilteredRelatedField(serializers.PrimaryKeyRelatedField):
    def get_queryset(self):
        user = self.context['request'].user
        user_groups = user.groups.values_list('id', flat=True)
        queryset = Group.objects.filter(id__in=user_groups)
        return queryset

class UserSerializer(serializers.ModelSerializer):
    groups = FilteredRelatedField(many=True)
    class Meta:
        model = User
        fields = ('username', 'groups')


# serializers.py, with read-only filtering
class UserSerializer(serializers.ModelSerializer):
    groups = serializers.SerializerMethodField()
    class Meta:
        model = User
        fields = ('username', 'groups')
    def get_groups(self, instance):
        instance_user_groups = instance.groups.values_list('id', flat=True)
        current_user_groups = self.context['request'].user.groups.values_list('id', flat=True)
        return [x for x in instance_user_groups if x in current_user_groups]

Upvotes: 2

Views: 824

Answers (1)

emft
emft

Reputation: 416

Here's what I ended up doing. I think it works decently but if anyone has another idea please do jump in.

The thing holding me back was a mistaken assumption about how to_representation works. What I wanted was to change the representation (in this case, by filtering a list) for an object that I do know I want to show in some form. If I wanted to not show that object at all, that's best handled in the views.

to_representation sets the representation for any single object. I wanted to filter a list attribute, so I overloaded to_representation to do that:

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        representation['groups'] = [x for x in representation['groups'] if x in [some_arbitrary_list_to_filter_against]]
        return representation

This filters out for reading. To make the filter work for read/write, make sure the many-to-many field is a custom subclass of RelatedField as outlined here.

Upvotes: 1

Related Questions