本文最终实现一个Web在线考试管理系统,可作为Python Web,Django的练手项目。

摘要

本系统实现学生在线考试管理的基本功能,包括学生登录、查看自己的个人信息及考试信息;提供了在线考试的界面;后台管理员有管理员添加学生、管理学生、管理成绩、添加课程、添加题库题目和组建试卷等功能。本次的学生在线考试管理系统采用Python Django做后端、前端框架采用Bootstrap4实现,实现学生考试的动态管理,使得对信息的管理更加及时、高效,提高了效率。同时还对系统的开发原理、功能特点和设计方案进行了介绍。
关键词:考试管理 openGauss数据库 Python Django Web

实验目的

本实验通过学习开发基于数据库的应用软件,达到:
(1)练习在应用程序中使用、操作数据库。
(2)掌握数据库模式设计、分析和实现的方法。
(3)掌握数据库设计和实现的基本过程。
(4)了解数据库应用软件开发的一般过程。
(5)掌握openGauss数据库在软件开发过程中的使用。

报告正文

1. 概述

1.1课题题目:学生在线考试管理系统
1.2系统的主要目标:
本系统的目标是实现在线考试管理系统所需的各种基本功能,包括学生登录、查看相关信息、导出学生信息等功能以及后台管理员添加学生、维护学生信息、成绩管理、管理考试相关信息等功能,还有公用的登录退出等功能
1.3 系统的开发环境及运行环境:
操作系统: Windows 10
数据库系统:openGauss数据库
开发工具: Pycharm(社区版)
服务端: Python 3.8
Web框架: Django 3.2
前端框架: Bootstrap 4

2. 系统需求分析

学生在线考试系统主要满足来自三方面的需求,这三个方面分别是学生用户、教师用户和管理员用户,也即是三类用户角色。
(1)学生用户是主要的需求者,主要需求功能是查看当前自己的考试信息、查看考试成绩并可导出以及进行在线考试等。
(2)教师用户主要需求功能是为自己所教授的课程组建题库和相对应的试卷,并可以查看学生的考试信息等。
(3)管理员用户的功能需求较为复杂,包括对学生信息、教师信息、考试信息进行管理。
在线考试管理系统,主要包含如下几个功能模块:

  • 用户登录:实现简单的登录及验证
  • 个人信息展示:展示考生个人信息
  • 考试管理:展示可以考试的试卷,考试及自动计算考试成绩。
  • 考试成绩管理:展示考试结果、导出考试信息
  • 后台基础数据管理:试卷,试题,考生等信息更新维护。

在这里插入图片描述

3. 系统总体设计及实现思路

系统设计包括三部分:数据库设计,功能函数视图设计,前端页面设计

3.1 数据库设计

根据对系统需求分析,得出该考试管理系统大致需要六个实体,他们的实体属性如下图所示:
在这里插入图片描述
根据所分析的各实体之间的联系可画完整的ER图如下所示:
在这里插入图片描述
根据上面的实体联系图可以得出以下几个表:

  • 学院表:Academy
  • 专业表:Major
  • 课程表:Course
  • 学生表:Student
  • 题库表:QuestionBank
  • 试卷表:TestPaper
  • 学生成绩表:Record
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
3.2 页面及功能设计

为了实现我们前面的功能模块我们设计如下几个功能页面:
在这里插入图片描述
1、登录页面:
其中需要登录,校验,登录后同时需要存储用户信息在Session中,以备登录后的页面使用。
2、首页(考试信息页):
页面需要显示当前用户可以参考的考试试卷信息,在此页面点击开始考试可以跳转到考试页面。
3、考试页面:
展示对应试卷的题目和选项信息,同时可以进行答题,交卷后可以自动计算考试分数并存入数据库。
4、成绩显示页面:
展示对应考试的考试结果
5、后台管理:
用于管理我们的专业,考生,试卷,题库等基础信息,为了快速实现系统将直接启用Django自带的Admin管理功能。
6、个人详情:
用于展示个人详情信息。

4. 详细设计

学生在线考试系统是实现学生在线考试、查看相关信息的一个平台,整个学生在线考试系统共分为3个大模块:管理员模块,学生模块和公有模块,其中复杂的方法和模块的详细设计流程图如下。

4.1学生用户登录流程图

在这里插入图片描述

4.2 学生考试流程图

在这里插入图片描述

4.3自动判卷流程图

在这里插入图片描述

4.4学生查看信息流程图

在这里插入图片描述

4.5后台流程图

