第四讲 Django编程填空题的测评

第四讲 Django编程填空题的测评,第1张

项目的几个修改的说明:
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

上个效果图

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/570378.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存