如何将过滤器应用于 Django REST 框架中的嵌套资源?
Posted
技术标签:
【中文标题】如何将过滤器应用于 Django REST 框架中的嵌套资源?【英文标题】:How can I apply a filter to a nested resource in Django REST framework? 【发布时间】: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
非常感谢您的解决方案!
【问题讨论】:
【参考方案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://***.com/a/28354281/3246023
您可以继承 ListSerializer 并覆盖 to_representation 方法。
默认情况下,to_representation 方法在嵌套查询集上调用 data.all()。因此,您实际上需要在调用该方法之前使 data = data.filter(**your_filters) 。然后,您需要将您的子类 ListSerializer 添加为嵌套序列化程序元上的 list_serializer_class。
子类 ListSerializer,覆盖 to_representation 然后调用 super 将子类 ListSerializer 添加为嵌套 Serializer 上的元 list_serializer_class
【讨论】:
我也更喜欢这种方法,因为它仍然允许在需要时保持字段可写。【参考方案5】:我面临着同样的情况。我发现的最佳解决方案是使用SerializerMethodField
并让该方法查询并返回所需的值。通过self.context['request'].user
,您可以通过该方法访问request.user
。
不过,这似乎有点像 hack。我是 DRF 的新手,所以也许有更多经验的人可以加入。
【讨论】:
感谢您的建议。SerializerMethodField
可以返回一个结构还是一个平面字段?
它可以返回一个结构。
我会尝试这种方法 - 谢谢。如果没有其他“官方”建议得到通过,我会接受这个作为答案。
您的建议有效,谢谢!我已将我在序列化程序中使用的代码添加为对上述问题的编辑。以上是关于如何将过滤器应用于 Django REST 框架中的嵌套资源?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 objects.filter() 将字典过滤为 django rest 框架中的 POST 方法