Commit 9b8add7ea0422d9f069e60fbe8c8a2f14cf8f699

Authored by Miguel Barao
1 parent 21ab708a
Exists in master and in 1 other branch dev

- new database.py makes the interface with sqlite, no more sqlite in

serve.py.
database.py 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +
  2 +import sqlite3
  3 +
  4 +class Database(object):
  5 + def __init__(self, db):
  6 + self.db = db # sqlite3 filename
  7 +
  8 + # get results from previous tests of a student
  9 + def student_grades(self, uid):
  10 + with sqlite3.connect(self.db) as c:
  11 + grades = c.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid])
  12 + return grades.fetchall()
  13 +
  14 + # return students results from a given test
  15 + def test_grades(self, test_id):
  16 + with sqlite3.connect(self.db) as c:
  17 + cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;'
  18 + results = c.execute(cmd, [test_id])
  19 + return results.fetchall()
  20 +
  21 + # get list of students in the database
  22 + def students(self):
  23 + with sqlite3.connect(self.db) as c:
  24 + students = c.execute('SELECT number,name,password FROM students ORDER BY number ASC;')
  25 + return students.fetchall()
  26 +
@@ -4,7 +4,6 @@ @@ -4,7 +4,6 @@
4 # The program runs a web server where students login to answer a sequence of questions. 4 # The program runs a web server where students login to answer a sequence of questions.
5 # Their grades are automatically calculated and stored in a sqlite3 database. 5 # Their grades are automatically calculated and stored in a sqlite3 database.
6 6
7 -  
8 import cherrypy 7 import cherrypy
9 from mako.lookup import TemplateLookup 8 from mako.lookup import TemplateLookup
10 import sqlite3 9 import sqlite3
@@ -12,20 +11,11 @@ import yaml @@ -12,20 +11,11 @@ import yaml
12 import argparse 11 import argparse
13 from datetime import datetime 12 from datetime import datetime
14 import os.path 13 import os.path
15 -from myauth import AuthController, require  
16 14
  15 +# my code
  16 +from myauth import AuthController, require
17 import test 17 import test
18 -  
19 -mylookup = TemplateLookup(directories=['templates'], input_encoding='utf-8')  
20 -  
21 -# get results from previous tests  
22 -def allgrades(db, uid):  
23 - conn = sqlite3.connect(db)  
24 - grades = conn.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]).fetchall()  
25 - conn.commit()  
26 - conn.close()  
27 - return grades  
28 - 18 +import database
29 19
30 # ============================================================================ 20 # ============================================================================
31 # Classes that respond to HTTP 21 # Classes that respond to HTTP
@@ -33,10 +23,11 @@ def allgrades(db, uid): @@ -33,10 +23,11 @@ def allgrades(db, uid):
33 class Root(object): 23 class Root(object):
34 24
35 def __init__(self, testconf): 25 def __init__(self, testconf):
36 - '''The values defined here are common to all students.  
37 - Data is stored in the student's respective sessions.'''  
38 - self.testconf = testconf 26 + self.testconf = testconf # base test dict (not instance)
  27 + self.database = database.Database(testconf['database'])
39 self.auth = AuthController(database=testconf['database']) 28 self.auth = AuthController(database=testconf['database'])
  29 + self.templates = TemplateLookup(directories=['templates'], input_encoding='utf-8')
  30 + self.loggedin = set() # students currently logged in #FIXME should be in database instead??
40 31
41 # --- DEFAULT ------------------------------------------------------------ 32 # --- DEFAULT ------------------------------------------------------------
42 # any path, e.g. /xpto/aargh is redirected to the test 33 # any path, e.g. /xpto/aargh is redirected to the test
@@ -45,40 +36,27 @@ class Root(object): @@ -45,40 +36,27 @@ class Root(object):
45 def default(self, *args): 36 def default(self, *args):
46 raise cherrypy.HTTPRedirect('/test') 37 raise cherrypy.HTTPRedirect('/test')
47 38
  39 + # --- STUDENTS -----------------------------------------------------------
