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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 03:10:34