Django Rest框架:视图中如何兼容JSON与URL编码格式的数据访问?
统一处理DRF中JSON与表单提交的嵌套字段访问
我在Django Rest Framework的视图POST方法中需要访问request.data里的嵌套信息,请求数据的预期格式如下:
{ ... "licence": { "tenant_name": "tenant1", ... } }
因为使用默认的JSONParser和FormParser解析器,请求可能携带JSON或HTML表单内容,且不想修改默认的parser_classes。这导致request.data的结构会随内容类型变化:
- HTML表单编码时:
<QueryDict: {..., 'licence.tenant_name': ['tenant1']}> - JSON编码时:
{..., 'licence': {'tenant_name': 'tenant1'}}
目前我通过判断request.data的类型来处理,但想知道有没有更通用的解决方案,当前代码如下:
class SubscribeView(views.APIView): serializer_class = SubscriptionSerializer permission_classes = (AllowAny, ) def post(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) tenant_name = request.data['licence']['tenant_name'] if type(request.data) is dict else request.data['licence.tenant_name'] # 对tenant_name执行操作 serializer.save() status_code = status.HTTP_201_CREATED response = { 'success': True, 'status_code': status_code, 'message': 'New subscription successfully created', 'subscription': serializer.data } return Response(response, status=status_code)
方案1:直接利用序列化器的validated_data
既然已经调用了serializer.is_valid(),最简洁的方式是直接从serializer.validated_data中取值。DRF序列化器会自动处理两种提交格式的字段映射,不管是JSON的嵌套结构还是表单的点分隔字段,只要序列化器定义正确,validated_data都会统一成嵌套字典格式:
def post(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) # 直接从validated_data获取,格式统一为嵌套字典 tenant_name = serializer.validated_data['licence']['tenant_name'] # 执行操作 serializer.save() # 后续响应代码不变
前提:需要在序列化器中正确定义嵌套字段,示例如下:
class LicenceSerializer(serializers.Serializer): tenant_name = serializers.CharField() # 其他licence相关字段... class SubscriptionSerializer(serializers.Serializer): licence = LicenceSerializer() # 其他订阅相关字段...
方案2:编写工具函数统一解析嵌套字段
如果不想依赖序列化器,可以写一个工具函数,同时支持字典和QueryDict的嵌套字段获取:
def get_nested_data(data, key_path): """ 从data中按路径获取嵌套字段,兼容字典(JSON)和QueryDict(表单) key_path格式示例:'licence.tenant_name' """ keys = key_path.split('.') # 先尝试字典式嵌套访问 try: current = data for key in keys: current = current[key] return current except (KeyError, TypeError): # 尝试直接访问点分隔的键(QueryDict场景) return data.get(key_path)
视图中使用方式:
def post(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) tenant_name = get_nested_data(request.data, 'licence.tenant_name') # 执行操作 serializer.save() # 后续响应代码不变
方案3:将QueryDict转换为嵌套字典
把QueryDict转成普通字典后,再展开点分隔的键为嵌套结构,统一数据格式:
def flatten_to_nested(data): """将带点分隔键的字典/QueryDict转换为嵌套字典""" nested = {} for key, value in data.items(): keys = key.split('.') current = nested for k in keys[:-1]: current = current.setdefault(k, {}) # 处理QueryDict中值为列表的情况 current[keys[-1]] = value[0] if isinstance(value, list) else value return nested
视图中使用方式:
def post(self, request): # 统一转换为嵌套字典格式 normalized_data = flatten_to_nested(request.data) serializer = self.serializer_class(data=normalized_data) serializer.is_valid(raise_exception=True) tenant_name = normalized_data['licence']['tenant_name'] # 执行操作 serializer.save() # 后续响应代码不变
内容的提问来源于stack exchange,提问作者browser-bug
相关产品推荐
相关产品推荐

