Как я могу применить фильтр к вложенному ресурсу в Django Rest framework?



в моем приложении у меня есть следующие модели:

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

Я использую Django Rest framework для создания ресурса, который возвращает сведения о зоне плюс вложенный ресурс, показывающий разрешения аутентифицированного пользователя для этой зоны. Вывод должен быть примерно таким:

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

Я создал сериализаторы вот так:

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

проблема в том, что когда я запрашиваю определенную зону, вложенный ресурс возвращает записи ZonePermission для все пользователи с разрешениями для этой зоны. Есть ли способ применить фильтр на request.user к вложенному ресурсу?

кстати, я не хочу использовать HyperlinkedIdentityField для этого (чтобы минимизировать http-запросы).

решение

это решение я реализовал на основе ответов ниже. Я добавил следующий код в свой класс сериализатора:

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

большое спасибо за решение!

285   5  

5 ответов:

я столкнулся с тем же сценарием. Лучшее решение, которое я нашел-это использовать SerializerMethodField и этот метод запрашивает и возвращает нужные значения. Вы можете иметь доступ к request.user в этом методе через self.context['request'].user.

тем не менее, это кажется немного Хак. Я довольно новичок в DRF, поэтому, возможно, кто-то с большим опытом может вмешаться.

вы должны использовать фильтр вместо get, в противном случае при возврате нескольких записей вы получите исключение.

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

теперь вы можете подкласс ListSerializer, используя метод, который я описал здесь:https://stackoverflow.com/a/28354281/3246023

вы можете подкласс ListSerializer и перезаписать метод to_representation.

по умолчанию метод to_representation вызывает data.all () на вложенном наборе запросов. Таким образом, вы эффективно должны сделать data = data.фильтр(**your_filters) до того, как метод называется. Затем вам нужно добавить свой подкласс ListSerializer как класс list_serializer_class на мете вложенного сериализатора.

  1. подкласс ListSerializer, перезапись to_representation и затем вызов super
  2. добавить подкласс ListSerializer в качестве meta list_serializer_class на вложенный сериализатор

Если вы используете QuerySet / filter в нескольких местах, вы можете используйте функцию getter на вашей модели, а затем даже отбросьте "исходный" кварг для сериализатора / поля. DRF автоматически вызывает функции / вызываемые объекты, если он находит их при использовании это get_attribute.

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

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

мне нравится этот метод, потому что он поддерживает согласованность вашего API под капотом с api по HTTP.

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

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

надеюсь, это помогает некоторым людям!

Примечание: имена не нужно чтобы соответствовать, вы все еще можете использовать исходный кварг, если вам нужно/хотите.

Edit: я только что понял, что функция на модели не имеет доступа к пользователю или запросу. Поэтому, возможно, пользовательская модель field / ListSerializer будет более подходящей для этой задачи.

Я бы сделал это одним из двух способов.

1) либо сделать это через prefetch в вашем представлении:

    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) или сделать это хотя .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
    Ничего не найдено.

Добавить ответ:
Отменить.