Django学生心理测试系统开发:从数据模型到评分算法全解析
2026/9/17 4:00:25 网站建设 项目流程

简介:面向Python课程设计与Django入门学习者,这套基于Django的学生心理测试系统是一份可直接运行的完整课程设计项目,覆盖心理测试题目管理、答题评分、结果反馈、用户登录与后台管理等典型功能,属于老师指导并通过的高分作品。资源共121个文件,以Python源码为主(48个.py),另有39个.pyc编译文件、4个.sql数据库脚本、7个HTML页面与5个CSS样式文件,以及17张用于测试页面的图片素材;整包仅1.83MB,轻量紧凑,下载后即可导入数据库并启动服务。项目目录遵循Django常规分层,模型、视图、模板、静态资源划分清晰,便于研读MTV架构、ORM查询与会话控制等关键知识点,也可快速改造为其他测评类系统。已有2657人学习使用,非常适合作为课程设计大作业、答辩演示或Django综合练习的参考模板。

1. 一次课程设计把“心理健康”做成可运行系统

学生心理测试系统几乎每年都会出现在 Python 课程设计的选题列表里,原因是它的功能边界足够清晰——量表展示、测试作答、结果评分,再加一个管理后台——但每个环节又能扩展出不少真实工程问题。用 Django 来做这个题目,核心优势不只是“自带后台管理”和 ORM 写起来省事,而是 MTV 架构天然地把“测试题目维护”和“用户作答流程”拆成了两个相对独立的模块,数据模型理清楚之后,后面的路由、视图、模板只是按部就班地填肉。本文从数据库表结构设计讲起,手把手把一套可作为课程设计提交的方案落地:模型层怎么建、量表怎么管理、评分算法放哪个位置、Django Admin 怎么把题目编辑功能白送给你,最后给出答辩前必查的清单和常见扣分点。

适合三类人阅读:选了类似题目但还没想清楚数据模型的在校生;需要快速搭一套内部心理测评工具但不想从零写表单校验的 Python 工程师;以及负责指导课程设计、想给学生提供一套“能跑通且有扩展空间”的参考实现的老师。

2. Django 学生心理测试系统的数据模型设计

2.1 先想清楚实体关系:量表、题目、选项、作答记录

常见的设计失误是一上来就建一张大表,字段从学号一直排到“第 50 题答案”,写完才发现没法扩展。心理测试系统的核心实体其实只有四类:量表(Scale)、题目(Question)、选项(Option)、作答记录(AnswerRecord)。它们的关系是:一个量表包含多道题目,一道题目包含多个选项,一个学生的一次测试产生一条作答记录,而作答记录又和具体题目、具体选项关联。用 Django 的 ORM 来表达,最外层的模型不需要写任何外键字段,关系全部挂在子表上,直接映射成数据库里的外键列。

以经典的 SCL-90 症状自评量表为例,90 道题每题 5 个选项,如果按“每题一个数字字段”的扁平设计,后端代码会写死到无法维护,换一张量表就要改表结构。正确做法是把量表抽象成配置数据,题目和选项都是独立的行记录。

# models.py from django.db import models class Scale(models.Model): name = models.CharField('量表名称', max_length=100) desc = models.TextField('量表说明', blank=True) is_active = models.BooleanField('是否启用', default=True) created_at = models.DateTimeField(auto_now_add=True) class Question(models.Model): scale = models.ForeignKey(Scale, on_delete=models.CASCADE, related_name='questions') text = models.CharField('题目内容', max_length=500) order = models.PositiveIntegerField('排序', default=0) class Meta: ordering = ['order'] class Option(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='options') text = models.CharField('选项文本', max_length=100) score = models.IntegerField('分值', default=1) order = models.PositiveIntegerField('排序', default=0) class Meta: ordering = ['order']

这套模型把“一份量表长什么样”完全变成了数据库里的数据。要做第二份量表,不需要动代码,只需要往三张表里插入记录。on_delete=models.CASCADE保证删除量表时级联清理题目和选项,避免产生孤儿数据。related_name让反向查询语义清晰:scale.questions.all()拿到全部题目,question.options.all()拿到全部选项。

