serve.py
6.32 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
#!/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 yaml
import argparse
from datetime import datetime
import os.path
# my code
from myauth import AuthController, require
import test
import database
# ============================================================================
# Classes that respond to HTTP
# ============================================================================
class Root(object):
def __init__(self, testconf):
self.testconf = testconf # base test dict (not instance)
self.database = database.Database(testconf['database'])
self.auth = AuthController(database=testconf['database'])
self.templates = TemplateLookup(directories=['templates'], input_encoding='utf-8')
self.loggedin = set() # students currently logged in #FIXME should be in database instead??
# --- 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')
# --- STUDENTS -----------------------------------------------------------
@cherrypy.expose
@require()
# def students(self, reset_pw=None):
def students(self, **reset_pw):
uid = cherrypy.session.get('userid')
if uid != '0':
raise cherrypy.HTTPRedirect('/') #FIXME use authorization @require(admin)
if reset_pw:
self.database.student_reset_pw(reset_pw)
for num in reset_pw:
cherrypy.log.error('Password updated for student %s.' % str(num), 'APPLICATION')
students = self.database.students()
# print(students)
template = self.templates.get_template('students.html')
return template.render(students=students, loggedin=self.loggedin)
# --- RESULTS ------------------------------------------------------------
@cherrypy.expose
def results(self):
results = self.database.test_grades(self.testconf['ref'])
template = self.templates.get_template('results.html')
return template.render(t=self.testconf, results=results)
# --- 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:
# create instance and add the name and number of the student
cherrypy.session['test'] = t = test.Test(self.testconf)
t['number'] = uid
t['name'] = name
self.loggedin.add(uid) # track logged in students # FIXME should be in the auth module...
# Generate question
template = self.templates.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.'''
# shouldn't be here if there is no 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.
# variable `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 in the Test, correct it, save JSON and
# store results in the database
t.update_answers(ans)
t.correct()
if t['save_answers']:
t.save_json(self.testconf['answers_dir'])
t.save_database(self.testconf['database']) # FIXME save_database should be in database.py
# ---- Expire session ----
self.loggedin.discard(t['number'])
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 ----
grades = self.database.student_grades(t['number'])
template = self.templates.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:\n ', '\n '.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)