DRF 字段类型与验证 #

一、字段类型详解 #

1.1 数值字段 #

python
from rest_framework import serializers

class NumberSerializer(serializers.Serializer):
    age = serializers.IntegerField(
        min_value=0,
        max_value=150,
        error_messages={
            'min_value': '年龄不能小于0',
            'max_value': '年龄不能大于150'
        }
    )
    
    score = serializers.FloatField(
        min_value=0.0,
        max_value=100.0
    )
    
    price = serializers.DecimalField(
        max_digits=10,
        decimal_places=2,
        min_value=0
    )

1.2 字符串字段 #

python
class StringSerializer(serializers.Serializer):
    name = serializers.CharField(
        max_length=100,
        min_length=2,
        trim_whitespace=True
    )
    
    email = serializers.EmailField()
    
    website = serializers.URLField()
    
    slug = serializers.SlugField()
    
    regex_field = serializers.RegexField(
        regex=r'^[a-zA-Z0-9]+$',
        error_messages={'invalid': '只能包含字母和数字'}
    )
    
    uuid = serializers.UUIDField()
    
    file_path = serializers.FilePathField(
        path='/path/to/files',
        recursive=True
    )

1.3 布尔字段 #

python
class BooleanSerializer(serializers.Serializer):
    is_active = serializers.BooleanField()
    
    is_verified = serializers.BooleanField(
        default=False,
        null=True
    )
    
    agree_terms = serializers.BooleanField(
        required=True,
        error_messages={'required': '必须同意条款'}
    )

1.4 日期时间字段 #

python
from datetime import datetime

class DateTimeSerializer(serializers.Serializer):
    created_at = serializers.DateTimeField(
        format='%Y-%m-%d %H:%M:%S',
        input_formats=['%Y-%m-%d', '%Y-%m-%d %H:%M', 'iso-8601']
    )
    
    birth_date = serializers.DateField(
        format='%Y-%m-%d'
    )
    
    meeting_time = serializers.TimeField(
        format='%H:%M:%S'
    )
    
    duration = serializers.DurationField()

1.5 选择字段 #

python
STATUS_CHOICES = [
    ('draft', '草稿'),
    ('published', '已发布'),
    ('archived', '已归档'),
]

class ChoiceSerializer(serializers.Serializer):
    status = serializers.ChoiceField(
        choices=STATUS_CHOICES,
        default='draft'
    )
    
    multiple_choice = serializers.MultipleChoiceField(
        choices=STATUS_CHOICES
    )

1.6 文件字段 #

python
class FileSerializer(serializers.Serializer):
    file = serializers.FileField(
        max_length=100,
        allow_empty_file=False,
        use_url=True
    )
    
    image = serializers.ImageField(
        max_length=100,
        allow_empty_file=False
    )

1.7 复杂字段 #

python
class ComplexSerializer(serializers.Serializer):
    tags = serializers.ListField(
        child=serializers.CharField(max_length=50),
        allow_empty=False,
        min_length=1,
        max_length=10
    )
    
    metadata = serializers.DictField(
        child=serializers.CharField()
    )
    
    json_field = serializers.JSONField()
    
    points = serializers.ListField(
        child=serializers.ListField(
            child=serializers.FloatField(),
            min_length=2,
            max_length=2
        )
    )

二、字段参数详解 #

2.1 通用参数 #

python
class CommonParamsSerializer(serializers.Serializer):
    field = serializers.CharField(
        required=True,
        default='',
        allow_null=False,
        allow_blank=False,
        read_only=False,
        write_only=False,
        label='字段标签',
        help_text='字段帮助文本',
        error_messages={
            'required': '此字段必填',
            'blank': '不能为空字符串',
            'null': '不能为null'
        }
    )

2.2 参数说明表 #

参数 类型 说明
required bool 是否必需
default any 默认值
allow_null bool 是否允许null
allow_blank bool 是否允许空字符串
read_only bool 只读字段
write_only bool 只写字段
label str 字段标签
help_text str 帮助文本
validators list 验证器列表
error_messages dict 错误消息

2.3 数值字段参数 #

python
class NumberParamsSerializer(serializers.Serializer):
    value = serializers.IntegerField(
        min_value=0,
        max_value=100
    )
    
    decimal_value = serializers.DecimalField(
        max_digits=10,
        decimal_places=2,
        min_value=0,
        max_value=99999999.99,
        rounding='ROUND_HALF_UP'
    )

2.4 字符串字段参数 #

python
class StringParamsSerializer(serializers.Serializer):
    text = serializers.CharField(
        max_length=100,
        min_length=1,
        trim_whitespace=True,
        allow_blank=False
    )

三、内置验证器 #

3.1 UniqueValidator #

