How can I apply a filter to a nested resource in Django REST framework?

DjangoDjango Rest-Framework

Django Problem Overview


In my app I have the following models:

class Zone(models.Model):
    name = models.SlugField()

class ZonePermission(models.Model):
    zone = models.ForeignKey('Zone')
    user = models.ForeignKey(User)
    is_administrator = models.BooleanField()
    is_active = models.BooleanField()

I am using Django REST framework to create a resource that returns zone details plus a nested resource showing the authenticated user's permissions for that zone. The output should be something like this:

{
    "name": "test", 
    "current_user_zone_permission": {
        "is_administrator": true, 
        "is_active": true
    }
} 

I've created serializers like so:

class ZonePermissionSerializer(serializers.ModelSerializer):
    class Meta:
        model = ZonePermission
        fields = ('is_administrator', 'is_active')

class ZoneSerializer(serializers.HyperlinkedModelSerializer):
    current_user_zone_permission = ZonePermissionSerializer(source='zonepermission_set')

    class Meta:
        model = Zone
        fields = ('name', 'current_user_zone_permission')

The problem with this is that when I request a particular zone, the nested resource returns the ZonePermission records for all the users with permissions for that zone. Is there any way of applying a filter on request.user to the nested resource?

BTW I don't want to use a HyperlinkedIdentityField for this (to minimise http requests).

Solution

This is the solution I implemented based on the answer below. I added the following code to my serializer class:

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')

def get_user_zone_permission(self, obj):
    user = self.context['request'].user
    zone_permission = ZonePermission.objects.get(zone=obj, user=user)
    serializer = ZonePermissionSerializer(zone_permission)
    return serializer.data

Thanks very much for the solution!

Django Solutions


Solution 1 - Django

I'm faced with the same scenario. The best solution that I've found is to use a SerializerMethodField and have that method query and return the desired values. You can have access to request.user in that method through self.context['request'].user.

Still, this seems like a bit of a hack. I'm fairly new to DRF, so maybe someone with more experience can chime in.

Solution 2 - Django

You have to use filter instead of get, otherwise if multiple record return you will get Exception.

current_user_zone_permission = serializers.SerializerMethodField('get_user_zone_permission')

def get_user_zone_permission(self, obj):
    user = self.context['request'].user
    zone_permission = ZonePermission.objects.filter(zone=obj, user=user)
    serializer = ZonePermissionSerializer(zone_permission,many=True)
    return serializer.data

Solution 3 - Django

Now you can subclass the ListSerializer, using the method I described here: https://stackoverflow.com/a/28354281/3246023

> You can subclass the ListSerializer and overwrite the to_representation method. > >By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.

>1. subclass ListSerializer, overwriting to_representation and then calling super 2. add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer

Solution 4 - Django

If you're using the QuerySet / filter in multiple places, you could use a getter function on your model, and then even drop the 'source' kwarg for the Serializer / Field. DRF automatically calls functions/callables if it finds them when using it's get_attribute function.

class Zone(models.Model):
    name = models.SlugField()

    def current_user_zone_permission(self):
	    return ZonePermission.objects.get(zone=self, user=user)

I like this method because it keeps your API consistent under the hood with the api over HTTP.

class ZoneSerializer(serializers.HyperlinkedModelSerializer):
    current_user_zone_permission = ZonePermissionSerializer()

    class Meta:
        model = Zone
        fields = ('name', 'current_user_zone_permission')

Hopefully this helps some people!

Note: The names don't need to match, you can still use the source kwarg if you need/want to.

Edit: I just realised that the function on the model doesn't have access to the user or the request. So perhaps a custom model field / ListSerializer would be more suited to this task.

Solution 5 - Django

I would do it in one of two ways.

  1. Either do it through prefetch in your view:

     serializer = ZoneSerializer(Zone.objects.prefetch_related(
         Prefetch('zone_permission_set', 
             queryset=ZonePermission.objects.filter(user=request.user), 
             to_attr='current_user_zone_permission'))
         .get(id=pk))
    
  2. Or do it though the .to_representation:

    class ZoneSerializer(serializers.HyperlinkedModelSerializer):

     class Meta:
         model = Zone
         fields = ('name',)
    
     def to_representation(self, obj):
         data = super(ZoneSerializer, self).to_representation(obj)
         data['current_user_zone_permission'] = ZonePermissionSerializer(ZonePermission.objects.filter(zone=obj, user=self.context['request'].user)).data
         return data
    

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid Jones - iPushPullView Question on Stackoverflow
Solution 1 - Djangouser2437225View Answer on Stackoverflow
Solution 2 - DjangoIbrahimView Answer on Stackoverflow
Solution 3 - DjangoinperspectiveView Answer on Stackoverflow
Solution 4 - DjangoWill SView Answer on Stackoverflow
Solution 5 - DjangoChristofferView Answer on Stackoverflow