news 2026/9/16 15:31:45

Django学生信息管理系统开发实战:从数据库设计到部署答辩

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django学生信息管理系统开发实战:从数据库设计到部署答辩

简介:这套基于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 django==4.2.* mysqlclient==2.2.* django-admin startproject sims_config . python manage.py startapp students

python -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/Shanghai'

charset=utf8mb4主要解决中文和生僻字存储问题,用默认latin1写中文姓名时很容易报Incorrect string value。LANGUAGE_CODE设为zh-hans后,admin后台和表单错误提示变成中文,答辩演示观感更顺;TIME_ZONE影响auto_now_add字段写入的时间是否按本地时区。如果先不接MySQL,把ENGINE改回django.db.backends.sqlite3,把NAME指到BASE_DIR下的db.sqlite3,Django新项目默认就是这个配置。

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_length=20, unique=True, db_index=True) name = models.CharField('姓名', max_length=50) gender = models.CharField('性别', max_length=1, choices=GENDER_CHOICES, default='M') birth_date = models.DateField('出生日期', null=True, blank=True) phone = models.CharField('手机号', max_length=11, null=True, blank=True) enroll_year = models.PositiveSmallIntegerField('入学年份') created_at = models.DateTimeField('创建时间', auto_now_add=True) 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_length=10, unique=True) name = models.CharField('课程名', max_length=100) credit = models.DecimalField('学分', max_digits=3, decimal_places=1, default=2.0) class Meta: ordering = ['course_no'] class Score(models.Model): student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='scores') course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='scores') score = models.DecimalField('成绩', max_digits=5, decimal_places=1, validators=[MinValueValidator(0), MaxValueValidator(100)]) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('student', 'course')

字段参数里最能影响维护体验的是related_name和on_delete。related_name='scores'让反向查询变成student.scores.all(),不必写Score.objects.filter(student=student)。on_delete=models.CASCADE表示删学生时级联删除成绩,答辩演示最省事;若成绩需要留痕,把外键改成on_delete=models.PROTECT,删除学生时Django会抛ProtectedError,要求你先清理成绩表。

常用字段的参数速查表如下:

字段类型常用参数适用位置
CharFieldmax_length、unique、db_index学号、姓名、电话
DateFieldnull=True、blank=True出生日期
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 createsuperuser

createsuperuser用于登录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_no=f'C{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_no=f'2024{i:04d}', defaults={ 'name': f'同学{i}', 'gender': random.choice(['M', 'F']), 'enroll_year': 2024, 'phone': f'138{i:08d}', } ) for course in random.sample(courses, k=5): Score.objects.update_or_create( student=student, course=course, 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__icontains=keyword) | Q(student_no__icontains=keyword)) if gender: qs = qs.filter(gender=gender) return qs

Q(name__icontains=keyword)会转成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 phone

clean_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 lang="zh-CN"> <head> <meta charset="UTF-8"> <title>{% block title %}学生信息管理系统{% endblock %}</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.3.0/css/bootstrap.min.css"> </head> <body class="bg-light"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand px-3" href="{% url 'student_list' %}">学生信息系统</a> </nav> <main class="container mt-4"> {% block content %}{% endblock %} </main> </body> </html>

子模板继承它:

<!-- templates/students/student_list.html --> {% extends 'base.html' %} {% block content %} <div class="card"> <div class="card-body"> <form method="get" class="row g-2 mb-3"> <div class="col-auto"> <input type="text" name="keyword" class="form-control" placeholder="姓名或学号" value="{{ request.GET.keyword }}"> </div> <div class="col-auto"> <select name="gender" class="form-select"> <option value="">全部性别</option> <option value="M" {% if request.GET.gender == 'M' %}selected{% endif %}>男</option> <option value="F" {% if request.GET.gender == 'F' %}selected{% endif %}>女</option> </select> </div> <div class="col-auto"> <button type="submit" class="btn btn-primary">搜索</button> </div> </form> <table class="table table-striped"> <thead> <tr><th>学号</th><th>姓名</th><th>性别</th><th>操作</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 class="btn btn-sm btn-outline-secondary" href="{% url 'student_export' student.pk %}">导出成绩</a> </td> </tr> {% empty %} <tr><td colspan="4" class="text-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 class="pagination justify-content-center"> {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}&keyword={{ request.GET.keyword|urlencode }}&gender={{ request.GET.gender|urlencode }}">上一页</a> </li> {% endif %} <li class="page-item disabled"> <span class="page-link">{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span> </li> {% if page_obj.has_next %} <li class="page-item"> <a class="page-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(pk=pk) 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(bold=True) 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_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = f'attachment; filename=student_{student.student_no}.xlsx' wb.save(response) return response

prefetch_related('scores__course')一次取出成绩和课程数据,避免模板循环里逐条查询产生N+1问题。表头加粗、列宽调整后打印出来更清晰。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(avg=Avg('score'))) print(Student.objects.filter(gender='F').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-toolbar

5.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 -访问日志输出到标准输出排错好用

DEBUG=False后要同步配置ALLOWED_HOSTS和STATIC_ROOT,否则静态文件加载不出来。执行顺序也讲究:先python manage.py migrate建表,再python manage.py collectstatic收拢静态文件,最后重启gunicorn,颠倒了就会在第一次请求时报OperationalError。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:31:10

C语言通讯录管理系统进阶:结构体、文件读写与内存管理实战

简介&#xff1a;面向初学C语言及课程设计的学生&#xff0c;这份DevC通讯录管理系统项目完整覆盖通讯录的录入、显示、排序、查找、插入、删除与修改等核心功能&#xff1b;通讯录字段涵盖姓名、单位、手机、分类、EMAIL、QQ等&#xff0c;排序支持按姓名、单位、城市等多种方…

作者头像 李华
网站建设 2026/9/16 15:30:32

Java Web基础实战:Servlet+JSP校园二手交易系统

简介&#xff1a;本资源是一套面向计算机专业本科生的毕业设计级校园二手交易平台完整实现&#xff0c;采用JSPServletMySQL经典Java Web技术栈&#xff0c;覆盖用户管理、商品发布与浏览、交易流程、消息通知及基础安全防护等核心模块&#xff0c;适用于课程设计、毕设参考与W…

作者头像 李华
网站建设 2026/9/16 15:30:20

TeamAI Session Save 详解:如何脱敏存档有价值会话并保护隐私

TeamAI Session Save 详解&#xff1a;如何脱敏存档有价值会话并保护隐私 【免费下载链接】teamai-cli Make Every Team AI Native 项目地址: https://gitcode.com/GitHub_Trending/te/teamai-cli TeamAI&#xff08;teamai-cli&#xff09;是"让每个团队 AI 原生化…

作者头像 李华
网站建设 2026/9/16 15:29:56

Ryujinx Switch模拟器:如何免费运行4100款游戏

Ryujinx Switch模拟器&#xff1a;如何免费运行4100款游戏 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx Ryujinx 是一款用 C# 编写的开源 Nintendo Switch 模拟器&#xff0c;它在电…

作者头像 李华
网站建设 2026/9/16 15:29:51

Python本地家庭理财系统:SQLite数据建模与自动化记账

简介&#xff1a;本资源是一套基于SpringBoot的毕业设计级家庭理财管理系统源码&#xff0c;面向Java Web初学者与毕设开发者&#xff0c;解决个人及家庭日常收支记录、账户总览、多成员协同记账与可视化分析等实际财务管理需求。压缩包共495个文件&#xff0c;含48个Java核心业…

作者头像 李华