如何自定义ModelSerializer的验证错误消息?含模型与序列化器示例
自定义ModelSerializer的验证错误消息格式
嘿,我完全明白你想要把DRF默认的验证错误改成更结构化的格式,每个错误都用{"error": "code"}的形式呈现(还能带可选参数),并且整体包裹在errors键下。下面是一步步实现的方案,完全贴合你的需求:
1. 给每个字段配置自定义错误标识
首先,我们需要替换DRF默认的错误消息,直接映射成你需要的错误码(比如blank、inclusion)。通过extra_kwargs可以轻松给每个字段配置对应的错误映射:
class RegistrationSerializer(serializers.ModelSerializer): class Meta: model = Person exclude = ['last_login'] extra_kwargs = { 'first_name': { 'error_messages': { 'blank': 'blank', 'required': 'blank', # 把required错误统一映射为blank 'max_length': 'too_long', } }, 'last_name': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'max_length': 'too_long', } }, 'country_code': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_choice': 'inclusion', # 无效选项映射为inclusion } }, 'phone_number': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid': 'invalid', 'unique': 'taken', # 重复值映射为taken 'min_length': 'too_short', 'max_length': 'too_long', } }, 'gender': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_choice': 'inclusion', } }, 'birth_date': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid': 'invalid', } }, 'avatar': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_image': 'invalid_content_type', # 无效图片映射为指定标识 } }, 'email': { 'error_messages': { 'invalid': 'invalid', 'unique': 'taken', } }, }
2. 重写errors属性,转换格式
DRF默认的errors是{字段名: [错误消息列表]}的结构,我们需要把它改成你想要的嵌套格式。通过重写序列化器的errors属性就能实现:
class RegistrationSerializer(serializers.ModelSerializer): # ... 上面的Meta代码 ... @property def errors(self): # 先获取DRF默认生成的错误结构 default_errors = super().errors # 初始化我们目标格式的错误字典 formatted_errors = {"errors": {}} for field, messages in default_errors.items(): formatted_errors["errors"][field] = [] for msg in messages: error_item = {"error": msg} # 处理需要额外参数的错误,比如too_short/too_long if msg in ["too_short", "too_long"]: # 从字段定义中获取对应的长度限制值 field_obj = self.fields[field] count = field_obj.min_length if msg == "too_short" else field_obj.max_length error_item["count"] = count formatted_errors["errors"][field].append(error_item) return formatted_errors
3. 添加自定义验证逻辑(比如未来日期检查)
DRF默认不会检查生日是否在未来,这类自定义规则需要我们自己添加字段验证方法:
class RegistrationSerializer(serializers.ModelSerializer): # ... 上面的Meta和errors属性 ... def validate_birth_date(self, value): if value > datetime.date.today(): raise serializers.ValidationError("in_the_future") return value
同理,phone_number的not_a_number或not_exist这类自定义验证,也可以用同样的方式添加:
def validate_phone_number(self, value): # 示例:检查去除+号后是否全为数字 raw_number = str(value).lstrip('+') if not raw_number.isdigit(): raise serializers.ValidationError("not_a_number") # 可选:添加你自己的号码存在检查逻辑,比如调用外部API # if not check_phone_number_exists(value): # raise serializers.ValidationError("not_exist") return value
完整代码整合
把所有部分拼起来,完整的序列化器代码就是这样:
import datetime from rest_framework import serializers from .models import Person class RegistrationSerializer(serializers.ModelSerializer): class Meta: model = Person exclude = ['last_login'] extra_kwargs = { 'first_name': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'max_length': 'too_long', } }, 'last_name': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'max_length': 'too_long', } }, 'country_code': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_choice': 'inclusion', } }, 'phone_number': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid': 'invalid', 'unique': 'taken', 'min_length': 'too_short', 'max_length': 'too_long', } }, 'gender': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_choice': 'inclusion', } }, 'birth_date': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid': 'invalid', } }, 'avatar': { 'error_messages': { 'blank': 'blank', 'required': 'blank', 'invalid_image': 'invalid_content_type', } }, 'email': { 'error_messages': { 'invalid': 'invalid', 'unique': 'taken', } }, } def validate_birth_date(self, value): if value > datetime.date.today(): raise serializers.ValidationError("in_the_future") return value def validate_phone_number(self, value): raw_number = str(value).lstrip('+') if not raw_number.isdigit(): raise serializers.ValidationError("not_a_number") # 可选:添加号码存在检查逻辑 # if not check_phone_exists(value): # raise serializers.ValidationError("not_exist") return value @property def errors(self): default_errors = super().errors formatted_errors = {"errors": {}} for field, messages in default_errors.items(): formatted_errors["errors"][field] = [] for msg in messages: error_item = {"error": msg} if msg in ["too_short", "too_long"]: field_obj = self.fields[field] count = field_obj.min_length if msg == "too_short" else field_obj.max_length error_item["count"] = count formatted_errors["errors"][field].append(error_item) return formatted_errors
测试效果
当你用这个序列化器验证不合法的数据时,返回的错误就会完全符合你期望的格式啦!比如传入空的first_name和未来的生日,响应会是:
{ "errors": { "first_name": [ { "error": "blank" } ], "birth_date": [ { "error": "in_the_future" } ] } }
内容的提问来源于stack exchange,提问作者Salah Hegazi
相关产品推荐
相关产品推荐

