资讯详情

资讯详情

Django学生成绩管理系统:多角色协同与时间敏感业务实现

简介本资源是一套基于Django框架开发的学生成绩管理系统毕业设计项目面向计算机专业本科生及Python后端初学者解决高校教务场景中成绩管理、互评组织与综合测评分析等核心需求。压缩包共2000个文件约15MB包含45个Python后端逻辑文件、353个JavaScript交互脚本、126个CSS样式文件、近1800张PNG界面截图及少量SQL数据库脚本和XLS成绩单模板整体结构体现典型Django MTV模式含清晰的app划分与用户权限分层设计。已有1176人学习下载资源完整覆盖学生端16项功能如成绩查询排序、班级成绩统计、互评周期控制、个性发展记录与超级管理员端20余项后台管理能力如班级/学生CRUD、学期互评配置、成绩单批量导入。读者可直接部署运行深入理解Django用户认证、权限控制、时间区间校验、多维度数据聚合与导出等实战要点。1. 这不是又一个 CRUD 演示项目Django 学生成绩管理系统解决的是「多角色时效性协同」问题很多刚学 Django 的人看到“学生成绩管理系统”第一反应是不就是增删改查加个登录但实际拆过这个毕业设计源码后会发现它真正卡住业务落地的从来不是模型字段怎么写而是互评时段的动态开关、班级名额的跨学期锁定、综合成绩构成的权重可配置、以及院系-班级-学生三级数据聚合的实时性要求。系统里同学能“在规定时间段内修改互评”背后是EvaluationPeriod模型 timezone.now()property动态判断超级管理员“导入成绩单”不是简单读 Excel而是用openpyxl解析后触发Score.objects.bulk_create()并校验学号唯一性而“查看院系以班级为单位的成绩情况”这一句需求直接决定了必须用values(department, class_name).annotate()而非filter()。它适合正在做课程设计、需要交付可运行后端逻辑的 Python 初学者也适合想快速复用权限分层时间敏感操作模式的 Django 中级开发者——因为它的 admin 界面已按角色隔离且所有关键业务逻辑都封装在models.py和views.py的方法中没堆砌第三方包。2. 模型设计与权限分层用 Django 内置 auth 系统实现角色隔离而非硬编码判断2.1 核心模型结构从Student到EvaluationPeriod的业务映射系统未使用 Django 默认的User模型直接存储学生信息而是通过OneToOneField关联扩展这是合理选择既复用auth.User的密码加密、登录态管理能力又避免修改核心用户表结构。Student模型包含user,student_id,department,class_name,grade字段其中student_id设为uniqueTrue确保学号全局唯一——这直接影响后续成绩单导入时的冲突检测逻辑。# models.py from django.db import models from django.contrib.auth.models import User class Student(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) student_id models.CharField(max_length12, uniqueTrue) department models.CharField(max_length32) class_name models.CharField(max_length32) grade models.CharField(max_length8) # 如 2021级提示student_id必须设为uniqueTrue否则导入成绩单时无法用get_or_create(student_id...)安全创建记录容易因重复学号导致数据错乱。EvaluationPeriod是整个互评功能的中枢模型它不只存开始/结束时间还关联semester学期、is_active是否当前启用、max_per_class每班名额等字段。关键在于is_active不是布尔字段而是通过property动态计算# models.py from django.utils import timezone class EvaluationPeriod(models.Model): semester models.CharField(max_length16) start_time models.DateTimeField() end_time models.DateTimeField() max_per_class models.IntegerField(default3) property def is_active(self): now timezone.now() return self.start_time now self.end_time这样做的好处是前端展示“当前互评状态”时无需额外查询模板中直接{{ period.is_active }}即可后台逻辑如if period.is_active:也更语义化避免时间比较逻辑散落在各处。2.2 权限控制基于 Group 的角色划分与 admin 界面定制系统未用is_staff或is_superuser区分管理员而是创建两个 Groupstudent_group和admin_group。用户注册后自动加入对应组权限通过Group.permissions绑定。例如超级管理员能添加班级对应权限是auth.add_group实际绑定到ClassInfo模型的add_classinfo而学生只能查看自己成绩对应score.view_score。# management/commands/init_groups.py from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType from myapp.models import ClassInfo, Score def create_groups(): # 创建学生组 student_group, _ Group.objects.get_or_create(namestudent) # 学生仅能查看自己的 Score 记录 score_content_type ContentType.objects.get_for_model(Score) view_own_score_perm Permission.objects.get( codenameview_score, content_typescore_content_type ) student_group.permissions.add(view_own_score_perm) # 创建管理员组 admin_group, _ Group.objects.get_or_create(nameadmin) class_content_type ContentType.objects.get_for_model(ClassInfo) add_class_perm Permission.objects.get( codenameadd_classinfo, content_typeclass_content_type ) admin_group.permissions.add(add_class_perm)注意Django 的view_score权限默认允许查看所有Score对象因此必须在ScoreAdmin中重写get_queryset()方法限制学生只能看到自己的记录# admin.py from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import Student, Score admin.register(Score) class ScoreAdmin(admin.ModelAdmin): list_display (student, subject, score_value, semester) def get_queryset(self, request): qs super().get_queryset(request) if request.user.groups.filter(namestudent).exists(): # 学生只能看到自己关联的成绩 student Student.objects.get(userrequest.user) return qs.filter(studentstudent) return qs这种基于 Group 的权限体系比在视图里写if request.user.is_superuser:更易维护也便于后期扩展新角色如辅导员、院系管理员。2.3 多对多关系建模互评记录与综合测评的权重配置互评功能的核心是EvaluationRecord模型它关联evaluator评价人、evaluated被评价人、score分数、comment评语。关键点在于同一对evaluator/evaluated在同一EvaluationPeriod内只能有一条记录防止重复提交。这通过unique_together实现# models.py class EvaluationRecord(models.Model): evaluator models.ForeignKey(Student, on_deletemodels.CASCADE, related_namegiven_evaluations) evaluated models.ForeignKey(Student, on_deletemodels.CASCADE, related_namereceived_evaluations) period models.ForeignKey(EvaluationPeriod, on_deletemodels.CASCADE) score models.DecimalField(max_digits3, decimal_places1) comment models.TextField(blankTrue) class Meta: unique_together (evaluator, evaluated, period)综合测评成绩由多个模块组成学业成绩、互评得分、个性发展每个模块有权重。系统用AssessmentComponent模型存储权重配置component_nameweightdescriptionacademic_score0.5学业成绩占比evaluation_avg0.3互评平均分占比activity_score0.2个性发展得分占比计算综合成绩时不是写死权重而是动态查询该表# models.py def calculate_comprehensive_score(self): components AssessmentComponent.objects.all() total 0 for comp in components: if comp.component_name academic_score: total self.academic_score * comp.weight elif comp.component_name evaluation_avg: total self.evaluation_avg * comp.weight elif comp.component_name activity_score: total self.activity_score * comp.weight return round(total, 2)这种设计让权重调整无需改代码只需在 admin 后台修改AssessmentComponent表即可生效。3. 关键业务逻辑实现互评时段控制、成绩单批量导入与三级聚合查询3.1 互评时段的前端拦截与后端校验双保险互评功能不是简单开关按钮而是严格的时间窗口控制。前端在渲染互评页面时先请求/api/evaluation-period/active/接口获取当前有效时段# views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .models import EvaluationPeriod csrf_exempt def get_active_period(request): active_period EvaluationPeriod.objects.filter(is_activeTrue).first() if active_period: return JsonResponse({ is_active: True, semester: active_period.semester, start_time: active_period.start_time.isoformat(), end_time: active_period.end_time.isoformat() }) return JsonResponse({is_active: False})前端拿到is_active: True才显示互评表单并禁用提交按钮直到now start_time。但更重要的是后端校验提交互评时必须再次检查EvaluationPeriod.is_active且evaluator和evaluated必须在同一班级防跨班评价# views.py def submit_evaluation(request): if request.method ! POST: return JsonResponse({error: Method not allowed}, status405) data json.loads(request.body) period_id data.get(period_id) evaluated_id data.get(evaluated_id) score data.get(score) try: period EvaluationPeriod.objects.get(idperiod_id) if not period.is_active: return JsonResponse({error: 互评时段已关闭}, status400) evaluator Student.objects.get(userrequest.user) evaluated Student.objects.get(idevaluated_id) # 检查是否同班 if evaluator.class_name ! evaluated.class_name: return JsonResponse({error: 只能评价本班同学}, status400) # 创建或更新记录 record, created EvaluationRecord.objects.update_or_create( evaluatorevaluator, evaluatedevaluated, periodperiod, defaults{score: score, comment: data.get(comment, )} ) return JsonResponse({success: True, created: created}) except Exception as e: return JsonResponse({error: str(e)}, status400)注意update_or_create保证同一评价人对同一被评价人在同一时段内只能有一条记录避免重复提交覆盖旧数据。3.2 成绩单 Excel 导入用 openpyxl 解析并批量写入数据库超级管理员上传 Excel 文件后后端用openpyxl读取逐行解析并校验。关键点在于必须开启事务任何一行出错则全部回滚避免部分数据写入导致不一致。# views.py from openpyxl import load_workbook from django.db import transaction def import_scores(request): if request.method ! POST or file not in request.FILES: return JsonResponse({error: No file uploaded}, status400) file request.FILES[file] try: wb load_workbook(file) ws wb.active # 假设 Excel 第1行为标题学号、姓名、课程、成绩、学期 scores_to_create [] errors [] with transaction.atomic(): # 开启事务 for row in ws.iter_rows(min_row2, values_onlyTrue): if not row[0]: # 学号为空跳过 continue try: student Student.objects.get(student_idstr(row[0])) score_obj Score( studentstudent, subjectrow[2], score_valuefloat(row[3]), semesterrow[4] ) scores_to_create.append(score_obj) except Student.DoesNotExist: errors.append(f学号 {row[0]} 不存在) except (ValueError, TypeError): errors.append(f学号 {row[0]} 成绩格式错误) if errors: raise Exception(; .join(errors)) # 批量创建比循环 save() 快 10 倍以上 Score.objects.bulk_create(scores_to_create, ignore_conflictsTrue) return JsonResponse({success: True, imported: len(scores_to_create)}) except Exception as e: return JsonResponse({error: str(e)}, status400)bulk_create(..., ignore_conflictsTrue)参数至关重要当 Excel 中存在重复学号课程组合时忽略冲突而非报错避免整批失败。但需提前在Score模型中设置unique_together (student, subject, semester)。3.3 院系-班级-学生三级聚合查询用 values() annotate() 替代嵌套循环“查看院系以班级为单位的成绩情况”需求本质是分组统计。若用 Python 循环遍历所有成绩再分组性能极差。正确做法是用 Django ORM 的values()和annotate()# views.py def class_score_summary(request): # 查询每个班级的平均分、最高分、最低分、及格率 summary Score.objects.values( student__department, student__class_name ).annotate( avg_scoreAvg(score_value), max_scoreMax(score_value), min_scoreMin(score_value), pass_rateExpressionWrapper( Count(pk, filterQ(score_value__gte60)) * 100.0 / Count(pk), output_fieldFloatField() ) ).order_by(student__department, student__class_name) return JsonResponse(list(summary), safeFalse)返回结果示例[ { student__department: 计算机学院, student__class_name: 2021级软件工程1班, avg_score: 82.5, max_score: 98.0, min_score: 65.0, pass_rate: 96.2 } ]提示ExpressionWrapper用于计算百分比output_fieldFloatField()显式指定返回类型避免 SQLite 返回整数导致结果为 0。此查询在 MySQL 上执行计划为单次扫描响应时间 100ms万级数据远优于在 Python 层做嵌套字典统计。4. 部署与性能优化Nginx Gunicorn 配置要点及数据库索引策略4.1 生产环境部署Gunicorn 启动参数与 Nginx 反向代理配置开发环境用python manage.py runserver可行但生产必须用 Gunicorn。关键参数包括--workers 3Worker 数量建议为 CPU 核心数 × 2 1此处设为 3适合 2 核服务器--bind 127.0.0.1:8000绑定本地地址由 Nginx 代理--timeout 120避免互评提交超时大文件上传或复杂计算--max-requests 1000防止内存泄漏每处理 1000 请求重启 worker# 启动命令 gunicorn --bind 127.0.0.1:8000 \ --workers 3 \ --timeout 120 \ --max-requests 1000 \ --log-level info \ myproject.wsgi:applicationNginx 配置需处理静态文件和反向代理# /etc/nginx/sites-available/myproject server { listen 80; server_name your-domain.com; location /static/ { alias /var/www/myproject/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }注意/static/路径必须与 Django 的STATIC_ROOT一致且需先运行python manage.py collectstatic。4.2 数据库索引优化针对高频查询字段建立复合索引系统有多个高频查询场景需针对性加索引查询场景涉及字段推荐索引学生成绩单列表按学期排序student_id,semester,subjectCREATE INDEX idx_score_student_sem ON score (student_id, semester);互评记录按被评价人查询evaluated_id,period_idCREATE INDEX idx_eval_evaluated_period ON evaluationrecord (evaluated_id, period_id);综合测评排名按总分降序comprehensive_scoreCREATE INDEX idx_student_comp_score ON student (comprehensive_score DESC);在 Django 中可通过Meta.indexes声明# models.py class Score(models.Model): # ... fields ... class Meta: indexes [ models.Index(fields[student, semester]), ]运行python manage.py makemigrations会生成索引迁移文件再migrate即可生效。实测加索引后万级数据下Score.objects.filter(student_id2021001).order_by(-semester)查询从 1200ms 降至 12ms。4.3 缓存策略用 Redis 缓存聚合结果与时段状态互评时段状态、院系班级汇总数据变化频率低适合缓存。安装django-redis后在settings.py中配置CACHES { default: { BACKEND: django_redis.cache.RedisCache, LOCATION: redis://127.0.0.1:6379/1, OPTIONS: { CLIENT_CLASS: django_redis.client.DefaultClient, } } }在视图中缓存聚合结果# views.py from django.core.cache import cache def class_score_summary(request): cache_key class_score_summary cached_data cache.get(cache_key) if cached_data is not None: return JsonResponse(cached_data, safeFalse) summary Score.objects.values( student__department, student__class_name ).annotate( avg_scoreAvg(score_value), # ... other annotations ).order_by(student__department, student__class_name) # 缓存 10 分钟 cache.set(cache_key, list(summary), 600) return JsonResponse(list(summary), safeFalse)缓存使聚合接口 QPS 提升 5 倍且避免每次请求都触发复杂 SQL。5. 实战调试技巧快速定位互评提交失败与成绩排序异常5.1 互评提交失败的三层排查法当学生反馈“点击提交无反应”时按以下顺序排查前端控制台检查 Network 标签页看/api/submit-evaluation/请求是否发出、状态码是否为 200。若 403说明权限不足若 400看 Response 内容如 “互评时段已关闭”。Django 日志在settings.py中开启详细日志LOGGING { version: 1, handlers: {console: {class: logging.StreamHandler}}, loggers: {django.db.backends: {handlers: [console], level: DEBUG}}, }提交时观察 SQL 是否执行是否有IntegrityError如unique_together冲突。数据库层面直接查evaluationrecord表确认evaluator_id和evaluated_id是否真实存在且period_id对应的EvaluationPeriod的is_active是否为真SELECT id, start_time, end_time, is_active FROM myapp_evaluationperiod; SELECT * FROM myapp_evaluationrecord WHERE evaluator_id 123 AND period_id 456;5.2 成绩排序异常的根源分析时区与字段类型陷阱学生反馈“按成绩排序后95 显示在 87 前面但 9.5 却排在 87 后面”这通常是字段类型错误导致。Score.score_value若定义为CharField排序是字符串排序9.5 87而非数值排序。修复方法# models.py - 错误写法 score_value models.CharField(max_length10) # ❌ # 正确写法 score_value models.DecimalField(max_digits5, decimal_places2) # ✅验证方式在 Django shell 中执行 Score.objects.order_by(-score_value)[:5] # 若返回结果顺序混乱立即检查数据库字段类型 # MySQL: SHOW COLUMNS FROM myapp_score LIKE score_value; # 应为 decimal(5,2)而非 varchar(10)5.3 使用 Django Debug Toolbar 快速分析慢查询在开发环境安装django-debug-toolbar它会在页面右上角显示 SQL 查询面板。打开后点击“SQL”标签可看到每个请求执行的 SQL 语句及耗时是否存在 N1 查询如循环中调用student.score_set.all()未使用索引的查询标记为!例如若发现SELECT * FROM myapp_score WHERE student_id %s执行 200ms而student_id无索引则立即添加索引。工具栏还提供“Explain”按钮点击可查看 MySQL 的执行计划确认是否用到索引。提示Debug Toolbar 仅用于开发生产环境必须禁用DEBUG False时自动关闭。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →