简介这套基于Python与Django框架实现的学生信息管理系统源码是针对计算机专业毕业设计需求整理的完整项目包尤其适合需要快速搭建Web管理系统演示环境的本科生。资源压缩包体积仅3.67MB内部包含1108个文件其中Python源码与编译文件负责后台逻辑与数据模型HTML文件构建前端页面骨架CSS和JavaScript用于页面样式与交互效果PNG、GIF等图片丰富界面视觉同时还集成了Bootstrap前端框架以及时区、地区等配置文件整体结构清晰、依赖完整。截至目前已有2241人浏览或下载学习足以说明其在毕设场景中的实用价值。项目提供学生信息管理相关的核心业务模块使用者只需在配置文件中修改数据库用户名和密码执行数据迁移即可在本地快速运行既可作为毕业设计系统直接提交也可作为学习Django MVC架构的参考样例在此基础上进行功能扩展或界面定制对节省开发时间与理清开发思路都有明显帮助。1. 从数据库课程设计到可答辩的Web学生信息管理系统做毕业设计时很多人第一步就卡在数据库表怎么建。学生信息管理系统恰好把这个问题暴露得很彻底学生、课程、成绩三张表的关系以及如何用Django的ORM把关系落进实际项目。这个选题的价值在于覆盖了Web系统最高频的CRUD、搜索、分页与导出之后无论是转向业务开发还是继续读研由表驱动页面的思路都能复用。我会按一条可运行的源码路径从项目初始化讲到数据库模型、视图查询、模板渲染最后落到答辩现场能拿出手的验证与部署技巧。整个过程只需要Python 3.10及以上、Django 4.2 LTS、一个顺手的数据库适合正在做数据库课程设计或Web期末作业的同学也适合快速观摩Django后台系统完整做法的初级开发者。2. Django项目骨架与数据库模型设计2.1 环境准备与django创建app先明确一个原则数据库模型设计不应该从Navicat画表开始而应该以Python Model为单位定义再通过迁移生成表结构。原因是模型代码能被版本控制跟踪改动差异一目了然手绘ER图或孤立SQL脚本一旦改动就失去现场答辩时也说不清演进过程。所以这一节先从项目初始化说起。我的惯用步骤是这样mkdir sims_project cd sims_project python -m venv venv source venv/bin/activate pip install django4.2.* mysqlclient2.2.* django-admin startproject sims_config . python manage.py startapp studentspython -m venv创建独立虚拟环境避免项目依赖与系统Python打架source venv/bin/activate在Linux和macOS下生效Windows下换成venv\Scripts\activate。Django版本锁到4.2 LTS分支官方支持周期长换机器重装也不会出现版本差异导致的诡异报错mysqlclient是连接MySQL的驱动没装MySQL开发库时可以暂时跳过先让SQLite把业务流程跑通最后再切数据库这样排错面更小。接下来处理settings.py中最影响本项目的三处。把我这里写进去和django创建app后默认生成的配置做对比# sims_config/settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, students, ] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: sims_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } } LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghaicharsetutf8mb4主要解决中文和生僻字存储问题用默认latin1写中文姓名时很容易报Incorrect string value。LANGUAGE_CODE设为zh-hans后admin后台和表单错误提示变成中文答辩演示观感更顺TIME_ZONE影响auto_now_add字段写入的时间是否按本地时区。如果先不接MySQL把ENGINE改回django.db.backends.sqlite3把NAME指到BASE_DIR下的db.sqlite3Django新项目默认就是这个配置。2.2 学生、课程、成绩三个核心Model学生信息管理系统的数据核心是三张表Student存学生静态信息Course存课程信息Score存成绩关联。它们的关联是学生与成绩一对多课程与成绩一对多学生与课程通过成绩表形成多对多。下面是我会直接用的Model定义# students/models.py from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator class Student(models.Model): GENDER_CHOICES [(M, 男), (F, 女)] student_no models.CharField(学号, max_length20, uniqueTrue, db_indexTrue) name models.CharField(姓名, max_length50) gender models.CharField(性别, max_length1, choicesGENDER_CHOICES, defaultM) birth_date models.DateField(出生日期, nullTrue, blankTrue) phone models.CharField(手机号, max_length11, nullTrue, blankTrue) enroll_year models.PositiveSmallIntegerField(入学年份) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: ordering [enroll_year, student_no] verbose_name 学生 verbose_name_plural verbose_name def __str__(self): return f{self.student_no} {self.name} class Course(models.Model): course_no models.CharField(课程号, max_length10, uniqueTrue) name models.CharField(课程名, max_length100) credit models.DecimalField(学分, max_digits3, decimal_places1, default2.0) class Meta: ordering [course_no] class Score(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE, related_namescores) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_namescores) score models.DecimalField(成绩, max_digits5, decimal_places1, validators[MinValueValidator(0), MaxValueValidator(100)]) created_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (student, course)字段参数里最能影响维护体验的是related_name和on_delete。related_namescores让反向查询变成student.scores.all()不必写Score.objects.filter(studentstudent)。on_deletemodels.CASCADE表示删学生时级联删除成绩答辩演示最省事若成绩需要留痕把外键改成on_deletemodels.PROTECT删除学生时Django会抛ProtectedError要求你先清理成绩表。常用字段的参数速查表如下字段类型常用参数适用位置CharFieldmax_length、unique、db_index学号、姓名、电话DateFieldnullTrue、blankTrue出生日期DecimalFieldmax_digits、decimal_places、validators成绩、学分ForeignKeyon_delete、related_name成绩表指向学生、课程Meta里的ordering决定默认排序列表页不额外写order_by也能稳定输出unique_together在数据库层约束同一学生同一课程只有一条成绩比在视图里if判断更可靠。2.3 迁移命令与演示数据填充模型写完执行迁移。注意每次改models.py后都要重新makemigrations如果提示No changes detected说明模型定义和上一次迁移没有差异python manage.py makemigrations students python manage.py migrate python manage.py createsuperusercreatesuperuser用于登录admin后台。为了演示分页和筛选我倾向于再写一个自定义管理命令填充演示数据避免手动录几十条。下面是seed_demo的完整实现# students/management/commands/seed_demo.py import random from django.core.management.base import BaseCommand from django.db import transaction from students.models import Student, Course, Score class Command(BaseCommand): help 生成100名学生、10门课程、每人5门成绩的演示数据 transaction.atomic def handle(self, *args, **options): courses [] for i in range(1, 11): course, _ Course.objects.get_or_create( course_nofC{i:03d}, defaults{name: f专业课程{i}, credit: 2.0} ) courses.append(course) for i in range(1, 101): student, _ Student.objects.get_or_create( student_nof2024{i:04d}, defaults{ name: f同学{i}, gender: random.choice([M, F]), enroll_year: 2024, phone: f138{i:08d}, } ) for course in random.sample(courses, k5): Score.objects.update_or_create( studentstudent, coursecourse, defaults{score: round(random.uniform(60, 100), 1)} ) self.stdout.write(self.style.SUCCESS(seed done))这段代码的写法是幂等的重复执行不会产生重复数据因为get_or_create依赖唯一学号、update_or_create依赖唯一(student, course)组合。100名学生每人5条成绩已经足够把分页、搜索和报表都演示一遍。3. 查询、删除与表单校验的系统实现路径3.1 用ListView接住搜索与筛选参数Django视图有函数视图和类视图两种写法。对毕业设计这类页面结构稳定的项目我更愿意用ListView和DeleteView这类通用视图因为分页、删除确认这些流程已经预置不必每页重复写request.GET.get这种裸逻辑。# students/views.py from django.db.models import Q from django.views.generic import ListView from .models import Student class StudentListView(ListView): model Student template_name students/student_list.html context_object_name students paginate_by 20 def get_queryset(self): qs super().get_queryset() keyword self.request.GET.get(keyword, ).strip() gender self.request.GET.get(gender, ).strip() if keyword: qs qs.filter(Q(name__icontainskeyword) | Q(student_no__icontainskeyword)) if gender: qs qs.filter(gendergender) return qsQ(name__icontainskeyword)会转成LIKE %关键字%的SQL对姓名和学号两个字段做OR匹配gender过滤是简单的等值判断。两个filter条件同时出现时自动用AND连接空字符串参数不产生过滤条件。这样以参数绑定方式处理请求比在模板里直接拼参数安全得多因为它没有把用户输入拼进原生SQL。参数来源数据库操作keywordrequest.GETLIKE %关键字%姓名或学号genderrequest.GET等值匹配gender字段paginate_byListView属性每页条数模板里翻页时也要带上keyword和gender否则翻到第二页就丢了筛选结果。具体写法在第4章的分页代码里展示。3.2 ModelForm校验的边界新增和编辑表单建议交给ModelForm。它让字段声明和模型保持一致重复代码变少遇到学号、电话这种特殊业务规则再用clean_开头的方法补齐。先给出表单代码# students/forms.py from django import forms from .models import Student class StudentForm(forms.ModelForm): class Meta: model Student fields [student_no, name, gender, birth_date, phone, enroll_year] widgets { birth_date: forms.DateInput(attrs{type: date}), } def clean_student_no(self): no self.cleaned_data[student_no] if not no.isdigit(): raise forms.ValidationError(学号只能是数字) return no def clean_phone(self): phone self.cleaned_data.get(phone) if phone and (len(phone) ! 11 or not phone.isdigit()): raise forms.ValidationError(手机号需要是11位数字) return phoneclean_student_no这种命名约定会被Django在is_valid()阶段自动调用抛出的ValidationError会写进表单的errors字典而不会中断其他字段校验。widgets里的DateInput对应浏览器原生日期选择器省去自己写JavaScript的麻烦。这里的校验边界在于ModelForm管的是字段结构和必填业务规则的学号唯一、手机号格式放Form层因为Form能拿到用户当前提交的完整上下文比放Model层的save方法里更容易定位问题。3.3 django执行查询-删除对象时的级联处理删除是CRUD里最需要想清楚的环节。django执行查询-删除对象时如果被删对象被外键引用必须想明白on_delete策略。我在团队项目里的习惯是删除学生前先检查是否有成绩记录有的话在界面上给出提示而不是直接CASCADE。# students/views.py from django.http import HttpResponseRedirect from django.urls import reverse_lazy from django.views.generic import DeleteView from django.db import transaction class StudentDeleteView(DeleteView): model Student success_url reverse_lazy(student_list) transaction.atomic def form_valid(self, form): student self.get_object() student.scores.all().delete() student.delete() return HttpResponseRedirect(self.get_success_url())两个删除动作包在事务里要么一起成功要么一起回滚。如果外键用的是PROTECT必须先清成绩再删学生如果用的是CASCADE上面代码其实等价于直接student.delete()。手动写成两步是为了在删除前能够记录日志答辩时被问到删了学生成绩怎么办这段代码就是完整回答。4. 模板渲染、分页与报表导出的完整闭环4.1 模板继承与表单组件复用Django模板系统里最值得先掌握的是继承和block覆盖。base.html放公共框架子页面只写各自的content块。对Web期末作业这类前端基础有限的场景直接引入Bootstrap的CDN即可不必从零写CSS。!-- templates/base.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title{% block title %}学生信息管理系统{% endblock %}/title link relstylesheet hrefhttps://cdn.staticfile.org/twitter-bootstrap/5.3.0/css/bootstrap.min.css /head body classbg-light nav classnavbar navbar-expand-lg navbar-dark bg-dark a classnavbar-brand px-3 href{% url student_list %}学生信息系统/a /nav main classcontainer mt-4 {% block content %}{% endblock %} /main /body /html子模板继承它!-- templates/students/student_list.html -- {% extends base.html %} {% block content %} div classcard div classcard-body form methodget classrow g-2 mb-3 div classcol-auto input typetext namekeyword classform-control placeholder姓名或学号 value{{ request.GET.keyword }} /div div classcol-auto select namegender classform-select option value全部性别/option option valueM {% if request.GET.gender M %}selected{% endif %}男/option option valueF {% if request.GET.gender F %}selected{% endif %}女/option /select /div div classcol-auto button typesubmit classbtn btn-primary搜索/button /div /form table classtable table-striped thead trth学号/thth姓名/thth性别/thth操作/th/tr /thead tbody {% for student in students %} tr td{{ student.student_no }}/td td{{ student.name }}/td td{{ student.get_gender_display }}/td td a classbtn btn-sm btn-outline-secondary href{% url student_export student.pk %}导出成绩/a /td /tr {% empty %} trtd colspan4 classtext-center没有匹配结果/td/tr {% endfor %} /tbody /table /div /div {% endblock %}get_gender_display输出choices的显示值模板里直接比较request.GET.gender与选项值能保留上次选择的筛选条件empty标签在查询结果为空时展示替代文案比在视图里硬编码更干净。4.2 分页组件的参数与模板写法ListView启用paginate_by后模板里会出现page_obj和paginator。分页链接必须保留keyword和gender两个参数这是最容易踩的坑。下面是我确认可用的分页模板写法{% if page_obj.paginator.num_pages 1 %} nav ul classpagination justify-content-center {% if page_obj.has_previous %} li classpage-item a classpage-link href?page{{ page_obj.previous_page_number }}keyword{{ request.GET.keyword|urlencode }}gender{{ request.GET.gender|urlencode }}上一页/a /li {% endif %} li classpage-item disabled span classpage-link{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}/span /li {% if page_obj.has_next %} li classpage-item a classpage-link href?page{{ page_obj.next_page_number }}keyword{{ request.GET.keyword|urlencode }}gender{{ request.GET.gender|urlencode }}下一页/a /li {% endif %} /ul /nav {% endif %}模板变量含义page_obj当前页对象含has_previous等方法page_obj.paginator.num_pages总页数request.GET.keyword保留搜索关键字urlencode过滤器中文与特殊字符安全的URL编码keyword是中文时|urlencode也会把它转成URL安全的编码这样粘贴链接和收藏夹都不会出错。4.3 用openpyxl导出成绩单导出Excel是答辩里很常见的加分项。openpyxl只处理xlsx不依赖系统级组件比weasyprint这类渲染引擎轻得多。下面是按学生维度导出成绩单的视图# students/views.py from django.http import HttpResponse from openpyxl import Workbook from openpyxl.styles import Font def export_student_scores(request, pk): student Student.objects.prefetch_related(scores__course).get(pkpk) wb Workbook() ws wb.active ws.title f{student.name}成绩单 headers [课程号, 课程名, 学分, 成绩] ws.append(headers) for col, width in zip(ABCD, [10, 24, 8, 8]): ws.column_dimensions[col].width width for cell in ws[1]: cell.font Font(boldTrue) for sc in student.scores.all(): ws.append([sc.course.course_no, sc.course.name, float(sc.course.credit), float(sc.score)]) response HttpResponse( content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) response[Content-Disposition] fattachment; filenamestudent_{student.student_no}.xlsx wb.save(response) return responseprefetch_related(scores__course)一次取出成绩和课程数据避免模板循环里逐条查询产生N1问题。表头加粗、列宽调整后打印出来更清晰。Content-Disposition设置的filename决定浏览器下载的默认文件名。5. 答辩现场验证与Django项目部署调试技巧5.1 用manage.py shell核验业务数据答辩前留5分钟跑一遍shell验证比手动点页面快也更有说服力python manage.py shell -c from students.models import Student, Score from django.db.models import Avg print(Student.objects.count()) print(Score.objects.aggregate(avgAvg(score))) print(Student.objects.filter(genderF).count()) 输出结果和页面展示一致说明数据模型、视图和模板之间没有断裂。真出现不一致时优先用ORM确认数据本身再检查模板渲染这两步能定位大多数显示异常。5.2 用django-debug-toolbar定位查询瓶颈列表页每次刷新出现几十条SQL通常是模板里逐条访问了外键字段。在settings.py的INSTALLED_APPS中加入debug_toolbar.apps.DebugToolbarConfig在MIDDLEWARE中加入debug_toolbar.middleware.DebugToolbarMiddleware并在INTERNAL_IPS写入127.0.0.1然后打开任意列表页SQL面板会展示每条查询的语句、耗时和调用栈。把模板里scores的调用换成select_related或prefetch_related后查询次数往往从几十次降到两三次。pip install django-debug-toolbar5.3 部署环节的参数速查演示时把项目放到云服务器上让老师远程访问比较常见以宝塔部署Django为例本质是用Python项目管理器创建项目运行命令指向gunicorn再由Nginx转发静态资源和动态请求。pip install gunicorn gunicorn sims_config.wsgi:application -b 0.0.0.0:8000 --workers 2 --timeout 30参数作用建议值-b 0.0.0.0:8000监听地址本机调试用127.0.0.1更安全--workers 2Worker进程数2~4看内存--timeout 30单请求超时时间30~60--access-logfile -访问日志输出到标准输出排错好用DEBUGFalse后要同步配置ALLOWED_HOSTS和STATIC_ROOT否则静态文件加载不出来。执行顺序也讲究先python manage.py migrate建表再python manage.py collectstatic收拢静态文件最后重启gunicorn颠倒了就会在第一次请求时报OperationalError。本文还有配套的精品资源点击获取