48 @cherrypy.expose 40 @cherrypy.expose
49 - def results(self):  
50 - '''Show list of students'''  
51 - conn = sqlite3.connect(self.testconf['database'])  
52 -  
53 - cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;'  
54 - results = conn.execute(cmd, [self.testconf['ref']]).fetchall()  
55 - conn.commit()  
56 - conn.close()  
57 -  
58 - print(str(results))  
59 - template = mylookup.get_template('results.html')  
60 - return template.render(results=results)  
61 -  
62 -  
63 - # --- SEQUENCE------------------------------------------------------------  
64 - # @cherrypy.expose  
65 - # @require()  
66 - # def question(self):  
67 - # uid = cherrypy.session.get('userid')  
68 - # name = cherrypy.session.get('name')  
69 - # t = cherrypy.session.get('test', None)  
70 - # if t is None:  
71 - # cherrypy.session['test'] = t = test.Test(self.testconf)  
72 - # t['number'] = uid  
73 - # t['name'] = name  
74 - # t['current_question'] = 0  
75 - # else:  
76 - # t['current_question'] += 1  
77 -  
78 - # # Generate question  
79 - # template = mylookup.get_template('question.html')  
80 - # return template.render(t=t) 41 + def students(self, reset_pw=None):
  42 + if cherrypy.session.get('userid') != '0':
  43 + raise cherrypy.HTTPRedirect('/')
  44 +
  45 + print(reset_pw)
  46 + if reset_pw is not None:
  47 + self.database.reset_pw(reset_pw)
  48 +
  49 + students = self.database.students()
  50 + # print(students)
  51 + template = self.templates.get_template('students.html')
  52 + return template.render(students=students, loggedin=self.loggedin)
81 53
  54 + # --- RESULTS ------------------------------------------------------------
  55 + @cherrypy.expose
  56 + def results(self):
  57 + results = self.database.test_grades(self.testconf['ref'])
  58 + template = self.templates.get_template('results.html')
  59 + return template.render(t=self.testconf, results=results)
82 60
83 # --- TEST --------------------------------------------------------------- 61 # --- TEST ---------------------------------------------------------------
84 @cherrypy.expose 62 @cherrypy.expose
@@ -91,12 +69,14 @@ class Root(object): @@ -91,12 +69,14 @@ class Root(object):
91 name = cherrypy.session.get('name') 69 name = cherrypy.session.get('name')
92 t = cherrypy.session.get('test', None) 70 t = cherrypy.session.get('test', None)
93 if t is None: 71 if t is None:
  72 + # create instance and add the name and number of the student
94 cherrypy.session['test'] = t = test.Test(self.testconf) 73 cherrypy.session['test'] = t = test.Test(self.testconf)
95 t['number'] = uid 74 t['number'] = uid
96 t['name'] = name 75 t['name'] = name
  76 + self.loggedin.add(uid) # track logged in students # FIXME should be in the auth module...
97 77
98 # Generate question 78 # Generate question
99 - template = mylookup.get_template('test.html') 79 + template = self.templates.get_template('test.html')
100 return template.render(t=t, questions=t['questions']) 80 return template.render(t=t, questions=t['questions'])
101 81
102 # --- CORRECT ------------------------------------------------------------ 82 # --- CORRECT ------------------------------------------------------------
@@ -107,34 +87,36 @@ class Root(object): @@ -107,34 +87,36 @@ class Root(object):
107 the student, corrects and saves the answers, computes the final grade 87 the student, corrects and saves the answers, computes the final grade
108 of the test and stores in the database.''' 88 of the test and stores in the database.'''
109 89
110 - # if there is no test, redirect to the test 90 + # shouldn't be here if there is no test
111 t = cherrypy.session.get('test') 91 t = cherrypy.session.get('test')
112 if t is None: 92 if t is None:
113 raise cherrypy.HTTPRedirect('/test') 93 raise cherrypy.HTTPRedirect('/test')
114 94
115 # each question that is marked to be classified must have an answer. 95 # each question that is marked to be classified must have an answer.
116 - # ans contains the answers to be corrected 96 + # variable `ans` contains the answers to be corrected
117 ans = {} 97 ans = {}
118 for q in t['questions']: 98 for q in t['questions']:
119 if 'answered-' + q['ref'] in kwargs: 99 if 'answered-' + q['ref'] in kwargs:
120 ans[q['ref']] = kwargs.get(q['ref'], None) 100 ans[q['ref']] = kwargs.get(q['ref'], None)
121 101
122 - # store the answers provided by the student and correct the test 102 + # store the answers in the Test, correct it, save JSON and
  103 + # store results in the database
