动态限制相关字段的查询集

时间:2013-03-10 22:43:01

标签: python django django-rest-framework

使用Django REST Framework,我想限制哪些值可以在创建中的相关字段中使用。

例如,请考虑此示例(基于http://django-rest-framework.org/api-guide/filtering.html上的过滤示例,但已更改为ListCreateAPIView):

class PurchaseList(generics.ListCreateAPIView)
    model = Purchase
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        user = self.request.user
        return Purchase.objects.filter(purchaser=user)

在此示例中,如何确保在创建时购买者可能只等于self.request.user,并且这是可浏览API渲染器中表单下拉列表中填充的唯一值?

9 个答案:

答案 0 :(得分:35)

我最终做了类似于Khamaileon suggested here的事情。基本上我修改了我的序列化程序以查看请求,哪种气味有问题,但它完成了工作...这是它的外观(例如购买示例):

class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
    def get_fields(self, *args, **kwargs):
        fields = super(PurchaseSerializer, self).get_fields(*args, **kwargs)
        fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)
        return fields

    class Meta:
        model = Purchase

allowed_objects是一个接受用户和查询的函数,并返回一个过滤的查询,该查询仅包含用户有权链接的对象。这似乎适用于验证和可浏览的API下拉字段。

答案 1 :(得分:13)

我是这样做的:

class PurchaseList(viewsets.ModelViewSet):
    ...
    def get_serializer(self, *args, **kwargs):
        serializer_class = self.get_serializer_class()
        context = self.get_serializer_context()
        return serializer_class(*args, request_user=self.request.user, context=context, **kwargs)

class PurchaseSerializer(serializers.ModelSerializer):
    ...
    def __init__(self, *args, request_user=None, **kwargs):
        super(PurchaseSerializer, self).__init__(*args, **kwargs)
        self.fields['user'].queryset = User._default_manager.filter(pk=request_user.pk)

答案 2 :(得分:7)

我不喜欢必须为我需要访问用户数据的每个地方覆盖init方法的样式,或者在运行时限制查询集的实例。所以我选择了this solution

以下是内联代码。

from rest_framework import serializers


class LimitQuerySetSerializerFieldMixin:
    """
    Serializer mixin with a special `get_queryset()` method that lets you pass
    a callable for the queryset kwarg. This enables you to limit the queryset
    based on data or context available on the serializer at runtime.
    """

    def get_queryset(self):
        """
        Return the queryset for a related field. If the queryset is a callable,
        it will be called with one argument which is the field instance, and
        should return a queryset or model manager.
        """
        # noinspection PyUnresolvedReferences
        queryset = self.queryset
        if hasattr(queryset, '__call__'):
            queryset = queryset(self)
        if isinstance(queryset, (QuerySet, Manager)):
            # Ensure queryset is re-evaluated whenever used.
            # Note that actually a `Manager` class may also be used as the
            # queryset argument. This occurs on ModelSerializer fields,
            # as it allows us to generate a more expressive 'repr' output
            # for the field.
            # Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
            queryset = queryset.all()
        return queryset


class DynamicQuersetPrimaryKeyRelatedField(LimitQuerySetSerializerFieldMixin, serializers.PrimaryKeyRelatedField):
    """Evaluates callable queryset at runtime."""
    pass


class MyModelSerializer(serializers.ModelSerializer):
    """
    MyModel serializer with a primary key related field to 'MyRelatedModel'.
    """
    def get_my_limited_queryset(self):
        root = self.root
        if root.instance is None:
            return MyRelatedModel.objects.none()
        return root.instance.related_set.all()

    my_related_model = DynamicQuersetPrimaryKeyRelatedField(queryset=get_my_limited_queryset)

    class Meta:
        model = MyModel

这样做的唯一缺点是您需要显式设置相关的序列化器字段,而不是使用ModelSerializer提供的自动字段发现。但是我会希望默认情况下这样的东西在rest_framework中。

答案 3 :(得分:4)

在django rest framework 3.0中,删除了get_fields方法。但是以类似的方式,您可以在序列化程序的init函数中执行此操作:

class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Purchase

    def __init__(self, *args, **kwargs):
        super(PurchaseSerializer, self).__init__(*args, **kwargs)
        if 'request' in self.context:
            self.fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)

我添加了if检查,因为如果在get方法的另一个序列化程序中使用PurchaseSerializer作为字段,请求将不会传递给上下文。

答案 4 :(得分:3)

首先确保只有传入的http POST / PUT时才允许“self.request.user”(这假设您的序列化程序和模型上的属性字面上被命名为“user”)

