使用Django ORM filter更新时意外修改start字段问题求助
我用Django Rest Framework写了一个简单的api_view,意图通过患者ID过滤查询集,只筛选start日期大于今日的预约记录,然后将这些记录的is_cancelled字段设为True。但执行更新操作时,start字段会被自动设置为视图运行时的当前日期时间,而我只想要更新is_cancelled字段。
视图代码如下:
@api_view(['POST']) def BookingCancelAppointmentsView(request): if request.method == 'POST': patient_id = request.data today = date.today() bookings = Booking.objects.filter(patient=patient_id, start__gte=today).update(is_cancelled=True) return Response({'message': 'Appointments cancelled'})
Booking模型参考:
class Booking(models.Model): title = models.CharField(max_length=500, blank=True, null=True) patient = models.ForeignKey(Patient, on_delete=models.PROTECT, blank=True, null=True, related_name='bookings') start = models.DateTimeField(auto_now=False, auto_now_add=False) end = models.DateTimeField(auto_now=False, auto_now_add=False, blank=True, null=True) is_all_day = models.BooleanField(default=False) is_personal = models.BooleanField(default=False) practitioner = models.ForeignKey(Practitioner, on_delete=models.SET_NULL, blank=True, null=True, related_name='booking_practitioners') def __str__(self): if not self.patient: return self.practitioner.practitioner.get_full_name() return str(self.patient.get_full_name()) class Meta: unique_together = ('patient', 'start', 'practitioner')
软件版本:
- Python版本:3.10.5
- Django版本:3.2.15
- Django Rest Framework版本:3.13.1
原因
问题根源在unique_together约束:你的Booking模型在Meta类中定义了unique_together = ('patient', 'start', 'practitioner'),要求这三个字段的组合必须唯一。
Django 3.2版本在处理带有unique_together约束的批量更新时存在一个bug:如果过滤条件用到了约束中的字段(这里是start__gte=today),框架会错误地将过滤条件中的值(today)赋值给该字段,导致start被意外修改。这个问题在后续版本中已被修复。
解决方案
方法一:升级Django版本
将Django升级到3.2.16及以上的稳定版本,该版本修复了批量更新时因unique_together约束导致的字段意外赋值问题。
方法二:修改过滤条件,明确匹配日期部分
把start__gte=today替换为start__date__gte=today,明确指定匹配start字段的日期部分,而非直接用日期对象匹配DateTimeField:
@api_view(['POST']) def BookingCancelAppointmentsView(request): if request.method == 'POST': # 注意:建议用get方法获取具体字段,避免直接使用request.data patient_id = request.data.get('patient_id') today = date.today() bookings = Booking.objects.filter(patient=patient_id, start__date__gte=today).update(is_cancelled=True) return Response({'message': 'Appointments cancelled'})
修改后,Django只会用today过滤记录,不会将其赋值给start字段,同时保留unique_together约束的有效性。
另外补充:原代码中patient_id = request.data存在隐患,前端传递的JSON数据通常需要通过request.data.get('patient_id')获取具体ID值,避免因类型不匹配引发错误。
内容的提问来源于stack exchange,提问作者jAC