python
from rest_framework.validators import UniqueValidator

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(
        validators=[
            UniqueValidator(queryset=User.objects.all())
        ]
    )
    
    email = serializers.EmailField(
        validators=[
            UniqueValidator(
                queryset=User.objects.all(),
                message='该邮箱已被注册'
            )
        ]
    )

3.2 UniqueTogetherValidator #

python
from rest_framework.validators import UniqueTogetherValidator

class ArticleSerializer(serializers.Serializer):
    class Meta:
        validators = [
            UniqueTogetherValidator(
                queryset=Article.objects.all(),
                fields=['title', 'author'],
                message='该作者已存在相同标题的文章'
            )
        ]

3.3 BaseUniqueValidator #

python
from rest_framework.validators import BaseUniqueValidator

class CustomUniqueValidator(BaseUniqueValidator):
    def filter_queryset(self, value, queryset):
        return queryset.filter(field=value)

3.4 ProhibitSurrogateCharactersValidator #

python
from rest_framework.validators import ProhibitSurrogateCharactersValidator

class TextSerializer(serializers.Serializer):
    content = serializers.CharField(
        validators=[ProhibitSurrogateCharactersValidator()]
    )

四、自定义验证 #

4.1 字段级验证 #

python
class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=200)
    
    def validate_title(self, value):
        if len(value) < 5:
            raise serializers.ValidationError('标题至少需要5个字符')
        
        forbidden_words = ['广告', '推销', '垃圾']
        for word in forbidden_words:
            if word in value:
                raise serializers.ValidationError(f'标题不能包含"{word}"')
        
        return value

4.2 对象级验证 #

python
class EventSerializer(serializers.Serializer):
    title = serializers.CharField()
    start_date = serializers.DateField()
    end_date = serializers.DateField()
    max_participants = serializers.IntegerField()
    current_participants = serializers.IntegerField()
    
    def validate(self, data):
        if data['start_date'] > data['end_date']:
            raise serializers.ValidationError({
                'end_date': '结束日期不能早于开始日期'
            })
        
        if data['current_participants'] > data['max_participants']:
            raise serializers.ValidationError('当前参与人数不能超过最大人数')
        
        return data

4.3 验证器类 #

python
class ForbiddenWordsValidator:
    def __init__(self, forbidden_words):
        self.forbidden_words = forbidden_words

    def __call__(self, value):
        for word in self.forbidden_words:
            if word in value:
                raise serializers.ValidationError(f'不能包含"{word}"')

class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(
        validators=[ForbiddenWordsValidator(['广告', '垃圾'])]
    )

4.4 复杂验证器 #

python
import re

class PasswordValidator:
    def __init__(self, min_length=8):
        self.min_length = min_length

    def __call__(self, value):
        if len(value) < self.min_length:
            raise serializers.ValidationError(
                f'密码至少需要{self.min_length}个字符'
            )
        
        if not re.search(r'[A-Z]', value):
            raise serializers.ValidationError('密码必须包含大写字母')
        
        if not re.search(r'[a-z]', value):
            raise serializers.ValidationError('密码必须包含小写字母')
        
        if not re.search(r'[0-9]', value):
            raise serializers.ValidationError('密码必须包含数字')
        
        if not re.search(r'[!@#$%^&*]', value):
            raise serializers.ValidationError('密码必须包含特殊字符')

class UserSerializer(serializers.Serializer):
    password = serializers.CharField(
        validators=[PasswordValidator(min_length=8)]
    )

五、条件验证 #

5.1 基于其他字段的验证 #

python
class ArticleSerializer(serializers.Serializer):
    is_published = serializers.BooleanField(default=False)
    published_at = serializers.DateTimeField(required=False)
    
    def validate(self, data):
        if data.get('is_published') and not data.get('published_at'):
            raise serializers.ValidationError({
                'published_at': '发布文章必须设置发布时间'
            })
        return data

5.2 基于请求的验证 #

python
class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField()
    
    def validate_title(self, value):
        request = self.context.get('request')
        if request and not request.user.is_staff:
            if len(value) > 50:
                raise serializers.ValidationError('普通用户标题不能超过50字符')
        return value

5.3 基于实例的验证 #

python
class ArticleSerializer(serializers.Serializer):
    status = serializers.CharField()
    
    def validate_status(self, value):
        instance = self.instance
        if instance and instance.status == 'published':
            if value != 'published':
                raise serializers.ValidationError('已发布的文章不能修改状态')
        return value

六、错误消息处理 #

6.1 自定义错误消息 #

python
class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(
        max_length=200,
        error_messages={
            'required': '标题不能为空',
            'blank': '标题不能为空白',
            'max_length': '标题不能超过{max_length}个字符',
            'min_length': '标题至少需要{min_length}个字符'
        }
    )

