如何在 django REST 框架中修改 request.data

Posted

技术标签:

【中文标题】如何在 django REST 框架中修改 request.data【英文标题】:How can modify request.data in django REST framework 【发布时间】:2016-02-24 23:58:07 【问题描述】:

我正在使用 Django REST 框架

request.data = '"id": "10", "user": "tom"'

我想添加额外的属性,比如"age": "30",然后再发送给更多类似的对象

    request.data = new_data
    response = super().post(request, *args, **kwargs)

我有两个问题

    为什么 request.data 以字符串而不是字典的形式出现 如何更新 request.data

【问题讨论】:

请提供更多上下文,例如如何添加额外属性以及为什么要更新 request.data。请注意,更新 request.data 通常有点想法。 @Linovia 我想添加额外的属性request.data['age'] = 30。现在我不能这样做,因为 request.data 是字符串而不是字典。现在如果使用data = json.loads(request.data) 然后request.data = json_data 然后我得到错误无法根据请求设置属性 添加 "request.data['age'] = 30" 是您尝试做某事的方式,而不是您想要/需要这样做的原因。 @Linovia 因为在发布之前我需要更改 API 所需的字段名称 aqs,所以我需要更改请求数据 【参考方案1】:

它看起来像一个 json 字符串。要将其转换为 dict 你应该这样做:

import json
data = json.loads(request.data)

然后你可以添加额外的属性:

data['age'] = 30

然后您将不得不提出一个新请求,因为您似乎不能只更改旧请求。这假设您发布到 /notes/:

from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
request = factory.post('/notes/', data, format='json')

【讨论】:

如果我使用request.data = json_data,它表示无法根据请求设置属性 哎呀...虽然这行得通,但 DRF 应该有更简洁的方法来完成此操作。【参考方案2】:

根据您的评论:

“因为在发布之前我需要更改 API 所需的字段名称 aqs”

您应该改用Fieldsource argument。

这将使错误消息更加一致,否则您的用户将面临他们未提供的字段名称的错误。

【讨论】:

【参考方案3】:

如果您的端点是使用 DRF(Django REST 框架)ViewSet 实现的,则解决方案可以是实现相应的序列化程序类的 to_internal_value 方法并在那里修改数据。

class MyModelViewSet(viewsets.ModelViewSet):
    authentication_classes = ...
    ...
    serializer_class = MyModelSerializer


class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ('id', 'user', ...)

    def to_internal_value(self, data):
        instance = super(MyModelSerializer, self).to_internal_value(data)
        if "lastModified" in data:
            # instance["id"] = 10  # That's sketchy though
            instance["user"] = "tom"
        return instance

【讨论】:

顺便说一句,如果您覆盖ModelViewSetcreateupdate 方法,也可以进行一些操作。【参考方案4】:

我以不同的方式处理了这个问题。我重写了CreateAPIView的create方法,如下

class IPAnnotatedObject(CreateAPIView):
    model = IPAnnotatedObject
    queryset = IPAnnotatedObject.objects.all()
    serializer_class = IPAnnotatedObject
    def create(self, request, *args, **kwargs):
        request.data['ip_addr'] = self.get_ip()
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        ## perform_create calls serializer.save() which calls the serializer's create() method
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def get_ip(self):
        x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR',None)
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = self.request.META.get('REMOTE_ADDR',None)
        return ip

对应的序列化器类如下所示

class IPAnnotatedObjectSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(validators=[UniqueValidator(queryset=IPAnnotatedObject.objects.all())])
    password = serializers.CharField(write_only=True)
    ip_addr = serializers.IPAddressField(write_only=True)
    class Meta:
        model = IPAnnotatedObject
        fields = ['email','password','created_ip']

    def create(self, validated_data):
        email, password, created_ip = validated_data['email'], validated_data['password'],validated_data['created_ip']
        try:
            ipAnnoObject = IPAnnotatedObject.objects.create(email=email,password=make_password(password),ip_addr=ip_addr)
        except Exception as e:
            # you can think of better error handler
            pass
        return ipAnnoOjbect

