Commit 870abe3cb5d32d58c0e6d1f1a46be2e748999075
1 parent
3c36fb56
Exists in
master
and in
1 other branch
- code to give up test when exiting. Also saves test.
Showing
5 changed files
with
54 additions
and
12 deletions
Show diff stats
BUGS.md
1 | 1 | |
2 | 2 | # BUGS |
3 | 3 | |
4 | -- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova. | |
5 | -- 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) | |
6 | -- 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) | |
7 | - | |
8 | 4 | - usar thread.Lock para aceder a variaveis de estado? |
9 | 5 | - permitir adicionar imagens nas perguntas. |
10 | 6 | - debug mode: log levels not working |
11 | -- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. | |
12 | 7 | |
13 | 8 | # TODO |
14 | 9 | |
15 | 10 | - implementar practice mode. |
11 | +- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova. | |
12 | +- 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) | |
13 | +- 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) | |
16 | 14 | - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). |
17 | 15 | - aviso na pagina principal para quem usa browser da treta |
18 | 16 | - permitir varios testes, aluno escolhe qual o teste que quer fazer. |
... | ... | @@ -26,6 +24,7 @@ |
26 | 24 | |
27 | 25 | # FIXED |
28 | 26 | |
27 | +- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. | |
29 | 28 | - criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. |
30 | 29 | - sqlalchemy queixa-se de threads. |
31 | 30 | - SQLAlchemy em vez da classe database. | ... | ... |
app.py
... | ... | @@ -27,6 +27,7 @@ class App(object): |
27 | 27 | # uid2: {...} |
28 | 28 | # } |
29 | 29 | logger.info('============= Running perguntations =============') |
30 | + self.lock = threading.Lock() | |
30 | 31 | self.online = dict() # {uid: {'student':{}}} |
31 | 32 | self.allowed = set([]) # '0' is hardcoded to allowed elsewhere |
32 | 33 | |
... | ... | @@ -115,7 +116,9 @@ class App(object): |
115 | 116 | if uid in self.online: |
116 | 117 | logger.info('Student {}: generating new test.'.format(uid)) |
117 | 118 | student_id = self.online[uid]['student'] |
119 | + self.lock.acquire() # FIXME is it needed? | |
118 | 120 | self.online[uid]['test'] = self.testfactory.generate(student_id) |
121 | + self.lock.release() | |
119 | 122 | return self.online[uid]['test'] |
120 | 123 | else: |
121 | 124 | logger.error('Student {}: offline, can''t generate test'.format(uid)) |
... | ... | @@ -152,6 +155,16 @@ class App(object): |
152 | 155 | return grade |
153 | 156 | |
154 | 157 | # ----------------------------------------------------------------------- |
158 | + def giveup_test(self, uid): | |
159 | + logger.info('Student {0}: gave up.'.format(uid)) | |
160 | + t = self.online[uid]['test'] | |
161 | + t.giveup() | |
162 | + if t['save_answers']: | |
163 | + fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' | |
164 | + fpath = path.abspath(path.join(t['answers_dir'], fname)) | |
165 | + t.save_json(fpath) | |
166 | + | |
167 | + # ----------------------------------------------------------------------- | |
155 | 168 | |
156 | 169 | # --- helpers (getters) |
157 | 170 | def get_student_name(self, uid): | ... | ... |
serve.py
... | ... | @@ -152,7 +152,6 @@ class Root(object): |
152 | 152 | cherrypy.lib.sessions.expire() # session coockie expires client side |
153 | 153 | cherrypy.session[SESSION_KEY] = cherrypy.request.login = None |
154 | 154 | cherrypy.log.error('Student {0} logged out.'.format(uid), 'APPLICATION') |
155 | - | |
156 | 155 | self.app.logout(uid) |
157 | 156 | raise cherrypy.HTTPRedirect('/') |
158 | 157 | |
... | ... | @@ -164,10 +163,7 @@ class Root(object): |
164 | 163 | @require() |
165 | 164 | def test(self): |
166 | 165 | uid = cherrypy.session.get(SESSION_KEY) |
167 | - test = self.app.get_test(uid) | |
168 | - if test is None: | |
169 | - test = self.app.generate_test(uid) # try to generate a new test | |
170 | - | |
166 | + test = self.app.get_test(uid) or self.app.generate_test(uid) | |
171 | 167 | return self.template['test'].render(t=test) |
172 | 168 | |
173 | 169 | # --- CORRECT ------------------------------------------------------------ |
... | ... | @@ -213,6 +209,26 @@ class Root(object): |
213 | 209 | allgrades=self.app.get_student_grades_from_all_tests(uid) |
214 | 210 | ) |
215 | 211 | |
212 | + # --- GIVEUP ------------------------------------------------------------- | |
213 | + @cherrypy.expose | |
214 | + @require() | |
215 | + def giveup(self): | |
216 | + uid = cherrypy.session.get(SESSION_KEY) | |
217 | + grade = self.app.giveup_test(uid) | |
218 | + | |
219 | + # --- Expire session | |
220 | + cherrypy.lib.sessions.expire() # session coockie expires client side | |
221 | + cherrypy.session[SESSION_KEY] = cherrypy.request.login = None | |
222 | + | |
223 | + # --- Show result to student | |
224 | + return self.template['grade'].render( | |
225 | + title='title', | |
226 | + student_id=uid, | |
227 | + grade=grade, | |
228 | + allgrades=self.app.get_student_grades_from_all_tests(uid) | |
229 | + ) | |
230 | + | |
231 | + | |
216 | 232 | # --- ADMIN -------------------------------------------------------------- |
217 | 233 | @cherrypy.expose |
218 | 234 | @require(name_is('0')) | ... | ... |
templates/test.html
... | ... | @@ -4,7 +4,7 @@ |
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> ${t['title']} </title> | |
7 | + <title> Teste </title> | |
8 | 8 | <link rel="icon" href="/static/favicon.ico"> |
9 | 9 | |
10 | 10 | <!-- MathJax --> |
... | ... | @@ -327,7 +327,7 @@ |
327 | 327 | </div> |
328 | 328 | <div class="modal-footer"> |
329 | 329 | <button type="button" class="btn btn-success btn-lg" data-dismiss="modal">Não!</button> |
330 | - <a href="/logout" class="btn btn-danger btn-lg" role="button">Sim, desistir do teste</a> | |
330 | + <a href="/giveup" class="btn btn-danger btn-lg" role="button">Sim, desistir do teste</a> | |
331 | 331 | </div> |
332 | 332 | </div> |
333 | 333 | </div> | ... | ... |
test.py
... | ... | @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) |
11 | 11 | try: |
12 | 12 | # import yaml |
13 | 13 | import json |
14 | + import markdown | |
14 | 15 | except ImportError: |
15 | 16 | logger.critical('Python package missing. See README.md for instructions.') |
16 | 17 | sys.exit(1) |
... | ... | @@ -236,8 +237,21 @@ class Test(dict): |
236 | 237 | return self['grade'] |
237 | 238 | |
238 | 239 | # ----------------------------------------------------------------------- |
240 | + def giveup(self): | |
241 | + self['comments'] = 'DESISTIU' | |
242 | + self['grade'] = 0.0 | |
243 | + logger.info('Student {}: gave up.'.format(self['student']['number'])) | |
244 | + return self['grade'] | |
245 | + | |
246 | + # ----------------------------------------------------------------------- | |
239 | 247 | def save_json(self, filepath): |
240 | 248 | with open(filepath, 'w') as f: |
241 | 249 | json.dump(self, f, indent=2, default=str) |
242 | 250 | # HACK default=str is required for datetime objects |
243 | 251 | logger.info('Student {}: saved JSON file.'.format(self['student']['number'])) |
252 | + | |
253 | + # ----------------------------------------------------------------------- | |
254 | + # def generate_html(self): | |
255 | + # for q in self['questions']: | |
256 | + # q['text_html'] = markdown.markdown(q['text']) | |
257 | + | ... | ... |