Commit 5bad78cde6e7a158a3d6ccad34d472d008109c7b
1 parent
0aaf9568
Exists in
master
and in
1 other branch
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.
Showing
10 changed files
with
235 additions
and
114 deletions
Show diff stats
BUGS.md
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 | } |
package.json
@@ -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> |
setup.py
@@ -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={ |