在序列化程序字段查询集定义中使用序列化程序上下文
Using the serializer context in a Serializer field queryset definition
我正在寻找一种方法来使用 ModelViewSet 中定义的序列化器上下文,使用 get_serializer_context 用于特定 SlugRelatedField 的查询集声明:
class ReservationViewSet(ViewPermissionsMixin, viewsets.ModelViewSet):
serializer_class = ReservationSerializer
def get_queryset(self):
code = self.kwargs['project_code']
project= Project.objects.get(code=code)
queryset = Reservation.objects.filter(project=project)
return queryset
def get_serializer_context(self):
return {"project_code": self.kwargs['project_code'], 'request': self.request}
在所有序列化程序方法中,这都可以使用 self.context 访问,但我想使用上下文字典中的此信息过滤此字段的查询集:
class ReservationSerializer(serializers.ModelSerializer):
project= serializers.SlugRelatedField(slug_field='code', queryset=Project.objects.all(), required=False)
storage_location = serializers.SlugRelatedField(slug_field='description', queryset=StorageLocation.objects.filter(project__code = context['project_code'])), required=False)
此处应用到 StorageLocation 的查询集 (project__code = context['project_code']) 是我当前的问题所在。
一些额外的上下文:这个问题试图解决来自 rest_framework 的以下错误(StorageLocation 查询集被设置为 .all()):
projects.models.procurement.StorageLocation.MultipleObjectsReturned: get() 返回了多个 StorageLocation -- 它返回了 2!
为此,您需要创建自定义字段并覆盖 get_queryset
或 to_internal_value
的行为。在这种情况下使用 get_queryset
更简单,并且将所有良好的验证保留在基础 class 中,因此我们将使用它。
此示例字段使用非常通用的 过滤器样式。我是这样做的,所以它同样适用于遇到类似问题的任何人。
from typing import Optional, List
from rest_framework.relations import SlugRelatedField
class CustomSlugRelatedField(SlugRelatedField):
"""
Generic slug related field, with additional filters.
Filter functions take (queryset, context) and return a queryset
>>> class MySerializer:
>>> field = CustomSlugRelatedField(ModelClass, 'slug', filters=[
>>> lambda qs, ctx: qs.filter(field=ctx["value"])
>>> ])
"""
def __init__(self, model, slug_field: str, filters: Optional[List] = None):
assert isinstance(filters, list) or filters is None
super().__init__(slug_field=slug_field, queryset=model.objects.all())
self.filters = filters or []
def get_queryset(self):
qs = super().get_queryset()
for f in self.filters:
qs = f(qs, self.context)
return qs
class MySerializer(serializers.Serializer):
field = CustomSlugRelatedField(Product, 'slug', filters=[
lambda q, c: q.filter(product_code=c["product_code"])
])
此外,您应该修改 get_serializer_context
以先调用 super()
并在其上添加新数据。
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx.update(product_code=self.kwargs['product_code'])
return ctx
谢谢安德鲁,问题已解决:
- 由于这是我们的序列化程序中经常重复出现的模式,您的自定义字段方法是最干净的(稍作简化使其不太通用)
根据你的解决方案我也找到了这种方法,修改序列化程序的'get_fields'方法。如果模式经常出现,则不那么复杂,但也不那么干净:
class ReservationSerializer(serializers.ModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
fields['storage_location'].queryset = StorageLocation.objects.filter(project__code=self.context['project_code'])
return fields
我正在寻找一种方法来使用 ModelViewSet 中定义的序列化器上下文,使用 get_serializer_context 用于特定 SlugRelatedField 的查询集声明:
class ReservationViewSet(ViewPermissionsMixin, viewsets.ModelViewSet):
serializer_class = ReservationSerializer
def get_queryset(self):
code = self.kwargs['project_code']
project= Project.objects.get(code=code)
queryset = Reservation.objects.filter(project=project)
return queryset
def get_serializer_context(self):
return {"project_code": self.kwargs['project_code'], 'request': self.request}
在所有序列化程序方法中,这都可以使用 self.context 访问,但我想使用上下文字典中的此信息过滤此字段的查询集:
class ReservationSerializer(serializers.ModelSerializer):
project= serializers.SlugRelatedField(slug_field='code', queryset=Project.objects.all(), required=False)
storage_location = serializers.SlugRelatedField(slug_field='description', queryset=StorageLocation.objects.filter(project__code = context['project_code'])), required=False)
此处应用到 StorageLocation 的查询集 (project__code = context['project_code']) 是我当前的问题所在。
一些额外的上下文:这个问题试图解决来自 rest_framework 的以下错误(StorageLocation 查询集被设置为 .all()):
projects.models.procurement.StorageLocation.MultipleObjectsReturned: get() 返回了多个 StorageLocation -- 它返回了 2!
为此,您需要创建自定义字段并覆盖 get_queryset
或 to_internal_value
的行为。在这种情况下使用 get_queryset
更简单,并且将所有良好的验证保留在基础 class 中,因此我们将使用它。
此示例字段使用非常通用的 过滤器样式。我是这样做的,所以它同样适用于遇到类似问题的任何人。
from typing import Optional, List
from rest_framework.relations import SlugRelatedField
class CustomSlugRelatedField(SlugRelatedField):
"""
Generic slug related field, with additional filters.
Filter functions take (queryset, context) and return a queryset
>>> class MySerializer:
>>> field = CustomSlugRelatedField(ModelClass, 'slug', filters=[
>>> lambda qs, ctx: qs.filter(field=ctx["value"])
>>> ])
"""
def __init__(self, model, slug_field: str, filters: Optional[List] = None):
assert isinstance(filters, list) or filters is None
super().__init__(slug_field=slug_field, queryset=model.objects.all())
self.filters = filters or []
def get_queryset(self):
qs = super().get_queryset()
for f in self.filters:
qs = f(qs, self.context)
return qs
class MySerializer(serializers.Serializer):
field = CustomSlugRelatedField(Product, 'slug', filters=[
lambda q, c: q.filter(product_code=c["product_code"])
])
此外,您应该修改 get_serializer_context
以先调用 super()
并在其上添加新数据。
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx.update(product_code=self.kwargs['product_code'])
return ctx
谢谢安德鲁,问题已解决:
- 由于这是我们的序列化程序中经常重复出现的模式,您的自定义字段方法是最干净的(稍作简化使其不太通用)
根据你的解决方案我也找到了这种方法,修改序列化程序的'get_fields'方法。如果模式经常出现,则不那么复杂,但也不那么干净:
class ReservationSerializer(serializers.ModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
fields['storage_location'].queryset = StorageLocation.objects.filter(project__code=self.context['project_code'])
return fields