django 从POST请求分析嵌套URL编码数据

rta7y2nd  于 2023-02-05  发布在  Go
关注(0)|答案(1)|浏览(120)

我目前正在Django中为Mailchimp中的一个webhook创建一个回调URL,Mailchimp将以application/x-www-form-urlencoded的形式发送一个带有urlencoded数据的POST请求。
我遇到的问题是返回的数据包含嵌套数据,这个urlencoded字符串中的一些数据看起来像它定义的嵌套JSON,我认为这是非标准的(尽管我可能错了)。
例如,当用户更改其名称时,Mailchimp会发送一个POST请求,如下所示:
type=profile&fired_at=2021-05-25+18%3A03%3A23&data%5Bid%5D=abcd1234&data%5Bemail%5D=test%40domain.com&data%5Bemail_type%5D=html&data%5Bip_opt%5D=0.0.0.0&data%5Bweb_id%5D=1234&data%5Bmerges%5D%5BEMAIL%5D=test%40domain.com&data%5Bmerges%5D%5BFNAME%5D=first_name&data%5Bmerges%5D%5BLNAME%5D=last_name&data%5Blist_id%5D=5678
使用Django的request.POST,数据被解码为:

{
    'type': 'profile',
    'fired_at': '2021-05-25 18:03:23',
    'data[id]': 'abcd1234',
    'data[email]': 'test@domain.com',
    'data[email_type]': 'html',
    'data[ip_opt]': '0.0.0.0',
    'data[web_id]': '1234',
    'data[merges][EMAIL]': 'test@domain.com',
    'data[merges][FNAME]': 'first_name',
    'data[merges][LNAME]': 'last_name',
    'data[list_id]': '5678'
}

这在实践中看起来非常难看,因为要从request.POST访问用户的名字,我们必须执行以下操作

request.POST.get("data['merges']['FNAME']", None)

数据显然是为了看起来像

{
    'type': 'profile',
    'fired_at': '2021-05-25 18:03:23',
    'data': {
        'id': 'abcd1234',
        'email': 'test@domain.com',
        'email_type': 'html',
        'ip_opt': '0.0.0.0',
        'web_id': '1234',
        'merges':{
            'email': 'test@domain.com',
            'fname': 'first_name',
            'lname': 'last_name',
        },
        'list_id': '5678'
    },
}

并且可以像

data = request.POST.get('data', None)
first_name = data['merges']['FNAME']

我已经寻找了一种Django/Python特有的方法来将这种嵌套的URL编码的数据解码成更适合在Python中使用的格式,但是没有找到任何东西。Python的urllib库提供了诸如urllib.parse.parse_qs()之类的方法来解码urlencoded字符串,但是这些方法不处理这种嵌套类型的数据。
有没有办法用Django/Python正确地解码这个嵌套的urlencoded数据?

5f0d552i

5f0d552i1#

没有标准的库或Django实用函数来实现这一点。
我们可以这样实现convert_form_dict_to_json_dict
1.将json_dict初始化为空dict {}
1.对于每个form_key,使用示例'data[merges][EMAIL]'
1.使用正则表达式获得nested_keys,即('data', 'merges', 'EMAIL')
1.确定last_nesting_level,即从嵌套级别(0, 1, 2)确定2
1.将current_dict初始化为json_dict
1.对于每个nesting_level, current_key,即0, 'data'1, 'merges'2, 'EMAIL'
1.如果它在last_nesting_level之前,则使用current_key获取下一个current_dict
1.否则,将current_keycurrent_dict条目设置为value
1.返回json_dict

import re

def convert_form_dict_to_json_dict(form_dict):
    json_dict = {}
    for form_key, value in form_dict.items():
        nested_keys = (re.match(r'\w+', form_key).group(0), *re.findall(r'\[(\w+)]', form_key))
        last_nesting_level = len(nested_keys) - 1
        current_dict = json_dict
        for nesting_level, current_key in enumerate(nested_keys):
            if nesting_level < last_nesting_level:
                current_dict = current_dict.setdefault(current_key, {})
            else:
                current_dict[current_key] = value
    return json_dict

用法:

POST_dict = {
    'type': 'profile',
    'fired_at': '2021-05-25 18:03:23',
    'data[id]': 'abcd1234',
    'data[email]': 'test@domain.com',
    'data[email_type]': 'html',
    'data[ip_opt]': '0.0.0.0',
    'data[web_id]': '1234',
    'data[merges][EMAIL]': 'test@domain.com',
    'data[merges][FNAME]': 'first_name',
    'data[merges][LNAME]': 'last_name',
    'data[list_id]': '5678'
}

from pprint import pprint
pprint(convert_form_dict_to_json_dict(POST_dict))

相关问题