2013-05-29 2 views
53

В моем приложении у меня есть следующие модели:Как применить фильтр к вложенному ресурсу в структуре 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 записи for все пользователи пользователей с правами для этой зоны. Есть ли способ применения фильтра по request.user к вложенному ресурсу?

BTW Я не хочу использовать 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 

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

ответ

27

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

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

+0

Спасибо за ваше предложение. Может ли 'SerializerMethodField' вернуть структуру или просто плоское поле? –

+0

Он может вернуть структуру. – user2437225

+0

У меня будет такой подход - спасибо. Если никакие другие «официальные» предложения не придут, я приму это как ответ. –

6

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

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

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

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

  1. подкласс ListSerializer, перезапись to_representation и затем вызвать супер
  2. надстройку подклассы ListSerializer как мета list_serializer_class на вложенной Serializer
+0

Мне также нравится этот метод лучше, поскольку он по-прежнему позволяет сохранить поле для записи, если вам это нужно. – jgiralt

4

Если вы используете QuerySet/фильтр в нескольких местах, вы можете использовать функцию геттера на вашей модели, а затем даже сбросить «источник» kwarg для Сериализатора/поля.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') 

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

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

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

2

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

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 
Смежные вопросы