我正在使用 DRF 串行器来处理具有单位测量值的模型。这是我的模型:
class Cargo(models.Model):
length = models.FloatField()
height = models.FloatField()
weight = models.FloatField()
input_length_unit = models.IntegerField(choices=LengthUnits.choices)
input_weight_unit = models.IntegerField(choices=WeightUnits.choices)
我有以下序列化器可以将
{"length": {"value": 10, "unit": 1}, ...}
等数据转换为我的模型架构:
class FloatWithUnitSerializer(serializers.Serializer):
value = serializers.FloatField()
unit = serializers.IntegerField()
def __init__(self, unit_type, *args, **kwargs):
super().__init__(*args, **kwargs)
self.unit_type = unit_type
def to_representation(self, instance):
value = getattr(instance, self.field_name)
unit = getattr(instance, f"input_{self.unit_type}_unit")
# convert from database unit to input unit
value = value * UNITS[self.unit_type][unit]
return {"value": value, "unit": unit}
def to_internal_value(self, data):
# convert from input unit to database unit
value = data["value"] / UNITS[self.unit_type][data["unit"]]
return {self.field_name: value, f"input_{self.unit_type}_unit": data["unit"]}
class CargoSerializer(serializers.ModelSerializer):
length = FloatWithUnitSerializer("length", required=False, source="*")
height = FloatWithUnitSerializer("length", required=False, source="*")
weight = FloatWithUnitSerializer("weight", required=False, source="*")
class Meta:
model = models.Cargo
fields = ["length", "height", "weight", "input_length_unit", "input_weight_unit"]
这可行,但现在我想防止单位混合。例如,给定单位类型“长度”,其中包括“长度”和“高度”字段(单位为米、英尺等),他们需要为两者发布相同的单位,例如
{"length": {"value": 10, "unit": 1}, "height": {"value:15", "unit": 1}}
。如果它们传递具有不同单位的相同单位类型的 2 个字段,例如{"length": {"value": 10, "unit": 1}, "height": {"value:15", "unit": 2}}
,我想提出一个ValidationError。我还将添加其他单位类型,例如面积和体积。我无法在 FloatWithUnitSerializer 内部验证这一点,因为我只有单个字段的数据,并且我不确定如何在 CargoSerializer 中验证这一点 - 当调用我的验证函数时, FloatWithUnitSerializer.to_internal_value 被调用,所以我只有长度、高度、input_length_unit等字段,并且不知道是否传递了多个长度单位。我如何验证这一点,或者是否有更简单的方法可以构建它?谢谢。
我最终像这样重写了 to_internal_value :
class CargoSerializer(serializers.ModelSerializer):
length = FloatWithUnitSerializer("length", required=False, source="*")
height = FloatWithUnitSerializer("length", required=False, source="*")
weight = FloatWithUnitSerializer("weight", required=False, source="*")
class Meta:
model = models.Cargo
fields = ["length", "height", "weight", "input_length_unit", "input_weight_unit"]
def to_internal_value(self, data):
# Note the only change from Django's default to_internal_value is called out below.
if not isinstance(data, Mapping):
message = self.error_messages["invalid"].format(datatype=type(data).__name__)
raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: [message]}, code="invalid")
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, "validate_" + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
# The only change: Don't allow multiple units per unit type
if isinstance(field, FloatWithUnitSerializer):
unit_field_name, unit = next(
(k, v) for k, v in validated_value.items() if k.startswith("input_")
)
current_unit = ret.get(unit_field_name)
if current_unit is not None and current_unit != unit:
errors[api_settings.NON_FIELD_ERRORS_KEY] = "Received mixed units."
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
要实现此验证,您可以重写
validate
中的 CargoSerializer
方法。在该方法中,您可以访问所有已验证的数据,包括length
、height
等字段,然后检查是否存在同一单位类型的混合单位。如果您发现混合单位,请提出 serializers.ValidationError
。
以下是如何修改
CargoSerializer
的示例:
from rest_framework import serializers
class CargoSerializer(serializers.ModelSerializer):
length = FloatWithUnitSerializer("length", required=False, source="*")
height = FloatWithUnitSerializer("height", required=False, source="*")
weight = FloatWithUnitSerializer("weight", required=False, source="*")
class Meta:
model = models.Cargo
fields = ["length", "height", "weight", "input_length_unit", "input_weight_unit"]
def validate(self, data):
# Validate unit consistency within the same unit type
length_unit = data.get("length", {}).get("unit")
height_unit = data.get("height", {}).get("unit")
weight_unit = data.get("weight", {}).get("unit")
if length_unit is not None and height_unit is not None and length_unit != height_unit:
raise serializers.ValidationError("Length and height must have the same unit.")
# Add similar checks for other unit types (e.g., weight, area, volume)
return data
在此示例中,
validate
方法检查长度和高度是否具有相同的单位。您可以根据需要将此模式扩展到其他单位类型。
这样,您就可以访问所有经过验证的数据,并且可以在将数据保存到模型之前执行跨字段验证。