Commit cd56848f1c40fdd4c91399b1ed666fe11436c8da

Authored by Miguel Barão
1 parent 526721fd
Exists in master and in 1 other branch dev

- review is working.

- fixed markdown rendering in questions.
BUGS.md
1 1  
2 2 # BUGS
3 3  
  4 +- markdown no teste nao funciona
4 5 - numeracao das perguntas do teste esta a contar com paineis informativos...
5 6 - servir imagens das perguntas
6   -- review de um teste nao funciona (hardcoded...)
7   -- testar opcao --allow-all
8 7 - como alterar configuracao para mostrar logs de debug?
9 8 - hints nao funciona
10 9 - uniformizar question.py com a de aprendizations...
... ... @@ -14,6 +13,14 @@
14 13  
15 14 # TODO
16 15  
  16 +- mathjax-node:
  17 + sudo pkg install node npm
  18 + npm install mathjax-node mathjax-node-cli # pacotes em ~/node_modules
  19 + node_modules/mathjax-node-cli/bin/tex2svg '\sqrt{x}'
  20 + usar isto para gerar svg que passa a fazer parte do texto da pergunta (markdown suporta tags svg?)
  21 +
  22 +
  23 +
17 24 - Gerar pdf's com todos os testes no final (pdfkit).
18 25 - manter registo dos unfocus durante o teste e de qual a pergunta visivel nesse momento
19 26  
... ... @@ -31,6 +38,7 @@
31 38  
32 39 # FIXED
33 40  
  41 +- review de um teste nao funciona (hardcoded...)
34 42 - testar SSL
35 43 - text-numeric não está a gerar a pergunta. faltam templates?
36 44 - testar perguntas warning/warn
... ...
demo/questions/questions-tutorial.yaml
... ... @@ -2,19 +2,41 @@
2 2 ref: tut-information
3 3 type: information
4 4 title: information (ou info)
5   - text: Texto informativo. Não conta para avaliação.
  5 + text: |
  6 + Texto informativo. Não conta para avaliação.
  7 +
  8 + $$ p(x) = \frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(x-\mu)^2}{2\sigma^2}} $$
6 9 # ---------------------------------------------------------------------------
7 10 -
8 11 ref: tut-success
9 12 type: success
10 13 title: success
11   - text: Texto de positivo (sucesso). Não conta para avaliação.
  14 + text: |
  15 + Texto de positivo (sucesso). Não conta para avaliação.
  16 +
  17 + ```C
  18 + int main() {
  19 + printf("Hello world!");
  20 + return 0; // comentario
  21 + }
  22 + ```
  23 +
  24 + Inline `code`.
  25 +
12 26 # ---------------------------------------------------------------------------
13 27 -
14 28 ref: tut-warning
15 29 type: warning
16 30 title: warning (ou warn)
17   - text: Texto de aviso. Não conta para avaliação.
  31 + text: |
  32 + Texto de aviso. Não conta para avaliação.
  33 +
  34 + Left | Center | Right
  35 + -----------------|:-------------:|----------:
  36 + $\sin(x^2)$ | *hello* | $1600.00
  37 + $\frac{1}{2\pi}$ | **world** | $12.50
  38 + $\sqrt{\pi}$ | `code` | $1.99
  39 +
18 40 # ----------------------------------------------------------------------------
19 41 -
20 42 ref: tut-alert
... ... @@ -48,7 +70,7 @@
48 70 - Opção 3 (sim)
49 71 correct: [1,-1,-1,1]
50 72 # opcionais e valores por defeito
51   - shuffle: True
  73 + shuffle: True
52 74 # ----------------------------------------------------------------------------
53 75 -
54 76 ref: tut-text
... ... @@ -82,8 +104,8 @@
82 104 type: textarea
83 105 title: textarea
84 106 text: |
85   - Resposta num bloco de texto que pode ser usado para introduzir código.
86   - A resposta é avaliada por um programa externo.
  107 + Resposta num bloco de texto que pode ser usado para introduzir código.
  108 + A resposta é avaliada por um programa externo.