在这里插入图片描述

5. 软件实现和测试

5.1 openGauss数据库连接

psycopg2连接数据库
openGauss是基于PostgreSQL9.2版本开发的,基本包括了PostgreSQL9.4的功能。所以可以采用连接postgresql的方式连接openGauss数据库。
在新建django项目下的setting中的DATABASES下进行以下配置:

DATABASES = {
    'default': {
        # 'ENGINE': 'django.db.backends.sqlite3',
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres', #数据库名
        'USER': 'andy', #用户名
        'PASSWORD': 'test123', #密码
        'HOST': '192.168.56.101',#虚拟机ip
        'PORT': 26000 #openGauss数据口的端口
    }
}

在项目文件夹下打开终端执行python manage.py runserver
在这里插入图片描述
出现上面的界面且没有报错即为连接openGauss数据库成功。

5.2 功能模块详细开发

一个Django项目框架搭建起来后,我们所有对系统的前后台所有的程序开发都可以在这个项目中进行了,一个典型的Django项目模块功能的开发包括如下几个步骤:

  • 创建app
  • 注册app
  • 定义模型
  • 定义视图函数
  • 配置访问路由URL
  • 静态资源准备及配置
  • 前端模板开发
  • 测试及运行
    创建app
    在Django中的一个app代表一个功能模块,Django 规定,如果要使用模型,必须要创建一个 app。
    本来一般实际开发中方便管理一个功能模块需要单独新建一个app,我们为了快速开发,所有的功能和模型都在一个app中进行管理。
    在命令行中输入 python manage.py startapp exam指令,创建名为exam的app:
    python manage.py startapp exam
    注册APP
    在DjangoExam文件夹目录下的settings.py 中找到INSTALLED_APPS配置项,将新创建的exam添加到项目的app列表,如下:
    INSTALLED_APPS = [
    ‘django.contrib.admin’,
    ‘django.contrib.auth’,
    ‘django.contrib.contenttypes’,
    ‘django.contrib.sessions’,
    ‘django.contrib.messages’,
    ‘django.contrib.staticfiles’,
    ‘exam’, #添加此项
    ]
    定义模型(主要属性)
# 学院表
class Academy(models.Model):
    id = models.AutoField('序号', primary_key=True)
    name = models.CharField('学院', max_length=20)
# 专业表

class Major(models.Model):
    id = models.AutoField('序号', primary_key=True)
    academy = models.ForeignKey(Academy, on_delete=models.CASCADE, verbose_name='学院')
    major = models.CharField('专业', max_length=30)

# 课程表
class Course(models.Model):
    id = models.AutoField('序号', primary_key=True)
    course_id = models.CharField('课程号', max_length=10)
    course_name = models.CharField('课程名称', max_length=30)

# 学生表
class Student(models.Model):
    sid = models.CharField('学号', max_length=12, primary_key=True)
    name = models.CharField('姓名', max_length=20, unique=True)
    sex = models.BooleanField('性别', choices=((0, '女'), (1, '男')))
    age = models.IntegerField('年龄')
    academy = models.ForeignKey(Academy, on_delete=models.CASCADE, verbose_name='学院')
    major = models.ForeignKey(Major, on_delete=models.CASCADE, verbose_name='专业')
    sclass = models.CharField('班级', max_length=20, help_text='例如: 17-03')
    email = models.EmailField('邮箱', default=None)  # 默认为空   唯一值
    pwd = models.CharField('密码', max_length=20)

# 题库表
class QuestionBank(models.Model):
    id = models.AutoField('序号', primary_key=True)
    major = models.ForeignKey(Major, on_delete=models.CASCADE, verbose_name='专业')
    course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='科目')
    title = models.TextField('题目')
    qtype = models.CharField('题目类型', choices=(('单选', '单选'), ('多选', '多选'), ('判断', '判断')), max_length=40)
    a = models.CharField('A选项', max_length=40)
    b = models.CharField('B选项', max_length=40)
    c = models.CharField('C选项', max_length=40)
    d = models.CharField('D选项', max_length=40)
    answer = models.CharField('答案', choices=(('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')), max_length=4)
    difficulty = models.CharField('难度', choices=(('easy', '简单'), ('middle', '中等'), ('difficult', '难')), max_length=10)
    score = models.IntegerField('分值')
    
