DRF序列化器多嵌套字段单位一致性验证方案咨询
解决思路与实现方案
核心思路是在字段转换前获取原始输入数据完成单位一致性验证——DRF序列化器的to_internal_value方法是处理原始数据的首个环节,此时还未执行字段的单位转换,能直接拿到客户端传入的原始单位信息。
具体实现步骤
1. 定义单位一致性校验组
在CargoSerializer中明确哪些字段属于同一单位类型组,需要强制校验单位一致性:
from rest_framework import serializers from rest_framework.exceptions import ValidationError class CargoSerializer(serializers.ModelSerializer): # 假设你的字段使用嵌套序列化器定义 length = FloatWithUnitSerializer() height = FloatWithUnitSerializer() weight = FloatWithUnitSerializer() # 定义需校验单位一致性的字段组 UNIT_CONSISTENCY_GROUPS = [ ("length", "height"), # 长度类字段必须使用相同单位 # 可添加其他组,比如重量相关字段组 ]
2. 重写to_internal_value方法
在该方法中先完成单位一致性校验,再调用父类方法执行正常的字段转换:
def to_internal_value(self, data): # 先处理单位一致性校验 for field_group in self.UNIT_CONSISTENCY_GROUPS: units = [] for field_name in field_group: # 从原始输入数据中提取单位(根据你的嵌套结构调整) # 假设传入数据格式为 {"length": {"value": 10, "unit": "m"}, ...} field_data = data.get(field_name) if field_data: unit = field_data.get("unit") if unit: units.append(unit) # 检查组内单位是否完全一致 if units and len(set(units)) > 1: field_names = "和".join(field_group) raise ValidationError({ field_group: f"{field_names}必须使用相同单位,当前传入单位为{', '.join(units)}" }) # 校验通过后,执行父类的原始数据转换逻辑 return super().to_internal_value(data)
3. 适配非嵌套数据格式
如果字段是字符串格式(如"10m"),只需调整单位提取逻辑:
import re # 从字符串中提取单位的工具函数 def extract_unit(value_str): match = re.search(r"[a-zA-Z]+$", value_str) return match.group() if match else None # 在to_internal_value中替换单位提取逻辑: field_data = data.get(field_name) if field_data: unit = extract_unit(field_data) if unit: units.append(unit)
替代方案:自定义字段保留原始单位
若不想重写to_internal_value,可将FloatWithUnitSerializer改为自定义字段,在字段内部保留原始单位,再在序列化器的validate方法中做跨字段校验:
class FloatWithUnitField(serializers.Field): def to_internal_value(self, data): # 解析数值和单位 value = data.get("value") unit = data.get("unit") # 单字段单位合法性校验 if unit not in ALLOWED_UNITS: raise ValidationError(f"不支持的单位:{unit}") # 返回包含转换后值和原始单位的字典 converted_value = convert_unit(value, unit, target_unit="m") # 你的单位转换逻辑 return { "value": converted_value, "original_unit": unit } class CargoSerializer(serializers.ModelSerializer): length = FloatWithUnitField() height = FloatWithUnitField() def validate(self, attrs): # 提取原始单位并校验一致性 length_unit = attrs["length"]["original_unit"] height_unit = attrs["height"]["original_unit"] if length_unit != height_unit: raise ValidationError("length和height必须使用相同单位") # 最终只保留转换后的值(按需调整) attrs["length"] = attrs["length"]["value"] attrs["height"] = attrs["height"]["value"] return attrs
该方案将字段解析逻辑封装在自定义字段中,跨字段校验逻辑更清晰。
内容的提问来源于stack exchange,提问作者Luciano
相关产品推荐
相关产品推荐

