Commit 2780d0843fcfbcce121dddca36df7ab9079ef949
1 parent
1699423a
Exists in
master
and in
1 other branch
- changed database table "test" to include title and JSON filename where the test was saved.
- fixed bug where incorrect test results were shown to student after /correct.
Showing
5 changed files
with
79 additions
and
69 deletions
Show diff stats
BUGS.md
1 | 1 | |
2 | 2 | # BUGS |
3 | 3 | |
4 | -- implementar practice mode. | |
5 | 4 | - usar thread.Lock para aceder a variaveis de estado? |
5 | +- visualizar um teste ja realizado na página de administração | |
6 | 6 | |
7 | 7 | # TODO |
8 | 8 | |
9 | +- usar http://wtfforms.com para radio e checkboxes | |
10 | +- usar http://fontawesome.io/examples/ em vez dos do bootstrap3 | |
11 | +- implementar practice mode. | |
9 | 12 | - abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova. |
10 | 13 | - detectar scroll e enviar posição para servidor (analise de scroll para detectar copianço? ou simplesmente para analisar como os alunos percorrem o teste) |
11 | -- detectar se janela perde focus e alertar o prof (http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active) | |
12 | 14 | - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). |
13 | 15 | - aviso na pagina principal para quem usa browser da treta |
14 | 16 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. |
15 | 17 | - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput |
16 | 18 | - perguntas para professor corrigir mais tarde. |
17 | -- visualizar um teste ja realizado na página de administração | |
18 | 19 | - fazer uma calculadora javascript e por no menu. surge como modal |
19 | 20 | - GeoIP? |
20 | 21 | - alunos online têm acesso a /correct e servidor rebenta. (não é fácil impedir...) |
... | ... | @@ -22,6 +23,8 @@ |
22 | 23 | |
23 | 24 | # FIXED |
24 | 25 | |
26 | +- Depois da correcção, mostra testes realizados que não foram realizados pelo próprio | |
27 | +- detectar se janela perde focus e alertar o prof (http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active) | |
25 | 28 | - server nao esta a receber eventos focus/blur dos utilizadores diferentes de '0', estranho... |
26 | 29 | - permitir adicionar imagens nas perguntas. |
27 | 30 | - detect_unfocus.js so funciona se estiver inline no html. porquê??? | ... | ... |
app.py
... | ... | @@ -131,22 +131,25 @@ class App(object): |
131 | 131 | grade = t.correct() |
132 | 132 | logger.info('Student {0}: finished with {1} points.'.format(uid, grade)) |
133 | 133 | |
134 | - if t['save_answers']: | |
135 | - fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' | |
136 | - fpath = path.abspath(path.join(t['answers_dir'], fname)) | |
137 | - t.save_json(fpath) | |
134 | + # save JSON with the test | |
135 | + fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' | |
136 | + fpath = path.abspath(path.join(t['answers_dir'], fname)) | |
137 | + t.save_json(fpath) | |
138 | 138 | |
139 | + # insert test and questions into database | |
139 | 140 | with self.db_session() as s: |
140 | 141 | s.add(Test( |
141 | 142 | ref=t['ref'], |
143 | + title=t['title'], | |
142 | 144 | grade=t['grade'], |
143 | 145 | starttime=str(t['start_time']), |
144 | 146 | finishtime=str(t['finish_time']), |
147 | + filename=fpath, | |
145 | 148 | student_id=t['student']['number'])) |
146 | 149 | s.add_all([Question( |
147 | 150 | ref=q['ref'], |
148 | 151 | grade=q['grade'], |
149 | - starttime='', | |
152 | + starttime=str(t['start_time']), | |
150 | 153 | finishtime=str(t['finish_time']), |
151 | 154 | student_id=t['student']['number'], |
152 | 155 | test_id=t['ref']) for q in t['questions'] if 'grade' in q]) |
... | ... | @@ -176,9 +179,12 @@ class App(object): |
176 | 179 | return {q['ref']:q['type'] for q in self.online[uid]['test']['questions']} |
177 | 180 | def get_student_grades_from_all_tests(self, uid): |
178 | 181 | with self.db_session() as s: |
179 | - r = s.query(Test).filter(Student.id == uid).all() | |
180 | - return [(t.id, t.grade, t.finishtime) for t in r] | |
181 | - | |
182 | + r = s.query(Test).filter(Test.student_id == uid).all() | |
183 | + return [(t.title, t.grade, t.finishtime) for t in r] | |
184 | + # def get_saved_tests_filenames(self): | |
185 | + # with self.db_session() as s: | |
186 | + # rr = s.query(Test).filter(Test.ref == self.testfactory['ref']).all() | |
187 | + # return [(r.student_id, r.finishtime, r.grade) for r in rr] | |
182 | 188 | def get_online_students(self): |
183 | 189 | # [('uid', 'name', 'starttime')] |
184 | 190 | return [(k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0'] | ... | ... |
models.py
... | ... | @@ -29,9 +29,11 @@ class Test(Base): |
29 | 29 | __tablename__ = 'tests' |
30 | 30 | id = Column(Integer, primary_key=True) # auto_increment |
31 | 31 | ref = Column(String) |
32 | + title = Column(String) | |
32 | 33 | grade = Column(Float) |
33 | 34 | starttime = Column(String) |
34 | 35 | finishtime = Column(String) |
36 | + filename = Column(String) | |
35 | 37 | student_id = Column(String, ForeignKey('students.id')) |
36 | 38 | |
37 | 39 | # --- | ... | ... |
serve.py
... | ... | @@ -198,7 +198,6 @@ class Root(object): |
198 | 198 | # text - always returns string. no answer '', otherwise 'dskdjs' |
199 | 199 | uid = cherrypy.session.get(SESSION_KEY) |
200 | 200 | name = self.app.get_student_name(uid) |
201 | - title = self.app.get_test(uid)['title'] | |
202 | 201 | qq = self.app.get_test_qtypes(uid) # {'q1_ref': 'checkbox', ...} |
203 | 202 | |
204 | 203 | # each question that is marked to be classified must have an answer. |
... | ... | @@ -215,6 +214,7 @@ class Root(object): |
215 | 214 | ans[qref] = a |
216 | 215 | |
217 | 216 | grade = self.app.correct_test(uid, ans) |
217 | + t = self.app.get_test(uid) | |
218 | 218 | self.app.logout(uid) |
219 | 219 | |
220 | 220 | # --- Expire session |
... | ... | @@ -223,9 +223,7 @@ class Root(object): |
223 | 223 | |
224 | 224 | # --- Show result to student |
225 | 225 | return self.template['grade'].render( |
226 | - title=title, | |
227 | - student_id=uid + ' - ' + name, | |
228 | - grade=grade, | |
226 | + t=t, | |
229 | 227 | allgrades=self.app.get_student_grades_from_all_tests(uid) |
230 | 228 | ) |
231 | 229 | |
... | ... | @@ -267,6 +265,14 @@ class Root(object): |
267 | 265 | def admin(self, **reset_pw): |
268 | 266 | return self.template['admin'].render() |
269 | 267 | |
268 | + # --- REVIEW ------------------------------------------------------------- | |
269 | + @cherrypy.expose | |
270 | + @require(name_is('0')) | |
271 | + def review(self, uid=None): | |
272 | + if uid is None: | |
273 | + # get show list of files | |
274 | + pass | |
275 | + | |
270 | 276 | # ============================================================================ |
271 | 277 | def parse_arguments(): |
272 | 278 | 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.') | ... | ... |
templates/grade.html
... | ... | @@ -4,34 +4,17 @@ |
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> ${title} </title> | |
7 | + <title> Teste </title> | |
8 | 8 | <link rel="icon" href="/static/favicon.ico"> |
9 | 9 | |
10 | 10 | <!-- Bootstrap --> |
11 | 11 | <link rel="stylesheet" href="/static/css/bootstrap.min.css"> |
12 | 12 | <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <!-- optional --> |
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 | - --> | |
13 | + <link rel="stylesheet" href="/static/css/test.css"> | |
18 | 14 | |
19 | 15 | <script src="/static/js/jquery.min.js"></script> |
20 | 16 | <script src="/static/js/bootstrap.min.js"></script> |
21 | - | |
22 | - <style> | |
23 | - /* Fixes navigation panel overlaying content */ | |
24 | - body { | |
25 | - padding-top: 80px; | |
26 | - background: #aaa; | |
27 | - } | |
28 | - .drop-shadow { | |
29 | - -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, .5); | |
30 | - box-shadow: 0px 2px 10px 3px rgba(0, 0, 0, .2); | |
31 | - border-radius:5px; | |
32 | - } | |
33 | - </style> | |
34 | - </head> | |
17 | +</head> | |
35 | 18 | <!-- ===================================================================== --> |
36 | 19 | <body> |
37 | 20 | |
... | ... | @@ -43,13 +26,20 @@ |
43 | 26 | <span class="icon-bar"></span> |
44 | 27 | <span class="icon-bar"></span> |
45 | 28 | </button> |
46 | - <a class="navbar-brand" href="#">UÉvora</a> | |
29 | + <a class="navbar-brand" href="#"> | |
30 | + ${t['title']} | |
31 | + </a> | |
47 | 32 | </div> |
33 | + | |
48 | 34 | <div class="collapse navbar-collapse" id="myNavbar"> |
35 | + | |
49 | 36 | <ul class="nav navbar-nav navbar-right"> |
50 | 37 | <li class="dropdown"> |
51 | 38 | <a class="dropdown-toggle" data-toggle="dropdown" href="#"> |
52 | - ${student_id} <span class="caret"></span> | |
39 | + <span class="glyphicon glyphicon-user" aria-hidden="true"></span> | |
40 | + <span id="name">${t['student']['name']}</span> | |
41 | + (<span id="number">${t['student']['number']}</span>) | |
42 | + <!-- <span class="caret"></span> --> | |
53 | 43 | </a> |
54 | 44 | </li> |
55 | 45 | </ul> |
... | ... | @@ -60,46 +50,49 @@ |
60 | 50 | <div class="container"> |
61 | 51 | <div class="jumbotron drop-shadow"> |
62 | 52 | <h1>Resultado</h1> |
63 | - <p>Teve <strong>${'{}'.format(grade)}</strong> valores na escala de 0 a 20.</p> | |
53 | + <p>Teve <strong>${t['grade']}</strong> valores na escala de 0 a 20.</p> | |
64 | 54 | <p>A prova está terminada, pode fechar o browser e desligar o computador.</p> |
65 | 55 | </div> |
66 | 56 | |
67 | 57 | <div class="panel panel-default drop-shadow"> |
68 | 58 | <div class="panel-heading"> |
69 | - Testes realizados | |
59 | + Provas realizadas até ao momento | |
70 | 60 | </div> |
71 | - <table class="table table-condensed"> | |
72 | - <thead> | |
73 | - <tr> | |
74 | - <th>Data</th> | |
75 | - <th>Hora</th> | |
76 | - <th>Teste</th> | |
77 | - <th>Nota (0-20)</th> | |
78 | - </tr> | |
79 | - </thead> | |
80 | - <tbody> | |
81 | - % for g in allgrades: | |
61 | + <div class="panel-body"> | |
62 | + | |
63 | + <table class="table table-condensed"> | |
64 | + <thead> | |
82 | 65 | <tr> |
83 | - <td>${g[2][:10]}</td> <!-- data --> | |
84 | - <td>${g[2][11:19]}</td> <!-- hora --> | |
85 | - <td>${g[0]}</td> <!-- teste --> | |
86 | - <td> | |
87 | - <div class="progress"> | |
88 | - % if g[1] < 10: | |
89 | - <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
90 | - % elif g[1] < 15: | |
91 | - <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
92 | - % else: | |
93 | - <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
94 | - % endif | |
95 | - ${'{:.1f}'.format(g[1])} | |
96 | - </div> | |
97 | - </div> | |
98 | - </td> | |
66 | + <th>Prova</th> | |
67 | + <th>Data</th> | |
68 | + <th>Hora</th> | |
69 | + <th>Nota (0-20)</th> | |
99 | 70 | </tr> |
100 | - % endfor | |
101 | - </tbody> | |
102 | - </table> | |
71 | + </thead> | |
72 | + <tbody> | |
73 | + % for g in allgrades: | |
74 | + <tr> | |
75 | + <td>${g[0]}</td> <!-- teste --> | |
76 | + <td>${g[2][:10]}</td> <!-- data --> | |
77 | + <td>${g[2][11:19]}</td> <!-- hora --> | |
78 | + <td> | |
79 | + <div class="progress"> | |
80 | + % if g[1] < 10: | |
81 | + <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
82 | + % elif g[1] < 15: | |
83 | + <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
84 | + % else: | |
85 | + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="${'{0}'.format(round(g[1]))}" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ${'{0}'.format(round(5 * g[1]))}%;"> | |
86 | + % endif | |
87 | + ${'{:.1f}'.format(g[1])} | |
88 | + </div> | |
89 | + </div> | |
90 | + </td> | |
91 | + </tr> | |
92 | + % endfor | |
93 | + </tbody> | |
94 | + </table> | |
95 | + </div> | |
103 | 96 | </div> <!-- panel --> |
104 | 97 | </div> <!-- container --> |
105 | 98 | </body> | ... | ... |