【问题标题】:How can I apply a filter to a nested resource in Django REST framework?如何将过滤器应用于 Django REST 框架中的嵌套资源?
【发布时间】:2013-05-25 04:09:56
【问题描述】:

在我的应用中,我有以下模型:

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 记录。有什么方法可以将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

非常感谢您的解决方案!

【问题讨论】:

    标签: django django-rest-framework


    【解决方案1】:

    你必须使用filter而不是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
    

    【讨论】:

      【解决方案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
      

      【讨论】:

        【解决方案3】:

        如果您在多个地方使用 QuerySet / 过滤器,则可以在模型上使用 getter 函数,然后甚至删除 Serializer / Field 的“源”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 在底层与 HTTP 上的 api 保持一致。

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

        希望这对某些人有所帮助!

        注意:名称不需要匹配,如果需要/想要匹配,您仍然可以使用 source kwarg。

        编辑:我刚刚意识到模型上的函数无法访问用户或请求。所以也许自定义模型字段/ListSerializer 会更适合这项任务。

        【讨论】:

          【解决方案4】:

          现在您可以使用我在此处描述的方法对 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 然后调用 super
          2. 将子类 ListSerializer 添加为嵌套 Serializer 上的元 list_serializer_class

          【讨论】:

          • 我也更喜欢这种方法,因为它仍然允许在需要时保持字段可写。
          【解决方案5】:

          我面临着同样的情况。我发现的最佳解决方案是使用SerializerMethodField 并让该方法查询并返回所需的值。通过self.context['request'].user,您可以通过该方法访问request.user

          不过,这似乎有点像 hack。我是 DRF 的新手,所以也许有更多经验的人可以加入。

          【讨论】:

          • 感谢您的建议。 SerializerMethodField 可以返回一个结构还是一个平面字段?
          • 它可以返回一个结构。
          • 我会尝试这种方法 - 谢谢。如果没有其他“官方”建议得到通过,我会接受这个作为答案。
          • 您的建议有效,谢谢!我已将我在序列化程序中使用的代码添加为对上述问题的编辑。
          猜你喜欢
          • 2018-05-17
          • 2017-10-19
          • 1970-01-01
          • 1970-01-01
          • 2017-06-28
          • 2016-05-14
          • 2014-02-17
          • 2014-12-15
          • 2017-06-16
          相关资源
          最近更新 更多