87 109 O programa externo, recebe a resposta no stdin e devolve a classificação no stdout.
88 110 Neste exemplo, o programa de avaliação verifica se a resposta contém as três palavras red, green e blue.
89 111 correct: correct/correct-question.py
... ...
demo/questions/questions.yaml
... ... @@ -70,7 +70,7 @@
70 70 # ---------------------------------------------------------------------------
71 71 -
72 72 ref: fractions
73   - type: text-numeric
  73 + type: numeric-interval
74 74 text: Quanto é 1/4?
75 75 correct: [0.249, 0.251]
76 76 # ---------------------------------------------------------------------------
... ...
serve.py
... ... @@ -163,7 +163,7 @@ class TestHandler(BaseHandler):
163 163  
164 164 # --- REVIEW -------------------------------------------------------------
165 165 class ReviewHandler(BaseHandler):
166   - templates = {
  166 + _templates = {
167 167 'radio': 'review-question-radio.html',
168 168 'checkbox': 'review-question-checkbox.html',
169 169 'text': 'review-question-text.html',
... ... @@ -181,12 +181,13 @@ class ReviewHandler(BaseHandler):
181 181  
182 182 @tornado.web.authenticated
183 183 def get(self):
184   - test_id=45 # FIXME
185   -
186 184 uid = self.current_user
187 185 if uid != '0':
188 186 self.redirect('/') # FIXME 404 not found?
189 187  
  188 + test_id = self.get_query_argument('test_id', None)
  189 + # FIXME if test_id does not exist...
  190 +
190 191 fname = self.testapp.get_json_filename_of_test(test_id)
191 192 try:
192 193 f = open(path.expanduser(fname))
... ... @@ -197,7 +198,7 @@ class ReviewHandler(BaseHandler):
197 198 else:
198 199 with f:
199 200 t = json.load(f)
200   - self.render('review.html', t=t, md=md_to_html_review, templ=self.templates)
  201 + self.render('review.html', t=t, md=md_to_html_review, templ=self._templates)
201 202  
202 203  
203 204 # @cherrypy.expose
... ...
templates/grade.html
... ... @@ -16,7 +16,7 @@
16 16 <!-- ===================================================================== -->
17 17 <body>
18 18  
19   -<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark">
  19 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark">
20 20 <a class="navbar-brand" href="#">{{t['title']}}</a>
21 21 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
22 22 <span class="navbar-toggler-icon"></span>
... ...
templates/question-alert.html
... ... @@ -3,10 +3,10 @@
3 3  
4 4 <div class="alert alert-danger border-danger" role="alert">
5 5 <h3>
6   - {{ question['title'] }}
  6 + {{ q['title'] }}
7 7 </h3>
8 8  
9 9 <div id="text">
10   - {{ md(question['text']) }}
  10 + {{ md(q['text'], q) }}
11 11 </div>
12 12 </div>
... ...
templates/question-checkbox.html
... ... @@ -4,13 +4,12 @@
4 4 {% block answer %}
5 5 <fieldset data-role="controlgroup">
6 6 <div class="list-group">
7   - {% for n, opt in enumerate(question['options']) %}
  7 + {% for n, opt in enumerate(q['options']) %}
8 8 <a class="list-group-item">
9 9 <input type="checkbox" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}">
10   - <label for="{{n}}">{{ md(opt, question['ref'], question['files']) }}</label>
  10 + <label for="{{n}}">{{ md(opt, q['ref'], q['files']) }}</label>
11 11 </a>
12 12 {% end %}
13 13 </div>
14 14 </fieldset>
15   -<input type="hidden" name="question_ref" value="{{ question['ref'] }}">
16 15 {% end %}
17 16 \ No newline at end of file
... ...
templates/question-information.html
... ... @@ -3,10 +3,10 @@
3 3  
4 4 <div class="alert alert-info border-info" role="alert">
5 5 <h3>
6   - {{ question['title'] }}
  6 + {{ q['title'] }}
7 7 </h3>
8 8  
9 9 <div id="text">
10   - {{ md(question['text']) }}
  10 + {{ md(q['text'], q) }}
11 11 </div>
12 12 </div>
13 13 \ No newline at end of file
... ...
templates/question-radio.html
... ... @@ -4,10 +4,10 @@
4 4 {% block answer %}
5 5 <fieldset data-role="controlgroup">
6 6 <div class="list-group">
7   - {% for n, opt in enumerate(question['options']) %}
  7 + {% for n, opt in enumerate(q['options']) %}
8 8 <a class="list-group-item">
9 9 <input type="radio" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}">
10   - <label for="{{n}}">{{ md(opt, question['ref'], question['files']) }}</label>
  10 + <label for="{{n}}">{{ md(opt, q['ref'], q['files']) }}</label>