def validate_user(self, attrs, source):
    posted_user = attrs.get(source, None)
    if posted_user:
        raise serializers.ValidationError("invalid post data")
    else:
        user = self.context['request']._request.user
        if not user:
            raise serializers.ValidationError("invalid post data")
        attrs[source] = user
    return attrs

通过将上述内容添加到模型序列化程序,您可以确保只将request.user插入到数据库中。

2) - 关于你上面的过滤器(过滤器购买者=用户)我实际上建议使用自定义全局过滤器(以确保全局过滤)。我为一个软件作为我自己的服务应用程序做了一些事情,它有助于确保每个http请求被过滤掉(当有人试图查找他们无权访问的“对象”时,包括一个http 404 )

我最近在主分支中对此进行了修补,因此列表和单个视图都将过滤此

https://github.com/tomchristie/django-rest-framework/commit/1a8f07def8094a1e34a656d83fc7bdba0efff184

3) - 关于api渲染器 - 您是否让您的客户直接使用它?如果不是我会说避免它。如果你需要这个,可以添加一个有助于限制前端输入的自定义serlializer

答案 5 :(得分:1)

根据要求@ gabn88,您现在可能知道,使用DRF 3.0及更高版本,没有简单的解决方案。 即使您确实设法找到解决方案,它也不会很漂亮,并且很可能在后续版本的DRF上失败,因为它会覆盖一堆DRF源,这些源将在那时发生变化。

我忘记了我使用的确切实现,但想法是在序列化器上创建2个字段,一个是普通的序列化器字段(比如说PrimaryKeyRelatedField等...),另一个字段是序列化器方法字段,结果将是在某些情况下交换(例如基于请求,请求用户或其他)。这将在序列化器构造函数(即: init

上完成

您的序列化程序方法字段将返回所需的自定义查询。 您将弹出和/或交换这些字段结果,以便将序列化程序方法字段的结果相应地分配给普通/默认序列化程序字段(PrimaryKeyRelatedField等...)。这样你就可以处理那一个密钥(你的默认字段),而另一个密钥在你的应用程序中保持透明。

除此信息外,您真正需要的是修改它:http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

答案 6 :(得分:0)

我编写了一个自定义的CustomQueryHyperlinkedRelatedField类来概括这种行为:

class CustomQueryHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
    def __init__(self, view_name=None, **kwargs):
        self.custom_query = kwargs.pop('custom_query', None)
        super(CustomQueryHyperlinkedRelatedField, self).__init__(view_name, **kwargs)

    def get_queryset(self):
        if self.custom_query and callable(self.custom_query):
            qry = self.custom_query()(self)
        else:
            qry = super(CustomQueryHyperlinkedRelatedField, self).get_queryset()

        return qry

    @property
    def choices(self):
        qry = self.get_queryset()
        return OrderedDict([
            (
                six.text_type(self.to_representation(item)),
                six.text_type(item)
            )
            for item in qry
        ])

用法:

class MySerializer(serializers.HyperlinkedModelSerializer):
    ....
    somefield = CustomQueryHyperlinkedRelatedField(view_name='someview-detail',
                        queryset=SomeModel.objects.none(),
                        custom_query=lambda: MySerializer.some_custom_query)

    @staticmethod
    def some_custom_query(field):
        return SomeModel.objects.filter(somefield=field.context['request'].user.email)
    ...

答案 7 :(得分:0)

我做了以下事情:

class MyModelSerializer(serializers.ModelSerializer):
    myForeignKeyFieldName = MyForeignModel.objects.all()

    def get_fields(self, *args, **kwargs):
        fields = super(MyModelSerializer, self).get_fields()
        qs = MyModel.objects.filter(room=self.instance.id)
        fields['myForeignKeyFieldName'].queryset = qs
        return fields

答案 8 :(得分:0)

该示例链接似乎不再可用,但是通过阅读其他评论,我认为您正在尝试过滤与购买的用户关系。

如果我是正确的话,那么我可以说现在有一种官方的方式来做到这一点。经过django rest框架3.10.1的测试。

class UserPKField(serializers.PrimaryKeyRelatedField):
    def get_queryset(self):
        user = self.context['request'].user
        queryset = User.objects.filter(...)
        return queryset

class PurchaseSeriaizer(serializers.ModelSerializer):
    users = UserPKField(many=True)

    class Meta:
        model = Purchase
        fields = ('id', 'users')

与可浏览的API一起使用也是如此。

来源:

https://github.com/encode/django-rest-framework/issues/1985#issuecomment-328366412

https://medium.com/django-rest-framework/limit-related-data-choices-with-django-rest-framework-c54e96f5815e