diff --git a/BUGS.md b/BUGS.md index 92f2e4d..a175add 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,18 +1,16 @@ # BUGS -- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova. -- 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) -- 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) - - usar thread.Lock para aceder a variaveis de estado? - permitir adicionar imagens nas perguntas. - debug mode: log levels not working -- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. # TODO - implementar practice mode. +- abrir o teste numa janela maximizada e que nao permite que o aluno a redimensione/mova. +- 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) +- 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) - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). - aviso na pagina principal para quem usa browser da treta - permitir varios testes, aluno escolhe qual o teste que quer fazer. @@ -26,6 +24,7 @@ # FIXED +- Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. - 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. - sqlalchemy queixa-se de threads. - SQLAlchemy em vez da classe database. diff --git a/app.py b/app.py index 5c5073f..f904614 100644 --- a/app.py +++ b/app.py @@ -27,6 +27,7 @@ class App(object): # uid2: {...} # } logger.info('============= Running perguntations =============') + self.lock = threading.Lock() self.online = dict() # {uid: {'student':{}}} self.allowed = set([]) # '0' is hardcoded to allowed elsewhere @@ -115,7 +116,9 @@ class App(object): if uid in self.online: logger.info('Student {}: generating new test.'.format(uid)) student_id = self.online[uid]['student'] + self.lock.acquire() # FIXME is it needed? self.online[uid]['test'] = self.testfactory.generate(student_id) + self.lock.release() return self.online[uid]['test'] else: logger.error('Student {}: offline, can''t generate test'.format(uid)) @@ -152,6 +155,16 @@ class App(object): return grade # ----------------------------------------------------------------------- + def giveup_test(self, uid): + logger.info('Student {0}: gave up.'.format(uid)) + t = self.online[uid]['test'] + t.giveup() + if t['save_answers']: + fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' + fpath = path.abspath(path.join(t['answers_dir'], fname)) + t.save_json(fpath) + + # ----------------------------------------------------------------------- # --- helpers (getters) def get_student_name(self, uid): diff --git a/serve.py b/serve.py index 78e83e4..513038d 100755 --- a/serve.py +++ b/serve.py @@ -152,7 +152,6 @@ class Root(object): cherrypy.lib.sessions.expire() # session coockie expires client side cherrypy.session[SESSION_KEY] = cherrypy.request.login = None cherrypy.log.error('Student {0} logged out.'.format(uid), 'APPLICATION') - self.app.logout(uid) raise cherrypy.HTTPRedirect('/') @@ -164,10 +163,7 @@ class Root(object): @require() def test(self): uid = cherrypy.session.get(SESSION_KEY) - test = self.app.get_test(uid) - if test is None: - test = self.app.generate_test(uid) # try to generate a new test - + test = self.app.get_test(uid) or self.app.generate_test(uid) return self.template['test'].render(t=test) # --- CORRECT ------------------------------------------------------------ @@ -213,6 +209,26 @@ class Root(object): allgrades=self.app.get_student_grades_from_all_tests(uid) ) + # --- GIVEUP ------------------------------------------------------------- + @cherrypy.expose + @require() + def giveup(self): + uid = cherrypy.session.get(SESSION_KEY) + grade = self.app.giveup_test(uid) + + # --- Expire session + cherrypy.lib.sessions.expire() # session coockie expires client side + cherrypy.session[SESSION_KEY] = cherrypy.request.login = None + + # --- Show result to student + return self.template['grade'].render( + title='title', + student_id=uid, + grade=grade, + allgrades=self.app.get_student_grades_from_all_tests(uid) + ) + + # --- ADMIN -------------------------------------------------------------- @cherrypy.expose @require(name_is('0')) diff --git a/templates/test.html b/templates/test.html index 733c0f2..9fe89be 100644 --- a/templates/test.html +++ b/templates/test.html @@ -4,7 +4,7 @@ - ${t['title']} + Teste @@ -327,7 +327,7 @@ diff --git a/test.py b/test.py index e87629d..fea1f2f 100644 --- a/test.py +++ b/test.py @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) try: # import yaml import json + import markdown except ImportError: logger.critical('Python package missing. See README.md for instructions.') sys.exit(1) @@ -236,8 +237,21 @@ class Test(dict): return self['grade'] # ----------------------------------------------------------------------- + def giveup(self): + self['comments'] = 'DESISTIU' + self['grade'] = 0.0 + logger.info('Student {}: gave up.'.format(self['student']['number'])) + return self['grade'] + + # ----------------------------------------------------------------------- def save_json(self, filepath): with open(filepath, 'w') as f: json.dump(self, f, indent=2, default=str) # HACK default=str is required for datetime objects logger.info('Student {}: saved JSON file.'.format(self['student']['number'])) + + # ----------------------------------------------------------------------- + # def generate_html(self): + # for q in self['questions']: + # q['text_html'] = markdown.markdown(q['text']) + -- libgit2 0.21.2