11 11 </a>
12 12 {% end %}
13 13 </div>
... ...
templates/question-success.html
... ... @@ -3,10 +3,10 @@
3 3  
4 4 <div class="alert alert-success border-success" role="alert">
5 5 <h3>
6   - {{ question['title'] }}
  6 + {{ q['title'] }}
7 7 </h3>
8 8  
9 9 <div id="text">
10   - {{ md(question['text']) }}
  10 + {{ md(q['text'], q) }}
11 11 </div>
12 12 </div>
... ...
templates/question-textarea.html
1 1 {% extends "question.html" %}
2 2  
3 3 {% block answer %}
4   -
5   -<textarea class="form-control" rows="{{ question['lines'] }}" name="{{i}}">{{ question['answer'] or '' }}</textarea><br />
6   -<input type="hidden" name="question_ref" value="{{ question['ref'] }}">
7   -
  4 +<textarea class="form-control" rows="{{q['lines']}}" name="{{i}}">{{q['answer'] or ''}}</textarea><br />
8 5 {% end %}
... ...
templates/question-warning.html
... ... @@ -3,10 +3,10 @@
3 3  
4 4 <div class="alert alert-warning border-warning" role="alert">
5 5 <h3>
6   - {{ question['title'] }}
  6 + {{ q['title'] }}
7 7 </h3>
8 8  
9 9 <div id="text">
10   - {{ md(question['text']) }}
  10 + {{ md(q['text'], q) }}
11 11 </div>
12 12 </div>
... ...
templates/question.html
... ... @@ -3,7 +3,7 @@
3 3 {% block question %}
4 4 <div class="card border-dark mb-3">
5 5 <h5 class="card-header text-white bg-dark">
6   - {{ i+1 }}. {{ question['title'] }}
  6 + {{ i+1 }}. {{ q['title'] }}
7 7 <div class="pull-right">
8 8 <small>Classificar&nbsp;</small>
9 9 <input type="checkbox" class="question_disabler" data-size="mini" name="answered-{{i}}" id="answered-{{i}}" checked="">
... ... @@ -11,14 +11,14 @@
11 11 </h5>
12 12 <div class="card-body">
13 13 <div id="text">
14   - {{ question['text'] }}
  14 + {{ md(q['text'], md) }}
15 15 </div>
16 16  
17 17 {% block answer %}{% end %}
18 18  
19 19 <p class="text-right">
20 20 <small>
21   - (Cotação: {{ round(question['points'], 2) }} pontos)
  21 + (Cotação: {{ round(q['points'], 2) }} pontos)
22 22 </small>
23 23 </p>
24 24  
... ...
templates/review-question-checkbox.html
... ... @@ -11,7 +11,7 @@
11 11 {% if q['correct'][n] > 0 %}
12 12 <div class="text-right text-success">
13 13 <i class="fa fa-check" aria-hidden="true"></i>
14   - </div>
  14 + </div>
15 15 {% else %}
16 16 <div class="text-right text-danger">
17 17 <i class="fa fa-close" aria-hidden="true"></i>
... ... @@ -19,9 +19,8 @@
19 19 {% end %}
20 20 {% else %}
21 21 {{ md('<i class="fa fa-square-o" aria-hidden="true"></i> ' + opt, q) }}
22   -
23 22 {% if q['correct'][n] > 0 %}
24   - <div class="text-right text-info">
  23 + <div class="text-right text-danger">