6.2 动态错误消息 #

python
class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=200)
    
    def validate_title(self, value):
        if 'test' in value.lower():
            raise serializers.ValidationError(
                detail={'title': '标题不能包含"test"'},
                code='invalid_title'
            )
        return value

6.3 格式化错误响应 #

python
from rest_framework.exceptions import ValidationError

def format_errors(errors):
    formatted = []
    for field, messages in errors.items():
        if isinstance(messages, list):
            for message in messages:
                formatted.append({
                    'field': field,
                    'message': message
                })
        elif isinstance(messages, dict):
            for sub_field, sub_messages in messages.items():
                for message in sub_messages:
                    formatted.append({
                        'field': f'{field}.{sub_field}',
                        'message': message
                    })
    return formatted

class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField()
    
    def validate(self, data):
        errors = {}
        if not data.get('title'):
            errors['title'] = ['标题不能为空']
        if errors:
            raise ValidationError(format_errors(errors))
        return data

七、字段组合验证 #

7.1 至少一个字段必填 #

python
class SearchSerializer(serializers.Serializer):
    keyword = serializers.CharField(required=False)
    category = serializers.CharField(required=False)
    author = serializers.CharField(required=False)
    
    def validate(self, data):
        if not any([data.get('keyword'), data.get('category'), data.get('author')]):
            raise serializers.ValidationError('至少需要提供一个搜索条件')
        return data

7.2 互斥字段 #

python
class PaymentSerializer(serializers.Serializer):
    credit_card = serializers.CharField(required=False)
    bank_account = serializers.CharField(required=False)
    
    def validate(self, data):
        credit_card = data.get('credit_card')
        bank_account = data.get('bank_account')
        
        if credit_card and bank_account:
            raise serializers.ValidationError('只能选择一种支付方式')
        if not credit_card and not bank_account:
            raise serializers.ValidationError('必须选择一种支付方式')
        
        return data

7.3 条件必填 #

python
class OrderSerializer(serializers.Serializer):
    delivery_type = serializers.ChoiceField(choices=['pickup', 'delivery'])
    address = serializers.CharField(required=False)
    
    def validate(self, data):
        if data['delivery_type'] == 'delivery' and not data.get('address'):
            raise serializers.ValidationError({
                'address': '送货方式必须填写地址'
            })
        return data

八、高级验证技巧 #

8.1 异步验证 #

python
import asyncio

async def check_email_exists(email):
    await asyncio.sleep(0.1)
    return User.objects.filter(email=email).exists()

class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    
    def validate_email(self, value):
        loop = asyncio.new_event_loop()
        exists = loop.run_until_complete(check_email_exists(value))
        if exists:
            raise serializers.ValidationError('该邮箱已被使用')
        return value

8.2 外部API验证 #

python
import requests

class AddressSerializer(serializers.Serializer):
    zip_code = serializers.CharField()
    
    def validate_zip_code(self, value):
        response = requests.get(f'https://api.zipcode.com/{value}')
        if response.status_code != 200:
            raise serializers.ValidationError('无效的邮政编码')
        return value

8.3 数据库查询验证 #

python
class ArticleSerializer(serializers.Serializer):
    category_id = serializers.IntegerField()
    
    def validate_category_id(self, value):
        if not Category.objects.filter(id=value).exists():
            raise serializers.ValidationError('分类不存在')
        return value

九、验证最佳实践 #

9.1 验证器复用 #

python
def create_forbidden_words_validator(words):
    def validator(value):
        for word in words:
            if word in value:
                raise serializers.ValidationError(f'不能包含"{word}"')
        return value
    return validator

class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(
        validators=[create_forbidden_words_validator(['广告', '垃圾'])]
    )
    content = serializers.CharField(
        validators=[create_forbidden_words_validator(['违禁词'])]
    )

9.2 验证器组合 #

python
from rest_framework.validators import UniqueValidator

def validate_length(min_len, max_len):
    def validator(value):
        if len(value) < min_len or len(value) > max_len:
            raise serializers.ValidationError(
                f'长度必须在{min_len}到{max_len}之间'
            )
        return value
    return validator

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(
        validators=[
            UniqueValidator(queryset=User.objects.all()),
            validate_length(3, 20),
        ]
    )

十、总结 #

本章学习了DRF的字段类型和验证:

  • 字段类型:数值、字符串、日期、文件等各种类型
  • 字段参数:控制字段行为的各种参数
  • 内置验证器:UniqueValidator等内置验证器
  • 自定义验证:字段级、对象级和验证器类
  • 条件验证:基于其他字段或请求的验证
  • 错误处理:自定义错误消息和格式化

掌握验证机制是构建健壮API的关键,让我们继续学习嵌套序列化器!

最后更新:2026-03-28