# 试卷表
class TestPaper(models.Model):
    id = models.AutoField('序号', primary_key=True)
    title = models.CharField('题目', max_length=40, unique=True)
    pid = models.ManyToManyField(QuestionBank)
    course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='科目')
    major = models.ForeignKey(Major, on_delete=models.CASCADE, verbose_name='考卷适合专业')
    time = models.IntegerField('考试时长', help_text='单位是分钟')
    examtime = models.DateTimeField('上次考试时间')
    
## 学生成绩表
class Record(models.Model):
    id = models.AutoField('序号', primary_key=True)
    sid = models.ForeignKey(Student, on_delete=models.CASCADE, verbose_name='学号', related_name='stu_xuehao')
    course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='考试科目', related_name='stu_course')
    grade = models.FloatField('成绩')
    rtime = models.DateTimeField('考试时间', blank=True, null=True)
    

在这里插入图片描述
定义视图函数
Django 中视图的概念是「一类具有相同功能和模板的网页的集合」。
比如,在一个考试系统中,我们可能需要如下几个视图:
登录:输入用户和密码,根据校验结果进行登录处理。
考试:展示考试试题及选项,根据选择的结果记录考试成绩。
这些需求都靠视图(View)来完成。
每一个视图表现为一个简单的Python函数,它需要要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse对象,或者抛出一个异常,比如 Http404。
视图函数中的request与网页发来的请求有关,里面包含get或post的内容、用户浏览器、系统等信息。
根据系统设计过程中需要的功能,我们在exam/views.py文件中创建如下几个视图函数:
在这里插入图片描述
在这里插入图片描述
下面以其中几个视图函数为例:

# 学生登录
def studentLogin(request):
    if request.method == 'POST':
        # 获取表单信息
        sid = request.POST.get('sid')
        password = request.POST.get('password')
        print("sid", sid, "password", password)
        # 通过学号获取该学生实体
        student = models.Student.objects.get(sid=sid)
        print(student)
        if password == student.pwd:  # 登录成功
            request.session['username']=sid    #user的值发送给session里的username
            request.session['is_login']=True   #认证为真
            # 查询考试信息
            paper = models.TestPaper.objects.filter(major=student.major)
            # 查询成绩信息
            grade = models.Record.objects.filter(sid=student.sid)

            # 渲染index模板
            return render(request, 'index.html', {'student': student, 'paper': paper, 'grade': grade})
        else:
            return render(request, 'login.html', {'message': '密码不正确'})
    elif request.method == 'GET':
        return render(request, 'login.html')
    else:
        return HttpResponse("请使用GET或POST请求数据")

def userfile(request):
    if request.session.get('is_login',None):  #若session认证为真
        username = request.session.get('username',None)
        print(username )
        student = models.Student.objects.get(sid=username)
        # 查询考试信息
        paper = models.TestPaper.objects.filter(major=student.major)
        return render(request, 'userfile.html',{'student': student})

def exportexaminfo(request):
    if not request.session.get('is_login', None):
        return HttpResponseRedirect('/studentLogin')

    username = request.session.get('username',None)
    # print(username)
    student = models.Student.objects.get(sid=username)
    grades = models.Record.objects.filter(sid=username)

    response = HttpResponse(content_type='text/csv')
    response['Content-Dispostion'] = 'attachment;filename="exam-info.csv"'

    writer = csv.writer(response)
    writer.writerow(['姓名', '科目', '成绩', '考试时间'])
    for grade in grades:
        writer.writerow([student.name, grade.course, grade.grade, grade.rtime])
    return response
def calculateGrade(request):
    if request.method == 'POST':
        sid = request.POST.get('sid')
        subject1 = request.POST.get('subject')
        student = models.Student.objects.get(sid=sid)
        paper = models.TestPaper.objects.filter(major=student.major)
        grade = models.Record.objects.filter(sid=student.sid)
        course = models.Course.objects.filter(course_name=subject1).first()
        now =  datetime.now()
        # 计算考试成绩
        questions = models.TestPaper.objects.filter(course__course_name=subject1).\
            values('pid').values('pid__id','pid__answer','pid__score')

        stu_grade = 0  # 初始化一个成绩
        for p in questions:
            qid = str(p['pid__id'])
            stu_ans = request.POST.get(qid)
            cor_ans = p['pid__answer']
            if stu_ans == cor_ans:
                stu_grade += p['pid__score']
        models.Record.objects.create(sid_id=sid, course_id=course.id, grade=stu_grade,rtime=now)
        context = {
            'student': student,
            'paper': paper,
            'grade': grade
        }
        return render(request, 'index.html', context=context)

