Commit 5bad78cde6e7a158a3d6ccad34d472d008109c7b

Authored by Miguel Barão
1 parent 0aaf9568
Exists in master and in 1 other branch dev

highlights:

- mathjax update to version 3 improves performance.
- fix solution rendering in the review.
- minor text changes in the demo.
- minor version updates in javascript libraries.
1 1
2 # BUGS 2 # BUGS
3 3
4 -- nao esta a usar points das perguntas 4 +- codigo `hello world` nao esta a preservar o whitespace. O renderer de markdown gera a tag <code> que não preserva whitespace. Necessario adicionar <pre>.
5 - teste nao esta a mostrar imagens. 5 - teste nao esta a mostrar imagens.
6 - se houver erros a abrir ficheiros .yaml de perguntas, depois dos testes diz "No errors found". 6 - se houver erros a abrir ficheiros .yaml de perguntas, depois dos testes diz "No errors found".
7 - dizer quanto desconta em cada pergunta de escolha multipla 7 - dizer quanto desconta em cada pergunta de escolha multipla
@@ -19,6 +19,7 @@ ou usar push (websockets?) @@ -19,6 +19,7 @@ ou usar push (websockets?)
19 19
20 # TODO 20 # TODO
21 21
  22 +- nao esta a usar points das perguntas
22 - test: mostrar duração do teste com progressbar no navbar. 23 - test: mostrar duração do teste com progressbar no navbar.
23 - submissao fazer um post ajax? 24 - submissao fazer um post ajax?
24 - adicionar opcao para eliminar um teste em curso. 25 - adicionar opcao para eliminar um teste em curso.
demo/questions/questions-tutorial.yaml
@@ -330,9 +330,10 @@ @@ -330,9 +330,10 @@
330 podem ser úteis por exemplo para introduzir código. 330 podem ser úteis por exemplo para introduzir código.
331 331
332 A resposta é enviada para um programa externo para ser avaliada. 332 A resposta é enviada para um programa externo para ser avaliada.
333 - O programa externo é um programa qualquer executável pelo sistema.  
334 - Este recebe a resposta submetida pelo aluno via `stdin` e devolve a  
335 - classificação via `stdout`. 333 + O programa externo é um programa escrito numa linguagem qualquer, desde que
  334 + seja executável pelo sistema operativo (pode ser um script ou binário).
  335 + Este programa recebe a resposta submetida pelo aluno via `stdin` e devolve
  336 + a classificação via `stdout`.
336 Exemplo: 337 Exemplo:
337 338
338 ```yaml 339 ```yaml
@@ -348,12 +349,13 @@ @@ -348,12 +349,13 @@
348 Neste exemplo, o programa de avaliação é um script python que verifica se a 349 Neste exemplo, o programa de avaliação é um script python que verifica se a
349 resposta contém as três palavras red, green e blue, e calcula uma nota no 350 resposta contém as três palavras red, green e blue, e calcula uma nota no
350 intervalo 0.0 a 1.0. 351 intervalo 0.0 a 1.0.
351 - O programa externo é um programa executável no sistema, escrito em  
352 - qualquer linguagem de programação. A interacção com o servidor faz-se 352 + O programa externo é executado num processo separado do sistema operativo.
  353 + Pode escrito em qualquer linguagem de programação, desde que . A interacção com o servidor faz-se
353 sempre via stdin/stdout. 354 sempre via stdin/stdout.
354 355
355 Se o programa externo exceder o `timeout` indicado (em segundos), 356 Se o programa externo exceder o `timeout` indicado (em segundos),
356 - é automaticamente cancelado e é atribuída a classificação de 0.0 valores. 357 + este é automaticamente terminado e é atribuída a classificação de 0.0
  358 + valores na pergunta.
357 359
358 Após terminar a correcção, o programa externo deve enviar a classificação 360 Após terminar a correcção, o programa externo deve enviar a classificação
359 para o stdout. 361 para o stdout.
@@ -377,8 +379,9 @@ @@ -377,8 +379,9 @@
377 379
378 O comentário é mostrado na revisão de prova. 380 O comentário é mostrado na revisão de prova.
379 answer: | 381 answer: |
  382 + Aqui o aluno escreve a resposta.
