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

In my application, 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 use the Django REST framework to create a resource that returns information about a zone, as well as a sub-resource that shows the permissions the user has for that zone. The result should be something like this:

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

I created such serializers:

 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 is that when I request a specific zone, the attached resource returns ZonePermission entries for all users with permissions for that zone. Is there a way to apply a filter to request.user to a nested resource?

BTW I do not want to use HyperlinkedIdentityField for this (to minimize HTTP requests).

Decision

This is a solution that 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 so much for the solution!

+54
django django-rest-framework
May 29 '13 at 18:51
source share
5 answers

I came across the same scenario. The best solution I found is to use SerializerMethodField and request this method and return the desired values. You can access request.user in this method through self.context['request'].user .

However, this seems a bit hacked. I am new to DRF, so maybe someone with more experience can call back.

+28
May 30 '13 at 15:31
source share

You must use a filter instead of get, otherwise if multiple records are returned, you will get an 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 
+7
Sep 25 '13 at 12:09 on
source share

Now you can subclass the ListSerializer using the method described here: stack overflow

You can subclass the ListSerializer and overwrite the to_representation method.

By default, the to_representation method calls data.all () for a nested set of requests. Therefore, before calling the method, you need to do data = data.filter (** your_filters). Then you need to add your subclassified ListSerializer as list_serializer_class in the meta-nested serializer.

  • Subclass
  • ListSerializer rewriting to_representation and then calling super
  • add subclassed ListSerializer as meta list_serializer_class on nested Serializer
+5
Feb 05 '15 at 21:33
source share

If you use QuerySet / filter in several places, you can use the getter function on your model , and then even reset the 'source' kwarg for Serializer / Field. DRF automatically calls functions / called calls if it finds them when using get_attribute .

 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 supports your API according to hood with api via HTTP.

 class ZoneSerializer(serializers.HyperlinkedModelSerializer): current_user_zone_permission = ZonePermissionSerializer() class Meta: model = Zone fields = ('name', 'current_user_zone_permission') 

Hope this helps some people!

Note. Names do not match the need , you can still use the kwarg source if you need / want.

Edit: I just realized that the function on the model does not have access to the user or request. Therefore, perhaps a custom Model / ListSerializer field would be more appropriate for this task.

+4
Apr 17 '15 at 8:52
source share

I would do this in one of two ways.

1) Either do this using 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 .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 
+2
Aug 31 '15 at 10:46
source share



All Articles