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

使用serializers.Serializer批量提交数据时遇non_field_errors问题

问题:ViewSet批量创建数据时Serializer验证失败

我尝试通过ViewSet的create方法提交多条数据到Serializer,已经设置了many属性,但还是报错。提交的数据格式如下:

[
  {
    "weekday":"day",
    "start_time":"time",
    "end_time":"time"  
  },
  {
    "weekday":"day",
    "start_time":"time",
    "end_time":"time"  
  }
]

对应的ViewSet代码(viewset.py)

class SpecialistAvailabilityView(viewsets.ModelViewSet):

    model = SpecialistAvailability
    permission_classes = [IsAuthenticated, IsSpecialist]
    
    def get_serializer_class(self):
        saving_actions = ["create", "update", "partial_update"]
        if self.action in saving_actions:
            return WeekDayTimeSerializer
        
        return SpecialistAvailabilitySerializer

    def get_queryset(self):
        queryset = SpecialistAvailability.objects.filter(
            specialist__user=self.request.user
        ).order_by('-start_time__week_day')
        return queryset
    
    def translate_availability(self, data):
        pass

    def create(self, request, *args, **kwargs):
        serializer = WeekDayTimeSerializer(data=request.data, many=isinstance(request.data, list))
        print(serializer.initial_data)
        print(serializer.is_valid())
        if serializer.is_valid():
            self.translate_availability(serializer.validated_data)
        return super().create(request, *args, **kwargs)

对应的Serializer代码(serializer.py)

import pytz

from datetime import datetime

from rest_framework import serializers

from apps.specialist.models.specialist_availability import SpecialistAvailability
from apps.specialist.utils import WEEKDAYS

class WeekDayTimeSerializer(serializers.Serializer):
    weekday = serializers.ChoiceField(choices=WEEKDAYS, required=True)
    start_time = serializers.TimeField()
    end_time = serializers.TimeField()

    # TODO ADD TIME AND WEEKDAY CHECKING
    # START TIME MUST BE LESS THAN END TIME
    # JUST ONE DAY MUST BE SENDED IF MANY

    def validate(self, attrs):
        self.validate_start_time(attrs['start_time'])
        self.validate_start_time(attrs['end_time'])
        self.validate_end_time
        return super().validate(attrs)
    
    def validate_start_time(self, start_time):
        try:
            datetime.strptime(start_time, "%H:%M")
        except:
            raise serializers.ValidationError("Wrong time format")
        
        return start_time
    
    def validate_end_time(self, end_time):
        try:
            datetime.strptime(end_time, "%H:%M")
        except:
            raise serializers.ValidationError("Wrong time format")
        
        return end_time


class SpecialistAvailabilitySerializer(serializers.ModelSerializer):
    class Meta:
        model = SpecialistAvailability
        fields = ['id', 'specialist', 'start_time', 'end_time']

    def to_representation(self, instance):
        localized_start_time = self.set_time_to_local(instance.start_time)
        localized_end_time = self.set_time_to_local(instance.end_time)
        data = {
            'availability': {
                'id': instance.id,
                'start_day': localized_start_time.strftime('%A'),
                'start_day_code': localized_start_time.isoweekday(),
                'start_time': localized_start_time.time(),
                'end_day': localized_end_time.strftime('%A'),
                'end_day_code': localized_end_time.isoweekday(),
                'end_time': localized_end_time.time()
            }
        }
        return data

    def validate(self, attrs):
        instance = getattr(self, 'instance', None)
        if instance:
            attrs['id'] = instance.id

        specialist_availability = self.Meta.model(**attrs)
        specialist_availability.clean()
        super().validate(attrs)
        attrs['start_time'] = self.set_time_to_utc(attrs['start_time'])
        attrs['end_time'] = self.set_time_to_utc(attrs['end_time'])
        return attrs
    
    def set_time_to_local(self, time):
        request_user = self.context['request'].user
        user_tz = request_user.timezone
        if user_tz == pytz.utc:
            return time
        
        unlocalized_time = time.replace(tzinfo=None)
        utc_time = pytz.utc.localize(unlocalized_time)
        local_time = utc_time.astimezone(user_tz)
        return local_time

    def set_time_to_utc(self, time):
        request_user = self.context['request'].user
        user_tz = request_user.timezone
        if user_tz == pytz.utc:
            return time
        
        unlocalized_time = time.replace(tzinfo=None)
        local_time = user_tz.localize(unlocalized_time)
        utc_time = local_time.astimezone(pytz.utc)
        return utc_time

问题分析与修复方案

核心问题1:自定义create方法逻辑冲突

你在create里自己实例化了带many参数的Serializer,但最后又调用了super().create()——父类的create方法会重新通过get_serializer()获取Serializer,而这个默认Serializer并没有设置many=True,直接导致批量数据验证失败。

核心问题2:Serializer验证逻辑错误

  • validate方法里写了self.validate_end_time但没加括号,等于完全没执行这个验证
  • 用TimeField接收数据后,又在自定义验证里把它当字符串用datetime.strptime解析,而TimeField已经把输入转换成了datetime.time对象,这必然报错

修复步骤:

1. 重写create方法,完全接管批量创建逻辑

from rest_framework import status
from rest_framework.response import Response

def create(self, request, *args, **kwargs):
    # 批量序列化验证
    serializer = WeekDayTimeSerializer(data=request.data, many=isinstance(request.data, list))
    serializer.is_valid(raise_exception=True)
    
    # 处理数据并创建记录
    self.translate_availability(serializer.validated_data)
    
    # 返回自定义响应
    return Response(serializer.data, status=status.HTTP_201_CREATED)

2. 修复WeekDayTimeSerializer的验证逻辑

class WeekDayTimeSerializer(serializers.Serializer):
    weekday = serializers.ChoiceField(choices=WEEKDAYS, required=True)
    # 如果需要指定时间格式,直接在TimeField里设置input_formats
    start_time = serializers.TimeField(input_formats=["%H:%M"])
    end_time = serializers.TimeField(input_formats=["%H:%M"])

    def validate(self, attrs):
        # 验证开始时间早于结束时间
        if attrs['start_time'] >= attrs['end_time']:
            raise serializers.ValidationError("开始时间必须早于结束时间")
        return attrs
    
    # 移除多余的validate_start_time和validate_end_time,TimeField会自动验证格式

3. 完善translate_availability方法处理批量数据

因为many=True时serializer.validated_data是列表,所以需要遍历数据创建数据库记录:

def translate_availability(self, data):
    specialist = self.request.user.specialist
    for item in data:
        # 根据weekday和时间构造对应datetime(示例逻辑,需根据业务调整)
        # 假设WEEKDAYS是数字对应星期,比如1=周一
        start_datetime = datetime.combine(
            datetime.today(), item['start_time']
        ).replace(weekday=item['weekday']-1)
        end_datetime = datetime.combine(
            datetime.today(), item['end_time']
        ).replace(weekday=item['weekday']-1)
        
        SpecialistAvailability.objects.create(
            specialist=specialist,
            start_time=start_datetime,
            end_time=end_datetime
        )

内容的提问来源于stack exchange,提问作者Juan Trujillo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:27:04