You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:26:31