【问题标题】:Using the serializer context in a Serializer field queryset definition在序列化器字段查询集定义中使用序列化器上下文
【发布时间】:2021-05-08 16:22:20
【问题描述】:

我正在寻找一种方法来使用 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 个!

【问题讨论】:

    标签: python django-models serialization django-rest-framework drf-queryset


    【解决方案1】:

    为此,您需要创建一个自定义字段并覆盖get_querysetto_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
    

    【讨论】:

    • 谢谢 Andrew,您的通用示例已经解释了很多:如果我是对的,上下文字典会从序列化程序传递到它的字段,在那里它可以用于通过修改来创建所需的查询我们自定义 slugrelatedfield 的 get_queryset 方法。在那种情况下,我还可以使用 django.db.models.Q 将自定义过滤器传递到上下文中并在此处应用它们?
    • 您可以根据需要过滤查询集,是的。我的意见是在过滤器中进行过滤,而不是在上下文中传递预先构造的 Q 对象。
    【解决方案2】:

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

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

    根据您的解决方案,我也找到了这种方式,修改了序列化程序的“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
    

    【讨论】:

      猜你喜欢
      • 2018-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-24
      • 2019-03-16
      相关资源
      最近更新 更多