def gen_q(qtid, student=0, exam=0, position=0): """ Given a qtemplate, will generate a question instance. If student and/or exam is supplied it will be assigned appropriately. If exam is supplied, position must also be supplied. Will return the ID of the created instance. """ # Pick a variation randomly version = DB.get_qt_version(qtid) numvars = DB.get_qt_num_variations(qtid, version) if numvars > 0: variation = random.randint(1, numvars) else: L.warn("No question variations (qtid=%d)" % qtid) Audit.audit( 3, student, qtid, "General", "Failed to generate question %s for %s, exam %s" % (qtid, student, exam)) return False q_id = gen_q_from_var(qtid, student, exam, position, version, variation) if not q_id: Audit.audit( 3, student, qtid, "General", "Failed to generate instance of %s for %s, exam %s" % (qtid, student, exam)) return q_id
def render_q_html(q_id, readonly=False): """ Fetch the question html and get it ready for display - replacing links with appropriate targets and filling in form details.""" try: q_id = int(q_id) assert q_id > 0 except (ValueError, TypeError, AssertionError): log(WARN, "renderQuestionHTML(%s,%s) called with bad qid?" % (q_id, readonly)) qt_id = DB.get_q_parent(q_id) try: qt_id = int(qt_id) assert qt_id > 0 except (ValueError, TypeError, AssertionError): log(WARN, "renderQuestionHTML(%s,%s), getparent failed? " % (q_id, readonly)) variation = DB.get_q_variation(q_id) version = DB.get_q_version(q_id) data = DB.get_q_att(qt_id, "qtemplate.html", variation, version) if not data: log(WARN, "Unable to retrieve qtemplate for q_id: %s" % q_id) return "QuestionError" try: out = unicode(data, "utf-8") except UnicodeDecodeError: try: out = unicode(DB.get_q_att(qt_id, "qtemplate.html", variation, version), "latin-1") except UnicodeDecodeError, err: log(ERROR, "unicode error decoding qtemplate for q_id %s: %s" % (q_id, err)) raise
def remark_exam(exam, student): """Re-mark the exam using the latest marking. """ qtemplates = Exams.get_qts(exam) examtotal = 0.0 end = Exams.get_mark_time(exam, student) for qtemplate in qtemplates: question = DB.get_exam_q_by_qt_student(exam, qtemplate, student) answers = DB.get_q_guesses_before_time(question, end) try: marks = mark_q(question, answers) except OaMarkerError: L.warn("Marker Error, question %d while re-marking exam %s for student %s!" % (question, exam, student)) marks = {} parts = [int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0] parts.sort() total = 0.0 for part in parts: if marks['C%d' % part] == 'Correct': marks['C%d' % part] = "<b><font color='darkgreen'>Correct</font></b>" try: mark = float(marks['M%d' % part]) except (ValueError, TypeError, KeyError): mark = 0 total += mark DB.update_q_score(question, total) # OaDB.setQuestionStatus(question, 3) # 3 = marked examtotal += total Exams.save_score(exam, student, examtotal) return examtotal
def remark_exam(exam, student): """Re-mark the exam using the latest marking. """ qtemplates = Exams.get_qts(exam) examtotal = 0.0 end = Exams.get_mark_time(exam, student) for qtemplate in qtemplates: question = DB.get_exam_q_by_qt_student(exam, qtemplate, student) answers = DB.get_q_guesses_before_time(question, end) try: marks = mark_q(question, answers) except OaMarkerError: L.warn( "Marker Error, question %d while re-marking exam %s for student %s!" % (question, exam, student)) marks = {} parts = [ int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0 ] parts.sort() total = 0.0 for part in parts: if marks['C%d' % part] == 'Correct': marks['C%d' % part] = "<b><font color='darkgreen'>Correct</font></b>" try: mark = float(marks['M%d' % part]) except (ValueError, TypeError, KeyError): mark = 0 total += mark DB.update_q_score(question, total) # OaDB.setQuestionStatus(question, 3) # 3 = marked examtotal += total Exams.save_score(exam, student, examtotal) return examtotal
def exam_edit_submit(request, user_id, cid, exam_id): """ Accept the submitted exam edit/create form. If exam_id is not provided, create a new one. """ # TODO: More validation. Currently we trust the client validation, # although the user is authenticated staff so probably not too high a # risk of shenanigans. title = str(request.form['assess_title']) atype = int(request.form['assess_type']) startdate = request.form['startdate'] starthour = int(request.form['examstart_hour']) startmin = int(request.form['examstart_minute']) enddate = request.form['enddate'] endhour = int(request.form['examend_hour']) endmin = int(request.form['examend_minute']) duration = int(request.form['duration']) code = request.form['assess_code'] instant = int(request.form['assess_instant']) if "instructions" in request.form: instructions = request.form['instructions'] else: instructions = "" astart = datetime.datetime.strptime(startdate, "%a %d %b %Y") astart = astart.replace(hour=starthour, minute=startmin) aend = datetime.datetime.strptime(enddate, "%a %d %b %Y") aend = aend.replace(hour=endhour, minute=endmin) qns = {} for k in request.form.keys(): v = request.form.getlist(k) if k.startswith("question_"): _, q, p = k.split("_") if not q in qns: qns[q] = [] if not v[0] == '---': qns[q].append(int(v[0])) if not exam_id: exam_id = Exams.create(cid, user_id, title, atype, duration, astart, aend, instructions, code=code, instant=instant) else: # update Exams.set_title(exam_id, title) Exams.set_duration(exam_id, duration) Exams.set_type(exam_id, atype) Exams.set_description(exam_id, instructions) Exams.set_start_time(exam_id, astart) Exams.set_end_time(exam_id, aend) Exams.set_code(exam_id, code) Exams.set_instant(exam_id, instant) for pos, qts in qns.iteritems(): if pos: DB.update_exam_qt_in_pos(exam_id, int(pos), qts) return exam_id
def teardown(): """ Remove testing configuration file and otherwise clean up. """ with open(os.path.join(OaConfig.homedir, "sql", "eraseexisting.sql")) as f: sql = f.read() print "Removing tables." DB.run_sql(sql)
def practice_do_question(topic_id, qt_id): """ Show them a question and allow them to fill in some answers """ user_id = session['user_id'] try: course_id = Topics.get_course_id(topic_id) except KeyError: course_id = None abort(404) try: course = Courses2.get_course(course_id) except KeyError: course = None abort(404) topictitle = "UNKNOWN" try: topictitle = Topics.get_name(topic_id) except KeyError: abort(404) try: qtemplate = DB.get_qtemplate(qt_id) except KeyError: qtemplate = None abort(404) questions = Practice.get_sorted_questions(course_id, topic_id, user_id) q_title = qtemplate['title'] q_pos = DB.get_qtemplate_topic_pos(qt_id, topic_id) blocked = Practice.is_q_blocked(user_id, course_id, topic_id, qt_id) if blocked: return render_template( "practicequestionblocked.html", mesg=blocked, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) try: q_id = Practice.get_practice_q(qt_id, user_id) except (ValueError, TypeError), err: log(ERROR, "ERROR 1001 (%s,%s) %s" % (qt_id, user_id, err)) return render_template( "practicequestionerror.html", mesg="Error generating question.", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos="?", )
def practice_mark_question(topic_id, question_id): """ Mark the submitted question answersjust wa """ user_id = session['user_id'] course_id = Topics.get_course_id(topic_id) if not course_id: abort(404) course = Courses2.get_course(course_id) if not course: abort(404) topictitle = "UNKNOWN" try: topictitle = Topics.get_name(topic_id) except KeyError: abort(404) qt_id = DB.get_q_parent(question_id) q_title = DB.get_qt_name(qt_id) questions = Practice.get_sorted_questions(course_id, topic_id, user_id) q_pos = DB.get_qtemplate_topic_pos(qt_id, topic_id) blocked = Practice.is_q_blocked(user_id, course_id, topic_id, qt_id) if blocked: return render_template( "practicequestionblocked.html", mesg=blocked, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) marking = Practice.mark_q(user_id, topic_id, question_id, request) prev_id, next_id = Practice.get_next_prev(qt_id, topic_id) return render_template( "practicemarkquestion.html", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, q_id=question_id, marking=marking, next_id=next_id, prev_id=prev_id )
def mark_exam(user_id, exam_id): """ Submit the assessment and mark it. Returns True if it went well, or False if a problem. """ numquestions = Exams.get_num_questions(exam_id) status = Exams.get_user_status(user_id, exam_id) L.info("Marking assessment %s for %s, status is %s" % (exam_id, user_id, status)) examtotal = 0.0 errors = 0 for position in range(1, numquestions + 1): q_id = General.get_exam_q(exam_id, position, user_id) if not q_id: L.critical( "Unable to retrieve exam question page %s, exam %s, for user %s" % (position, exam_id, user_id)) errors += 1 continue answers = DB.get_q_guesses(q_id) # There's a small chance they got here without ever seeing a question, # make sure it exists. DB.add_exam_q(user_id, exam_id, q_id, position) # First, mark the question try: marks = General.mark_q(q_id, answers) DB.set_q_status(q_id, 3) # 3 = marked DB.set_q_marktime(q_id) except OaMarkerError: L.warn("Marker Error in question %s, exam %s, student %s!" % (q_id, exam_id, user_id)) return False parts = [ int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0 ] parts.sort() # Then calculate the mark total = 0.0 for part in parts: try: mark = float(marks['M%d' % (part, )]) except (KeyError, ValueError): mark = 0 total += mark DB.update_q_score(q_id, total) examtotal += total if not errors: Exams.set_user_status(user_id, exam_id, 5) Exams.set_submit_time(user_id, exam_id) Exams.save_score(exam_id, user_id, examtotal) Exams.touchuserexam(exam_id, user_id) if errors: return False L.info("user %s scored %s total on exam %s" % (user_id, examtotal, exam_id)) return True
def mark_exam(user_id, exam_id): """ Submit the assessment and mark it. Returns True if it went well, or False if a problem. """ numquestions = Exams.get_num_questions(exam_id) status = Exams.get_user_status(user_id, exam_id) L.info("Marking assessment %s for %s, status is %s" % (exam_id, user_id, status)) examtotal = 0.0 errors = 0 for position in range(1, numquestions + 1): q_id = General.get_exam_q(exam_id, position, user_id) if not q_id: L.critical("Unable to retrieve exam question page %s, exam %s, for user %s" % (position, exam_id, user_id ) ) errors += 1 continue answers = DB.get_q_guesses(q_id) # There's a small chance they got here without ever seeing a question, # make sure it exists. DB.add_exam_q(user_id, exam_id, q_id, position) # First, mark the question try: marks = General.mark_q(q_id, answers) DB.set_q_status(q_id, 3) # 3 = marked DB.set_q_marktime(q_id) except OaMarkerError: L.warn("Marker Error in question %s, exam %s, student %s!" % (q_id, exam_id, user_id)) return False parts = [int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0] parts.sort() # Then calculate the mark total = 0.0 for part in parts: try: mark = float(marks['M%d' % (part,)]) except (KeyError, ValueError): mark = 0 total += mark DB.update_q_score(q_id, total) examtotal += total if not errors: Exams.set_user_status(user_id, exam_id, 5) Exams.set_submit_time(user_id, exam_id) Exams.save_score(exam_id, user_id, examtotal) Exams.touchuserexam(exam_id, user_id) if errors: return False L.info("user %s scored %s total on exam %s" % (user_id, examtotal, exam_id)) return True
def gen_q(qtid, student=0, exam=0, position=0): """ Given a qtemplate, will generate a question instance. If student and/or exam is supplied it will be assigned appropriately. If exam is supplied, position must also be supplied. Will return the ID of the created instance. """ # Pick a variation randomly version = DB.get_qt_version(qtid) numvars = DB.get_qt_num_variations(qtid, version) if numvars > 0: variation = random.randint(1, numvars) else: L.warn("No question variations (qtid=%d)" % qtid) return False return gen_q_from_var(qtid, student, exam, position, version, variation)
def render_mark_results(qid, marks): """Take the marking results and display something for the student that tells them what they got right and wrong. If the question has an attachment "_rendermarks.py", it will be called, otherwise a default HTML table will be returned. _rendermarks.py should set variable "resultsHTML" to contain a suitable string for putting in an HTML page. """ qtid = DB.get_q_parent(qid) renderscript = DB.get_qt_att(qtid, "__results.py") if not renderscript: resultshtml = render_mark_results_standard(qid, marks) else: resultshtml = render_mark_results_script(qtid, qid, marks, renderscript) return resultshtml
def cadmin_edit_topic(course_id, topic_id): """ Present a page to view and edit a topic, including adding/editing questions and setting some parameters. """ user_id = session['user_id'] if not course_id: abort(404) course = Courses2.get_course(course_id) topic = { 'id': topic_id, 'position': Topics.get_pos(topic_id), 'name': Topics.get_name(topic_id) } questions = [question for question in Topics.get_qts(topic_id).values()] for question in questions: question['embed_id'] = DB.get_qt_embedid(question['id']) if question['embed_id']: question['embed_url'] = "%s/embed/question/%s/question.html" % \ (OaConfig.parentURL, question['embed_id']) else: question['embed_url'] = None question['editor'] = DB.get_qt_editor(question['id']) all_courses = Courses2.get_course_list() all_courses = [crse for crse in all_courses if satisfy_perms(user_id, int(crse['id']), ("questionedit", "courseadmin", "sysadmin"))] all_courses.sort(lambda f, s: cmp(f['name'], s['name'])) all_course_topics = [] for crse in all_courses: topics = Courses.get_topics_all(crse['id'], numq=False) if topics: all_course_topics.append({'course': crse['name'], 'topics': topics}) questions.sort(key=lambda k: k['position']) return render_template( "courseadmin_edittopic.html", course=course, topic=topic, questions=questions, all_course_topics=all_course_topics )
def api_qedit2_get_qtemplate_json(qt_id): """ Present a list of qtemplates that are available for use in the exam.""" if 'user_id' not in session: abort(401) user_id = session['user_id'] topic_id = DB.get_topic_for_qtemplate(qt_id) course_id = Topics.get_course_id(topic_id) if not satisfy_perms(user_id, course_id, ("questionedit",)): abort(401) # test data while we're building the frontend return jsonify(result={ 'type': "qtemplate data", 'title': "Test QE2 Question", 'embed_id': "aaaaaaaaa3", 'maxscore': 3, 'pre_vars': [ {'id': 1, 'name': "a", 'type': 'List', 'value': "2,3,4,5,6,7"}, {'id': 2, 'name': "b", 'type': 'Range', 'value': "1-10"}, {'id': 3, 'name': "a1", 'type': 'Calculation', 'value': "$a+$b"}, {'id': 4, 'name': "a2", 'type': 'Calculation', 'value': "$a*$b"}, ], 'qtext': "What is $a + $b ? <answer1>\nWhat is $a * $b? <answer2> ", 'answers': [ {'id': 1, 'source': 'Variable', 'value': '$a1', 'tolerance': 0, 'score': 1}, {'id': 2, 'source': 'Variable', 'value': '$a2', 'tolerance': 0, 'score': 1} ] })
def get_q_list(tid, uid=None, numdone=True): """ Return a list of dicts with question template information for the topic. [{ qtid: int QTemplate ID name: string Name of Question position: int Position of Question in topic done: Number of times the given user has submitted a question for practice },] """ qlist = [] qtemplates = Topics.get_qts(int(tid)) for qtid in qtemplates: if uid and numdone: num = DB.get_student_q_practice_num(uid, qtid) else: num = 0 qlist.append({ 'qtid': qtid, 'name': qtemplates[qtid]['name'], 'position': qtemplates[qtid]['position'], 'done': num }) # Sort them by position qlist.sort(lambda f, s: cmp(f["position"], s["position"])) return qlist
def setup(): """ Prepare database for testing. """ if not DB.check_safe(): print "Attempt to erase database with data." sys.exit(-1) with open(os.path.join(OaConfig.homedir, "sql", "eraseexisting.sql")) as f: sql = f.read() print "Removing existing tables." DB.run_sql(sql) with open(os.path.join(OaConfig.homedir, "sql", "emptyschema_396.sql")) as f: sql = f.read() DB.run_sql(sql) print "Installed v3.9.6 table structure."
def get_exam_q(exam, page, user_id): """ Find the appropriate exam question for the user. Generate it if there isn't one already. """ qid = DB.get_exam_q_by_pos_student(exam, page, user_id) if qid is not False: return int(qid) qid = int(gen_exam_q(exam, page, user_id)) try: qid = int(qid) assert qid > 0 except (ValueError, TypeError, AssertionError): L.warn("generateExamQuestion(%s,%s, %s) Failed (returned %s)" % (exam, page, user_id, qid)) DB.set_q_viewtime(qid) return qid
def setUpClass(cls): cls.app = app cls.app.testing = True cls.app.config["TESTING"] = True cls.adminpass = DB.generate_admin_passwd() (fptr, cls.test_question_fname) = tempfile.mkstemp() os.close(fptr)
def test_create_qtemplate(self): """ Test qtemplates creation """ qt1_id = DB.create_qt(1, "TESTQ1", "Test question 1", 0, 5.0, 1) qt2_id = DB.create_qt(1, "TESTQ2", "Test question 2", 0, 4.1, 2) self.assertIsInstance(qt1_id, int) self.assertIsInstance(qt2_id, int) qt1 = DB.get_qtemplate(qt1_id) qt2 = DB.get_qtemplate(qt2_id) self.assertEqual(qt1['title'], "TESTQ1") self.assertEqual(qt2['title'], "TESTQ2") self.assertEqual(qt1['description'], "Test question 1") self.assertEqual(qt2['description'], "Test question 2") course_id = Courses.create("TEST107", "Test create qtemplate", 1, 1) topic1_id = Topics.create(course_id, "TESTTOPIC9", 1, 2) qt3_id = DB.create_qt(1, "TESTQ3", "Test question 3", 0, 5.0, 1, topic1_id) self.assertIsInstance(qt3_id, int) qt3 = DB.get_qtemplate(qt3_id) self.assertEqual(qt3['title'], "TESTQ3") self.assertEqual(qt3['description'], "Test question 3") self.assertEqual(DB.get_topic_for_qtemplate(qt3_id), topic1_id)
def mark_q(qid, answers): """ Mark the question according to the answers given in a dictionary and return the result in a dictionary: input: {"A1":"0.345", "A2":"fred", "A3":"-26" } return: {"M1": Mark One, "C1": Comment One, "M2": Mark Two..... } """ qtid = DB.get_q_parent(qid) version = DB.get_q_version(qid) variation = DB.get_q_variation(qid) qvars = DB.get_qt_variation(qtid, variation, version) if not qvars: qvars = {} L.warn("markQuestion(%s, %s) unable to retrieve variables." % (qid, answers)) qvars['OaQID'] = int(qid) marktype = DB.get_qt_marker(qtid) if marktype == 1: # standard marks = mark_q_standard(qvars, answers) else: # We want the latest version of the marker, so no version given markerscript = DB.get_qt_att(qtid, "__marker.py") if not markerscript: markerscript = DB.get_qt_att(qtid, "marker.py") L.info("'marker.py' should now be called '__marker.py' (qtid=%s)" % qtid) if not markerscript: L.info("Unable to retrieve marker script for smart marker question (qtid=%s)!" % qtid) marks = mark_q_standard(qvars, answers) else: marks = mark_q_script(qvars, markerscript, answers) return marks
def mark_q(qid, answers): """ Mark the question according to the answers given in a dictionary and return the result in a dictionary: input: {"A1":"0.345", "A2":"fred", "A3":"-26" } return: {"M1": Mark One, "C1": Comment One, "M2": Mark Two..... } """ qtid = DB.get_q_parent(qid) version = DB.get_q_version(qid) variation = DB.get_q_variation(qid) qvars = DB.get_qt_variation(qtid, variation, version) if not qvars: qvars = {} L.warn("markQuestion(%s, %s) unable to retrieve variables." % (qid, answers)) qvars['OaQID'] = int(qid) marktype = DB.get_qt_marker(qtid) if marktype == 1: # standard marks = mark_q_standard(qvars, answers) else: # We want the latest version of the marker, so no version given markerscript = DB.get_qt_att(qtid, "__marker.py") if not markerscript: markerscript = DB.get_qt_att(qtid, "marker.py") L.info("'marker.py' should now be called '__marker.py' (qtid=%s)" % qtid) if not markerscript: L.info( "Unable to retrieve marker script for smart marker question (qtid=%s)!" % qtid) marks = mark_q_standard(qvars, answers) else: marks = mark_q_script(qvars, markerscript, answers) return marks
def setup(): """ Prepare database and configuration file for testing """ # Switch us to use a test database test_config = """ """ f = open(TESTINI, "w") f.write(test_config) f.close() global DB from oasis.lib import DB # Do this *after* writing the test ini file. DB.run_sql("SELECT name, value FROM config WHERE name='test_status';")
def get_exam_q(exam, page, user_id): """ Find the appropriate exam question for the user. Generate it if there isn't one already. """ qid = DB.get_exam_q_by_pos_student(exam, page, user_id) if qid is not False: return int(qid) qid = int(gen_exam_q(exam, page, user_id)) try: qid = int(qid) assert qid > 0 except (ValueError, TypeError, AssertionError): L.warn("generateExamQuestion(%s,%s, %s) Failed (returned %s)" % (exam, page, user_id, qid)) qid = None if qid: DB.set_q_viewtime(qid) return qid
def import_qts_from_zip(data, topic_id): """ Open the given OAQ file and import any qtemplates found. Return False if it's not valid Return 0 if it's valid but has no qtemplates Return NUM of templates imported. """ # TODO: How do we protect against malicious uploads? # At the moment they're allowed for trusted people only, # but we'll eventually want this in the UI for end users. # eg. unzip to huge size # add digital signatures? sdata = StringIO(data) tmpd = tempfile.mkdtemp(prefix="oa") qdir = os.path.join(tmpd, "oasisqe") os.mkdir(qdir) num = 0 try: with zipfile.ZipFile(sdata, "r") as zfile: zfile.extractall(qdir) data = open("%s/info.json" % qdir, "r").read() info = json.loads(data) qtids = info['qtemplates'].keys() qtids.sort() for qtid in qtids: qtemplate = info['qtemplates'][qtid]['qtemplate'] attachments = info['qtemplates'][qtid]['attachments'] if 'position' in info['qtemplates'][qtid]: position = info['qtemplates'][qtid]['position'] else: position = 0 newid = DB.create_qt(owner=1, # ownerid title=qtemplate['title'], desc=qtemplate['description'], marker=qtemplate['marker'], score_max=qtemplate['scoremax'], status=qtemplate['status'], topic_id=topic_id) DB.update_qt_practice_pos(newid, position) num += 1 # print "%s attachments" % len(attachments) for att in attachments: (att_name, att_type, att_size) = att data = open("%s/%s/attach/%s" % (qdir, qtemplate['id'], att_name)).read() DB.create_qt_att(newid, att_name, att_type, data, 1) if att_name == "datfile.txt" or att_name == "datfile.dat" or att_name == "datfile" or att_name == "_datfile" or att_name == "__datfile": qvars = QEditor.parse_datfile(data) for row in range(0, len(qvars)): DB.add_qt_variation(newid, row + 1, qvars[row], 1) except zipfile.BadZipfile: return False Topics.flush_num_qs(topic_id) return num
def gen_q(qtid, student=0, exam=0, position=0, variation=None): """ Given a qtemplate, will generate a question instance. If student and/or exam is supplied it will be assigned appropriately. If exam is supplied, position must also be supplied. Will return the ID of the created instance. """ # Pick a variation randomly if not supplied version = DB.get_qt_version(qtid) numvars = DB.get_qt_num_variations(qtid, version) if variation is None and numvars > 0: variation = random.randint(1, numvars) if numvars == 0: L.warn("No question variations (qtid=%d)" % qtid) Audit.audit(3, student, qtid, "General", "Failed to generate question %s for %s, exam %s" % (qtid, student, exam)) return False q_id = gen_q_from_var(qtid, student, exam, position, version, variation) if not q_id: Audit.audit(3, student, qtid, "General", "Failed to generate instance of %s for %s, exam %s" % (qtid, student, exam)) return q_id
def cadmin_view_topic(course_id, topic_id): """ Present a page to view a topic, including basic stats """ user_id = session["user_id"] if not course_id: abort(404) course = Courses2.get_course(course_id) topic = {"id": topic_id, "position": Topics.get_pos(topic_id), "name": Topics.get_name(topic_id)} questions = [question for question in Topics.get_qts(topic_id).values()] for question in questions: question["embed_id"] = DB.get_qt_embedid(question["id"]) if question["embed_id"]: question["embed_url"] = "%s/embed/question/%s/question.html" % (OaConfig.parentURL, question["embed_id"]) else: question["embed_url"] = None question["editor"] = DB.get_qt_editor(question["id"]) all_courses = Courses2.get_course_list() all_courses = [ crse for crse in all_courses if satisfy_perms(user_id, int(crse["id"]), ("questionedit", "courseadmin", "sysadmin")) ] all_courses.sort(lambda f, s: cmp(f["name"], s["name"])) all_course_topics = [] for crse in all_courses: topics = Courses.get_topics_all(crse["id"], numq=False) if topics: all_course_topics.append({"course": crse["name"], "topics": topics}) questions.sort(key=lambda k: k["position"]) return render_template( "courseadmin_viewtopic.html", course=course, topic=topic, questions=questions, all_course_topics=all_course_topics, )
def create_new(qt_id, name): """ Set the given qtemplate up as a new (default) question. Makes sure the appropriate things the editor needs are configured and in place. Assumes the QT has not previously been set up. :param qt_id: The ID of the qtemplate to set up. :param name: Name of the question. :return: """ # The _editor.qe2 file contains a json object with most of the structural # information about the question. default_ = { 'name': name, 'qe_version': 0.1 } DB.create_qt_att(qt_id, "__editor.qe2", "application/oasis-qe2", json.dumps(default_), 1) return
def remark_prac(question): """ Re-mark the practice question and store the score back in the questions table. """ answers = DB.get_q_guesses(question) try: marks = mark_q(question, answers) except OaMarkerError: return None parts = [int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0] parts.sort() total = 0.0 for part in parts: try: mark = float(marks['M%d' % part]) except (ValueError, TypeError, KeyError): mark = 0 total += mark DB.update_q_score(question, total) DB.set_q_status(question, 3) # 3 = marked return total
def cadmin_view_qtemplate_history(course_id, topic_id, qt_id): """ Show the practice history of the question template. """ if not course_id: abort(404) course = Courses2.get_course(course_id) topic = {"id": topic_id, "position": Topics.get_pos(topic_id), "name": Topics.get_name(topic_id)} qtemplate = DB.get_qtemplate(qt_id) year = datetime.now().year years = range(year, year - 6, -1) return render_template( "courseadmin_viewqtemplate.html", course=course, topic=topic, qtemplate=qtemplate, years=years )
def cadmin_prev_assessments(course_id): """ Show a list of older assessments.""" course = Courses2.get_course(course_id) if not course: abort(404) exams = [ Exams.get_exam_struct(exam_id, course_id) for exam_id in DB.get_course_exam_all(course_id, prev_years=True) ] years = [exam["start"].year for exam in exams] years = list(set(years)) years.sort(reverse=True) exams.sort(key=lambda y: y["start_epoch"]) return render_template("courseadmin_previousassessments.html", course=course, exams=exams, years=years)
def remark_prac(question): """ Re-mark the practice question and store the score back in the questions table. """ answers = DB.get_q_guesses(question) try: marks = mark_q(question, answers) except OaMarkerError: return None parts = [ int(var[1:]) for var in marks.keys() if re.search("^A([0-9]+)$", var) > 0 ] parts.sort() total = 0.0 for part in parts: try: mark = float(marks['M%d' % part]) except (ValueError, TypeError, KeyError): mark = 0 total += mark DB.update_q_score(question, total) DB.set_q_status(question, 3) # 3 = marked return total
def process_save(qt_id, topic_id, request, session, version): """ Have received a web form POST to save the current changes. :param qt_id: ID of the question template being edited :param topic_id: Topic the question template is in :param request: The POST request. :param session: The web session object :param version: (int) the new version of the qt being saved :return: None """ form = request.form if 'blocks' in form: blocks = form['blocks'].encode("utf8") DB.create_qt_att(qt_id, "__editor.qe2", "text/json", blocks, version) else: raise KeyError("blocks") return
def import_qts_from_zip(data, topicid): """ Open the given OAQ file and import any qtemplates found. Return False if it's not valid Return 0 if it's valid but has no qtemplates Return NUM of templates imported. """ # TODO: How do we protect against malicious uploads? # At the moment they're allowed for trusted people only, # but we'll eventually want this in the UI for end users. # eg. unzip to huge size # add digital signatures? sdata = StringIO(data) tmpd = tempfile.mkdtemp(prefix="oa") qdir = os.path.join(tmpd, "oasisqe") os.mkdir(qdir) with zipfile.ZipFile(sdata, "r") as zfile: zfile.extractall(qdir) data = open("%s/info.json" % qdir, "r").read() info = json.loads(data) print "%s questions found" % (len(info['qtemplates'])) position = 1 for qtid in info['qtemplates'].keys(): qtemplate = info['qtemplates'][qtid]['qtemplate'] print "%s" % qtemplate['title'] newid = DB.create_qt(1, qtemplate['title'], qtemplate['description'], qtemplate['marker'], qtemplate['scoremax'], qtemplate['status']) DB.update_qt_pos(newid, topicid, position) position += 1 attachments = info['qtemplates'][qtid]['attachments'] print "%s attachments" % len(attachments) for att in attachments: (att_name, att_type, att_size) = att data = open("%s/%s/attach/%s" % (qdir, qtemplate['id'], att_name)).read() DB.create_qt_att(newid, att_name, att_type, data, 1) if att_name == "datfile.txt" or att_name == "datfile.dat" or att_name == "datfile" or att_name == "_datfile" or att_name == "__datfile": qvars = QEditor.parse_datfile(data) print "generating variations..." for row in range(0, len(qvars)): DB.add_qt_variation(newid, row + 1, qvars[row], 1) Topics.flush_num_qs(topicid) return 0
def gen_exam_q(exam, position, student): """ Generate an exam question instance for the given student and exam. If there are multiple qtemplates listed in a given position, one will be chosen at random. """ qtemplates = DB.get_exam_qts_in_pos(exam, position) if not qtemplates: L.warn("DB.get_exam_qts_in_pos(%s,%s) returned a non list." % (exam, position)) return False if len(qtemplates) < 1: L.warn("DB.get_exam_qts_in_pos(%s,%s) returned an empty list." % (exam, position)) return False whichqtemplate = random.randint(1, len(qtemplates)) qtid = qtemplates[whichqtemplate - 1] # lists count from 0 return gen_q(qtid, student, exam, position)
def q_att_details(qtid, version, variation, name): """ Find a question attachment and return its details. """ # for the two biggies we hit the question first, # otherwise check the question template first if name == "image.gif" or name == "qtemplate.html": fname = DB.get_q_att_fname(qtid, name, variation, version) if fname: return DB.get_q_att_mimetype(qtid, name, variation, version), fname fname = DB.get_qt_att_fname(qtid, name, version) if fname: return DB.get_qt_att_mimetype(qtid, name, version), fname else: fname = DB.get_qt_att_fname(qtid, name, version) if fname: return DB.get_qt_att_mimetype(qtid, name, version), fname fname = DB.get_q_att_fname(qtid, name, variation, version) if fname: return DB.get_q_att_mimetype(qtid, name, variation, version), fname return None, None
def qtlog_as_html(topic, qtid): """Show the most recent log errors for the given qtid. """ versionre = re.compile(r'version=(\d+),') variationre = re.compile(r'variation=(\d+),') priorityre = re.compile(r'priority=([^,]+),') facilityre = re.compile(r'facility=([^,]+),') messagere = re.compile(r'message=(.+)$', re.MULTILINE) out = "" name = DB.get_qt_name(qtid) out += "<h2>Log Entries for %s, topic %s</h2>" % (name, topic) out += "<p><i>These can be created from within __marker.py or __results.py by calling " out += "log(priority, mesg), for example:</i> " out += "<pre>log('error','User entered a value we can't parse.')</pre></p>" out += "<p><i>Typical priorities might be 'error', 'info', 'noise'</i></p>" out += "<table style='border: solid 1px black;' border='1'><tr><th>Time</th><th>Ver</th>" out += "<th>Variation</th><th>Pri</th><th>Fac</th><th>Message</th></tr>" entries = Audit.get_records_by_object(qtid, limit=100, offset=0) for entry in entries: try: version = versionre.findall(entry['message'])[0] except (IndexError, TypeError): version = '.' try: variation = variationre.findall(entry['message'])[0] except (IndexError, TypeError): variation = '.' try: priority = priorityre.findall(entry['message'])[0] except (IndexError, TypeError): priority = '.' try: facility = facilityre.findall(entry['message'])[0] except (IndexError, TypeError): facility = '.' try: message = messagere.findall(entry['message'])[0] except (IndexError, TypeError): message = '.' out += "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>" % (entry['time'].strftime("%Y/%b/%d %H:%M:%S"), version, variation, priority, facility, message) out += "</table>" return out
def index(): """ Main landing page. Welcome them and give them some login instructions. """ if 'user_id' in session: return redirect(url_for("main_top")) if OaConfig.default == "landing": mesg_login = DB.get_message("loginmotd") alt_landing = os.path.join(OaConfig.theme_path, "landing_page.html") if os.path.isfile(alt_landing): tmpf = open(alt_landing) tmpl = tmpf.read() tmpf.close() return render_template_string(tmpl, mesg_login=mesg_login) return render_template("landing_page.html", mesg_login=mesg_login) if OaConfig.default == "locallogin": return redirect(url_for("login_local")) if OaConfig.default == "webauth": return redirect(url_for("login_webauth_submit")) return render_template("landing_page.html")
def student_exam_duration(student, exam_id): """ How long did the assessment take. returns starttime, endtime either could be None if it hasn't been started/finished """ firstview = None examsubmit = Exams.get_submit_time(exam_id, student) questions = General.get_exam_qs(student, exam_id) # we're working out the first time the assessment was viewed is the # earliest time a question in it was viewed # It's possible (although unlikely) that they viewed a question # other than the first page, first. for question in questions: questionview = DB.get_q_viewtime(question) if firstview: if questionview < firstview: firstview = questionview else: firstview = questionview return firstview, examsubmit
def get_q_list(tid, uid=None, numdone=True): """ Return a list of dicts with question template information for the topic. [{ qtid: int QTemplate ID name: string Name of Question position: int Position of Question in topic done: Number of times the given user has submitted a question for practice },] """ qlist = [] qtemplates = Topics.get_qts(int(tid)) for qtid in qtemplates: if uid and numdone: num = DB.get_student_q_practice_num(uid, qtid) else: num = 0 qlist.append({'qtid': qtid, 'name': qtemplates[qtid]['name'], 'position': qtemplates[qtid]['position'], 'done': num}) # Sort them by position qlist.sort(lambda f, s: cmp(f["position"], s["position"])) return qlist
def exam_edit_submit(request, user_id, cid, exam_id): """ Accept the submitted exam edit/create form. If exam_id is not provided, create a new one. """ # TODO: More validation. Currently we trust the client validation, # although the user is authenticated staff so probably not too high a # risk of shenanigans. title = str(request.form['assess_title']) atype = int(request.form['assess_type']) startdate = request.form['startdate'] starthour = int(request.form['examstart_hour']) startmin = int(request.form['examstart_minute']) enddate = request.form['enddate'] endhour = int(request.form['examend_hour']) endmin = int(request.form['examend_minute']) duration = int(request.form['duration']) code = request.form['assess_code'] instant = int(request.form['assess_instant']) if "instructions" in request.form: instructions = request.form['instructions'] else: instructions = "" astart = datetime.datetime.strptime(startdate, "%a %d %b %Y") astart = astart.replace(hour=starthour, minute=startmin) aend = datetime.datetime.strptime(enddate, "%a %d %b %Y") aend = aend.replace(hour=endhour, minute=endmin) qns = {} for k in request.form.keys(): v = request.form.getlist(k) if k.startswith("question_"): _, q, p = k.split("_") if q not in qns: qns[q] = [] if not v[0] == '---': qns[q].append(int(v[0])) if not exam_id: exam_id = Exams.create(cid, user_id, title, atype, duration, astart, aend, instructions, code=code, instant=instant) else: # update Exams.set_title(exam_id, title) Exams.set_duration(exam_id, duration) Exams.set_type(exam_id, atype) Exams.set_description(exam_id, instructions) Exams.set_start_time(exam_id, astart) Exams.set_end_time(exam_id, aend) Exams.set_code(exam_id, code) Exams.set_instant(exam_id, instant) for pos, qts in qns.iteritems(): if pos: DB.update_exam_qt_in_pos(exam_id, int(pos), qts) return exam_id
def get_q_att(qid, name): """ Return (mimetype, data) with the relevant attachment. If it's not found in question, look in questiontemplate. """ qtid = DB.get_q_parent(qid) variation = DB.get_q_variation(qid) version = DB.get_q_version(qid) # for the two biggies we hit the question first, # otherwise check the question template first if name == "image.gif" or name == "qtemplate.html": data = DB.get_q_att(qtid, name, variation, version) if data: return DB.get_q_att_mimetype(qtid, name, variation, version), data data = DB.get_qt_att(qtid, name, version) if data: return DB.get_qt_att_mimetype(qtid, name, version), data else: data = DB.get_qt_att(qtid, name, version) if data: return DB.get_qt_att_mimetype(qtid, name, version), data data = DB.get_q_att(qtid, name, variation, version) if data: return DB.get_q_att_mimetype(qtid, name, variation, version), data return None, None
def render_q_html(q_id, readonly=False): """ Fetch the question html and get it ready for display - replacing links with appropriate targets and filling in form details.""" try: q_id = int(q_id) assert q_id > 0 except (ValueError, TypeError, AssertionError): L.warn("renderQuestionHTML(%s,%s) called with bad qid?" % (q_id, readonly)) qt_id = DB.get_q_parent(q_id) try: qt_id = int(qt_id) assert qt_id > 0 except (ValueError, TypeError, AssertionError): L.warn("renderQuestionHTML(%s,%s), getparent failed? " % (q_id, readonly)) variation = DB.get_q_variation(q_id) version = DB.get_q_version(q_id) data = DB.get_q_att(qt_id, "qtemplate.html", variation, version) if not data: L.warn("Unable to retrieve qtemplate for q_id: %s" % q_id) return "QuestionError" try: out = unicode(data, "utf-8") except UnicodeDecodeError: try: out = unicode( DB.get_q_att(qt_id, "qtemplate.html", variation, version), "latin-1") except UnicodeDecodeError as err: L.error("unicode error decoding qtemplate for q_id %s: %s" % (q_id, err)) raise out = out.replace( "This question is not verified yet, please report any error!", "") out = out.replace("ANS_", "Q_%d_ANS_" % (q_id, )) out = out.replace( "$IMAGES$", "%s/att/qatt/%s/%s/%s/" % (OaConfig.parentURL, qt_id, version, variation)) out = out.replace( "$APPLET$", "%s/att/qatt/%s/%s/%s/" % (OaConfig.parentURL, qt_id, version, variation)) out = out.replace( "$STATIC$", "%s/att/qtatt/%s/%s/%s/" % (OaConfig.parentURL, qt_id, version, variation)) if readonly: out = out.replace("<INPUT ", "<INPUT READONLY ") out = out.replace("<SELECT ", "<SELECT DISABLED=DISABLED STYLE='color: black;'") guesses = DB.get_q_guesses(q_id) for guess in guesses.keys(): # noinspection PyComparisonWithNone if guesses[guess] == None: # If it's 0 we want to leave it alone guesses[guess] = "" if guesses[guess] == "None": guesses[guess] = "" # for each question if guesses: for ques in range(25, 0, -1): if ("G%d" % ques) in guesses: out = out.replace("VAL_%d" % ques, htmlesc(guesses["G%d" % ques])) for part in range(50, 0, -1): if guesses["G%d" % ques] == "%s.0" % part or guesses[ "G%d" % ques] == "%s" % part: out = out.replace("Oa_SEL_%d_%d" % (ques, part), "SELECTED") out = out.replace("Oa_CHK_%d_%d" % (ques, part), "CHECKED") else: out = out.replace("Oa_SEL_%d_%d" % (ques, part), "") out = out.replace("Oa_CHK_%d_%d" % (ques, part), "") else: out = out.replace("VAL_%d" % (ques, ), "") for ques in range(25, 0, -1): out = out.replace("VAL_%d" % (ques, ), "") return out
def practice_do_question_id(topic_id, qt_id): """ Show them a question and allow them to fill in some answers """ user_id = session['user_id'] try: course_id = Topics.get_course_id(topic_id) except KeyError: course_id = None abort(404) try: course = Courses2.get_course(course_id) except KeyError: course = None abort(404) topictitle = "UNKNOWN" try: topictitle = Topics.get_name(topic_id) except KeyError: abort(404) qtemplate = DB.get_qtemplate(qt_id) questions = Practice.get_sorted_questions(course_id, topic_id, user_id) q_title = qtemplate['title'] q_pos = DB.get_qtemplate_topic_pos(qt_id, topic_id) blocked = Practice.is_q_blocked(user_id, course_id, topic_id, qt_id) if blocked: return render_template( "practicequestionblocked.html", mesg=blocked, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) try: q_id = Practice.get_practice_q(qt_id, user_id) except (ValueError, TypeError) as err: L.error("ERROR 1001 (%s,%s) %s" % (qt_id, user_id, err)) return render_template( "practicequestionerror.html", mesg="Error generating question.", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) if not q_id > 0: L.error("ERROR 1002 (%s,%s) Question not generated" % (qt_id, user_id)) return render_template( "practicequestionerror.html", mesg="Error generating question.", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) q_body = General.render_q_html(q_id) q_body = q_body.replace(r"\240", u" ") # TODO: why is this here? return render_template( "practicedoquestion.html", q_body=q_body, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, q_id=q_id, )
def qts_to_zip(qt_ids, extra_info=None): """ Take a list of QTemplate IDs and return a binary string containing them as an .oaq file. (a zip file in special format) """ qdir = tempfile.mkdtemp(prefix="oa") info = { 'oasis': { 'oa_version': "3.9.4", 'qt_version': '0.9', 'url': OaConfig.parentURL }, 'qtemplates': {}, 'extra': extra_info } arc = zipfile.ZipFile(os.path.join(qdir, "oasisqe.zip"), 'w', zipfile.ZIP_DEFLATED) for qt_id in qt_ids: qtemplate = DB.get_qtemplate(qt_id) qtdir = os.path.join(qdir, str(qt_id)) attachments = DB.get_qt_atts(qt_id) if 'qtemplate.html' not in attachments: attachments.append('qtemplate.html') if 'datfile.txt' not in attachments: attachments.append('datfile.txt') if 'image.gif' not in attachments: attachments.append('image.gif') os.mkdir(qtdir) os.mkdir(os.path.join(qtdir, "attach")) info["qtemplates"][qt_id] = {'qtemplate': qtemplate} info["qtemplates"][qt_id]["attachments"] = [] for name in attachments: if not name: name = 'UNKNOWN' mtype = DB.get_qt_att_mimetype(qt_id, name) if not mtype: mtype = "" data = DB.get_qt_att(qt_id, name) if not data: data = "" info["qtemplates"][qt_id]["attachments"].append([name, mtype, len(data)]) subdir = os.path.join(qtdir, "attach", name) outf = open(subdir, "wb") outf.write(data) outf.close() arc.write(subdir, os.path.join("%s" % qt_id, "attach", name), zipfile.ZIP_DEFLATED) infof = open(os.path.join(qdir, "info.json"), "wb") infof.write(json.dumps(info)) infof.close() arc.write(os.path.join(qdir, "info.json"), os.path.join("info.json"), zipfile.ZIP_DEFLATED) arc.close() readback = open(os.path.join(qdir, "oasisqe.zip"), "rb") data = readback.read() readback.close() shutil.rmtree(qdir) return data
def get_q_att_fname(qid, name): """ Return (mimetype, filename) with the relevant filename. If it's not found in question, look in questiontemplate. """ qtid = DB.get_q_parent(qid) variation = DB.get_q_variation(qid) version = DB.get_q_version(qid) # for the two biggies we hit the question first, # otherwise check the question template first if name == "image.gif" or name == "qtemplate.html": fname = DB.get_q_att_fname(qtid, name, variation, version) if fname: return DB.get_q_att_mimetype(qtid, name, variation, version), fname fname = DB.get_qt_att_fname(qtid, name, version) if fname: return DB.get_qt_att_mimetype(qtid, name, version), fname else: fname = DB.get_qt_att_fname(qtid, name, version) if fname: return DB.get_qt_att_mimetype(qtid, name, version), fname fname = DB.get_q_att_fname(qtid, name, variation, version) if fname: return DB.get_q_att_mimetype(qtid, name, variation, version), fname return None, None
def practice_do_question(topic_id, position): """ Show them a question and allow them to fill in some answers """ user_id = session['user_id'] try: course_id = Topics.get_course_id(topic_id) except KeyError: course_id = None abort(404) try: course = Courses2.get_course(course_id) except KeyError: course = None abort(404) topictitle = "UNKNOWN" try: topictitle = Topics.get_name(topic_id) except KeyError: abort(404) try: choices = DB.get_qtemplates_in_topic_position(topic_id, position) except KeyError: choices = None abort(404) if len(choices) == 1: qt_id = choices[0] elif len(choices) > 1: L.debug("Practice choosing random from: %s" % repr(choices)) qt_id = random.choice(choices) else: L.warn("Access to non existent practice topic %s question %s" % (topic_id, position)) return render_template( "practicequestionerror.html", mesg="Error accessing question.", topic_id=topic_id, course=course, q_pos=position ) qtemplate = DB.get_qtemplate(qt_id) questions = Practice.get_sorted_questions(course_id, topic_id, user_id) q_title = qtemplate['title'] q_pos = DB.get_qtemplate_topic_pos(qt_id, topic_id) blocked = Practice.is_q_blocked(user_id, course_id, topic_id, qt_id) if blocked: return render_template( "practicequestionblocked.html", mesg=blocked, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) try: q_id = Practice.get_practice_q(qt_id, user_id) except (ValueError, TypeError) as err: L.error("ERROR 1001 (%s,%s) %s" % (qt_id, user_id, err)) return render_template( "practicequestionerror.html", mesg="Error generating question.", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) if not q_id > 0: L.error("ERROR 1002 (%s,%s) Question not generated" % (qt_id, user_id)) return render_template( "practicequestionerror.html", mesg="Error generating question.", topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, ) q_body = General.render_q_html(q_id) q_body = q_body.replace(r"\240", u" ") # TODO: why is this here? return render_template( "practicedoquestion.html", q_body=q_body, topictitle=topictitle, topic_id=topic_id, qt_id=qt_id, course=course, q_title=q_title, questions=questions, q_pos=q_pos, q_id=q_id, )
def gen_q_from_var(qt_id, student, exam, position, version, variation): """ Generate a question given a specific variation. """ qvars = None q_id = DB.create_q(qt_id, DB.get_qt_name(qt_id), student, 1, variation, version, exam) try: q_id = int(q_id) assert (q_id > 0) except (ValueError, TypeError, AssertionError): L.error("OaDB.createQuestion(%s,...) FAILED" % qt_id) imageexists = DB.get_q_att_mimetype(qt_id, "image.gif", variation, version) if not imageexists: if not qvars: qvars = DB.get_qt_variation(qt_id, variation, version) qvars['Oasis_qid'] = q_id image = DB.get_qt_att(qt_id, "image.gif", version) if image: newimage = gen_q_image(qvars, image) DB.create_q_att(qt_id, variation, "image.gif", "image/gif", newimage, version) htmlexists = DB.get_q_att_mimetype(qt_id, "qtemplate.html", variation, version) if not htmlexists: if not qvars: qvars = DB.get_qt_variation(qt_id, variation, version) html = DB.get_qt_att(qt_id, "qtemplate.html", version) if html: qvars['Oasis_qid'] = q_id newhtml = gen_q_html(qvars, html) L.info("generating new qattach qtemplate.html for %s" % q_id) DB.create_q_att(qt_id, variation, "qtemplate.html", "application/oasis-html", newhtml, version) try: q_id = int(q_id) assert (q_id > 0) except (ValueError, TypeError, AssertionError): L.error("generateQuestionFromVar(%s,%s), can't find qid %s? " % (qt_id, student, q_id)) if exam >= 1: DB.add_exam_q(student, exam, q_id, position) return q_id
def render_mark_results_script(qtid, qid, marks, script): """Run the provided script to show the marking for the question. """ version = DB.get_q_version(qid) variation = DB.get_q_variation(qid) qvars = DB.get_qt_variation(qtid, variation, version) questionhtml = render_q_html(qid, readonly=True) reshtml = "" qvars["__builtins__"] = { 'MyFuncs': OqeSmartmarkFuncs, 'withinTolerance': script_funcs.within_tolerance, 'math': math, 'round': round, 'float': float, 'log': script_funcs.result_log_fn(qid), 'dir': dir, 'abs': abs, 'None': None, 'True': True, 'False': False, 'questionHTML': questionhtml, 'int': int, 'resultsHTML': reshtml } qvars['markeroutput'] = marks guesses = [ int(var[1:]) for var in marks.keys() if re.search(r"^G([0-9]+)$", var) > 0 ] answers = [ int(var[1:]) for var in marks.keys() if re.search(r"^A([0-9]+)$", var) > 0 ] tolerances = [ int(var[1:]) for var in marks.keys() if re.search(r"^T([0-9]+)$", var) > 0 ] scores = [ int(var[1:]) for var in marks.keys() if re.search(r"^M([0-9]+)$", var) > 0 ] comments = [ int(var[1:]) for var in marks.keys() if re.search(r"^C([0-9]+)$", var) > 0 ] qvars['guesses'] = {} qvars['answers'] = {} qvars['tolerances'] = {} qvars['scores'] = {} qvars['comments'] = {} for guess in guesses: qvars['guesses'][guess] = marks['G%d' % guess] for answer in answers: qvars['answers'][answer] = marks['A%d' % answer] for tolerance in tolerances: qvars['tolerances'][tolerance] = marks['T%d' % tolerance] for score in scores: qvars['scores'][score] = marks['M%d' % score] for comment in comments: qvars['comments'][comment] = marks['C%d' % comment] qvars['numparts'] = len(answers) qvars['parts'] = range(1, len(answers) + 1) try: exec(script, qvars) except BaseException: (etype, value, tb) = sys.exc_info() script_funcs.q_log( qid, "error", "__results.py", "Reverting to standard display: __results.py: %s" % (traceback.format_exception(etype, value, tb)[-2:])) if 'resultsHTML' in qvars: if len(qvars['resultsHTML']) > 2: reshtml = qvars['resultsHTML'] for v in qvars.keys(): reshtml = reshtml.replace("<IMG SRC %s>" % v, '<IMG SRC="$OaQID$%s" />' % qvars[v]) reshtml = reshtml.replace("$OaQID$", "%d/" % qid) return reshtml script_funcs.q_log(qid, "error", "__results.py", "'resultsHTML' not set, using standard renderer.") return render_mark_results_standard(qid, marks)