Django的Querydict奇怪行为:将多个POST字典合并为单个键

6 投票
3 回答
5527 浏览
提问于 2025-04-16 19:22

我在使用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 个回答

0

但是你把它声明成了一个字符串——你在data的值周围用了单引号。

1

编辑:当然,我开始查看的那一行上面就是正确的答案。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之前,在你的代码中进行上述转换,但我还没有测试过。

7

问题在于你提供了一个内容类型(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数据负载。

撰写回答