如何在 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的主要内容,如果未能解决你的问题,请参考以下文章