380 Esta caixa aumenta de tamanho automaticamente e 383 Esta caixa aumenta de tamanho automaticamente e
381 - pode estar previamente preenchida (use answer: texto). 384 + pode estar previamente preenchida como neste caso (use `answer: texto`).
382 correct: correct/correct-question.py 385 correct: correct/correct-question.py
383 timeout: 5 386 timeout: 5
384 387
@@ -423,7 +426,7 @@ @@ -423,7 +426,7 @@
423 text: | 426 text: |
424 Também não conta para avaliação. É apenas o aspecto gráfico que muda. 427 Também não conta para avaliação. É apenas o aspecto gráfico que muda.
425 428
426 - Além das fórmulas LaTeX, também se pode escrever troços de código: 429 + Além das fórmulas LaTeX, também se podem escrever troços de código:
427 430
428 ```C 431 ```C
429 int main() { 432 int main() {
package-lock.json
@@ -3,9 +3,9 @@ @@ -3,9 +3,9 @@
3 "lockfileVersion": 1, 3 "lockfileVersion": 1,
4 "dependencies": { 4 "dependencies": {
5 "@fortawesome/fontawesome-free": { 5 "@fortawesome/fontawesome-free": {
6 - "version": "5.8.1",  
7 - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.8.1.tgz",  
8 - "integrity": "sha512-GJtx6e55qLEOy2gPOsok2lohjpdWNGrYGtQx0FFT/++K4SYx+Z8LlPHdQBaFzKEwH5IbBB4fNgb//uyZjgYXoA==" 6 + "version": "5.11.1",
  7 + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.1.tgz",
  8 + "integrity": "sha512-DtXLVYAkDU0ce1cFUgLvZaMd1R2J/LviBYih9xr4ZLhQMrgvYX7w2vOxlpKLRALfIj5GyC5zoVrcACOkLcFgvg=="
9 }, 9 },
10 "bootstrap": { 10 "bootstrap": {
11 "version": "4.3.1", 11 "version": "4.3.1",
@@ -13,9 +13,14 @@ @@ -13,9 +13,14 @@
13 "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" 13 "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
14 }, 14 },
15 "codemirror": { 15 "codemirror": {
16 - "version": "5.45.0",  
17 - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.45.0.tgz",  
18 - "integrity": "sha512-c19j644usCE8gQaXa0jqn2B/HN9MnB2u6qPIrrhrMkB+QAP42y8G4QnTwuwbVSoUS1jEl7JU9HZMGhCDL0nsAw==" 16 + "version": "5.48.4",
  17 + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz",
  18 + "integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA=="
  19 + },
  20 + "commander": {
  21 + "version": "3.0.1",
  22 + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz",
  23 + "integrity": "sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ=="
19 }, 24 },
20 "datatables": { 25 "datatables": {
21 "version": "1.10.18", 26 "version": "1.10.18",
@@ -25,20 +30,62 @@ @@ -25,20 +30,62 @@
25 "jquery": ">=1.7" 30 "jquery": ">=1.7"
26 } 31 }
27 }, 32 },
  33 + "esm": {
  34 + "version": "3.2.25",
  35 + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
  36 + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
  37 + },
28 "jquery": { 38 "jquery": {
29 - "version": "3.3.1",  
30 - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",  
31 - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" 39 + "version": "3.4.1",
  40 + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
  41 + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw=="
32 }, 42 },
33 "mathjax": { 43 "mathjax": {
34 - "version": "2.7.5",  
35 - "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz",  
36 - "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==" 44 + "version": "3.0.0",
  45 + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz",
  46 + "integrity": "sha512-z4uLbDHNbs/aRuR6zCcnzwFQuMixkHCcWqgVaommfK/3cA1Ahq7OXemn+m8JwTYcBApSHgcrSbPr9sm3sZFL+A==",
  47 + "requires": {
  48 + "mathjax-full": "git://github.com/mathjax/MathJax-src.git"
  49 + }
  50 + },
  51 + "mathjax-full": {
  52 + "version": "git://github.com/mathjax/MathJax-src.git#0d74266e1820220d33cb6b29d4ca3575b352ac0d",
  53 + "from": "git://github.com/mathjax/MathJax-src.git",
  54 + "requires": {
  55 + "esm": "^3.2.25",
  56 + "mj-context-menu": "^0.2.0",
  57 + "speech-rule-engine": "^3.0.0-beta.6"
  58 + }
  59 + },
  60 + "mj-context-menu": {
  61 + "version": "0.2.0",
  62 + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz",
  63 + "integrity": "sha512-yJxrWBHCjFZEHsZgfs7m5g9OSCNzsVYadW6f6lX3pgZL67vmodtSW/4zhsYmuDKweXfHs0M1kJge1uQIasWA+g=="
37 }, 64 },
38 "popper.js": { 65 "popper.js": {
39 "version": "1.15.0", 66 "version": "1.15.0",
40 "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", 67 "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
41 "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" 68 "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
  69 + },
  70 + "speech-rule-engine": {
  71 + "version": "3.0.0-beta.6",
  72 + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz",
  73 + "integrity": "sha512-B7gcT53jAsKpx7WvFYQcyUlFmgS3Wa9KlDy0FY8SOTa+Wz5EqmI0MpCD5/fYm8/2qiCPp8HwZg+H3cBgM+sNVw==",
  74 + "requires": {
  75 + "commander": "*",
  76 + "wicked-good-xpath": "*",
  77 + "xmldom-sre": "^0.1.31"
  78 + }
  79 + },
  80 + "wicked-good-xpath": {
  81 + "version": "1.3.0",
  82 + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
  83 + "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w="
  84 + },
  85 + "xmldom-sre": {
  86 + "version": "0.1.31",
  87 + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
  88 + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
42 } 89 }
43 } 90 }
44 } 91 }
@@ -2,12 +2,12 @@ @@ -2,12 +2,12 @@
2 "description": "Javascript libraries required to run the server", 2 "description": "Javascript libraries required to run the server",
3 "email": "mjsb@uevora.pt", 3 "email": "mjsb@uevora.pt",
4 "dependencies": { 4 "dependencies": {
5 - "@fortawesome/fontawesome-free": "^5.8.1",  
6 - "bootstrap": "^4.3.1",  
7 - "codemirror": "^5.45.0",  
8 - "datatables": "^1.10.18",  
9 - "jquery": "^3.3.1",  
10 - "mathjax": "^2.7.5",  
11 - "popper.js": "^1.15.0" 5 + "@fortawesome/fontawesome-free": "^5.11.1",
  6 + "bootstrap": "^4.3",
  7 + "codemirror": "^5.48",
  8 + "datatables": "^1.10",
  9 + "jquery": "^3.4.1",
  10 + "mathjax": "^3",
  11 + "popper.js": "^1.15"
12 } 12 }
13 } 13 }
perguntations/__init__.py
@@ -32,7 +32,7 @@ proof of submission and for review. @@ -32,7 +32,7 @@ proof of submission and for review.
32 ''' 32 '''
33 33
34 APP_NAME = 'perguntations' 34 APP_NAME = 'perguntations'
35 -APP_VERSION = '2019.06.dev1' 35 +APP_VERSION = '2019.09.dev1'
36 APP_DESCRIPTION = __doc__ 36 APP_DESCRIPTION = __doc__
37 37
38 __author__ = 'Miguel Barão' 38 __author__ = 'Miguel Barão'
perguntations/serve.py
@@ -17,6 +17,7 @@ import ssl @@ -17,6 +17,7 @@ import ssl
17 # user installed libraries 17 # user installed libraries
18 import tornado.ioloop 18 import tornado.ioloop
19 import tornado.web 19 import tornado.web
  20 +# import tornado.websocket
