项目的几个修改的说明:
1、样式表采用bootstrap5,静态文件引用相应地修改
2、应用webcoding更名为coding,在settings.py、coding/apps.py及其它引用的地方都要更名为coding
本文的主要内容:
1、数据模型的查询方法
2、模板语法与模板继承
3、在线代码编码功能的调用
4、difflib.Differ()方法对编程填空题的测评
一、应用场景
江苏省高中信息技术新课程有关编程算法的内容,python编程是重要内容,对编程能力的检验方法之一就是对给定的问题,试题有相应的算法和代码,代码中有几处填空。
要求学生根据算法和题目的思路,把几处空白给填写出来,调试运行,且解答符合标准答案。
解决方案:创建应用pygram,在视图中调用第二讲的在线编程的功能,结合题库建设,就可给出让学生来训练了。
学生填空内容与标准答案的对比,采用全文对比的算法找出几处解答与多个可能的标准答案对照,给出评分。
二、数据模型查询的两种方法
这是pygram应用中用到的两个数据模型,Pyoperate对应python编程填空题数据表,Examination对应存放学生作答结果的表
class Pyoperate(models.Model):
pid = models.AutoField(primary_key=True)
psubid = models.IntegerField(blank=True, null=True)
ptitle = models.TextField(blank=True, null=True)
plevel = models.TextField(blank=True, null=True)
pcontent = models.TextField(blank=True, null=True)
pcount = models.IntegerField(blank=True, null=True)
pcode = models.TextField(blank=True, null=True)
panswer = models.TextField(blank=True, null=True)
pinput = models.TextField(blank=True, null=True)
poutput = models.TextField(blank=True, null=True)
piscompil = models.IntegerField(blank=True, null=True)
pbigclass = models.TextField(blank=True, null=True)
psmallclass = models.TextField(blank=True, null=True)
ptag = models.TextField(blank=True, null=True)
class Examination(models.Model):
#学生作答的题型选项
whatid = (('pyid','python'),('chid','choice'),('wpsid','wps'),('accid','access'))
eid = models.AutoField(primary_key=True)
#外键esid,对应Student中的sid
esid = models.ForeignKey(Student, to_field="sid", on_delete=models.CASCADE, db_column='esid', related_name='stu_exam')
#题型的名称简称,见上面第一行
whidname = models.TextField(choices=whatid, blank=True, null=True)
#题型所在表中的id值
whidval = models.IntegerField(choices=whatid, blank=True, null=True)
egrade = models.FloatField(blank=True, null=True)
eanswer = models.TextField(blank=True, null=True)
eansfile = models.FileField(blank=True, null = True, upload_to=r'stuploads/%y%m%d/', validators=[FileExten(['xls', 'exlx','et','mdb','accdb']) ])
edate = models.DateTimeField(verbose_name = '提交时间',default=datetime.now(), blank=True)
eip = models.TextField(blank=True, null=True)
ename = models.TextField(blank=True, null=True)
在pygram.views.py视图文件中创建三个函数,依次来看。
第1个pyindex视图函数列出所有的编程题目,提供题目内容查询和题目关键字查询,并分页显示。
在django中对于复杂的查询可以使用sql原来语句来查询,简单的可以使用模型来查询。
详情看代码说明。
@login_required(login_url='/login/')#需要登录,才能访问这个视图
def pyindex(request, content='all', page='1'):
#有两个参数,content是查询内容,page是用来分页的
#以上是默认值
#django request.user 保存登录用户信息
curuser = request.user
#以下是显示出来的tag按钮,可以点击查询相应内容的题目
cs = {'zf':'卓帆', 'br':'百日冲刺', 'js':'数据与计算','rm':'入门', 'rando':'随机抽取' ,...... }
sqlstr = ""
#request.POST获取查询字符串,
searchstr = request.POST.get('searchpyinput',None)
if searchstr != None :
#查询出学生做了哪些题目,且所得的最高分,这是一个嵌套查询结合联结查询
sqlstr = f"""select pid,ptitle,plevel,pbigclass,ptag,esid,eanswer,egrade from pyoperate
left join (select * from examination where eid in (select max(eid) from examination where esid={curuser.sid} and whidname='pyid' group by whidval) ) exam
on pid = whidval
where ptitle like CONCAT(%s,%s,%s) or ptag like CONCAT(%s,%s,%s)
order by pid"""
content = 'serch'
cs['serch'] = searchstr
#查询方法一:原生sql语句 django的数据库模型可以执行原生的sql查询,当sql语句比较复杂时可采用
#postgresql的like查询比较特殊,请参照这个做法
thetimu = Pyoperate.objects.raw(sqlstr, params=['%',searchstr, '%','%', searchstr ,'%'])
if sqlstr=="":
if content == "all":
#几个查询差不多,只是查询条件有变化
sqlstr = f"""select pid,ptitle,plevel,pbigclass,ptag,esid,eanswer,egrade from pyoperate
left join (select * from examination where eid in (select max(eid) from examination where esid={curuser.sid} and whidname='pyid' group by whidval) ) as exam
on pid = whidval
order by pid"""
thetimu = Pyoperate.objects.raw(sqlstr)
elif content == "rando" :
#随机抽取几个题目,不超过一个页面
sqlstr = f"""select pid,ptitle,plevel,pbigclass,ptag,esid,eanswer,egrade from pyoperate
left join (select * from examination where eid in (select max(eid) from examination where esid={curuser.sid} and whidname='pyid' group by whidval) ) as exam
on pid = whidval
where pid in {tuple( random.sample([v for v in range(1,127)], random.randint(15,20) ))}
order by pid"""
thetimu = Pyoperate.objects.raw(sqlstr)
else :
#查询标签关键字
sqlstr = f"""select pid,ptitle,plevel,pbigclass,ptag,esid,eanswer,egrade from pyoperate
left join (select * from examination where eid in (select max(eid) from examination where esid={curuser.sid} and whidname='pyid' group by whidval) ) as exam
on pid = whidval
where ptitle like CONCAT(%s,%s,%s) or ptag like CONCAT(%s,%s,%s) or pbigclass like CONCAT(%s,%s,%s) or plevel like CONCAT(%s,%s,%s)
order by pid"""
thetimu = Pyoperate.objects.raw(sqlstr, params=['%',cs[content], '%','%', cs[content] ,'%','%',cs[content], '%','%', cs[content] ,'%'])
print(content)
#分页,每页面20个记录,没有采用django提供的分页功能
per_page = 20
theall = len(thetimu)
pagerange = list(range(1, theall//per_page+2))
pagelast = theall // per_page + 1
thetimu = thetimu[ (page-1)*per_page : page*per_page ]
#sqlstr = f"select count(distinct whidval) as pcount from examination where esid={curuser.sid} and whidname='pyid' "
#查询方法二,django模型Examintiont查询学生已经做了多少题,效果与上一行的sql语句一样
pregres = Examination.objects.filter(Q( esid=curuser.sid ) & Q( whidname='pyid' )).distinct('whidval').count()
print(pregres)
#查询pyoperate题库中题目总数
pyall = Pyoperate.objects.all().count()
return render(request, 'pygram/pyindex.html', locals() )
三、模板的继承和改写
在templates.pygram中创建两个模板文件pyindex.html、pygram.html。
先来看看模板文件base.html,父页面base.html在templatets中,是html网页的结构框架,公共的部分放在其中,如导航菜单、页脚、head中的内容,这些内容会继承到其它子网页中。
在父网页中用{%block <块名>%} [可继承可改写内容] {% endlock%}来标识,在各个子网页用这个标识来改写,若不改写就继承了这个块内容。
再看一下templates/pygram/pyindex.html的效果:
在这个页面中,菜单行和页脚行是继承自base.thml ,中间题目列表是pyindex.html中改写{% block content %}而成的。
分别解释一下两个页面的具体内容:
<html>
{% load static %}
<head>
{% block title %}
<title>Spring Dawn`s web class online ide for python itc.title>
{% endblock %}
<link rel="stylesheet" href="/static/bootstrap5/css/bootstrap.css">
{% block codemirror %}
{% endblock %}
{% block mycss%}
{% endblock %}
head>
<body>
<nav id="main_nav" class="navbar navbar-expand-lg navbar-light bg-white shadow">
<div class="container d-flex justify-content-between align-items-center">
<div class="align-self-center collapse navbar-collapse flex-fill d-lg-flex justify-content-lg-between" id="navbar-toggler-success">
<div class="flex-fill mx-xl-5 mb-2">
<ul class="nav navbar-nav d-flex justify-content-between mx-xl-5 text-center text-dark">
<li class="nav-item">
<a class="nav-link btn-outline-primary rounded-pill px-3" href="{% url 'codev' %}">Codinga>
li>
<li class="nav-item active">
<a class="nav-link btn-outline-primary rounded-pill px-3" href="{% url 'pyindex' %}">Pythona>
li>
.......................
div>
div>
div>
nav>
{% block content %}
{% endblock %}
<footer class="bg-three pt-4">
footer>
body>
html>
{% extends '../base.html' %}
{% load static %}
{% block title %}
<title>python 编程题练习 title>
{% endblock %}
{% block content%}
<section class="container py-5">
<div class="row" >
<div class="col-sm-1">div>
<div class="col-md-8" >
<table><tr><td >{{depart}}部分有{{theall}}题 td>
{% if page > 1 %}
<td style="border:0px solid #ccc;"> <a href="{% url 'pyindex' content 1 %}"> 首页 a>td>
<td style="border:0px solid #ccc;"> <a href="{% url 'pyindex' content page|add:-1 %}"> <h10> 上页 h10>a> td> {% else %}
<td style="border:0px solid #ccc;"> <h10> 首页 h10>td>
<td style="border:0px solid #ccc;"> <h10> 上页 h10>td>
{% endif %}
{% for i in pagerange %}
{% if i == page %}
<td style="border:0px solid #ccc;"> <h10> {{i}} h10>td>
{% else %}
<td style="border:0px solid #ccc;"> <a href="{% url 'pyindex' content i %}"> <h10> {{i}} h10>a>td>
{% endif %}
{% endfor %}
{% if page < pagelast %}
<td style="border:0px solid #ccc;"> <a href="{% url 'pyindex' content page|add:1 %}"> <h10> 下页 h10>a> td>
<td style="border:0px solid #ccc;"> <a href="{% url 'pyindex' content pagelast %} "> <h10> 尾页 h10>a>td>
{% else %}
<td style="border:0px solid #ccc;"> <h10> 下页 h10>td>
<td style="border:0px solid #ccc;"> <h10> 尾页 h10>td>
{% endif %}
tr>
table>
<table class="table table-striped table-bordered table-hover table-condensed" style="width:100%;">
<thead>
<tr><th> 序号 th><th> 标题 th><th> 难度 th><th> 来源 th><th> 标签 th><th> 进度 th><th> *** 作 th> tr>
thead>
<tbody>
{% if thetimu %}
{% for item in thetimu %}
<tr>
<td>{{ item.pid }} td><td>{{ item.ptitle }} td>
{% if item.plevel == "入门" %}
<td style="color:green"> {{item.plevel}}td>
{% elif item.plevel == "容易" %}
<td style="color:green"> {{item.plevel}}td>
{% elif item.plevel == "中等" %}
<td style="color:blue"> {{item.plevel}}td>
{% elif item.plevel == "困难" %}
<td style="color:#ff5555"> {{item.plevel}}td>
{% else %}
<td style="color:red">{{item.plevel}}td>
{% endif%}
<td> {{ item.pbigclass }} td>
<td> {{ item.ptag|join:',' }} td>
<td>
{% if item.esid == None %}
<font style="color:red">☆font>
{% elif item.eanswer != None %}
{% if item.egrade > 9.9 %}
<font style="color:green">★ {{item.egrade}}font>
{% else %}
<font style="color:red">✪ {{item.egrade}}font>
{% endif %}
{% endif %}
td>
<td> <a href="{% url 'pyid' item.pid %}" > 练习 a> td>
{% endfor %}
{% endif %}
tbody>
table>
div>
<div class="col-md-3" style="border:1px solid #dddddd; height:100% " align="center">
<br>
<h4><a href="{% url 'pyid' 0 %}"> 随机练习 a> <a href="{% url 'pyindex' %}">全部a> h4>
<br>
<form name='searchpy' action='/pygram/' method='post'>
{% csrf_token %}
<div class="input-group">
<input name='searchpyinput' type="text" class="form-control input-md required" placeholder="搜索编程问题">
<span class="input-group-addon btn btn-primary" onclick="searchpy.submit()">搜索span>
div>
form>
<br>
<div >
{% for key,item in cs.items %}
{% if key != 'serch' %}
<button style="margin:10px;padding:5px;" type='button' class="btn {{ btnclass | random}}" onclick="location='{% url "pyindex" key %}' " > {{item}} button>
{% endif %}
{% endfor %}
div>
<br><br><br>
{{ curuser.sname }} {{ curuser.sno }} {{ curuser.sclass }} <br>已完成{{ pregres }}题/共有{{ pyall }}题
<br>
div>
div>
section>
{% endblock%}
在pyindex.html大量使用了django模板语法,请仔细阅读并理解。
总结如下:
{% for item in thetimu %}
{{ forloop.count }}
{{ item.ptag|join:',' }}
{{ forloog.count0 }}
{% endfor %}
{% for key,item in cs.items %}
{% if key != 'serch' %}
{{item}}
{% endif %}
{% endfor %}
三、编程填空题的测评 1、视图函数
应用pygram.views中第2个视图函数pygram(),是向学生解答界面提供试题信息,
@login_required(login_url='/login/')
def pygram(request,num):
curuser = request.user #获取当前用户
pyall = Pyoperate.objects.all().count() #试题总数
if num == 0:
num = random.randint(1,pyall) #随机抽题
request.session['timuid'] = num #保存题号,供测评用
sqlstr = f"""select py.*,esid,eanswer,egrade,eid
from pyoperate as py
left join (select * from examination where eid in (select max(eid) from examination where esid={curuser.sid} and whidname='pyid' group by whidval) ) as exam
on pid = whidval
where pid = {num}
"""
#查询相应试题内容,及学生以前的得分
py_one = Pyoperate.objects.raw(sqlstr)
if py_one:
#print(py_one.pcode)
return render(request, 'pygram/pygram.html', locals() )
else:
return redirect('/pygram/')
2、模板文件
与上面对应的模板文件templates/pygram/pygram.html 部分内容 与 templates/coding/codit.html相似,介绍一下。
{% extends '../base.html' %}
{% load static %}
{% block title %}
<title> python ide 编程测试 title>
{% endblock%}
{% block codemirror %}
<script type="text/javascript" src="/static/js/clipboard.min.js">script>
<script>
function cpypst(element,sources) {
var clipboard = new Clipboard(element, { //绑定元素id
text: function () {
var str = document.getElementById(sources).innerText;
editor.setValue(str)
return str;
},
});
}
script>
{% endblock %}
{% block content%}
<div class="container py-5">
<header>
<div ><font size='6'> python 编 程 测 试 font>
<a href="javascript:history.go(-1)">返回上一页a> <a href="{% url 'pyid' 0 %}"> 随机下一题 a>div>
header>
<br>
<div class="main-content">
<div class="row ">
<div class="col-sm-1" >div>
<div class="col-sm-8">
<b>第{{ py_one.pid | safe}}题: {{ py_one.ptitle | safe}} ,难度: {{ py_one.plevel | safe}}b> <br>
{{ py_one.pcontent | safe}}
div>
<div class="col-sm-2">
<br>
<div class="from-group">
<pre id="preme" style="display:none;">{{py_one.pcode|safe}}pre>
<input type="button" class="btn btn-primary" id="btn_Share" onclick="cpypst('#btn_Share','preme')" value="重新导入代码">
div><br />
{% if py_one.eanswer != None %}
<div class="form-group">
<pre id="preold" style="display:none;">{{py_one.eanswer|safe}}pre>
<input type="button" class="btn btn-primary" id="btn_Share1" onclick="cpypst('#btn_Share1','preold')" value="导入上次作答">
div>
{% endif %}<br />
div>
<div class="col-sm-1" >div>
div>
div>
<br />
{% endblock%}
3、测评函数
应用pygram.views中第3个视图函数pyjudge,根据上述模板文件sjax提交的学生作答代码,编译运行后,对比标准答案并给评分。
编译运行与应用coding.views.codejudgeserver视图相同,这里重点介绍答案比照标准答案方法。
@login_required(login_url='/login/')
@require_POST
def pyjudge(request):
curuser = request.user
timuid = request.session.get("timuid", -1)
if timuid == -1:
return redirect('/pygram/')
'''
这一部分与coding.views.codejudgeserver相同,
学生的代码stu_src, 通过judgeSever 编译运行
结果result,向下执行
'''
try:
#获取题目,其中的填空是用 ①②③④⑤⑥⑦⑧⑨⑩ 数字,前后加两个下划线表达的 __①__
# 学生作答,只能修改这些标识,不得篡改代码的其它部分
thetimu = Pyoperate.objects.get(Q( pid = timuid ))
print(thetimu)
if thetimu != None:
# calgrade这个函数调用了difflib.Differ()方法来全文对比,找出学生作答代码与题目中不同的地方
# 找出 学生作答代码与题目中不同的地方,并与标答比照,给出评分
result_grade = calgrade(stu_src, thetimu)
stu_grade = float(z.split("\n")[0][5:])
print(stu_grade)
except:
result_grade = "由于某种原因,没有正常判分。
"
stu_grade = 0
if stu_grade>0:
#找出学生曾经作答该题的最高分,如果现在得分更高,则学生作答记录存入examination表中
from django.db.models import Max
#查询最高分
resumore = Examination.objects.filter( Q( whidname = 'pyid' ) & Q( whidval = thetimu.pid ) \
& Q( esid = curuser.sid ) ).all().aggregate(Max('egrade'))
print(resumore)
if resumore == None :
resumore = { 'egrade__max': 0 }
if resumore['egrade__max'] == None or resumore['egrade__max'] < 10 and stu_grade > resumore['egrade__max'] :
stu_exam = Examination(esid = curuser, whidname='pyid', whidval = thetimu.pid, eanswer = stu_src,
egrade = stu_grade )
#save方法可以update 或 insert into
stu_exam.save(force_insert=True)
#根据result 及resut_grade 组织返回信息
y=""
if err != None:
y = result['data']
y = y + "\n\n" + result_grade
else:
y = result['data'][0]['output']
y = y + "\n" + result_grade
#print(y)
return JsonResponse(y)
最后贴出函数calgrade(stu_src, thetimu),代码有些丑陋,虽然解决了问题:
import difflib
def calgrade(stu_src, thetimu):
d = difflib.Differ() #创建Differ()对象,文本分解成行
a = thetimu.pcode.splitlines()
a.pop(0)
b = stu_src.splitlines()
print("a:",len(a))
print("b:",len(b))
r1=[]
r2=[]
for i in range(len(a)):
diff = d.compare(a[i],b[i]) # 采用compare方法对字符串进行比较,
dd = list(diff)
#print(dd)
bb=[]
cc=[]
n = 0
N = len(dd)
be = ''
af = ''
while n <N:
#print(n)
k = 0
while n<N and dd[n][0]==" " :
n += 1
k += 1
while n<N and dd[n][0]=="-" :
bb.append(dd[n][2:])
n += 1
be = '-'
while n<N and dd[n][0]=="+" :
cc.append(dd[n][2:])
n += 1
be = '+'
bbb="".join(bb)
ccc="".join(cc)
if bbb!="" and ccc!="":
tmpi = 0
for ch in bbb:
if ch in "①②③④⑤⑥⑦⑧⑨⑩":
tmpi += 1
if tmpi >= 2 :
r1 += [item for item in "".join(bbb).split("\n") if item!=" " and item!=""]
r2 += [item for item in "".join(ccc).split("\n") if item!=" " and item!=""]
else:
r1.append(bbb)
r2.append(ccc)
r1=["".join([t for t in list(item) if t not in ['\n','\t',' ']]) for item in r1]
r2=["".join([t for t in list(item) if t not in ['\n','\t']]) for item in r2]
print(r1)
print(r2)
stdans = thetimu.panswer
print(stdans)
t="①②③④⑤⑥⑦⑧⑨⑩"
r1r = []
for it in r1:
tmp=""
for ch in it:
if ch in t:
tmp += ch
r1r.append(tmp)
print(r1r)
r1index = []
for it in r1r:
r1index.append(t.find(it))
stugrade = 0
judgestring = [0 for _ in range(len(stdans))]
for i,v in enumerate(stdans):
if i in r1index:
j = r1index.index(i)
for item in v:
if r2[j].replace(" ","").replace("'",'"') == item.replace(" ","").replace("'",'"'):
stugrade += 1
judgestring[i] = t[i] +" 得分: 标准答案:" + item + "\t 你的答案:" + r2[j]
break
else:
judgestring[i] = t[i] +" 错误: 标准答案:" + item + "\t 你的答案:" + r2[j]
else:
judgestring[i] = t[i] +" 错误: 标准答案:" + ",".join(stdans[i]) + "\t 你的答案:没有作答"
stugrade = 10 * stugrade /len(stdans)
judgestring= "本题得分:" +str(round(stugrade,2))+"\n"+'\n'.join(judgestring)
print(judgestring)
return judgestring
上个效果图
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)