From 526721fdac27a947ac5c001137c29fc6ba40f164 Mon Sep 17 00:00:00 2001 From: Miguel Barão Date: Fri, 20 Oct 2017 16:06:49 +0100 Subject: [PATCH] - changed text-numeric to numeric-interval - many changes and fixes. review still hardcoded... --- BUGS.md | 14 +++++++++----- demo/questions/questions-tutorial.yaml | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ demo/test-tutorial.yaml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ questions.py | 37 ++++++++++++++++--------------------- serve.py | 17 +++++++++-------- static/css/test.css | 4 ++++ templates/question-success.html | 2 +- templates/question-warning.html | 2 +- templates/question.html | 2 +- templates/review-question.html | 57 +++++++++++++++++++++++++++++---------------------------- templates/test.html | 4 ++-- test.py | 29 ++++++++++++++++------------- 12 files changed, 236 insertions(+), 80 deletions(-) create mode 100644 demo/questions/questions-tutorial.yaml create mode 100644 demo/test-tutorial.yaml diff --git a/BUGS.md b/BUGS.md index bbd1291..72aa58a 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,8 +1,12 @@ # BUGS -- testar perguntas warning/warn -- text-numeric não está a gerar a pergunta. faltam templates? +- numeracao das perguntas do teste esta a contar com paineis informativos... +- servir imagens das perguntas +- review de um teste nao funciona (hardcoded...) +- testar opcao --allow-all +- como alterar configuracao para mostrar logs de debug? +- hints nao funciona - uniformizar question.py com a de aprendizations... - Review de um teste que foi apagado rebenta. - permitir eliminar teste a decorrer de modo a que o aluno possa recomeçar (e.g. noutro browser) @@ -11,13 +15,10 @@ # TODO - Gerar pdf's com todos os testes no final (pdfkit). -- testar SSL - manter registo dos unfocus durante o teste e de qual a pergunta visivel nesse momento - permitir varios testes, aluno escolhe qual o teste que quer fazer. -- usar thread.Lock para aceder a variaveis de estado? - se ocorrer um erro na correcçao avisar aluno para contactar o professor. -- 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) - single page web no teste/correcçao. Página construída em javascript, obter perguntas com ajax (para practice?). @@ -30,6 +31,9 @@ # FIXED +- testar SSL +- text-numeric não está a gerar a pergunta. faltam templates? +- testar perguntas warning/warn - qd user 0 faz logout rebenta. - Quando grava JSON do teste deve usar 'path' tal como definido na configuração e não expandido. Isto porque em OSX /home é /Users e quando se muda de um sistema para outro não encontra os testes. Assim, usando ~ na configuração deveria funcionar sempre. - configuração do teste não joga bem com o do aprendizations. Em particular os scripts não ficam com o mesmo path!!! diff --git a/demo/questions/questions-tutorial.yaml b/demo/questions/questions-tutorial.yaml new file mode 100644 index 0000000..d00d7aa --- /dev/null +++ b/demo/questions/questions-tutorial.yaml @@ -0,0 +1,92 @@ +- + ref: tut-information + type: information + title: information (ou info) + text: Texto informativo. Não conta para avaliação. +# --------------------------------------------------------------------------- +- + ref: tut-success + type: success + title: success + text: Texto de positivo (sucesso). Não conta para avaliação. +# --------------------------------------------------------------------------- +- + ref: tut-warning + type: warning + title: warning (ou warn) + text: Texto de aviso. Não conta para avaliação. +# ---------------------------------------------------------------------------- +- + ref: tut-alert + type: alert + title: alert + text: Texto negativo (alerta). Não conta para avaliação. +# ---------------------------------------------------------------------------- +- + ref: tut-radio + type: radio + title: radio + text: Escolha simples, apenas uma opção está correcta. + options: + - Opção 0 (correcta) + - Opção 1 + - Opção 2 + - Opção 3 + # opcionais e valores por defeito + shuffle: True + correct: 0 +# ---------------------------------------------------------------------------- +- + ref: tut-checkbox + type: checkbox + title: checkbox + text: Escolha simples, apenas uma opção está correcta. + options: + - Opção 0 (sim) + - Opção 1 (não) + - Opção 2 (não) + - Opção 3 (sim) + correct: [1,-1,-1,1] + # opcionais e valores por defeito + shuffle: True +# ---------------------------------------------------------------------------- +- + ref: tut-text + type: text + title: text + text: | + Resposta numa linha de texto. A resposta está correcta se coincidir com alguma das respostas admissíveis. + Neste exemplo a resposta correcta é `azul`, `Azul` ou `AZUL`. + correct: ['azul', 'Azul', 'AZUL'] +# --------------------------------------------------------------------------- +- + ref: tut-text-regex + type: text-regex + title: text-regex + text: | + Resposta numa linha de texto. A resposta é validada com uma expressão regular. + Neste exemplo a expressão regular é `(VERDE|[Vv]erde)`. + correct: !regex '(VERDE|[Vv]erde)' +# --------------------------------------------------------------------------- +- + ref: tut-numeric-interval + type: numeric-interval + title: numeric-interval + text: | + Resposta numérica numa linha de texto. A resposta é convertida para um float e tem de pertencer a um intervalo de valores. + Neste exemplo o intervalo é [3.14, 3.15]. + correct: [3.14, 3.15] +# --------------------------------------------------------------------------- +- + ref: tut-textarea + type: textarea + title: textarea + text: | + Resposta num bloco de texto que pode ser usado para introduzir código. + A resposta é avaliada por um programa externo. + O programa externo, recebe a resposta no stdin e devolve a classificação no stdout. + Neste exemplo, o programa de avaliação verifica se a resposta contém as três palavras red, green e blue. + correct: correct/correct-question.py + # opcionais e defaults + lines: 3 + timeout: 5 diff --git a/demo/test-tutorial.yaml b/demo/test-tutorial.yaml new file mode 100644 index 0000000..2f6708e --- /dev/null +++ b/demo/test-tutorial.yaml @@ -0,0 +1,56 @@ +#============================================================================= +# The test reference should be a unique identifier. It is saved in the database +# so that queries for the results can be done in the terminal with +# $ sqlite3 students.db "select * from tests where ref='demo'" +ref: tutorial + +# (optional, default: '') You may wish to refer the course, year or kind of test +title: Teste tutorial + +# (optional) duration in minutes FIXME +duration: 90 + +# Database with student credentials and grades of all questions and tests done +# The database is an sqlite3 file generate with the script initdb.py +database: demo/students.db + +# Generate a file for each test done by a student. +# It includes the questions, answers and grades. +answers_dir: demo/ans + +# (optional, default: False) Show points for each question, scale 0-20. +show_points: True + +# (optional, default: False) Show hints if available +show_hints: True + +# (optional, default: False) Show lots of information for debugging +# debug: True + +#----------------------------------------------------------------------------- +# Base path applied to the questions files and all the scripts +# including question generators and correctors. +# Either absolute path or relative to current directory can be used. +questions_dir: demo/questions + +# (optional) List of files containing questions in yaml format. +# Selected questions will be obtained from these files. +# If undefined, all yaml files in questions_dir are loaded (not recommended). +files: + - questions-tutorial.yaml + +# This is the list of questions that will make up the test. +# The order is preserved. +# There are several ways to define each question (explained below). +questions: + - tut-information + - tut-success + - tut-warning + - tut-alert + + - tut-radio + - tut-checkbox + - tut-text + - tut-text-regex + - tut-numeric-interval + - tut-textarea diff --git a/questions.py b/questions.py index 6c978db..669f676 100644 --- a/questions.py +++ b/questions.py @@ -275,7 +275,7 @@ class QuestionTextRegex(Question): # =========================================================================== -class QuestionTextNumeric(Question): +class QuestionNumericInterval(Question): '''An instance of QuestionTextNumeric will always have the keys: type (str) text (str) @@ -302,6 +302,7 @@ class QuestionTextNumeric(Question): try: answer = float(self['answer']) + # TODO: # alternative using locale (1.2 vs 1,2) # import locale # locale.setlocale(locale.LC_ALL, 'pt_PT') @@ -401,8 +402,8 @@ class QuestionFactory(dict): 'radio' : QuestionRadio, 'checkbox' : QuestionCheckbox, 'text' : QuestionText, - 'text_regex': QuestionTextRegex, 'text-regex': QuestionTextRegex, - 'text_numeric': QuestionTextNumeric, 'text-numeric': QuestionTextNumeric, + 'text-regex': QuestionTextRegex, + 'numeric-interval': QuestionNumericInterval, 'textarea' : QuestionTextArea, # -- informative panels -- 'information': QuestionInformation, 'info': QuestionInformation, @@ -420,22 +421,17 @@ class QuestionFactory(dict): # Add single question provided in a dictionary. # After this, each question will have at least 'ref' and 'type' keys. # ----------------------------------------------------------------------- - def add(self, question): - # if ref missing try ref='/path/file.yaml:3' - try: - question.setdefault('ref', question['filename'] + ':' + str(question['index'])) - except KeyError: - logger.error('Missing "ref". Cannot add question to the pool.') - return + def add_question(self, question): + # if missing defaults to ref='/path/file.yaml:3' + question.setdefault('ref', f'{question["filename"]}:{question["index"]}') - # check duplicate references if question['ref'] in self: - logger.error(f'Duplicate reference "{question["ref"]}". Replacing the original one!') + logger.error(f'Duplicate reference "{question["ref"]}" replaces the original.') question.setdefault('type', 'information') self[question['ref']] = question - logger.debug('Added question "{0}" to the pool.'.format(question['ref'])) + logger.debug(f'Added question "{question["ref"]}" to the pool.') # ----------------------------------------------------------------------- # load single YAML questions file @@ -453,20 +449,19 @@ class QuestionFactory(dict): questions = load_yaml(fullpath, default=[]) - n = 0 for i, q in enumerate(questions): - if isinstance(q, dict): + try: q.update({ 'filename': filename, 'path': dirname, - 'index': i # position in the file, 0 based + 'index': i # position in the file, 0 based }) - self.add(q) # add question - n += 1 # counter - else: - logger.error(f'Question index {i} from file {pathfile} is not a dictionary. Skipped!') + except AttributeError: + logger.error(f'Question {pathfile}:{i} is not a dictionary. Skipped!') + else: + self.add_question(q) - logger.info(f'Loaded {n} questions from "{pathfile}".') + logger.info(f'Loaded {len(self)} questions from "{pathfile}".') # ----------------------------------------------------------------------- # load multiple YAML question files diff --git a/serve.py b/serve.py index 39358dc..f822795 100755 --- a/serve.py +++ b/serve.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python3.6 # base from os import path @@ -26,7 +26,7 @@ class WebApplication(tornado.web.Application): (r'/login', LoginHandler), (r'/logout', LogoutHandler), (r'/test', TestHandler), - (r'/review', ReviewHandler), # FIXME + (r'/review', ReviewHandler), (r'/admin', AdminHandler), # (r'/change_password', ChangePasswordHandler), (r'/static/(.+)', FileHandler), # FIXME @@ -76,7 +76,7 @@ class LoginHandler(BaseHandler): self.set_secure_cookie("user", str(uid), expires_days=30) self.redirect(self.get_argument("next", "/")) else: - self.render("login.html", error='Número ou senha incorrectos') + self.render("login.html", error='Não autorizado ou número/senha inválido') # ------------------------------------------------------------------------- class LogoutHandler(BaseHandler): @@ -110,10 +110,8 @@ class TestHandler(BaseHandler): 'radio': 'question-radio.html', 'checkbox': 'question-checkbox.html', 'text': 'question-text.html', - # 'text_regex': 'question-text.html', 'text-regex': 'question-text.html', - # 'text_numeric': 'question-text.html', - 'text-numeric': 'question-text.html', + 'numeric-interval': 'question-text.html', 'textarea': 'question-textarea.html', # -- information panels -- 'information': 'question-information.html', @@ -153,7 +151,7 @@ class TestHandler(BaseHandler): ans[i] = None else: ans[i] = ans[i][0] - elif q['type'] in ('textarea', 'text', 'text-numeric', 'text-regex'): + elif q['type'] in ('text', 'text-regex', 'textarea', 'numeric-interval'): ans[i] = ans[i][0] self.testapp.correct_test(uid, ans) @@ -170,10 +168,12 @@ class ReviewHandler(BaseHandler): 'checkbox': 'review-question-checkbox.html', 'text': 'review-question-text.html', 'text-regex': 'review-question-text.html', - 'text-numeric': 'review-question-text.html', + 'numeric-interval': 'review-question-text.html', 'textarea': 'review-question-text.html', # -- information panels -- + 'information': 'question-information.html', 'info': 'question-information.html', + 'warning': 'question-warning.html', 'warn': 'question-warning.html', 'alert': 'question-alert.html', 'success': 'question-success.html', @@ -322,6 +322,7 @@ def main(): logging.critical('Failed to start application.') sys.exit(1) + # --- start web application try: webapp = WebApplication(app, debug=arg.debug) except Exception as e: diff --git a/static/css/test.css b/static/css/test.css index ebbe10e..914ca64 100644 --- a/static/css/test.css +++ b/static/css/test.css @@ -1,4 +1,8 @@ /* Fixes navigation panel overlaying content */ +html { + font-size: 14px; +} + body { padding-top: 100px; /* make room at top of page for the navbar */ background: #aaa; diff --git a/templates/question-success.html b/templates/question-success.html index a2a674f..e3b7b13 100644 --- a/templates/question-success.html +++ b/templates/question-success.html @@ -1,7 +1,7 @@ {% autoescape %} -