serve.py
6.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3.4
# -*- coding: utf-8 -*-
# The program runs a web server where students login to answer a sequence of questions.
# Their grades are automatically calculated and stored in a sqlite3 database.
import cherrypy
from mako.lookup import TemplateLookup
import sqlite3
import yaml
import argparse
from datetime import datetime
import os.path
from myauth import AuthController, require
import test
mylookup = TemplateLookup(directories=['templates'], input_encoding='utf-8')
# get results from previous tests
def allgrades(db, uid):
conn = sqlite3.connect(db)
grades = conn.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]).fetchall()
conn.commit()
conn.close()
return grades
# ============================================================================
# Classes that respond to HTTP
# ============================================================================
class Root(object):
def __init__(self, testconf):
'''The values defined here are common to all students.
Data is stored in the student's respective sessions.'''
self.testconf = testconf
self.auth = AuthController(database=testconf['database'])
# --- DEFAULT ------------------------------------------------------------
# any path, e.g. /xpto/aargh is redirected to the test
# (and possibly through the login first)
@cherrypy.expose
def default(self, *args):
raise cherrypy.HTTPRedirect('/test')
@cherrypy.expose
def results(self):
'''Show list of students'''
conn = sqlite3.connect(self.testconf['database'])
cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;'
results = conn.execute(cmd, [self.testconf['ref']]).fetchall()
conn.commit()
conn.close()
print(str(results))
template = mylookup.get_template('results.html')
return template.render(results=results)
# --- SEQUENCE------------------------------------------------------------
# @cherrypy.expose
# @require()
# def question(self):
# uid = cherrypy.session.get('userid')
# name = cherrypy.session.get('name')
# t = cherrypy.session.get('test', None)
# if t is None:
# cherrypy.session['test'] = t = test.Test(self.testconf)
# t['number'] = uid
# t['name'] = name
# t['current_question'] = 0
# else:
# t['current_question'] += 1
# # Generate question
# template = mylookup.get_template('question.html')
# return template.render(t=t)
# --- TEST ---------------------------------------------------------------
@cherrypy.expose
@require()
def test(self):
# Get student number and assigned questions from current session.
# If it's the first time, create instance of the test and register the
# time.
uid = cherrypy.session.get('userid')
name = cherrypy.session.get('name')
t = cherrypy.session.get('test', None)
if t is None:
cherrypy.session['test'] = t = test.Test(self.testconf)
t['number'] = uid
t['name'] = name
# Generate question
template = mylookup.get_template('test.html')
return template.render(t=t, questions=t['questions'])
# --- CORRECT ------------------------------------------------------------
@cherrypy.expose
@require()
def correct(self, **kwargs):
'''Receives a dictionary {'question_id': 'answer', ...} submited by
the student, corrects and saves the answers, computes the final grade
of the test and stores in the database.'''
# if there is no test, redirect to the test
t = cherrypy.session.get('test')
if t is None:
raise cherrypy.HTTPRedirect('/test')
# each question that is marked to be classified must have an answer.
# ans contains the answers to be corrected
ans = {}
for q in t['questions']:
if 'answered-' + q['ref'] in kwargs:
ans[q['ref']] = kwargs.get(q['ref'], None)
# store the answers provided by the student and correct the test
t.update_answers(ans)
t.correct()
if t['save_answers']:
t.save_json(self.testconf['answers_dir'])
t.save_database(self.testconf['database'])
grades = allgrades(self.testconf['database'], t['number'])
# ---- Expire session ----
cherrypy.lib.sessions.expire() # session coockie expires client side
cherrypy.session['userid'] = cherrypy.request.login = None
cherrypy.log.error('Student %s terminated with grade = %.2f points.' %
(t['number'], t['grade']), 'APPLICATION')
# ---- Show result to student ----
template = mylookup.get_template('grade.html')
return template.render(t=t, allgrades=grades)
# ============================================================================
def parse_arguments():
argparser = argparse.ArgumentParser(description='Server for online tests. Enrolled students and tests have to be previously configured. Please read the documentation included with this software before running the server.')
argparser.add_argument('--server', default='conf/server.conf', type=str, help='server configuration file')
argparser.add_argument('testfile', type=str, help='test in YAML format.')
return argparser.parse_args()
# ============================================================================
if __name__ == '__main__':
# --- parse command line arguments and build base test
arg = parse_arguments()
testconf = test.read_configuration(arg.testfile)
print('=' * 79)
print('- Title: %s' % testconf['title'])
print('- StudentsDB: %s' % testconf['database'])
print('- Questions: ', ', '.join(testconf['files']))
print(' (%i questions read)' % len(testconf['questions']))
print('-' * 79)
print('- Starting server...')
# --- controller
root = Root(testconf)
# --- Mount and run server.
cherrypy.quickstart(root, '/', config=arg.server)
cherrypy.log.error('Terminated OK ------------------------', 'APPLICATION')
print('\n- Server terminated OK')
print('=' * 79)