25 24 <i class="fa fa-close" aria-hidden="true"></i>
26 25 </div>
27 26 {% elif q['correct'][n] < 0 %}
... ...
templates/review-question-text.html
... ... @@ -2,5 +2,12 @@
2 2 {% autoescape %}
3 3  
4 4 {% block answer %}
  5 +
  6 +<div class="card bg-light">
  7 + <div class="card-body">
5 8 <pre>{{ q['answer'] if q['answer'] is not None else '' }}</pre>
  9 + </div>
  10 +</div>
  11 +
  12 +
6 13 {% end %}
7 14 \ No newline at end of file
... ...
templates/review-question.html
... ... @@ -16,16 +16,16 @@
16 16 </h5>
17 17  
18 18 <div class="card-body">
19   - <div id="text">
  19 + <p id="text">
20 20 {{ q['text'] }}
21   - </div>
  21 + </p>
22 22  
23 23 {% block answer %}{% end %}
24 24  
25 25 {% if t['show_points'] %}
26 26 <p class="text-right">
27 27 <small>
28   - (Cotação: {{ q['points'] }})
  28 + (Cotação: {{ round(q['points'], 2) }})
29 29 </small>
30 30 </p>
31 31 {% end %}
... ... @@ -43,14 +43,14 @@
43 43 {% elif q['grade'] > 0.49 %}
44 44 <p class="text-warning">
45 45 <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
46   - {{ round(q['grade'] * q['points'], 2) }}
  46 + {{ round(q['grade'] * q['points'], 2) }}
47 47 pontos<br>
48 48 {{ q['comments'] }}
49 49 </p>
50 50 {% else %}
51 51 <p class="text-danger">
52 52 <i class="fa fa-thumbs-o-down" aria-hidden="true"></i>
53   - {{ round(q['grade'] * q['points'], 2) }}
  53 + {{ round(q['grade'] * q['points'], 2) }}
54 54 pontos<br>
55 55 {{ q['comments'] }}
56 56 </p>
... ...
templates/review.html
... ... @@ -27,32 +27,53 @@
27 27 </head>
28 28 <!-- ===================================================================== -->
29 29 <body>
30   -<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary">
  30 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark">
31 31 <a class="navbar-brand" href="#">Revisão de prova</a>
32 32 </nav>
33 33 <!-- ===================================================================== -->
34 34 <div class="container">
35   - <div class="jumbotron">
36   - <big>
37   - <dl class="dl-horizontal">
38   - <dt>Prova:</dt><dd>{{t['title']}}</dd>
39   - <dt>Nome:</dt><dd>{{t['student']['name']}}</dd>
40   - <dt>Número:</dt><dd>{{t['student']['number']}}</dd>
41   - <dt>Início:</dt><dd>{{t['start_time'][:19]}}</dd>
42   - <dt>Fim:</dt><dd>{{t['finish_time'][:19]}}</dd>
43   - <dt>Nota:</dt>
44   - <dd>
45   - <span class="label label-primary">{{ t['grade'] }}</span> valores
46   - {% if t['state'] == 'QUIT' %}
47   - (DESISTÊNCIA)
48   - {% end %}
49   - </dd>
50   - {% if t['comment'] != '' %}
51   - <dt>Comentário:</dt><dd>{{ t['comment'] }}</dd>
52   - {% end %}
53   - </dl>
54   - </big>
55   - </div>
  35 + <div class="jumbotron">
  36 + <h1 class="display-5">{{ t['title'] }}</h1>
  37 + {{ t.get('duration', '') }}
  38 + <hr>
  39 +
  40 + <h3>
  41 + <div class="row">
  42 + <!-- <label for="nome" class="col-sm-1">Nome:</label> -->
  43 + <div class="col-sm-8" id="nome">{{t['student']['name']}}</div>
  44 + <label for="nome" class="col-sm-1">Nº.</label>
  45 + <div class="col-sm-3" id="numero">{{t['student']['number']}}</div>
  46 + </div>
  47 + </h3>
  48 +
  49 + <h5>
  50 + <div class="row">
  51 + <label for="inicio" class="col-sm-2">Início:</label>
  52 + <div class="col-sm-10" id="inicio">{{t['start_time'][:19]}}</div>
  53 + </div>
  54 + <div class="row">
  55 + <label for="fim" class="col-sm-2">Fim:</label>
  56 + <div class="col-sm-10" id="fim">{{t['finish_time'][:19]}}</div>
  57 + </div>
  58 + </h5>
  59 + <h3>
  60 + <div class="row">
  61 + <label for="nota" class="col-sm-2">Nota:</label>
  62 + <div class="col-sm-10" id="nota">
  63 + <span class="badge badge-primary">{{ t['grade'] }}</span> valores
  64 + {% if t['state'] == 'QUIT' %}
  65 + (DESISTÊNCIA)
  66 + {% end %}
  67 + </div>
  68 + </div>
  69 + {% if t['comment'] != '' %}
  70 + <div class="row">
  71 + <label for="comentario" class="col-sm-2">Comentário:</label>
  72 + <div class="col-sm-10" id="comentario">{{ t['comment'] }}</div>
  73 + </div>
  74 + {% end %}
  75 + </h3>
  76 + </div>