20 import tornado.httpserver 21 import tornado.httpserver
21 22
22 # this project 23 # this project
@@ -27,19 +28,6 @@ from perguntations.parser_markdown import md_to_html @@ -27,19 +28,6 @@ from perguntations.parser_markdown import md_to_html
27 28
28 29
29 # ---------------------------------------------------------------------------- 30 # ----------------------------------------------------------------------------
30 -# Decorator used to restrict access to the administrator  
31 -# ----------------------------------------------------------------------------  
32 -def admin_only(func):  
33 - @functools.wraps(func)  
34 - async def wrapper(self, *args, **kwargs):  
35 - if self.current_user != '0':  
36 - raise tornado.web.HTTPError(403) # forbidden  
37 - else:  
38 - await func(self, *args, **kwargs)  
39 - return wrapper  
40 -  
41 -  
42 -# ----------------------------------------------------------------------------  
43 # Web Application. Routes to handler classes. 31 # Web Application. Routes to handler classes.
44 # ---------------------------------------------------------------------------- 32 # ----------------------------------------------------------------------------
45 class WebApplication(tornado.web.Application): 33 class WebApplication(tornado.web.Application):
@@ -51,7 +39,8 @@ class WebApplication(tornado.web.Application): @@ -51,7 +39,8 @@ class WebApplication(tornado.web.Application):
51 (r'/review', ReviewHandler), 39 (r'/review', ReviewHandler),
52 (r'/admin', AdminHandler), 40 (r'/admin', AdminHandler),
53 (r'/file', FileHandler), 41 (r'/file', FileHandler),
54 - # (r'/ws', AdminWebSocketHandler), 42 + # (r'/root', MainHandler), # FIXME
  43 + # (r'/ws', AdminSocketHandler),
