Подтвердить что ты не робот

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

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

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 для создания ресурса, который возвращает информацию о зоне, а также вложенный ресурс, показывающий разрешенные пользователем разрешения для этой зоны. Результат должен быть примерно таким:

{
    "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 для all пользователей с разрешениями для этой зоны. Есть ли способ применить фильтр к request.user к вложенному ресурсу?

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

Решение

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

Вы должны использовать фильтр вместо 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

Ответ 3

Теперь вы можете подклассифицировать ListSerializer, используя описанный здесь метод: fooobar.com/questions/102907/...

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

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

  • Подкласс
  • ListSerializer, переписывающий to_representation, а затем вызов super
  • добавить подклассифицированный ListSerializer в качестве meta list_serializer_class на вложенном Serializer

Ответ 4

Если вы используете QuerySet/filter в нескольких местах, вы можете использовать функцию getter на вашей модели, а затем даже сбросить 'source' kwarg для Serializer/Field. 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 в соответствии с hood с api через HTTP.

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

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

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

Примечание. Имена не соответствуют нуждаются, вы все равно можете использовать источник kwarg, если вам нужно/хотите.

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

Ответ 5

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

1) Либо сделайте это с помощью предварительной выборки в своем представлении:

    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