56 77  
57 78 {% for i, q in enumerate(t['questions']) %}
58 79 {% module Template(templ[q['type']], i=i, q=q, md=md, t=t) %}
... ...
templates/test.html
... ... @@ -61,17 +61,28 @@
61 61 </span>
62 62 </div>
63 63 </nav>
64   -<!-- ===================================================================== -->
65 64 <div class="container">
66 65  
67 66 <div class="jumbotron">
68 67 <h1 class="display-5">{{ t['title'] }}</h1>
69 68 {{ t.get('duration', '') }}
  69 + <hr>
  70 +
  71 + <h5>
  72 + <div class="row">
  73 + <label for="inicio" class="col-sm-2">Início:</label>
  74 + <div class="col-sm-10" id="inicio">{{ str(t['start_time'].time())[:8]}}</div>
  75 + </div>
  76 + <div class="row">
  77 + <label for="duracao" class="col-sm-2">Duração:</label>
  78 + <div class="col-sm-10" id="duracao">{{ t.get('duration', chr(8734)) }}</div>
  79 + </div>
  80 + </h5>
70 81 </div>
71 82  
72 83 <form action="/test" method="post" id="test" autocomplete="off">
73 84 {% for i, q in enumerate(t['questions']) %}
74   - {% module Template(templ[q['type']], i=i, question=q, md=md) %}
  85 + {% module Template(templ[q['type']], i=i, q=q, md=md) %}
75 86 {% end %}
76 87  
77 88 <div class="form-row">
... ... @@ -84,47 +95,51 @@
84 95 </div>
85 96 </form>
86 97 <hr>
  98 +</div> <!-- container -->
87 99  
88   - <!-- Modal de confirmacao submissao -->
89   - <div class="modal fade" id="confirmar" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
90   - <div class="modal-dialog" role="document">
91   - <div class="modal-content">
92   - <div class="modal-header">
93   - <h4 class="modal-title">Deseja submeter o teste?</h4>
94   - </div>
95   - <div class="modal-body">
96   - O teste será enviado para classificação e já não poderá voltar atrás.
97   - Antes de submeter, veja se respondeu a todas as questões e desactive as que não pretende classificar.
98   - </div>
99   - <div class="modal-footer">
100   - <button type="button" class="btn btn-danger btn-lg" data-dismiss="modal">Oops, NÃO!!!</button>
101   - <button form="test" type="submit" class="btn btn-success btn-lg">Sim, submeter...</button>
102   - </div>
103   - </div>
  100 +<!-- ===================================================================== -->
  101 +
  102 +<!-- Modal de confirmacao submissao -->
  103 +<div class="modal fade" id="confirmar" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  104 + <div class="modal-dialog" role="document">
  105 + <div class="modal-content">
  106 + <div class="modal-header">
  107 + <h4 class="modal-title">Deseja submeter o teste?</h4>
  108 + </div>
  109 + <div class="modal-body">
  110 + O teste será enviado para classificação e já não poderá voltar atrás.
  111 + Antes de submeter, veja se respondeu a todas as questões e desactive as que não pretende classificar.
  112 + </div>
  113 + <div class="modal-footer">
  114 + <button type="button" class="btn btn-danger btn-lg" data-dismiss="modal">Oops, NÃO!!!</button>
  115 + <button form="test" type="submit" class="btn btn-success btn-lg">Sim, submeter...</button>