55 (r'/', RootHandler), # TODO multiple tests 44 (r'/', RootHandler), # TODO multiple tests
56 ] 45 ]
57 46
@@ -69,6 +58,19 @@ class WebApplication(tornado.web.Application): @@ -69,6 +58,19 @@ class WebApplication(tornado.web.Application):
69 58
70 59
71 # ---------------------------------------------------------------------------- 60 # ----------------------------------------------------------------------------
  61 +# Decorator used to restrict access to the administrator
  62 +# ----------------------------------------------------------------------------
  63 +def admin_only(func):
  64 + @functools.wraps(func)
  65 + async def wrapper(self, *args, **kwargs):
  66 + if self.current_user != '0':
  67 + raise tornado.web.HTTPError(403) # forbidden
  68 + else:
  69 + await func(self, *args, **kwargs)
  70 + return wrapper
  71 +
  72 +
  73 +# ----------------------------------------------------------------------------
72 # Base handler. Other handlers will inherit this one. 74 # Base handler. Other handlers will inherit this one.
73 # ---------------------------------------------------------------------------- 75 # ----------------------------------------------------------------------------
74 class BaseHandler(tornado.web.RequestHandler): 76 class BaseHandler(tornado.web.RequestHandler):
@@ -83,6 +85,113 @@ class BaseHandler(tornado.web.RequestHandler): @@ -83,6 +85,113 @@ class BaseHandler(tornado.web.RequestHandler):
83 85
84 86
85 # ---------------------------------------------------------------------------- 87 # ----------------------------------------------------------------------------
  88 +# class MainHandler(BaseHandler):
  89 +
  90 +# @tornado.web.authenticated
  91 +# @admin_only
  92 +# def get(self):
  93 +# self.render("admin-ws.html", students=self.testapp.get_students_state())
  94 +
  95 +
  96 +# # ----------------------------------------------------------------------------
  97 +# class AdminSocketHandler(tornado.websocket.WebSocketHandler):
  98 +# waiters = set()
  99 +# # cache = []
  100 +
  101 +# # def get_compression_options(self):
  102 +# # return {} # Non-None enables compression with default options.
  103 +
  104 +# # called when opening connection
  105 +# def open(self):
  106 +# logging.debug('[AdminSocketHandler.open]')
  107 +# AdminSocketHandler.waiters.add(self)
  108 +
  109 +# # called when closing connection
  110 +# def on_close(self):
  111 +# logging.debug('[AdminSocketHandler.on_close]')
  112 +# AdminSocketHandler.waiters.remove(self)
  113 +
  114 +# # @classmethod
  115 +# # def update_cache(cls, chat):
  116 +# # logging.debug(f'[AdminSocketHandler.update_cache] "{chat}"')
  117 +# # cls.cache.append(chat)
  118 +
  119 +# # @classmethod
  120 +# # def send_updates(cls, chat):
  121 +# # logging.info("sending message to %d waiters", len(cls.waiters))
  122 +# # for waiter in cls.waiters:
  123 +# # try:
  124 +# # waiter.write_message(chat)
  125 +# # except Exception:
  126 +# # logging.error("Error sending message", exc_info=True)
  127 +
  128 +# # handle incomming messages
  129 +# def on_message(self, message):
  130 +# logging.info(f"[AdminSocketHandler.onmessage] got message {message}")
  131 +# parsed = tornado.escape.json_decode(message)
  132 +# print(parsed)
  133 +# chat = {"id": str(uuid.uuid4()), "body": parsed["body"]}
  134 +# print(chat)
  135 +# chat["html"] = tornado.escape.to_basestring(
  136 +# '<div>' + chat['body'] + '</div>'
  137 +# # self.render_string("message.html", message=chat)
  138 +# )
  139 +# print(chat)
  140 +
  141 +# AdminSocketHandler.update_cache(chat) # store msgs
  142 +# AdminSocketHandler.send_updates(chat) # send to clients
  143 +
  144 +
  145 +# --- ADMIN ------------------------------------------------------------------
  146 +class AdminHandler(BaseHandler):
  147 + SUPPORTED_METHODS = ['GET', 'POST']
  148 +
  149 + @tornado.web.authenticated
  150 + @admin_only
  151 + async def get(self):
  152 + cmd = self.get_query_argument('cmd', default=None)
  153 +
  154 + if cmd == 'students_table':
  155 + data = {'data': self.testapp.get_students_state()}
  156 + self.write(json.dumps(data, default=str))
  157 + elif cmd == 'test': # FIXME which test?
  158 + data = {
  159 + 'data': {
  160 + 'title': self.testapp.testfactory['title'],
  161 + 'ref': self.testapp.testfactory['ref'],
  162 + 'filename': self.testapp.testfactory['filename'],
  163 + 'database': self.testapp.testfactory['database'],
  164 + 'answers_dir': self.testapp.testfactory['answers_dir'],
  165 + }
  166 + }
  167 + self.write(json.dumps(data, default=str))
  168 + else:
  169 + self.render('admin.html')
  170 +
  171 + @tornado.web.authenticated
  172 + @admin_only
  173 + async def post(self):
  174 + cmd = self.get_body_argument('cmd', None)
  175 + value = self.get_body_argument('value', None)
  176 +
  177 + if cmd == 'allow':
  178 + self.testapp.allow_student(value)
  179 +
  180 + elif cmd == 'deny':
  181 + self.testapp.deny_student(value)
  182 +
  183 + elif cmd == 'reset_password':
  184 + await self.testapp.update_student_password(uid=value, pw='')
  185 +
  186 + elif cmd == 'insert_student':
  187 + s = json.loads(value)
  188 + self.testapp.insert_new_student(uid=s['number'], name=s['name'])
  189 +
  190 + else:
  191 + logging.error(f'Unknown command: "{cmd}"')
  192 +
  193 +
  194 +# ----------------------------------------------------------------------------