def calculateGrade(request):
    if request.method == 'POST':
        sid = request.POST.get('sid')
        subject1 = request.POST.get('subject')
        student = models.Student.objects.get(sid=sid)
        paper = models.TestPaper.objects.filter(major=student.major)
        grade = models.Record.objects.filter(sid=student.sid)
        course = models.Course.objects.filter(course_name=subject1).first()
        now =  datetime.now()
        # 计算考试成绩
        questions = models.TestPaper.objects.filter(course__course_name=subject1).\
            values('pid').values('pid__id','pid__answer','pid__score')

        stu_grade = 0  # 初始化一个成绩
        for p in questions:
            qid = str(p['pid__id'])
            stu_ans = request.POST.get(qid)
            cor_ans = p['pid__answer']
            if stu_ans == cor_ans:
                stu_grade += p['pid__score']
        models.Record.objects.create(sid_id=sid, course_id=course.id, grade=stu_grade,rtime=now)
        context = {
            'student': student,
            'paper': paper,
            'grade': grade
        }
        return render(request, 'index.html', context=context)

# 考试信息
def startExam(request):
    sid = request.GET.get('sid')
    title = request.GET.get('title')  # 试卷名字 唯一
    subject1 = request.GET.get('subject')  # 考试科目
    # 获取学生信息
    student = models.Student.objects.get(sid=sid)
    # 试卷信息
    paper = models.TestPaper.objects.filter(title=title,course__course_name=subject1)
    count = (models.TestPaper.objects.filter(title=title).values('pid','time'))
    print(count)
    print(count[0]['time'])
    context = {
        'student': student,
        'paper': paper,
        'title': title,
        'subject':subject1,
        'count': len(count),   # 数据表中数据的条数
        'time': count[0]['time']
    }
    # print(context)
    return render(request, 'exam.html', context=context)

剩下具体的视图代码见源码。
配置访问路由URL
有了视图后,还需要将视图函数和Web网页链接对应起来。
url可以理解为访问网站时输入的网址链接,配置好url后Django才知道怎样定位app。
打开DjangoExam/urls.py,输入如下代码:

urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^$',views.index),#默认访问首页
    url('index/',views.index,name='index'),
    url('studentLogin/',views.studentLogin,name='studentLogin'),#学生登录
    url('startExam/',views.startExam,name='startExam'),#开始考试
    url('calculateGrade/',views.calculateGrade,name='calculateGrade'),#考试评分
    path('stulogout/',views.stulogout,name='stulogout'), # 学生退出登录
    path('userfile/',views.userfile,name='userfile'), # 个人信息
    path('examinfo/',views.examinfo,name='examinfo'), # 考试信息
    path('exportexaminfo', views.exportexaminfo) # 导出考试成绩信息
]

静态资源准备及配置
本系统前后端不分离,前端框架选用当前比较受欢迎的Bootstrap4,为了快速开发,本系统所有的页面都使用原生Bootstrap进行开发,未采用第三方的模板和主题。
在项目根目录下新建一个文件夹static用于存放前端模板静态资源。
把下载的Bootstrap解压出来的css和js两个文件夹复制到static文件夹去。
把解压出来的jQuery文件夹复制到static文件夹去。
完成后Static文件夹结构如下图:
在这里插入图片描述

模板创建
在创建模板之前,我们先在根目录下新建一个文件夹templates用于存放我们的所有的模板文件。
模板位置也同样需要进行配置指定模板的存放位置,在DjangoExam/settings.py 中进行如下配置:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

页面html采用模板继承的写法来简化页面代码的编写,这样的也可以将页面中的功能高度相关的模块独立出来,有利于代码的复用和后期功能的维护。具体页面代码结构如下图所示:
在这里插入图片描述
下面仅展示其中几个的具体代码
Header:

<!-- 定义导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
    <div class="container">
        <!-- 导航栏商标 -->
        <a class="navbar-brand" href="#">在线考试</a>
        <!-- 导航入口 -->
        <div>
            <ul class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" href="/index/">首页</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/examinfo/">考试记录</a>
                </li>
                <!-- Django的 if 模板语句 -->
                {% if request.session.username %}
                <!-- 如果用户已经登录,则显示用户名下拉框 -->
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        {{ request.session.username }}
                    </a>
                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                        <a class="dropdown-item" href="/userfile/">个人信息</a>
                        <a class="dropdown-item" href="/stulogout/">退出登录</a>
                    </div>
                </li>
                <!-- 如果用户未登录,则显示 “登录” -->
                {% else %}
                <li class="nav-item">
                    <a class="nav-link" href="/studentLogin/">登录</a>
                </li>
                <!-- if 语句在这里结束 -->
                {% endif %}
                <li class="nav-item">
                    <a class="nav-link" href="/admin">管理员</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