【讨论】:

在通用视图中进行更改的最佳方法是覆盖 get_serializer 方法...这有点多余,因为如果您帮助创建序列化程序,您的序列化程序根本无法工作【参考方案5】:

一个好朋友带我去学校的方法比我上面说明的要简单得多

class CreateSomething(CreateAPIView):
    model = Something
    queryset = Something.objects.all()
    serializer_class = SomethingSerializer

    perform_create(self,serializer):
    def perform_create(self,serializer):
        ip = self.get_ip()
        ## magic here: add kwargs for extra fields to write to db
        serializer.save(ip_addr=ip)

    def get_ip(self):
        x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR',None)
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = self.request.META.get('REMOTE_ADDR',None)
        return ip

class SomethingSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(validators=[UniqueValidator(queryset=Something.objects.all())])
    fieldA = serializers.CharField()
    fieldB = serializers.CharField()

    class Meta:
        model = Customer2
        fields = ['email','fieldA','fieldB','ip_addr']
        read_only_fields = ['ip_addr']

【讨论】:

很遗憾没有更直观的方法来做到这一点。可能是一个更好的 mixin 或一些预定义的函数。 @Altus,有办法做到这一点,或者你的意思是 djangoish 方式?【参考方案6】:

通常 request 在 drf 视图中是 rest_framework.request.Request 实例。按照它的源代码(djangorestframework==3.8.2):

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data

你可以这样做:

request._full_data = your_data

【讨论】:

仅供参考访问私有方法或属性是一个非常糟糕的主意,但如果你没有其他选择,你“可以”这样做。【参考方案7】:

如果您的 API 是 APIView,那么您应该使用更新函数来扩展您的请求数据对象,而不会丢失从客户端发送的数据。

request.data.update("id": "10", "user": "tom")

【讨论】:

【参考方案8】:

request.data 应该是不可变的QueryDict,而不是字符串。如果需要修改:

if isinstance(request.data, QueryDict): # optional
    request.data._mutable = True
request.data['age'] = "30"

您可能会检查它是否是QueryDict 的实例的唯一原因是使用常规dict 进行单元测试更容易。

【讨论】:

【参考方案9】:

如果你害怕改变你的请求对象,那么使用深拷贝来复制对象,然后你就可以很容易地改变它。

用法::

from copy import deepcopy

# here is your other code and stuffs
data = deepcopy(request.data)

您可以随意更改数据,因为它现在是可变的。

到目前为止,如果不使用通用视图,这是我首选的更改方式。

对于此方法的任何缺点,请在下方评论!

【讨论】:

【参考方案10】:

我做了以下事情:

import json

data = json.dumps(request.data)
data = json.loads(data)

data['age'] = 100

现在使用变量data 而不是request.data

【讨论】:

【参考方案11】:

虽然其他答案很好,但我只想在这里添加一件事;

我们现在有两种方法来更新 request.data 对象但在此之前,请检查它是否是 QueryDict(正如 @mikebridge 已经提到的);

from django.http.request import QueryDict


if isInstance(request.data, QueryDict):
    request.data._mutable = True

之后,更新request.data,第一种方法是;

request.data.update('key': 'new_value')

这可以正常工作,但是如果您要更新的 request.data['key'] 是一个列表,那么 new_value 不会完全更改该值,但它会附加到旧的列出可能会引起麻烦的列表,您可能无法得到想要的结果。

所以要克服这个问题,即完全改变某个键的值,请使用第二种方法;

request.data['key'] = 'new_value'

这会将request.data['key'] 的值完全更改为new_value

【讨论】:

以上是关于如何在 django REST 框架中修改 request.data的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django REST 框架中使用事务?

如何在 django rest 框架中添加自定义权限和角色

如何在 django rest 框架中定义列表字段?

如何在 Django REST 框架中更改字段名称

如何在 django rest 框架 ModelSerializer 中覆盖模型字段验证

你如何在 django rest 框架中实现 CSRF 令牌?