如何在 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”
您应该改用Field
的source
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
【讨论】:
顺便说一句,如果您覆盖ModelViewSet
的create
和update
方法,也可以进行一些操作。【参考方案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的主要内容,如果未能解决你的问题,请参考以下文章