Django训练预约系统:预约后已预订时段未从可用列表消失的问题
问题分析与解决方案
即时修复:触发时段锁定逻辑
当前代码中,WorkingHour类定义了reserve_hour()方法用于标记时段为不可用,但预约流程中从未调用该方法,导致is_available字段始终保持True,已预约时段不会从可用列表中移除。
修改Views.py的form_valid方法,在保存预约后锁定对应时段:
class TrainerAppointmentCreate(CreateView): model = TrainerAppointment form_class = TrainerAppointmentForm template_name = 'create_appointment.html' success_url = reverse_lazy("home") def form_valid(self, form): if self.request.user.entries > 0: form.instance.user = self.request.user appointment = form.save() # 保存预约并获取关联时段对象 # 调用方法锁定时段 appointment.chosen_hour.reserve_hour() self.request.user.entries -= 1 self.request.user.save() return super().form_valid(form) else: form.add_error(None, "没有训练套餐,请先购买套餐。") return self.form_invalid(form)
核心设计缺陷修正(必做)
当前模型存在逻辑漏洞:WorkingHour的is_available是全局字段,意味着任意教练的某个时段被预约后,所有教练的该时段都会被标记为不可用,完全不符合实际业务逻辑(不同教练的时段应独立管理)。
优化方案:
- 添加唯一约束:在
TrainerAppointment模型中,确保同一教练、同一日期、同一时段只能被预约一次:
class TrainerAppointment(Appointment): trainer = models.ForeignKey(Trainer, on_delete=models.CASCADE) services = models.ForeignKey(TrainerServices, on_delete=models.CASCADE) user = models.ForeignKey(MyUser, on_delete=models.CASCADE, null=True, blank=True) class Meta: unique_together = ('trainer', 'date', 'chosen_hour') # 新增唯一约束 def __str__(self): return f"{self.user}-{self.services}"
- 动态过滤可用时段:修改表单查询逻辑,排除当前教练在指定日期已被预约的时段,而非依赖全局的
is_available字段:
class TrainerAppointmentForm(forms.ModelForm): chosen_hour = forms.ModelChoiceField( queryset=WorkingHour.objects.none(), # 初始为空,后续动态填充 empty_label=None, widget=forms.Select(attrs={"class": "form-select is_valid"}) ) class Meta: model = TrainerAppointment fields = "__all__" widgets = { "trainer": forms.Select(attrs={"class": "form-select is_valid"}), "date": forms.DateInput(attrs={"class": "form-control is_valid", "type": "date"}), "services": forms.Select(attrs={"class": "form-select is_valid"}), } def clean_date(self): date = self.cleaned_data.get('date') today = timezone.now().date() if date < today: raise forms.ValidationError("日期无效,请选择正确的日期。") return date def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 根据选中的教练和日期动态过滤可用时段 trainer_id = self.data.get('trainer') date = self.data.get('date') if trainer_id and date: booked_hours = TrainerAppointment.objects.filter( trainer_id=trainer_id, date=date ).values_list('chosen_hour_id', flat=True) self.fields['chosen_hour'].queryset = WorkingHour.objects.exclude(id__in=booked_hours) else: # 默认显示所有时段(可结合Trainer的working_hours多对多关系进一步过滤) self.fields['chosen_hour'].queryset = WorkingHour.objects.all()
- 清理冗余字段:优化后不再需要
WorkingHour中的is_available字段及reserve_hour方法,直接删除即可。
额外防并发建议
在表单中添加验证,避免并发提交导致的重复预约:
def clean(self): cleaned_data = super().clean() trainer = cleaned_data.get('trainer') date = cleaned_data.get('date') chosen_hour = cleaned_data.get('chosen_hour') if trainer and date and chosen_hour: if TrainerAppointment.objects.filter( trainer=trainer, date=date, chosen_hour=chosen_hour ).exists(): raise forms.ValidationError("该时段已被预约,请选择其他时段。") return cleaned_data
内容的提问来源于stack exchange,提问作者Noszo
相关产品推荐
相关产品推荐

