Comment puis-je appliquer un filtre à une ressource nestede dans le framework REST Django?

Dans mon application, j’ai les modèles suivants:

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

J’utilise le framework REST Django pour créer une ressource qui renvoie les détails de la zone plus une ressource nestede indiquant les permissions de l’utilisateur authentifié pour cette zone. Le résultat devrait être quelque chose comme ceci:

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

J’ai créé des sérialiseurs comme ça:

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

Le problème est que lorsque je demande une zone particulière, la ressource nestede renvoie les enregistrements ZonePermission pour tous les utilisateurs ayant des permissions pour cette zone. Existe-t-il un moyen d’appliquer un filtre sur request.user à la ressource nestede?

BTW je ne veux pas utiliser un HyperlinkedIdentityField pour cela (pour minimiser les requêtes http).

Solution

C’est la solution que j’ai implémentée en fonction de la réponse ci-dessous. J’ai ajouté le code suivant à ma classe de sérialiseur:

 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 

Merci beaucoup pour la solution!

Je suis confronté au même scénario. La meilleure solution que j’ai trouvée consiste à utiliser SerializerMethodField et à interroger cette méthode et à renvoyer les valeurs souhaitées. Vous pouvez avoir access à request.user dans cette méthode via self.context['request'].user .

Pourtant, cela semble être un peu un hack. Je suis assez nouveau à DRF, alors peut-être que quelqu’un avec plus d’expérience peut intervenir.

Vous devez utiliser le filtre au lieu de get, sinon, si plusieurs enregistrements sont retournés, vous obtiendrez une 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 

Maintenant, vous pouvez sous-classer le ListSerializer, en utilisant la méthode que j’ai décrite ici: https://stackoverflow.com/a/28354281/3246023

Vous pouvez sous-classer le ListSerializer et remplacer la méthode to_representation.

Par défaut, la méthode to_representation appelle data.all () sur le jeu de requêtes nested. Il est donc nécessaire de créer data = data.filter (** your_filters) avant d’appeler la méthode. Ensuite, vous devez append votre sous-classe ListSerializer en tant que list_serializer_class sur le méta du sérialiseur nested.

  1. sous-classe ListSerializer, écrasant to_representation puis appelant super
  2. append sous-classé ListSerializer en tant que meta list_serializer_class sur le sérialiseur nested

Si vous utilisez le filtre QuerySet à plusieurs endroits, vous pouvez utiliser une fonction de lecture sur votre modèle , puis supprimer le kwarg ‘source’ pour le sérialiseur / champ. DRF appelle automatiquement les fonctions / callables s’il les trouve lors de l’utilisation de sa fonction get_atsortingbute .

 class Zone(models.Model): name = models.SlugField() def current_user_zone_permission(self): return ZonePermission.objects.get(zone=self, user=user) 

J’aime cette méthode car elle maintient votre API cohérente sous le capot avec l’API sur HTTP.

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

Espérons que cela aide certaines personnes!

Note: Les noms n’ont pas besoin de correspondre, vous pouvez toujours utiliser le code source kwarg si vous en avez besoin / souhaitez.

Edit: Je viens de me rendre compte que la fonction sur le modèle n’a pas access à l’utilisateur ou à la requête. Ainsi, un champ de modèle / ListSerializer personnalisé serait peut-être plus adapté à cette tâche.

Je le ferais de deux manières.

1) Faites-le via prefetch dans votre vue:

  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) Ou faites-le par le biais de la .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