Django的Querydict奇怪行为:将多个POST字典合并为单个键
我在使用Django的测试客户端时遇到了一些奇怪的问题。
我用POST
方法向我的Django应用发送数据。通常我是在iPhone应用或者测试用的HTML表单中这样做的。在服务器端,我是这样处理这些数据的:
def handle_query(request):
print request
q = con.QueryLog()
q.ID = request.POST.get('ID', '')
q.device = request.POST.get('device-model', '')
....
这个打印语句看起来是正常的,也就是说,POST请求中的每个参数都变成了字典中的一个键:
POST: QueryDict: {u'app-version': [u'3.0'], u'server-version': [u'v3d0'],
但是,我开始用Django的测试客户端写一些测试,无论我怎么尝试,我发送的POST参数字典在QueryDict
中都被归为一个单一的键。让我用一些代码来说明:
class SearchTest(TestCase):
def setUp(self):
pass
def test_search(self):
request = HttpRequest()
data = '{"amzn_locale": "com"}'
# request._raw_post_data = data
resp = self.client.post(
'/is/',
data=data,
content_type='application/x-www-form-urlencoded',
# content_type='application/json',
)
在服务器端,打印语句显示字典被莫名其妙地归为一个字符串:
POST: QueryDict: {u'{"amzn_locale":"com"}': [u'']}>,
如果我把数据设置为一个实际的字典,结果也是一样。
data = {"amzn_locale": "com"}
设置request._raw_post_data并没有改变任何东西。更改
content_type='application/json'
也没有效果。希望能得到一些帮助。从这个stackoverflow的问题来看,似乎我不是第一个遇到这个问题的人。iPhone的Json POST请求到Django服务器会创建QueryDict中的QueryDict
3 个回答
但是你把它声明成了一个字符串——你在data
的值周围用了单引号。
编辑:当然,我开始查看的那一行上面就是正确的答案。post_data的处理方式是根据content_type来决定的。请看下面的答案。下面的更改不需要应用。
看起来发生的事情是,你传给post的数据字典会被一个字符串编码的函数立即转化成字符串形式,这样后面的QueryDict就无法读取了。我不太清楚预期的行为是什么,但如果你在数据被转化成字符串之前先对post数据进行url编码,那么它至少能以所需的格式到达QueryDict。在django/test/client.py的第244行,我们可以看到
post_data = smart_str(data, encoding=charset)
这行代码就是把字典转化成字符串并进行序列化。一个可能的解决办法是在序列化之前,先应用和GET请求相同的格式化方式,因此
post_data = smart_str(urlencode(data, doseq=True), encoding=charset)
我觉得这个方法是合理的,尽管我不能保证它不会在其他地方产生影响。看起来你可以在调用client.post之前,在你的代码中进行上述转换,但我还没有测试过。
问题在于你提供了一个内容类型(content_type)。因为你这么做了,客户端就期待收到一个像下面这样的url编码字符串:
"username=hi&password=there&this_is_the_login_form=1"
而不是像下面这样的字典:
{'username': 'hi', 'password': 'there', 'this_is_the_login_form': 1}
如果你去掉这个内容类型的参数,就没问题了。
补充说明:实际上,如果你传入的内容类型不是MULTIPART_CONTENT,测试客户端会寻找一个url编码的字符串——这个内容类型只会用来决定用什么字符集来编码这个url编码的字符串。这个内容在这里有说明。相关内容是:
如果你提供了内容类型(比如,XML数据的text/xml),那么数据的内容会原样发送在POST请求中,并在HTTP的Content-Type头中使用这个内容类型。
如果你没有提供内容类型,数据中的值会以multipart/form-data的内容类型传输。在这种情况下,数据中的键值对会被编码成一个多部分消息,用来创建POST数据负载。