动态限制相关领域的查询集(Dynamically limiting queryset of rela

2019-07-21 07:22发布

使用Django的REST框架,我想限制其值可以在相关领域的创作中。

例如,考虑本实施例中(基于在滤波示例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渲染器唯一的价值?

Answer 1:

最后我做类似的事如何Khamaileon这里建议 。 基本上,我修改了我的序列化不期而遇的请求,这样的气味不对,但它能够完成任务......这里是它的外观(与购买,例如examplified):

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

permitted_objects是一个函数,它接受一个用户查询,并返回一个过滤查询只包含对象的用户有权限链接。 这似乎既为验证和可浏览的API下拉菜单里干活。



Answer 2:

下面是我如何做到这一点:

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)


Answer 3:

我不喜欢不必重写init方法对每一个地方我需要在运行时访问用户数据或实例来限制查询集的地方风格。 所以,我选择了这个解决方案 。

这里是内联代码。

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。



Answer 4:

在Django其余框架3.0除去get_fields方法。 但是,以类似的方式,你可以在串行器的初始化函数做到这一点:

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)

我加入了,如果检查,因为如果你在GET方法的另一个串行使用PurchaseSerializer的领域,请求将不会被传递到上下文。



Answer 5:

首先,确保你只允许“self.request.user”当你有一个进入的HTTP POST / PUT(这里假设你的序列化和模型属性被命名为“用户”的字面)

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,这将有助于限制对前端输入



Answer 6:

根据要求@ gabn88,你可能已经知道,有DRF 3.0及以上,没有简单的解决方案。 即使你设法找出解决的办法,也不会是漂亮,将在DRF的后续版本很可能会失败,因为它会覆盖一堆将由则已经改变DRF源。

我忘记了我所使用的具体实现,但这个想法是建立在串行2场,一个正常的串行场(可以说PrimaryKeyRelatedField等),以及其他领域中的串行方法场,其结果将在被交换某些情况下(如基于该请求,请求用户,或其他)。 这对串行构造函数来完成(即:INIT)

你的串行方法字段将返回所需的自定义查询。 你会弹出和/或交换这些领域的结果,让您的串行方法场的结果将被分配到正常/默认的序列字段(PrimaryKeyRelatedField等)相应。 这样,你总是处理一个键(默认域),而其他关键仍然是你的应用程序中是透明的。

伴随着这个信息,你真正需要的是修改此: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields



Answer 7:

我写了一个自定义的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)
    ...


Answer 8:

我做了以下内容:

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


文章来源: Dynamically limiting queryset of related field