123 t.update_answers(ans) 104 t.update_answers(ans)
124 t.correct() 105 t.correct()
125 if t['save_answers']: 106 if t['save_answers']:
126 t.save_json(self.testconf['answers_dir']) 107 t.save_json(self.testconf['answers_dir'])
127 - t.save_database(self.testconf['database'])  
128 - grades = allgrades(self.testconf['database'], t['number']) 108 + t.save_database(self.testconf['database']) # FIXME save_database should be in database.py
129 109
130 # ---- Expire session ---- 110 # ---- Expire session ----
  111 + self.loggedin.discard(t['number'])
131 cherrypy.lib.sessions.expire() # session coockie expires client side 112 cherrypy.lib.sessions.expire() # session coockie expires client side
132 cherrypy.session['userid'] = cherrypy.request.login = None 113 cherrypy.session['userid'] = cherrypy.request.login = None
133 cherrypy.log.error('Student %s terminated with grade = %.2f points.' % 114 cherrypy.log.error('Student %s terminated with grade = %.2f points.' %
134 (t['number'], t['grade']), 'APPLICATION') 115 (t['number'], t['grade']), 'APPLICATION')
135 116
136 # ---- Show result to student ---- 117 # ---- Show result to student ----
137 - template = mylookup.get_template('grade.html') 118 + grades = self.database.student_grades(t['number'])
  119 + template = self.templates.get_template('grade.html')
138 return template.render(t=t, allgrades=grades) 120 return template.render(t=t, allgrades=grades)
139 121
140 # ============================================================================ 122 # ============================================================================
@@ -153,8 +135,8 @@ if __name__ == '__main__': @@ -153,8 +135,8 @@ if __name__ == '__main__':
153 print('=' * 79) 135 print('=' * 79)
154 print('- Title: %s' % testconf['title']) 136 print('- Title: %s' % testconf['title'])
155 print('- StudentsDB: %s' % testconf['database']) 137 print('- StudentsDB: %s' % testconf['database'])
156 - print('- Questions: ', ', '.join(testconf['files']))  
157 - print(' (%i questions read)' % len(testconf['questions'])) 138 + print('- Questions:\n ', '\n '.join(testconf['files']))
  139 + print(' (%i questions read)' % len(testconf['questions']))