2.2 作答记录的两种设计方案:推荐 JSON 存储明细

作答记录是心理测试系统里最容易设计过度的一张表。一个 90 题的量表,如果每题都建一行作答明细,一次测试会产生 90 行记录,学生表、班级表再关联进来,查询“某学生最近一次测试结果”需要连三张表。对课程设计来说,这个复杂度不必要;对真实系统来说,90 题的明细行也不算大,但没必要为“理论上的严谨”付出多表联查的代价。

常见的做法是:一张主表存一次测试的概要,答案解析结果用 JSON 字段直接存进去,明细表只在需要做“逐题分析报告”时才补充。

class AnswerRecord(models.Model): student = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='test_records') scale = models.ForeignKey(Scale, on_delete=models.PROTECT) answers = models.JSONField('作答明细', default=dict) total_score = models.IntegerField('总分', default=0) result_text = models.TextField('测评结果', blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at']

answers字段用 JSON 存的是{"题目ID": 选项ID}的映射,后端拿到后可以随时重新计算总分,也能在模板里循环输出逐题对照表。Django 3.1+ 内置JSONField,底层在 SQLite 里是文本存储,在 PostgreSQL 里是原生 JSONB,不需要额外安装扩展。选择这个方案后,代码里不出现任何一张“逐题明细表”,这是刻意的——因为课程设计提交的数据库文件要保持简洁,源码包里的db.sqlite3给别人打开时,三张核心表就能看懂整个系统的数据结构。

2.3 用 Django 迁移管理数据库版本

数据模型定稿后,迁移文件就是课程设计源码包里最容易被忽略但最加分的部分。很多人直接删掉migrations目录再python manage.py makemigrations,重新生成一份0001_initial.py,这没错,但如果做到一半改过模型,迁移历史里能看到整个演进过程,这才是“版本管理意识”的体现。

python manage.py makemigrations psychology python manage.py migrate
# 如果想在提交前重建一个干净的数据库文件 rm -f db.sqlite3 python manage.py migrate

迁移到 SQLite 的物理表现就是源码包里的db.sqlite3文件。答辩时老师问“数据库在哪里”,直接把db.sqlite3路径指出来,再用 SQLite 工具打开看一下三张表的字段,胜过任何口头解释。如果课程设计要求提供“数据库设计文档”,那就把python manage.py sqlmigrate psychology 0001的输出复制到文档里,这是 Django 自动生成的真实 SQL,比手写更不容易出错。

3. 测试流程与评分逻辑的代码实现

3.1 答题页的会话处理:不要求登录也能测

学生心理测试系统的典型使用场景是机房统一开测,如果强制每个学生先注册账号再登录,体验差且浪费时间。常见做法是允许匿名作答,用 Django session 记录“本次测试到哪一题了”,提交时再要求学生填写学号姓名。这样既保证了流程顺畅,又不会把匿名数据污染到用户表里。

# views.py from django.shortcuts import render, redirect from .models import Scale, Question, Option, AnswerRecord def test_start(request, scale_id): scale = Scale.objects.get(id=scale_id, is_active=True) request.session['scale_id'] = scale_id request.session['answers'] = {} return redirect('test_question', scale_id=scale_id, q_index=0) def test_question(request, scale_id, q_index): scale = Scale.objects.get(id=scale_id) questions = list(scale.questions.all()) total = len(questions) q = questions[q_index] if request.method == 'POST': option_id = request.POST.get('option') if option_id: answers = request.session.get('answers', {}) answers[str(q.id)] = int(option_id) request.session['answers'] = answers if q_index + 1 < total: return redirect('test_question', scale_id=scale_id, q_index=q_index + 1) return redirect('test_submit', scale_id=scale_id) options = q.options.all() current = q_index + 1 return render(request, 'psychology/test_question.html', { 'question': q, 'options': options, 'current': current, 'total': total, 'progress': round(current / total * 100), })

request.session在这里承担了临时存储的角色,answers在 session 里的形态和最终写入AnswerRecord.answersJSON 字段的形态完全一致,提交时不需要做格式转换。这种做法的好处是:即使学生中途关闭浏览器重新打开,session 还在(默认两周有效),点继续答题能回到中断位置。list(scale.questions.all())把 QuerySet 转为列表,是因为在循环里多次访问同一个 QuerySet 会重复查询数据库,转成列表只查一次。

3.2 评分算法放后端,不把分数判断写进模板

心理量表评分最常见的方法是:把所有题目的分值累加,再把总分和预设的常模分数区间对比,落到对应等级。关键设计决策是:常模区间要放在数据库或者一个独立配置模块里,不要写死在视图函数的 if 判断里,否则换一张量表就要改视图代码。

# services.py def calculate_result(scale, answers): total = 0 details = [] questions = scale.questions.prefetch_related('options') for q in questions: opt_id = answers.get(str(q.id)) opt = q.options.filter(id=opt_id).first() score = opt.score if opt else 0 total += score details.append({'question': q.text, 'option': opt.text if opt else '未作答', 'score': score}) if scale.name == 'SCL-90': # 总分超过 160 或阳性项目数超过 43,建议关注 positive_count = sum(1 for d in details if d['score'] >= 2) if total >= 160 or positive_count >= 43: result_text = '需要关注,建议预约心理咨询。' else: result_text = '总体状态良好,请继续保持规律作息。' else: result_text = '测试完成,感谢参与。' return total, result_text, details

评分函数放在services.py而不是视图内,是为了让单元测试可以绕过 HTTP 层直接调用。prefetch_related('options')在这里有实际意义:90 道题如果逐题查询选项,会产生 91 条 SQL;预取后只产生 2 条。课程设计里不一定会被问到这个细节,但在代码注释里写一句“用预取避免 N+1 查询”,能直接拉开和普通作业的差距。

3.3 查看历史结果的视图与 URL 配置

测试提交后生成一条AnswerRecord,学生最关心的就是“我到底测出了什么”。历史记录页面按时间倒序列出最近几次测试,点击可以展开看明细。这部分的 URL 配置虽然基础,但要保证/test/1/这样的路径和/record/5/这样的路径不冲突。

# urls.py from django.urls import path from . import views app_name = 'psychology' urlpatterns = [ path('scale/<int:scale_id>/', views.test_start, name='test_start'), path('scale/<int:scale_id>/question/<int:q_index>/', views.test_question, name='test_question'), path('scale/<int:scale_id>/submit/', views.test_submit, name='test_submit'), path('record/<int:record_id>/', views.record_detail, name='record_detail'), ]

视图层的test_submit从 session 里取答案、调calculate_result、创建AnswerRecord、最后清空request.session['answers']。注意不能清掉scale_id,因为得分页还要展示量表名。代码里用一个session.pop('answers', None)只移除答案,保留其他字段,这种细粒度操作在答辩时也能说清楚。

4. Django Admin 与数据可视化:让系统不止于“能跑”

4.1 用 Admin 管理量表和题目

Django 自带的后台管理功能对课程设计来说是最划算的加分项——不需要写一行前端代码,就能获得量表、题目、选项的增删改查界面。默认的ModelAdmin只显示idstr,但对嵌套了外键的模型,得自定义列表页和编辑页,否则作为课程设计提交会被老师质疑“功能完整度”。

# admin.py from django.contrib import admin from .models import Scale, Question, Option, AnswerRecord class OptionInline(admin.TabularInline): model = Option extra = 3 class QuestionAdmin(admin.ModelAdmin): list_display = ('id', 'scale', 'text', 'order') list_filter = ('scale',) inlines = [OptionInline] class AnswerRecordAdmin(admin.ModelAdmin): list_display = ('id', 'student', 'scale', 'total_score', 'created_at') list_filter = ('scale', 'created_at') date_hierarchy = 'created_at' readonly_fields = ('answers', 'total_score', 'result_text') admin.site.register(Scale) admin.site.register(Question, QuestionAdmin) admin.site.register(Option) admin.site.register(AnswerRecord, AnswerRecordAdmin)

OptionInline嵌入到QuestionAdmin里,后台录入一道题时直接在同一页面把 3 个选项填完,不用来回跳转。readonly_fields用于作答记录,防止管理员手动改分数——心理测试的分数应该由系统算出来,而不是人工修正。date_hierarchy会根据created_at自动生成一个按日期钻取的时间轴,数据量大时后台筛选效率明显提升。

4.2 给测试结果加一个简单的可视化页

课程设计要求“界面友好”,最直接有效的不是引入 ECharts 或其他前端图表库,而是用纯 HTML + CSS 做一个结果条。Django 模板里可以直接计算百分比宽度,不需要任何 JavaScript。

<div class="result-bar"> <div class="result-bar-inner" style="width: {{ score_percent }}%;"></div> </div> <p>您的总分:{{ record.total_score }}</p> <p>评估结果:{{ record.result_text }}</p>

后端给模板传一个score_percent,把总分映射到 0-100 的百分数。SCL-90 的总分范围一般是 90-450,用(total - 90) / 360 * 100算出比例。这个设计不占用任何额外依赖,只是模板渲染层面的简单变换,但在提交源码时不会因为引入了前端库而让项目体积显得臃肿。真正的图表展示不是课程设计的核心目标,把“结果解读”用文本表达清楚优先级更高。

4.3 导出测试数据为 CSV:老师最爱检查的功能

一个容易被问到但很多人没做的功能是“数据导出”。用 Django 的HttpResponse配合 CSV 模块,在后台加一个 action,把筛选出的作答记录导出成 Excel 可打开的文件,实现成本二十分钟,但带来的完整度提升非常明显。

import csv from django.http import HttpResponse def export_records_csv(modeladmin, request, queryset): response = HttpResponse(content_type='text/csv; charset=utf-8') response['Content-Disposition'] = 'attachment; filename="test_records.csv"' writer = csv.writer(response) writer.writerow(['学号', '量表', '总分', '结果', '日期']) for obj in queryset: writer.writerow([obj.student.username, obj.scale.name, obj.total_score, obj.result_text, obj.created_at.strftime('%Y-%m-%d %H:%M')]) return response export_records_csv.short_description = '导出所选记录为 CSV'

CSV 文件需要utf-8编码并给出Content-Disposition头,这样浏览器会直接触发下载。课程设计的源码包里有这个功能,在文档里加一句“管理员可一键导出测试数据进行初步统计”,就能把系统从“个人工具”抬升到“可以给辅导员用的管理平台”级别。注意 Django 4.x 及以上版本默认不要求django_extensions,这个功能只用标准库的csv就能完成,没有任何兼容性问题。

5. 答辩验收清单:数据库文件的完整性与可复现性

python课程设计基于Django的学生心理测试系统源码+数据库.zip作为交付形态时,核心评判标准不是功能多炫酷,而是另一个人拿到压缩包后能否三步跑起来。

# 步骤一:解压后确认项目结构包含 manage.py 和 db.sqlite3 # 步骤二:安装依赖 pip install django # 步骤三:启动并访问 python manage.py runserver

db.sqlite3文件里至少要预置一条 SCL-90 量表数据、配套的 90 道题和每题 5 个选项,以及至少 2 条模拟作答记录。如果答辩现场直接打开题目管理页是空的,系统再怎么演示也会被扣分。准备数据的过程可以用python manage.py shell批量插入,不要在代码里写死 JSON 导入,保持数据库和代码的边界清晰。

需要重点检查的细节包括:settings.pyALLOWED_HOSTS是否配置为['*'](本地演示时需要)、DEBUG是否保持True(课程设计没必要关,展示报错页反而显得项目未完成)、sqlite3文件权限是否为可写(只读文件会导致每次提交都报错)。源码包里不要包含__pycache__目录和虚拟环境文件夹,.gitignore要在提交前就配好,这是工程素养的直接体现。

答辩时建议演示三条路径:管理员登录后台新建一张量表并添加题目;学生匿名完成一次 5 题的短测试并查看结果;在记录列表中导出 CSV。这三条路径分别对应数据建模能力、流程设计能力和后台功能完整性,覆盖了课程设计评分的绝大部分权重。最后把db.sqlite3用 SQLite 浏览器打开给老师看表关系图,ER 图在文档中手画一份,本次课程设计在数据层面就站得住脚了。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询