Django表单组件求助:自定义预约表单的日期时间选择与联动
Django ModelForm实现预约日期时间动态选择方案
1. 改造ModelForm,替换日期时间组件
将原Model中的app_time(DateTimeField)拆分为独立的日期和时间字段,分别使用下拉日期选择器和时分选择器:
import datetime from django import forms from .models import Appointments class AppointmentBookForm(forms.ModelForm): # 下拉框选择日期(年/月/日分三个下拉选项) app_date = forms.DateField(widget=forms.SelectDateWidget()) # 仅显示小时和分钟的时间选择器(调用浏览器原生组件) app_time = forms.TimeField(widget=forms.TimeInput(attrs={'type': 'time'})) class Meta: model = Appointments fields = ['doctor', 'app_date', 'app_time'] def clean(self): cleaned_data = super().clean() # 合并日期和时间为模型所需的DateTime类型 app_date = cleaned_data.get('app_date') app_time = cleaned_data.get('app_time') if app_date and app_time: cleaned_data['app_time'] = datetime.datetime.combine(app_date, app_time) else: raise forms.ValidationError("日期和时间均为必填项") # 复用模型的unique_together约束,验证该时段是否已被预约 doctor = cleaned_data.get('doctor') if doctor and app_date and app_time: if Appointments.objects.filter( doctor=doctor, app_time__date=app_date, app_time__time=app_time ).exists(): raise forms.ValidationError("该医生此时段已被预约") return cleaned_data
2. 后端实现动态获取可用时间接口
创建视图接口,根据选中的医生和日期,返回该医生当天未被占用的可预约时段(示例中假设医生工作时间为9:00-18:00,每30分钟一个时段,可根据业务调整):
import datetime from django.http import JsonResponse from django.views.decorators.http import require_GET from .models import Appointments, Doctor @require_GET def get_available_times(request): doctor_id = request.GET.get('doctor_id') app_date = request.GET.get('app_date') if not doctor_id or not app_date: return JsonResponse({'error': '缺少医生ID或日期参数'}, status=400) try: doctor = Doctor.objects.get(id=doctor_id) target_date = datetime.datetime.strptime(app_date, '%Y-%m-%d').date() # 获取该医生当天已预约的时间列表 booked_times = Appointments.objects.filter( doctor=doctor, app_time__date=target_date ).values_list('app_time__time', flat=True) # 生成可选时段 available_times = [] start_time = datetime.time(9, 0) end_time = datetime.time(18, 0) current_time = start_time while current_time <= end_time: if current_time not in booked_times: available_times.append(current_time.strftime('%H:%M')) # 每次增加30分钟 current_time = (datetime.datetime.combine(datetime.date.today(), current_time) + datetime.timedelta(minutes=30)).time() return JsonResponse({'available_times': available_times}) except Doctor.DoesNotExist: return JsonResponse({'error': '指定医生不存在'}, status=404)
在项目的urls.py中添加接口路由:
from django.urls import path from .views import get_available_times urlpatterns = [ # 其他路由... path('available-times/', get_available_times, name='get_available_times'), ]
3. 前端实现动态联动逻辑
在渲染表单的模板中,添加JavaScript监听医生和日期选择的变化,实时请求可用时间并更新下拉框:
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">提交预约</button> </form> <script> document.addEventListener('DOMContentLoaded', () => { const doctorSelect = document.getElementById('id_doctor'); // SelectDateWidget生成的三个日期下拉框 const yearSel = document.getElementById('id_app_date_year'); const monthSel = document.getElementById('id_app_date_month'); const daySel = document.getElementById('id_app_date_day'); const timeSel = document.getElementById('id_app_time'); // 组合选中的日期为YYYY-MM-DD格式 function getFormattedDate() { const year = yearSel.value; const month = monthSel.value.padStart(2, '0'); const day = daySel.value.padStart(2, '0'); return year && month && day ? `${year}-${month}-${day}` : null; } // 加载可用时间选项 function loadTimes() { const doctorId = doctorSelect.value; const targetDate = getFormattedDate(); if (!doctorId || !targetDate) { timeSel.innerHTML = '<option value="">请先选择医生和日期</option>'; return; } fetch(`/available-times/?doctor_id=${doctorId}&app_date=${targetDate}`) .then(res => res.json()) .then(data => { if (data.error) { timeSel.innerHTML = `<option value="">${data.error}</option>`; return; } // 清空并重新生成时间选项 timeSel.innerHTML = '<option value="">选择预约时段</option>'; data.available_times.forEach(time => { const opt = document.createElement('option'); opt.value = time; opt.textContent = time; timeSel.appendChild(opt); }); }) .catch(err => { timeSel.innerHTML = '<option value="">加载失败,请重试</option>'; console.error(err); }); } // 绑定监听事件 doctorSelect.addEventListener('change', loadTimes); yearSel.addEventListener('change', loadTimes); monthSel.addEventListener('change', loadTimes); daySel.addEventListener('change', loadTimes); // 页面初始化时执行一次 loadTimes(); }); </script>
内容的提问来源于stack exchange,提问作者LEO_007
相关产品推荐
相关产品推荐