86 # /login 195 # /login
87 # ---------------------------------------------------------------------------- 196 # ----------------------------------------------------------------------------
88 class LoginHandler(BaseHandler): 197 class LoginHandler(BaseHandler):
@@ -281,55 +390,6 @@ class ReviewHandler(BaseHandler): @@ -281,55 +390,6 @@ class ReviewHandler(BaseHandler):
281 templ=self._templates) 390 templ=self._templates)
282 391
283 392
284 -# --- ADMIN ------------------------------------------------------------------  
285 -class AdminHandler(BaseHandler):  
286 - SUPPORTED_METHODS = ['GET', 'POST']  
287 -  
288 - @tornado.web.authenticated  
289 - @admin_only  
290 - async def get(self):  
291 - cmd = self.get_query_argument('cmd', default=None)  
292 -  
293 - if cmd == 'students_table':  
294 - data = {'data': self.testapp.get_students_state()}  
295 - self.write(json.dumps(data, default=str))  
296 - elif cmd == 'test': # FIXME which test?  
297 - data = {  
298 - 'data': {  
299 - 'title': self.testapp.testfactory['title'],  
300 - 'ref': self.testapp.testfactory['ref'],  
301 - 'filename': self.testapp.testfactory['filename'],  
302 - 'database': self.testapp.testfactory['database'],  
303 - 'answers_dir': self.testapp.testfactory['answers_dir'],  
304 - }  
305 - }  
306 - self.write(json.dumps(data, default=str))  
307 - else:  
308 - self.render('admin.html')  
309 -  
310 - @tornado.web.authenticated  
311 - @admin_only  
312 - async def post(self):  
313 - cmd = self.get_body_argument('cmd', None)  
314 - value = self.get_body_argument('value', None)  
315 -  
316 - if cmd == 'allow':  
317 - self.testapp.allow_student(value)  
318 -  
319 - elif cmd == 'deny':  
320 - self.testapp.deny_student(value)  
321 -  
322 - elif cmd == 'reset_password':  
323 - await self.testapp.update_student_password(uid=value, pw='')  
324 -  
325 - elif cmd == 'insert_student':  
326 - s = json.loads(value)  
327 - self.testapp.insert_new_student(uid=s['number'], name=s['name'])  
328 -  
329 - else:  
330 - logging.error(f'Unknown command: "{cmd}"')  
331 -  
332 -  
333 # ---------------------------------------------------------------------------- 393 # ----------------------------------------------------------------------------
334 def signal_handler(signal, frame): 394 def signal_handler(signal, frame):
335 r = input(' --> Stop webserver? (yes/no) ') 395 r = input(' --> Stop webserver? (yes/no) ')
perguntations/templates/review-question.html
@@ -42,7 +42,7 @@ @@ -42,7 +42,7 @@
42 {{ q['comments'] }} 42 {{ q['comments'] }}
43 {% if 'solution' in q %} 43 {% if 'solution' in q %}
44 <hr> 44 <hr>
45 - {{ md('**Solução:** ' + q['solution']) }} 45 + {{ md('**Solução:** \n\n' + q['solution']) }}
46 {% end %} 46 {% end %}
47 </p> 47 </p>
48 {% else %} 48 {% else %}
@@ -53,7 +53,7 @@ @@ -53,7 +53,7 @@
53 {{ q['comments'] }} 53 {{ q['comments'] }}
54 {% if 'solution' in q %} 54 {% if 'solution' in q %}
55 <hr> 55 <hr>
56 - {{ md('**Solução:** ' + q['solution']) }} 56 + {{ md('**Solução:** \n\n' + q['solution']) }}
57 {% end %} 57 {% end %}
58 </p> 58 </p>
59 {% end %} 59 {% end %}
@@ -96,7 +96,7 @@ @@ -96,7 +96,7 @@
96 {{ q['comments'] }} 96 {{ q['comments'] }}
97 {% if 'solution' in q %} 97 {% if 'solution' in q %}
98 <hr> 98 <hr>
99 - {{ md('**Solução:** ' + q['solution']) }} 99 + {{ md('**Solução:** \n\n' + q['solution']) }}
100 {% end %} 100 {% end %}
101 </p> 101 </p>
102 102
perguntations/templates/review.html
@@ -6,15 +6,20 @@ @@ -6,15 +6,20 @@
6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7 <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> 7 <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8 8
9 -<!-- MathJax -->  
10 - <script defer type="text/x-mathjax-config">  
11 - MathJax.Hub.Config({  
12 - tex2jax: {  
13 - inlineMath: [["$$$","$$$"], ["\\(","\\)"]]  
14 - }  
15 - }); 9 +<!-- MathJax3 -->
  10 + <script>
  11 + MathJax = {
  12 + tex: {
  13 + inlineMath: [['$$$', '$$$'], ['\\(', '\\)']]
  14 + },
  15 + svg: {
  16 + fontCache: 'global'
  17 + }
  18 + };
  19 + </script>
  20 + <script type="text/javascript" id="MathJax-script" async
  21 + src="/static/mathjax/es5/tex-svg.js">
