Django 4.1表单无法保存数据求助(Windows10环境)
学生管理应用表单提交失败问题排查与修复
我正在开发一款小型学生管理应用,设计了存储学生信息的数据表,支持注册录入和后续状态更新(比如学生缺课3周可设为Onhold)。但在Windows10环境用Django4.1开发时,提交注册表单后跳转到空表单,数据没保存。相关代码及问题分析、修复方案如下:
原代码展示
Model.py
from django.db import models from django.contrib import admin from django.utils.translation import gettext_lazy as _ class Student(models.Model): #std_matricule = models.CharField(verbose_name='Student matricule', max_length=6, null=False, unique=True, primary_key=True) std_matricule = models.CharField(verbose_name='Matricule', max_length=6, null=False, unique=True, blank=False, help_text='Matricule of the student') std_parents = models.ForeignKey(Parents, on_delete=models.DO_NOTHING, related_name='Parents', unique=False, default=1, verbose_name='Student parents') std_email = models.EmailField(verbose_name='Email', null=False, blank=True, help_text='Enter the email of the student or leave blank if not exist') std_password = models.CharField(verbose_name='Password', max_length=64, null=False, blank=True, help_text='Type the password with 6 characters minimum') std_surname = models.CharField(verbose_name='Surname', null=False, blank=False, max_length=64, help_text='Type the Surname of the student as in the birth certificate') std_firstname = models.CharField(verbose_name='First name', null=False, blank=True, max_length=64, help_text='Type the student first name') std_nickname = models.CharField(verbose_name='Student Nickname', max_length=64, null=False, blank=True, help_text='If exist, type student nickname here') class Sex(models.TextChoices): MALE = 'Male', _('Male') FEMALE = 'Female', _('Female') std_sex = models.CharField( max_length=8, choices=Sex.choices, default=Sex.MALE, verbose_name='Sex', help_text='Select the sex from the dropdown list', ) std_dateOfBirth = models.DateField(verbose_name='Date of birth', null=False, blank=False, help_text='Birth date of the student as in the birth certificate') std_placeOfBirth = models.CharField(verbose_name='Place of birth', max_length=64, null=False, blank=False, help_text='Place of birth of the student as in the birth certificate') std_countryOfBirth = models.CharField(verbose_name='Country of birth', max_length=64, null=True, blank=True, help_text='Country of birth of the student as in the birth certificate') std_nationality = models.CharField(verbose_name='Student nationality', max_length=64, null=True, blank=True, help_text='Nationality of the student') std_adress = models.CharField(verbose_name='Student adress', max_length=256, null=False, blank=True) std_phoneNumber = models.CharField(verbose_name='Student phone number', max_length=32, null=False, blank=True, help_text='Student phone number') std_enrolmentDate = models.DateField(verbose_name='Student enrolment date', null=False, blank=False, help_text='Enter the enrolment date') std_photo = models.ImageField(verbose_name='Student photo', null=False, help_text='Please, upload a recent photo of the student') std_birthCertificate = models.FileField('Student birth certificate scann', null=False, blank=True, help_text='Upload a readable scan copy of the birth certificate here') std_otherDocs = models.FileField('Student others documents', null=False, blank=True, help_text='If exist, upload those support documents') class STD_status(models.TextChoices): ACTIF = 'Active', _('Active') INACTIF = 'Inactive', _('Inactive') ONHOLD = 'Onhold', _('Onhold') COMPLETED_P ='Completed Primary', _('Completed Primary') COMPLETED_S ='Completed Secondary', _('Completed Secondary') std_status = models.CharField( max_length=32, choices=STD_status.choices, default=STD_status.ACTIF, verbose_name='Status', help_text='Student status, select from the dropdown list', ) std_deleteStatus = models.BooleanField(verbose_name='Delete status', default=False) std_last_loginDate = models.DateField(verbose_name='Last login date', null=True, blank=True, help_text='Last login date') std_last_loginIP = models.CharField(verbose_name='Last login IP', max_length=64, null=True, blank=True, help_text='Last login IP') std_createDate = models.DateField(verbose_name='Create date', auto_now=True) std_modifyDate = models.DateField(verbose_name='Modify date', auto_now=True) def __str__(self): return 'Matricule: ' + self.std_matricule + ' - Full name: ' + self.std_surname + ' ' + self.std_firstname
View.py
from django.shortcuts import render, redirect from django.http import HttpResponse, Http404, HttpResponseRedirect, JsonResponse from django.urls import reverse from django.contrib import messages from django.core.files.storage import FileSystemStorage from django.views.decorators.csrf import csrf_exempt from students_management_app.forms import StudentRegistrationForm from students_management_app.models import Student def addStudent(request): form = StudentRegistrationForm() if request.method == "POST": form = StudentRegistrationForm(request.POST) if form.is_valid(): form.save() return redirect('listStudents') return render(request, 'students_management_app/student_template/addStudent.html', {'form': form}) def addStudentSave(request): if request.method != "POST": messages.error(request, "Invalid Method") return redirect('addStudent') else: print(request.POST) form = StudentRegistrationForm(request.POST, request.FILES) if form.is_valid(): std_surname = form.cleaned_data['std_surname'] std_firstname = form.cleaned_data['std_firstname'] std_nickname = form.cleaned_data['std_nickname'] std_matricule = form.cleaned_data['std_matricule'] std_email = form.cleaned_data['std_email'] std_sex = form.cleaned_data['std_sex'] std_dateOfBirth = form.cleaned_data['std_dateOfBirth'] std_placeOfBirth = form.cleaned_data['std_placeOfBirth'] std_countryOfBirth = form.cleaned_data['std_countryOfBirth'] std_nationality = form.cleaned_data['std_nationality'] std_adress = form.cleaned_data['std_adress'] std_phoneNumber = form.cleaned_data['std_phoneNumber'] std_enrolmentDate = form.cleaned_data['std_enrolmentDate'] if len(request.FILES) != 0: std_photo = request.FILES['std_photo'] fs = FileSystemStorage() filename = fs.save(std_photo.name, std_photo) std_photo_url = fs.url(filename) else: std_photo = None try: student = Student.objects.create(std_surname=std_surname, std_firstname=std_firstname, std_nickname=std_nickname, std_matricule=std_matricule, std_email=std_email, std_sex=std_sex, std_dateOfBirth=std_dateOfBirth, std_placeOfBirth=std_placeOfBirth, std_countryOfBirth=std_countryOfBirth, std_nationality=std_nationality, std_adress=std_adress, std_phoneNumber=std_phoneNumber, std_enrolmentDate=std_enrolmentDate, std_photo=std_photo_url) student.save() messages.success(request, "Student Added Successfully!") return redirect('addStudent') except: messages.error(request, "Failed to Add Student!") return redirect('addStudent') else: return redirect('addStudent')
Form.py
from django import forms from django.forms import ModelForm from students_management_app.models import Student class StudentRegistrationForm(forms.Form): std_surname = forms.CharField(label='Surname', max_length=64, required=True) std_firstname = forms.CharField(label='First name', max_length=64, required=True) std_nickname = forms.CharField(label='Student Nickname', max_length=64, required=True) std_matricule = forms.CharField(label='Matricule', max_length=6, required=True) ###std_parents = models.ForeignKey(Parents, on_delete=models.DO_NOTHING, related_name='Parents', unique=False, default=1, verbose_name='Student parents') std_email = forms.EmailField(label='Email') gender_list = ( ('Male','Male'), ('Female','Female') ) std_sex = forms.ChoiceField(label='Sex', choices=gender_list) std_dateOfBirth = forms.DateField(label='Date of birth', widget=forms.DateInput(format='%d/%m/%Y')) std_placeOfBirth = forms.CharField(label='Place of birth', max_length=64) std_countryOfBirth = forms.CharField(label='Country of birth', max_length=64) std_nationality = forms.CharField(label='Nationality', max_length=64) std_adress = forms.CharField(label='Adress', max_length=256) std_phoneNumber = forms.CharField(label='Phone number', max_length=32) std_enrolmentDate = forms.DateField(label='Enrolment date', widget=forms.DateInput(format='%d/%m/%Y') ) std_photo = forms.ImageField(label='Student photo') std_birthCertificate = forms.FileField(label='Scan of the Birth certificate') std_otherDocs = forms.FileField(label='Student others documents')
HTML模板
{% load static %} <title>List of the students</title> </head> <body> <h1>Add a Student:</h1> <form action="" method="POST"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="Save"> </form> </body> </html>
URL.py
urlpatterns = [ #path('admin/', admin.site.urls), path('', views.index), path('listStudents/', views.listStudents), path('addStudent/', views.addStudent), ###path('addStudent/', views.addStudent, name='addStudent'), ##path('listStudents/', views.listStudents, name='listStudents'), ]
问题根源分析
- 表单未支持文件上传:HTML表单缺少
enctype="multipart/form-data"属性,导致图片、文件无法提交,request.FILES为空,触发表单验证失败。 - Form类使用错误:用了普通
forms.Form而非ModelForm,不仅要手动定义所有字段,还没有内置的save()方法,原视图里的form.save()根本不生效。 - 视图逻辑混乱:存在两个处理表单的视图,但URL只映射了
addStudent,且该视图处理POST时未传入request.FILES,文件字段验证失败后直接返回空表单。 - 必填字段缺失:模型中
std_parents、std_password是必填项,但表单中未包含这些字段,创建Student实例时会抛出异常。 - 日期格式不兼容:表单里的日期输入用了
%d/%m/%Y格式,但未配置Django的解析格式,导致日期无法被正确识别,验证失败。
修复方案
1. 修改HTML表单,添加文件上传编码
在form标签中加入enctype="multipart/form-data",确保文件能正常提交:
<form action="" method="POST" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="Save"> </form>
2. 将Form改为ModelForm,简化代码
使用ModelForm自动关联模型字段,自带save()方法,减少重复代码:
# form.py from django import forms from django.forms import ModelForm from students_management_app.models import Student class StudentRegistrationForm(ModelForm): class Meta: model = Student fields = [ 'std_matricule', 'std_parents', 'std_email', 'std_password', 'std_surname', 'std_firstname', 'std_nickname', 'std_sex', 'std_dateOfBirth', 'std_placeOfBirth', 'std_countryOfBirth', 'std_nationality', 'std_adress', 'std_phoneNumber', 'std_enrolmentDate', 'std_photo', 'std_birthCertificate', 'std_otherDocs' ] widgets = { 'std_dateOfBirth': forms.DateInput(format='%d/%m/%Y', attrs={'type': 'date'}), 'std_enrolmentDate': forms.DateInput(format='%d/%m/%Y', attrs={'type': 'date'}), } input_formats = ['%d/%m/%Y', '%Y-%m-%d'] # 支持两种日期格式解析 # 自定义密码验证 def clean_std_password(self): password = self.cleaned_data.get('std_password') if password and len(password) < 6: raise forms.ValidationError("密码长度至少6位") return password
3. 简化视图逻辑,统一处理提交
删除冗余的addStudentSave视图,修改addStudent视图同时处理GET和POST请求:
# views.py from django.shortcuts import render, redirect from django.contrib import messages from students_management_app.forms import StudentRegistrationForm def addStudent(request): if request.method == "POST": form = StudentRegistrationForm(request.POST, request.FILES) if form.is_valid(): form.save() messages.success(request, "学生信息添加成功!") return redirect('listStudents') else: # 打印错误信息用于调试 print(form.errors) messages.error(request, "添加失败!请检查表单填写内容") else: form = StudentRegistrationForm() return render(request, 'students_management_app/student_template/addStudent.html', {'form': form})
4. 修复URL配置,添加name属性
给URL命名,确保redirect能正确跳转:
# urls.py urlpatterns = [ path('', views.index), path('listStudents/', views.listStudents, name='listStudents'), path('addStudent/', views.addStudent, name='addStudent'), ]
5. 处理模型必填字段
- 确保
Parents表中存在id=1的记录,或者在表单中添加std_parents字段让用户选择家长; - 若允许密码为空,可在模型中设置
std_password = models.CharField(..., blank=True, default=''),避免创建实例时出错。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

