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

Django ModelForm表单验证失效问题排查求助

预订表单验证失效与错误信息不显示的问题修复

问题描述

我创建了如下BookingForm模型表单,在clean方法中实现了仅允许预订未来时段的验证逻辑,但验证未生效。同时希望在模板中显示错误信息,附上表单、模板及views.py代码,请求排查错误。

原表单代码

"""class used for booking a time slot."""
class BookingForm(forms.ModelForm):
    class Meta:
        model = Booking
        fields = ['check_in_date', 'check_in_time', 'check_out_time',
                    'person', 'no_of_rooms']

    """Function to check if username and password match or not."""
    def clean(self):
        cleaned_data = super().clean()

        normal_book_date = cleaned_data.get("check_in_date")
        normal_check_in = cleaned_data.get("check_in_time")

        if (normal_book_date < now.date() or
            (normal_book_date == now.date() and
            normal_check_in < now.time())):

            #self._errors['check_in_date'] = self.error_class([
            #    'You can only book for future.])
            raise ValidationError(
                "You can only book for future."
            )
        return cleaned_data

原模板代码

{% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Booking</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" type="text/css" href="{% static 'hotel/css/base.css' %}">
    <style>
      *{
          padding: 0;
          margin: 0;
      }

  </style>
  </head>
    <body id="body-color">
    <div class="container2" id="boxx">
      <h2 class="w3-center">Room Slot Booking</h2><br/>
      <form method="POST">
          {% csrf_token %}
          {{ form.as_p }}
          <input type="submit" class= "submit submit-right" value="Search Availability" />
      </form>
      <br/>
    </div>
    </body>
</html>

原视图代码

"""Function to return the available categories."""
@login_required(login_url="/hotel/signin/")
def booking(request):
    if request.method == 'POST':
        form = BookingForm(request.POST)
        if form.is_valid():
            request.session['normal_book_date'] = request.POST['check_in_date']
            normal_book_date = convert_to_date(request.session['normal_book_date'])
            request.session['normal_check_in'] = request.POST['check_out_time']
            normal_check_in = convert_to_time(request.session['normal_check_in'])
            request.session['normal_check_out'] = request.POST['check_out_time']
            # now is the date and time on which the user is booking.
            if (normal_book_date > now.date() or
                (normal_book_date == now.date() and
                normal_check_in >= now.time())):
                request.session['normal_person'] = int(request.POST['person'])
                request.session['normal_no_of_rooms_required'] = int(
                    request.POST['no_of_rooms']
                    )
                normal_check_out = convert_to_time(request.session['normal_check_out'])
                response = list()
                response = search_availability(True,
                                               normal_book_date,
                                               normal_check_in,
                                               normal_check_out,
                                               request.session['normal_person'],
                                               request.session['normal_no_of_rooms_required'])
                if response:
                    context = {
                        'categories': response,
                        'username': request.session['normal_username']
                        }
                    return render(request, 'categories.html', context)
                return HttpResponse("Not Available")
            else:
                context = {
                    'form': BookingForm(),
                    'username': request.session['normal_username']
                    }
                return render(request, 'book.html', context)
        else:
            context = {
                'form': BookingForm(),
                'username': request.session['normal_username']
                }
            return render(request, 'book.html', context)
    context = {
        'form': BookingForm(),
        'username': request.session['normal_username']
        }
    return render(request, 'book.html', context)

错误原因分析

  • 表单验证逻辑存在漏洞:原clean方法未处理字段为空的情况,若check_in_date或check_in_time未填写,cleaned_data.get()返回None,直接进行日期时间比较会抛出异常,导致验证逻辑中断;同时使用raise ValidationError抛出全局错误,错误不会绑定到具体字段,用户体验差。
  • 视图函数错误处理完全错误:
    1. 表单验证通过后,重复实现了时间验证逻辑,完全冗余;
    2. 无论表单验证失败还是自定义判断不通过,都返回全新的空表单,丢弃了原表单的错误信息,导致模板无法显示任何错误提示;
    3. 直接从request.POST获取数据,未使用表单cleaned_data,绕开了表单的字段转换与验证。

修复方案

1. 修正表单验证逻辑

"""class used for booking a time slot."""
from django.utils import timezone
from django.core.exceptions import ValidationError
from django import forms
from .models import Booking

class BookingForm(forms.ModelForm):
    class Meta:
        model = Booking
        fields = ['check_in_date', 'check_in_time', 'check_out_time',
                  'person', 'no_of_rooms']

    def clean(self):
        cleaned_data = super().clean()
        now = timezone.now()
        
        # 先检查必填字段是否填写完整
        check_in_date = cleaned_data.get("check_in_date")
        check_in_time = cleaned_data.get("check_in_time")
        
        if not check_in_date or not check_in_time:
            # 字段为空时,Django会自动处理必填错误,直接返回即可
            return cleaned_data

        # 验证预订时间是否在未来
        if (check_in_date < now.date() or
            (check_in_date == now.date() and check_in_time < now.time())):
            # 将错误绑定到具体字段,让错误信息显示在对应字段下方
            self.add_error('check_in_date', "只能预订未来的时段")
            # 可选:给时间字段添加错误标记,让用户明确哪部分有问题
            self.add_error('check_in_time', "")
        
        return cleaned_data

2. 彻底重构视图函数

"""Function to return the available categories."""
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from .forms import BookingForm
from .utils import search_availability

@login_required(login_url="/hotel/signin/")
def booking(request):
    if request.method == 'POST':
        form = BookingForm(request.POST)
        if form.is_valid():
            # 直接从表单cleaned_data获取验证后的安全数据
            cleaned_data = form.cleaned_data
            normal_book_date = cleaned_data['check_in_date']
            normal_check_in = cleaned_data['check_in_time']
            normal_check_out = cleaned_data['check_out_time']
            normal_person = cleaned_data['person']
            normal_no_of_rooms = cleaned_data['no_of_rooms']
            
            # 调用可用性查询
            response = search_availability(
                True,
                normal_book_date,
                normal_check_in,
                normal_check_out,
                normal_person,
                normal_no_of_rooms
            )
            if response:
                # 存储会话数据(注意转换为可序列化类型)
                request.session['normal_book_date'] = str(normal_book_date)
                request.session['normal_check_in'] = str(normal_check_in)
                request.session['normal_check_out'] = str(normal_check_out)
                request.session['normal_person'] = normal_person
                request.session['normal_no_of_rooms_required'] = normal_no_of_rooms
                
                context = {
                    'categories': response,
                    'username': request.session.get('normal_username')
                }
                return render(request, 'categories.html', context)
            else:
                # 无可用房间时,给表单添加全局错误
                form.add_error(None, "当前时段没有可用房间")
                context = {
                    'form': form,
                    'username': request.session.get('normal_username')
                }
                return render(request, 'book.html', context)
        else:
            # 表单验证失败,直接传回带错误信息的原表单
            context = {
                'form': form,
                'username': request.session.get('normal_username')
            }
            return render(request, 'book.html', context)
    # GET请求返回空表单
    context = {
        'form': BookingForm(),
        'username': request.session.get('normal_username')
    }
    return render(request, 'book.html', context)

3. 模板无需修改

你的模板已经使用{{ form.as_p }},Django会自动将字段错误显示在对应字段下方,全局错误会显示在表单顶部,无需额外修改。


内容的提问来源于stack exchange,提问作者Anshul Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:05:25