16 </script> 22 </script>
17 - <script defer type="text/javascript" src="/static/mathjax/MathJax.js?config=TeX-AMS_CHTML-full"></script>  
18 23
19 <!-- Styles --> 24 <!-- Styles -->
20 <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css"> 25 <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css">
@@ -69,7 +74,7 @@ @@ -69,7 +74,7 @@
69 <h1 class="display-5">{{ t['title'] }}</h1> 74 <h1 class="display-5">{{ t['title'] }}</h1>
70 <h5> 75 <h5>
71 <div class="row"> 76 <div class="row">
72 - <label for="duracao" class="col-sm-2">Duração (min):</label> 77 + <label for="duracao" class="col-sm-2">Duração (minutos):</label>
73 <div class="col-sm-10" id="duracao">{{ t.get('duration', chr(8734)) }}</div> 78 <div class="col-sm-10" id="duracao">{{ t.get('duration', chr(8734)) }}</div>
74 </div> 79 </div>
75 </h5> 80 </h5>
perguntations/templates/test.html
@@ -6,15 +6,20 @@ @@ -6,15 +6,20 @@
6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7 <link rel="icon" href="/static/favicon.ico"> 7 <link rel="icon" href="/static/favicon.ico">
8 8
9 -<!-- MathJax -->  
10 - <script type="text/x-mathjax-config">  
11 - MathJax.Hub.Config({  
12 - tex2jax: {  
13 - inlineMath: [["$$$","$$$"], ["\\(","\\)"]]  
14 - }  
15 - }); 9 +<!-- MathJax3 -->
  10 + <script>
  11 + MathJax = {
  12 + tex: {
  13 + inlineMath: [['$$$', '$$$'], ['\\(', '\\)']]
  14 + },
  15 + svg: {
  16 + fontCache: 'global'
  17 + }
  18 + };
  19 + </script>
  20 + <script type="text/javascript" id="MathJax-script" async
  21 + src="/static/mathjax/es5/tex-svg.js">
