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

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'),
]

问题根源分析

  1. 表单未支持文件上传:HTML表单缺少enctype="multipart/form-data"属性,导致图片、文件无法提交,request.FILES为空,触发表单验证失败。
  2. Form类使用错误:用了普通forms.Form而非ModelForm,不仅要手动定义所有字段,还没有内置的save()方法,原视图里的form.save()根本不生效。
  3. 视图逻辑混乱:存在两个处理表单的视图,但URL只映射了addStudent,且该视图处理POST时未传入request.FILES,文件字段验证失败后直接返回空表单。
  4. 必填字段缺失:模型中std_parents、std_password是必填项,但表单中未包含这些字段,创建Student实例时会抛出异常。
  5. 日期格式不兼容:表单里的日期输入用了%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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 19:10:30