0

我正在寻找一种使用在特定 SlugRelatedField 的查询集声明中使用的 get_serializer_context 在 ModelViewSet 中定义的序列化器上下文的方法:

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 个!

4

2 回答 2

1

为此,您需要创建一个自定义字段并覆盖get_queryset或的行为to_internal_value。在这种情况下使用get_queryset更简单,并将所有良好的验证保留在基类中,因此我们将使用它。

此示例字段使用非常通用的过滤器样式。我已经这样做了,因此它同样适用于遇到类似问题的任何人。

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
于 2021-02-04T21:14:56.523 回答
0

谢谢安德鲁,问题解决了:

  • 由于这是我们的序列化程序中经常出现的模式,因此您的自定义字段方法是最干净的(稍作简化使其不太通用)

根据您的解决方案,我也找到了这种方式,修改了序列化程序的“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
于 2021-02-09T08:15:03.727 回答