使用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
相关产品推荐
相关产品推荐

