Reputation: 416
I have User
s and Group
s, where Group
s are a PrimaryKeyRelatedField
on User
s. When writing a User
, I only want to assign it to Group
s the current user is in. When retrieving a User
, I only want to show the Group
s 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:
to_representation
seems like it will just change how the group is shown in the list of groups, not whether it is shown at allgroup
choices here, meaning that user_1
couldn't, for instance, add itself to group_2
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
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