104 116 </div>
105 117 </div>
  118 + </div>
  119 +</div>
106 120  
107   - <!-- Modal de confirmacao sair -->
108   - <div class="modal fade" id="sair" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
109   - <div class="modal-dialog" role="document">
110   - <div class="modal-content">
111   - <div class="modal-header">
112   - <h4 class="modal-title">Deseja desistir?</h4>
113   - </div>
114   - <div class="modal-body">
115   - Se desistir, será registada a desistência da prova e terá uma classificação de 0 valores.
116   - </div>
117   - <div class="modal-footer">
118   - <button type="button" class="btn btn-danger btn-lg" data-dismiss="modal">Não!</button>
119   - <a href="/giveup" class="btn btn-success btn-lg" role="button">Sim, desisto</a>
120   - </div>
121   - </div>
  121 +<!-- Modal de confirmacao sair -->
  122 +<div class="modal fade" id="sair" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
  123 + <div class="modal-dialog" role="document">
  124 + <div class="modal-content">
  125 + <div class="modal-header">
  126 + <h4 class="modal-title">Deseja desistir?</h4>
  127 + </div>
  128 + <div class="modal-body">
  129 + Se desistir, será registada a desistência da prova e terá uma classificação de 0 valores.
  130 + </div>
  131 + <div class="modal-footer">
  132 + <button type="button" class="btn btn-danger btn-lg" data-dismiss="modal">Não!</button>
  133 + <a href="/giveup" class="btn btn-success btn-lg" role="button">Sim, desisto</a>
122 134 </div>
123 135 </div>
124   -
  136 + </div>
125 137 </div>
126 138  
127 139  
  140 +
  141 +<!-- ===================================================================== -->
  142 +
128 143 <!-- Scripts -->
129 144 <script src="/static/js/jquery.min.js"></script>
130 145 <script src="/static/popper/umd/popper.min.js"></script>
... ...
test.py
... ... @@ -145,6 +145,7 @@ class TestFactory(dict):
145 145 test = []
146 146 total_points = 0.0
147 147  
  148 + n = 1
148 149 for qq in self['questions']:
149 150 # generate Question() selected randomly from list of references
150 151 qref = random.choice(qq['ref'])
... ... @@ -160,6 +161,8 @@ class TestFactory(dict):
160 161 q['points'] = qq.get('points', 0.0)
161 162 else:
162 163 q['points'] = qq.get('points', 1.0)
  164 + q['number'] = n
  165 + n += 1
163 166  
164 167 total_points += q['points']
165 168 test.append(q)
... ...
tools.py
... ... @@ -58,6 +58,7 @@ def run_script(script, stdin=&#39;&#39;, timeout=5):
58 58 else:
59 59 return output
60 60  
  61 +# ---------------------------------------------------------------------------
61 62 def md_to_html(text, ref=None, files={}):
62 63 if ref is not None:
63 64 # given q['ref'] and q['files'] replaces references to files by a
... ... @@ -72,6 +73,7 @@ def md_to_html(text, ref=None, files={}):
72 73 'markdown.extensions.sane_lists'
73 74 ])
74 75  
  76 +# ---------------------------------------------------------------------------
75 77 def md_to_html_review(text, q):
76 78 for k,f in q['files'].items():
77 79 text = text.replace(k, '/absfile?name={}'.format(q['files'][k]))
... ...