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抛出全局错误,错误不会绑定到具体字段,用户体验差。 - 视图函数错误处理完全错误:
- 表单验证通过后,重复实现了时间验证逻辑,完全冗余;
- 无论表单验证失败还是自定义判断不通过,都返回全新的空表单,丢弃了原表单的错误信息,导致模板无法显示任何错误提示;
- 直接从
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
相关产品推荐
相关产品推荐