16 </script> 22 </script>
17 - <script type="text/javascript" src="/static/mathjax/MathJax.js?config=TeX-AMS_CHTML-full"></script>  
18 23
19 <!-- Scripts --> 24 <!-- Scripts -->
20 <script src="/static/jquery/jquery.min.js"></script> 25 <script src="/static/jquery/jquery.min.js"></script>
@@ -88,7 +93,7 @@ @@ -88,7 +93,7 @@
88 </div> 93 </div>
89 <div class="row"> 94 <div class="row">
90 <label for="duracao" class="col-sm-3">Duração:</label> 95 <label for="duracao" class="col-sm-3">Duração:</label>
91 - <div class="col-sm-9" id="duracao">{{ str(t['duration'])+' min.' if t['duration'] > 0 else chr(8734) }}</div> 96 + <div class="col-sm-9" id="duracao">{{ str(t['duration'])+' minutos' if t['duration'] > 0 else chr(8734) }}</div>
92 </div> 97 </div>
93 </h5> 98 </h5>
94 </div> 99 </div>
@@ -18,7 +18,7 @@ setup( @@ -18,7 +18,7 @@ setup(
18 url="https:USERNAME//bitbucket.org/USERNAME/perguntations.git", 18 url="https:USERNAME//bitbucket.org/USERNAME/perguntations.git",
19 packages=find_packages(), 19 packages=find_packages(),
20 include_package_data=True, # install files from MANIFEST.in 20 include_package_data=True, # install files from MANIFEST.in
21 - python_requires='>=3.6.*', 21 + python_requires='>=3.7.*',
22 install_requires=[ 22 install_requires=[
23 'tornado', 'mistune', 'pyyaml', 'pygments', 'sqlalchemy', 'bcrypt'], 23 'tornado', 'mistune', 'pyyaml', 'pygments', 'sqlalchemy', 'bcrypt'],
24 entry_points={ 24 entry_points={