在序列化程序字段查询集定义中使用序列化程序上下文

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_querysetto_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