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 @@
  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 +
... ...
serve.py
... ... @@ -4,7 +4,6 @@
4 4 # The program runs a web server where students login to answer a sequence of questions.
5 5 # Their grades are automatically calculated and stored in a sqlite3 database.
6 6  
7   -
8 7 import cherrypy
9 8 from mako.lookup import TemplateLookup
10 9 import sqlite3
... ... @@ -12,20 +11,11 @@ import yaml
12 11 import argparse
13 12 from datetime import datetime
14 13 import os.path
15   -from myauth import AuthController, require
16 14  
  15 +# my code
  16 +from myauth import AuthController, require
17 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 21 # Classes that respond to HTTP
... ... @@ -33,10 +23,11 @@ def allgrades(db, uid):
33 23 class Root(object):
34 24  
35 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 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 32 # --- DEFAULT ------------------------------------------------------------
42 33 # any path, e.g. /xpto/aargh is redirected to the test
... ... @@ -45,40 +36,27 @@ class Root(object):
45 36 def default(self, *args):
46 37 raise cherrypy.HTTPRedirect('/test')
47 38  
  39 + # --- STUDENTS -----------------------------------------------------------
48 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 61 # --- TEST ---------------------------------------------------------------
84 62 @cherrypy.expose
... ... @@ -91,12 +69,14 @@ class Root(object):
91 69 name = cherrypy.session.get('name')
92 70 t = cherrypy.session.get('test', None)
93 71 if t is None:
  72 + # create instance and add the name and number of the student
94 73 cherrypy.session['test'] = t = test.Test(self.testconf)
95 74 t['number'] = uid
96 75 t['name'] = name
  76 + self.loggedin.add(uid) # track logged in students # FIXME should be in the auth module...
97 77  
98 78 # Generate question
99   - template = mylookup.get_template('test.html')
  79 + template = self.templates.get_template('test.html')
100 80 return template.render(t=t, questions=t['questions'])
101 81  
102 82 # --- CORRECT ------------------------------------------------------------
... ... @@ -107,34 +87,36 @@ class Root(object):
107 87 the student, corrects and saves the answers, computes the final grade
108 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 91 t = cherrypy.session.get('test')
112 92 if t is None:
113 93 raise cherrypy.HTTPRedirect('/test')
114 94  
115 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 97 ans = {}
118 98 for q in t['questions']:
119 99 if 'answered-' + q['ref'] in kwargs:
120 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 104 t.update_answers(ans)
124 105 t.correct()
125 106 if t['save_answers']:
126 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 110 # ---- Expire session ----
  111 + self.loggedin.discard(t['number'])
131 112 cherrypy.lib.sessions.expire() # session coockie expires client side
132 113 cherrypy.session['userid'] = cherrypy.request.login = None
133 114 cherrypy.log.error('Student %s terminated with grade = %.2f points.' %
134 115 (t['number'], t['grade']), 'APPLICATION')
135 116  
136 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 120 return template.render(t=t, allgrades=grades)
139 121  
140 122 # ============================================================================
... ... @@ -153,8 +135,8 @@ if __name__ == '__main__':
153 135 print('=' * 79)
154 136 print('- Title: %s' % testconf['title'])
155 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 140 print('-' * 79)
159 141 print('- Starting server...')
160 142  
... ...
templates/results.html
... ... @@ -4,18 +4,13 @@
4 4 <meta charset="UTF-8">
5 5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 6 <meta name="viewport" content="width=device-width, initial-scale=1">
7   - <title> Resultados </title>
  7 + <title> ${t['title']} </title>
8 8 <link rel="icon" href="favicon.ico">
9 9  
10 10 <!-- Bootstrap -->
11 11 <link rel="stylesheet" href="/css/bootstrap.min.css">
12 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 14 <script src="/js/jquery.min.js"></script>
20 15 <script src="/js/bootstrap.min.js"></script>
21 16  
... ... @@ -35,40 +30,10 @@
35 30 <!-- ===================================================================== -->
36 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 33 <div class="container">
69 34 <div class="panel panel-default drop-shadow">
70 35 <div class="panel-heading">
71   - Testes realizados
  36 + ${t['title']}
72 37 </div>
73 38 <!-- <div class="panel-body"> -->
74 39 <table class="table">
... ...
templates/students.html 0 → 100644
... ... @@ -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>
... ...