基于Django设计一个驾校在线考试系统,包括前台考试系统和后台管理系统,前台考试系统包括考生登录、科目一和科目四自动组卷、调用摄像头拍照和成绩计算。后台管理系统包括学员管理、考试预约、成绩管理和题库管理。
一、后台管理系统
1.后台登录界面
使用request.POST获取前端form表单提交的用户名查找user表,在获取用户名后再获取form表单提交的密码与用户密码进行比对,成功在session中录入用户信息,之后重定向至后台管理首页,否则提示登录失败的具体信息。
try:
#执行验证码校验
if request.POST['code']!=request.session['verifycode']:
context = {'info': '验证码错误!'}
return render(request,'myadmin/index/login.html',context)
#根据登录账号获取用户信息
user = User.objects.get(username=request.POST['username'])
md5 = hashlib.md5()
s = request.POST['pass'] + user.password_salt # 从表单中获取密码并添加干扰值
md5.update(s.encode('utf-8')) # 将要产生md5的子串放进去
if user.password_hash==md5.hexdigest(): # 获取md5值
#登录成功
request.session['adminuser']=user.toDict() #将当前登录成功的用户信息以adminuser为key写入到session中
return redirect(reverse('myadmin_index')) #重定向到后台管理首页
else:
context={'info':'登录密码错误!'}
except Exception as err:
print(err)
context={"info":"登录账号不存在!"}
return render(request,"myadmin/index/login.html",context)
2.学员管理
(1)添加学员
表单通过POST提交给View中的insert()方法,该方法使用request.FILES.get()方获取照片,若没有则返回None,之后将图片保存到项目根目录的static/uploads/student路径下。之后创建一个Student对象,通过request.POST方法获取POST提交的数据,并将密码做md5处理,最后通过save()方法保存到数据库。
# 照片的上传处理
myfile = request.FILES.get('photo', None)
print(myfile)
if not myfile:
return HttpResponse('没有照片上传文件信息')
photo = str(time.time()) + '.' + myfile.name.split(',').pop()
destination = open('./static/uploads/student/' + photo, 'wb+')
for chunk in myfile.chunks(): # 分块写入文件
destination.write(chunk)
destination.close()
ob=Student()
ob.name=request.POST['name']
ob.sex=request.POST['sex']
ob.birthday=request.POST['birthday']
ob.id_card=request.POST['id_card']
ob.tel=request.POST['tel']
ob.status_one=request.POST['one']
ob.status_four=request.POST['four']
ob.photo=photo
(2)删除学员
点击删除按钮时,弹出确认删除提示框,点击确定后先通过request.GET.get()方法获取要删除的学员id,之后通过id获取学员的照片路径,然后通过os.remove()方法删除图片,最后使用delete()方法从数据库中删除对应的学员信息。
try:
bid = request.GET.get('oid', 0)
ob=Student.objects.get(id=bid)
photo_path='./static/uploads/student/' + ob.photo
if os.path.exists(photo_path):
os.remove(photo_path)
ob.delete() #从数据库中删除
return HttpResponse('Y')
except Exception as err:
print(err)
context = {'info': '删除失败!'}
return HttpResponse('N')
(3)修改学员
点击编辑按钮先获取学员id,之后跳转到对应学员的信息修改表单,点击提交后调用View中的update()方法,该方法先对图片进行判断,如果没有新图片上传,则保留原图片,如果上传了新的图片则会先删除原照片,再写入新照片,最后在使用request.POST方法获取其他数据并保存。
try:
# 获取原照片
oldphoto = request.POST['oldphoto']
# 图片的上传处理
myfile = request.FILES.get("photo", None)
if not myfile:
photo = oldphoto
else:
photo = str(time.time()) + "." + myfile.name.split('.').pop()
destination = open("./static/uploads/student/" + photo, "wb+")
for chunk in myfile.chunks(): # 分块写入文件
destination.write(chunk)
destination.close()
if ob.photo:
# 删除原照片
oldphoto_path = './static/uploads/student/' + ob.photo
if os.path.exists(oldphoto_path):
os.remove(oldphoto_path)
(4)学员信息查找
通过学号或姓名查找学员,搜索框为空时,读取Student表的所有数据显示在页面上,在搜索框中使用条件查找之后对数据进行条件过滤,将过滤后的信息显示在页面上。
mywhere=[]#保存搜索条件
pageIndex=[]
#获取并判断搜索条件
kw=request.GET.get('keyword',None)
if kw:
slist= slist.filter(Q(id__contains=kw) | Q(name__contains=kw))
mywhere.append('keyword='+kw)
(5)查看学员详细信息
点击学员姓名后,前端调用js的doShow()方法,该方法使用ajax将学员的学号提交给后台,再通过View中的detail()方法接收,根据学号查找对应的学员信息并显示在界面上。
function doShow(id){
//$('#myModal').modal({keyboard:false})
$.ajax({
type:'get',
url:"{% url 'myadmin_student_detail' %}",
dataType:'text',
data:{sid:id},
async: false,
success:function(res){
$("#myModal div.modal-body").empty().append(res);
$('#myModal').modal({keyboard:false})
},
});
}
def detail(request):
#加载学员信息详情
sid = request.GET.get("sid", 0)
print(sid)
# 加载预约详情
slist = Student.objects.get(id=sid)
# 放置模板变量,加载模板并输出
context = {'slist': slist}
return render(request, "myadmin/student/detail.html", context)
3.考试预约
(1)添加预约
点击考试预约后,跳转到预约界面,该界面可以选择考试科目和考试日期,每日限制50个预约名额,没有名额则无法预约。
$.ajax({
url:'{% url 'myadmin_bookexam_surplus' %}',
type:"post",
data:{book_time:time,
book_subject:subject},
dataType:"JSON",
success:function(count){
$("#count").html("当日已预约人数:"+count.count);
$("#sur").html("当日剩余人数:"+(50-count.count));
if(50-count.count<=0){
$("#stop").html("当日预约人数已达上限");
document.getElementById("submit").style.backgroundColor = '#555555';
document.getElementById("submit").disabled=true;}
else{
document.getElementById("submit").style.backgroundColor = '#399BFF';
document.getElementById("submit").disabled=false;}
}
})
(2)查看预约详情
(3)修改预约
def update(request,uid=0):
"""执行修改"""
try:
ob=BookExam.objects.get(id=uid)
ob.subject = request.POST['subject']
ob.book_time = request.POST['book_time']
ob.save()
context = {'info': '修改成功!'}
except Exception as err:
context = {'info': '修改失败!'}
print(err)
return render(request,'myadmin/info.html',context)
4.成绩管理
每个学员在完成考试后都会将考试成绩保存到数据库中,在成绩管理模块中可以显示所有学员的考试成绩,并且可以查看学员的成绩单详情。
管理员只能对学员的成绩进行查看和删除操作,点击查看成绩单按钮后跳转到对应的成绩单页面,成绩单内容包括考生姓名、身份证号、考试时间、考试科目、考试成绩和考试结果。
5.题库管理
(1)添加试题
点击添加试题跳转至添加页面,其中题目图片选择图片链接的方式添加,在题目类型和正确答案的下拉框中设计二级联动,当题目类型为单选或多选题时开放ABCD四个选项的输入框,题目为判断题时清空并禁用CD选项的输入框,正确答案中也只保留AB选项。
//二级联动部分代码
if(first.selectedIndex == 2)
{
optionC.value="";
optionD.value="";
second.options.add(new Option("A", "1"));
second.options.add(new Option("B", "2"));
optionC.readOnly=true;
optionD.readOnly=true;
optionC.required=false;
optionD.required=false;
}
(2)修改试题
二、前台考试系统
1.登录界面
2.前台考试系统首页
登录成功时,系统会先获取考生最新的预约信息,并将其存入session,如果考生没有预约或预约时间与当前时间不符,则无法开始考试。
考生第一次交卷后,session中保存的考试次数信息增加一次,若第一次考试未通过,考生有第二次机会进行补考,若补考也没有通过,则无法再继续考试。
<h3>小车理论考试 科目一</h3>
<p>驾照类型: C1、C2 考试时间: 45分钟 题目数量: 100</p>
{% if request.session.student.booktime1 == request.session.student.today %}
{% if request.session.student.count1 < 2 %}
<a href="{% url 'web_startexam_one' 1 %}" target="_self" class="center">
{% else %}
<a href="javascript:void(0);" onclick="count();" target="_self" class="center">
{% endif %}
开始考试
<em><img src="{% static '/public/images/exam/img2_5.png' %}"></em>
</a>
{% else %}
<a href="javascript:void(0);" onclick="book()" target="_self" class="center">
开始考试
<em><img src="{% static '/public/images/exam/img2_5.png' %}"></em>
</a>
{% endif %}
3.考试界面
考试信息核对无误后,考生即可开始考试,考试界面包括学员信息,题目信息,题目提示和答题信息。如果考生是首次登陆考试,则会先生成试卷,以科目一为例,后台会先从数据库的题库表中随机选取60道选择和40道判断题。
在考试过程中,如果考生因异常情况退出,重新登陆后会根据session获取之前的题目和作答信息,考生可以继续作答。
if request.method == 'GET':
omod = QuestionOne.objects
clist = omod.filter(qtype=1).all() # 单选
tflist = omod.filter(qtype=2).all() # 判断
choice_list = random.sample(list(clist), 60) # 选择60道选择题
judge_list = random.sample(list(tflist), 40) # 选择40道判断题
question_list = choice_list + judge_list # 所有试题
request.session['student']['now1'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") #获得考试时间
data3 = [] # 题目缓存
(1)调用摄像头
摄像头的调用和拍照功能主要依靠JavaScript中的takePhotos()方法实现,该方法用到了 Video + Canvas + getUserMedia,在视频开启时主要是获取摄像头的视频流并显示在video标签中。
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
video = document.getElementById("video");
function successCallback(stream) {
// Set the source of the video element with the stream from the camera
if (video.mozSrcObject !== undefined) {
video.mozSrcObject = stream;
} else {
video.srcObject = stream;
}
video.play();
}
if (navigator.getUserMedia) {
navigator.getUserMedia({video: true}, successCallback, errorCallback);
} else {
alert('浏览器不支持getUserMedia.');
// Display a friendly "sorry" message to the user
}
(2)拍照
使用拍照功能时用到了Html Dom中的setInterval()方法,该方法可以按照指定的周期来调用函数,本系统指定5分钟拍照一次。在拍照函数中,先获取浏览器页面的canvas对象,再将获取的内容使用toDateURL()转换成base64编码,由于该编码以”data:image/png;base64,”开头,因此要写入文件还需进行剪切,这里使用substr()方法去除开头字符,获取一串base64数据,之后将该数据以ajax的方式提交给后台的getphoto()方法,该方法使用base64模块的b64decode()函数进行解码,将解码后的数据写入/static/getphoto路径下。
//每隔5分钟自动拍照
setInterval(function () { //每5分钟执行一次
context.drawImage(video, 0, 0, 320, 320);
//获取浏览器页面的画布对象
var canvans = document.getElementById("canvas");
//以下开始编 数据
var imgData = canvans.toDataURL();
//将图像转换为base64数据
var base64Data = imgData.substr(22);
//将获得的base64数据设置为photo的背景图
document.getElementById("photo").src='data:image/png;base64,'+base64Data;
$.ajax({
url:'{% url 'web_getphoto1' %}',
type:"post",
data:{img_str1:base64Data},
dataType:"JSON"
})},
300000);
img_data = base64.b64decode(img_str)
today=time.strftime('%Y-%m-%d',time.localtime(time.time()))+' '
strtime=str(time.time())[:10]
name=request.session['student']['name']+' 科目一 '
with open('./static/getphoto/'+name+today+strtime+'.jpg', 'wb+') as f:
f.write(img_data)
f.close()
return HttpResponse(img_str)
(3)判断题
(4)多选题
(5)成绩计算
考生完成作答后点击交卷,后台接收作答数组,将其与答案数组进行比对,如果相等,则正确数目+1。正确数计算完成后,将本次考试信息保存到数据库,若本次成绩大于90分,则将对应学员该科目的考试情况变更为通过。
for i in range(100):
op = 0
if answer[i] == 'A':
op = 1
elif answer[i] == 'B':
op = 2
elif answer[i] == 'C':
op = 3
elif answer[i] == 'D':
op = 4
else:
op = 0
if int(r_answer[i]) == op:
right_num += 1
ob = Grade()
ob.subject='科目一'
ob.grade=r['right_num']
ob.examtime=request.session['examtime']
ob.sid_id=request.session['student']['id']
ob.save()
if right_num>=90:
so = Student.objects.get(id=request.session['student']['id'])
so.status_one=1
so.update_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
so.save()