web-dev-qa-db-fra.com

Comment puis-je appliquer un filtre à une ressource imbriquée dans Django REST?

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 Django REST framework pour créer une ressource qui retourne les détails de la zone plus une ressource imbriquée montrant les autorisations de l'utilisateur authentifié pour cette zone. La sortie doit être quelque chose comme ça:

{
    "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 avec cela est que lorsque je demande une zone particulière, la ressource imbriquée renvoie les enregistrements ZonePermission pour tous les utilisateurs avec des autorisations pour cette zone. Existe-t-il un moyen d'appliquer un filtre sur request.user à la ressource imbriquée?

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 sur la base 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 un SerializerMethodField et à demander cette méthode et à renvoyer les valeurs souhaitées. Vous pouvez avoir accès à request.user dans cette méthode via self.context['request'].user.

Pourtant, cela semble être un peu un hack. Je suis assez nouveau sur DRF, donc peut-être que quelqu'un avec plus d'expérience peut sonner.

29
user2437225

Vous devez utiliser un filtre au lieu de get, sinon si plusieurs enregistrements reviennent, 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
8
Ibrahim

Vous pouvez maintenant sous-classer ListSerializer, en utilisant la méthode que j'ai décrite ici: https://stackoverflow.com/a/28354281/324602

Vous pouvez sous-classer 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 imbriqué. Vous devez donc effectivement créer data = data.filter (** your_filters) avant d'appeler la méthode. Ensuite, vous devez ajouter votre ListSerializer sous-classé en tant que list_serializer_class sur la méta du sérialiseur imbriqué.

  1. sous-classe ListSerializer, écrasant to_representation puis appelant super
  2. ajouter ListSerializer sous-classé en tant que méta list_serializer_class sur le sérialiseur imbriqué
5
inperspective

Si vous utilisez le QuerySet/filter à plusieurs endroits, vous pouvez utiliser une fonction getter sur votre modèle , puis même 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_attribute .

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!

Remarque: Les noms n'ont pas besoin de pour correspondre, vous pouvez toujours utiliser le kwarg source si vous en avez besoin/voulez.

Edit: Je viens de réaliser que la fonction sur le modèle n'a pas accès à l'utilisateur ou à la requête. Alors peut-être qu'un champ de modèle personnalisé/ListSerializer serait plus adapté à cette tâche.

4
Will S

Je le ferais de deux manières.

1) Soit vous effectuez une prélecture 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 via 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
2
Christoffer