Footer:

{% load static %}
<!-- Footer -->
<div>
    <br><br><br>
</div>
<footer class="py-3 bg-dark fixed-bottom">
    <div class="container">
        <p class="m-0 text-center text-muted">Copyright &copy; DjangoExam 2021</p>
    </div>
</footer>
Base:
<!--    载入静态文件 引入static标签-->
{% load static %}
<!-- 网站主语言 -->
<html lang="zh-cn">
<head>
    <!-- 网站采用的字符编码 -->
    <meta charset="utf-8">
    <!-- 预留网站标题的位置 -->
    <title>{% block title %}{% endblock %}</title>
    <!-- 引入bootstrap的css文件 -->
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
</head>
<body>
<!-- 引入导航栏 -->
{% include 'header.html' %}
<!-- 预留具体页面的位置 -->
{% block content %}{% endblock content %}
<!-- 引入注脚 -->
{% include 'footer.html' %}
<!-- bootstrap.js 依赖 jquery.js 和popper.js,因此在这里引入 -->
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<script src="{% static 'jquery/jquery-3.6.0.min.js' %}"></script>
<!--
    popper.js 采用 cdn 远程引入,意思是你不需要把它下载到本地。
    在实际的开发中推荐静态文件尽量都使用 cdn 的形式。
    教程采用本地引入是为了让读者了解静态文件本地部署的流程。
-->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1-lts/dist/umd/popper.min.js"></script>

<!-- 引入bootstrap的js文件 -->
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
</body>

</html>

Index:

<!-- extends表明此页面继承自 base.html 文件 -->
{% extends "base.html" %}
{% load static %}
<!-- 写入 base.html 中定义的 title -->
{% block title %}
在线考试系统
{% endblock title %}
<!-- 写入 base.html 中定义的 content -->
{% block content %}
<div class="container">
    <div class="container">
        <br>
        <h3>考试信息</h3>
        <div class="container">
            <div class="row mt-4">
                {% for paper1 in paper %}
                <!-- 文章内容 -->
                <div class="col-6 mb-6">
                    <!-- 卡片容器 -->
                    <div class="card">
                        <!-- 标题 -->
                        <h4 class="card-header">{{ paper1.title }}</h4>
                        <!-- 摘要 -->
                        <div class="card-body">
                            <h4 class="card-title">{{ paper1.course }}</h4>
                            <p class="card-text">{{ paper1.examtime  }}</p>
                            <a href="/startExam/?sid={{ student.sid }}&title={{ paper1.title }}&subject={{ paper1.course }}"  class="card-link">开始考试</a>
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        </div>
        <p></p>
    </div>
</div>
{% endblock content %}

其他具体的html代码见源码所示。

5.3 功能测试

学生在线考试考试管理系统的模块功能测试如下所示:
登录界面:
在这里插入图片描述
登录成功后进入主界面:
在这里插入图片描述
学生选择开始考试:
在这里插入图片描述
考试时间到,自动交卷
在这里插入图片描述

查看考试成绩:
在这里插入图片描述
点击按钮导出考试成绩记录:在这里插入图片描述
在这里插入图片描述
查看学生信息:
在这里插入图片描述
后台管理登录界面:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
具体操作就不再一一截图了。

openGauss数据库中的内容如下所示:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实验总结

1、通过这次数据库大作业的练习,进一步掌握数据库的方法和技术,提高软件开发的实际能力,培养工程设计能力和综合分析、解决问题的能力。

  • 学习和实践了分析和设计软件系统的各种知识,包括面向对象的系统分析与设计,编码和测试方面的知识。
  • 熟悉了如何根据实际需要分析实体关系,画出ER图从而设计出符合要求的数据库表。
  • 学习和实践的数据库的增删改查功能在具体功能开发中的使用。
  • 熟悉了openGauss数据库的相关操作。
  • 学习了利用django框架进行web开发的流程。
    2、本系统基本实现了关键的功能模块,在功能上基本满足了预期的需求,但是由于时间较紧,有些模块的功能还不太完善,比如可以增加按科目导出成绩、增加教师管理模块、增加题目类型等。

实验代码

https://www.aliyundrive.com/s/RESTEu8osUK
提取码:k36p

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