158 print('-' * 79) 140 print('-' * 79)
159 print('- Starting server...') 141 print('- Starting server...')
160 142
templates/results.html
@@ -4,18 +4,13 @@ @@ -4,18 +4,13 @@
4 <meta charset="UTF-8"> 4 <meta charset="UTF-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 - <title> Resultados </title> 7 + <title> ${t['title']} </title>
8 <link rel="icon" href="favicon.ico"> 8 <link rel="icon" href="favicon.ico">
9 9
10 <!-- Bootstrap --> 10 <!-- Bootstrap -->
11 <link rel="stylesheet" href="/css/bootstrap.min.css"> 11 <link rel="stylesheet" href="/css/bootstrap.min.css">
12 <link rel="stylesheet" href="/css/bootstrap-theme.min.css"> <!-- optional --> 12 <link rel="stylesheet" href="/css/bootstrap-theme.min.css"> <!-- optional -->
13 13
14 -<!-- <link rel="stylesheet" href="/js/jquery/jquery.mobile-1.4.5.min.css" />  
15 - <script src="/js/jquery/jquery-2.1.1.min.js"></script>  
16 - <script src="/js/jquery/jquery.mobile-1.4.5.min.js"></script>  
17 - -->  
18 -  
19 <script src="/js/jquery.min.js"></script> 14 <script src="/js/jquery.min.js"></script>
20 <script src="/js/bootstrap.min.js"></script> 15 <script src="/js/bootstrap.min.js"></script>
21 16
@@ -35,40 +30,10 @@ @@ -35,40 +30,10 @@
35 <!-- ===================================================================== --> 30 <!-- ===================================================================== -->
36 <body> 31 <body>
37 32
38 -<nav class="navbar navbar-default navbar-fixed-top" role="navigation">  
39 - <div class="container-fluid drop-shadow">  
40 - <div class="navbar-header">  
41 - <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">  
42 - <span class="icon-bar"></span>  
43 - <span class="icon-bar"></span>  
44 - <span class="icon-bar"></span>  
45 - </button>  
46 - <a class="navbar-brand" href="#">UÉvora</a>  
47 - </div>  
48 - <div class="collapse navbar-collapse" id="myNavbar">  
49 -<!-- <ul class="nav navbar-nav">  
50 - <li><a href="#">Teoria</a></li>  
51 - <li><a href="#">Exemplos</a></li>  
52 - <li><a href="#">Exercícios</a></li>  
53 - </ul>  
54 - -->  
55 - <ul class="nav navbar-nav navbar-right">  
56 - <li class="dropdown">  
57 - <a class="dropdown-toggle" data-toggle="dropdown" href="#">${t['number']} - ${t['name']} <span class="caret"></span></a>  
58 -<!-- <ul class="dropdown-menu">  
59 - <li><a href="#">Toggle colors (day/night)</a></li>  
60 - <li><a href="#">Change password</a></li>  
61 - </ul>  
62 - --> </li>  
63 - </ul>  
64 - </div>  
65 - </div>  
66 -</nav>  
67 -  
68 <div class="container"> 33 <div class="container">
69 <div class="panel panel-default drop-shadow"> 34 <div class="panel panel-default drop-shadow">
70 <div class="panel-heading"> 35 <div class="panel-heading">
71 - Testes realizados 36 + ${t['title']}
72 </div> 37 </div>
73 <!-- <div class="panel-body"> --> 38 <!-- <div class="panel-body"> -->
74 <table class="table"> 39 <table class="table">
templates/students.html 0 → 100644
@@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <meta charset="UTF-8">
  5 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1">
  7 + <title> List of students </title>
  8 + <link rel="icon" href="favicon.ico">
  9 +
  10 + <!-- Bootstrap -->
  11 + <link rel="stylesheet" href="/css/bootstrap.min.css">
  12 + <link rel="stylesheet" href="/css/bootstrap-theme.min.css"> <!-- optional -->
  13 +
  14 + <script src="/js/jquery.min.js"></script>
  15 + <script src="/js/bootstrap.min.js"></script>
  16 +
  17 + <style>
  18 + /* Fixes navigation panel overlaying content */
  19 + body {
  20 + padding-top: 80px;
  21 + background: #aaa;
  22 + }
  23 + .drop-shadow {
  24 + -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, .5);
  25 + box-shadow: 0px 2px 10px 3px rgba(0, 0, 0, .2);
  26 + border-radius:5px;
  27 + }
  28 + </style>
  29 + </head>
  30 + <!-- ===================================================================== -->
  31 +<body>
  32 +
  33 +<div class="container">
  34 + <div class="panel panel-default drop-shadow">
  35 + <div class="panel-heading">
  36 + List of students
  37 + </div>
  38 + <!-- <div class="panel-body"> -->
  39 + <table class="table">
  40 + <thead>
  41 + <tr>
  42 + <th>Número</th>
  43 + <th>Nome</th>
  44 + <th>Password</th>
  45 + </tr>
  46 + </thead>
  47 + <tbody>
  48 + % for r in students:
  49 + % if r[0] in loggedin:
  50 + <tr class="success">
  51 + % else:
  52 + <tr>
  53 + % endif
  54 + <td>${r[0]}</td> <!-- numero -->
  55 + <td>${r[1]}</td> <!-- nome -->
  56 + <td class="col-sm-6">
  57 + <form action="/students" method="post">
  58 + <div class="input-group">
  59 + <span class="input-group-btn">
  60 + <button class="btn btn-danger" type="submit">reset</button>
  61 + </span>
  62 + <input type="text" class="form-control" placeholder="${r[2]}" name="number">
  63 + </div><!-- /input-group -->
  64 + </form>
  65 + </td> <!-- password -->
  66 + </tr>
  67 + % endfor
  68 + </tbody>
  69 + </table>
  70 + </div> <!-- panel -->
  71 +</div> <!-- container -->
  72 +</body>
  73 +</html>