From 2bc46c06c01e2482f434d7089fd16ecdea8f9d2d Mon Sep 17 00:00:00 2001 From: Miguel Barão Date: Thu, 19 Sep 2019 18:36:36 +0100 Subject: [PATCH] - small corrections in the demo. - updated to mathjax-3 (major improvement). - fixed rendering of solution in the review. --- demo/questions/questions-tutorial.yaml | 19 +++++++++++-------- package-lock.json | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ package.json | 14 +++++++------- perguntations/serve.py | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------- perguntations/templates/review-question.html | 6 +++--- perguntations/templates/review.html | 23 ++++++++++++++--------- perguntations/templates/test.html | 23 ++++++++++++++--------- 7 files changed, 231 insertions(+), 111 deletions(-) diff --git a/demo/questions/questions-tutorial.yaml b/demo/questions/questions-tutorial.yaml index bbe1fc1..c210cf2 100644 --- a/demo/questions/questions-tutorial.yaml +++ b/demo/questions/questions-tutorial.yaml @@ -330,9 +330,10 @@ podem ser úteis por exemplo para introduzir código. A resposta é enviada para um programa externo para ser avaliada. - O programa externo é um programa qualquer executável pelo sistema. - Este recebe a resposta submetida pelo aluno via `stdin` e devolve a - classificação via `stdout`. + O programa externo é um programa escrito numa linguagem qualquer, desde que + seja executável pelo sistema operativo (pode ser um script ou binário). + Este programa recebe a resposta submetida pelo aluno via `stdin` e devolve + a classificação via `stdout`. Exemplo: ```yaml @@ -348,12 +349,13 @@ Neste exemplo, o programa de avaliação é um script python que verifica se a resposta contém as três palavras red, green e blue, e calcula uma nota no intervalo 0.0 a 1.0. - O programa externo é um programa executável no sistema, escrito em - qualquer linguagem de programação. A interacção com o servidor faz-se + O programa externo é executado num processo separado do sistema operativo. + Pode escrito em qualquer linguagem de programação, desde que . A interacção com o servidor faz-se sempre via stdin/stdout. Se o programa externo exceder o `timeout` indicado (em segundos), - é automaticamente cancelado e é atribuída a classificação de 0.0 valores. + este é automaticamente terminado e é atribuída a classificação de 0.0 + valores na pergunta. Após terminar a correcção, o programa externo deve enviar a classificação para o stdout. @@ -377,8 +379,9 @@ O comentário é mostrado na revisão de prova. answer: | + Aqui o aluno escreve a resposta. Esta caixa aumenta de tamanho automaticamente e - pode estar previamente preenchida (use answer: texto). + pode estar previamente preenchida como neste caso (use `answer: texto`). correct: correct/correct-question.py timeout: 5 @@ -423,7 +426,7 @@ text: | Também não conta para avaliação. É apenas o aspecto gráfico que muda. - Além das fórmulas LaTeX, também se pode escrever troços de código: + Além das fórmulas LaTeX, também se podem escrever troços de código: ```C int main() { diff --git a/package-lock.json b/package-lock.json index b04e633..ca36c14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,9 +3,9 @@ "lockfileVersion": 1, "dependencies": { "@fortawesome/fontawesome-free": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.8.1.tgz", - "integrity": "sha512-GJtx6e55qLEOy2gPOsok2lohjpdWNGrYGtQx0FFT/++K4SYx+Z8LlPHdQBaFzKEwH5IbBB4fNgb//uyZjgYXoA==" + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.11.1.tgz", + "integrity": "sha512-DtXLVYAkDU0ce1cFUgLvZaMd1R2J/LviBYih9xr4ZLhQMrgvYX7w2vOxlpKLRALfIj5GyC5zoVrcACOkLcFgvg==" }, "bootstrap": { "version": "4.3.1", @@ -13,9 +13,14 @@ "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, "codemirror": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.45.0.tgz", - "integrity": "sha512-c19j644usCE8gQaXa0jqn2B/HN9MnB2u6qPIrrhrMkB+QAP42y8G4QnTwuwbVSoUS1jEl7JU9HZMGhCDL0nsAw==" + "version": "5.48.4", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.48.4.tgz", + "integrity": "sha512-pUhZXDQ6qXSpWdwlgAwHEkd4imA0kf83hINmUEzJpmG80T/XLtDDEzZo8f6PQLuRCcUQhmzqqIo3ZPTRaWByRA==" + }, + "commander": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.1.tgz", + "integrity": "sha512-UNgvDd+csKdc9GD4zjtkHKQbT8Aspt2jCBqNSPp53vAS0L1tS9sXB2TCEOPHJ7kt9bN/niWkYj8T3RQSoMXdSQ==" }, "datatables": { "version": "1.10.18", @@ -25,20 +30,62 @@ "jquery": ">=1.7" } }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" + }, "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "mathjax": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.5.tgz", - "integrity": "sha512-OzsJNitEHAJB3y4IIlPCAvS0yoXwYjlo2Y4kmm9KQzyIBZt2d8yKRalby3uTRNN4fZQiGL2iMXjpdP1u2Rq2DQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.0.0.tgz", + "integrity": "sha512-z4uLbDHNbs/aRuR6zCcnzwFQuMixkHCcWqgVaommfK/3cA1Ahq7OXemn+m8JwTYcBApSHgcrSbPr9sm3sZFL+A==", + "requires": { + "mathjax-full": "git://github.com/mathjax/MathJax-src.git" + } + }, + "mathjax-full": { + "version": "git://github.com/mathjax/MathJax-src.git#0d74266e1820220d33cb6b29d4ca3575b352ac0d", + "from": "git://github.com/mathjax/MathJax-src.git", + "requires": { + "esm": "^3.2.25", + "mj-context-menu": "^0.2.0", + "speech-rule-engine": "^3.0.0-beta.6" + } + }, + "mj-context-menu": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.2.0.tgz", + "integrity": "sha512-yJxrWBHCjFZEHsZgfs7m5g9OSCNzsVYadW6f6lX3pgZL67vmodtSW/4zhsYmuDKweXfHs0M1kJge1uQIasWA+g==" }, "popper.js": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==" + }, + "speech-rule-engine": { + "version": "3.0.0-beta.6", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.0.0-beta.6.tgz", + "integrity": "sha512-B7gcT53jAsKpx7WvFYQcyUlFmgS3Wa9KlDy0FY8SOTa+Wz5EqmI0MpCD5/fYm8/2qiCPp8HwZg+H3cBgM+sNVw==", + "requires": { + "commander": "*", + "wicked-good-xpath": "*", + "xmldom-sre": "^0.1.31" + } + }, + "wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=" + }, + "xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==" } } } diff --git a/package.json b/package.json index e41ecff..1f191e6 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,12 @@ "description": "Javascript libraries required to run the server", "email": "mjsb@uevora.pt", "dependencies": { - "@fortawesome/fontawesome-free": "^5.8.1", - "bootstrap": "^4.3.1", - "codemirror": "^5.45.0", - "datatables": "^1.10.18", - "jquery": "^3.3.1", - "mathjax": "^2.7.5", - "popper.js": "^1.15.0" + "@fortawesome/fontawesome-free": "^5.11.1", + "bootstrap": "^4.3", + "codemirror": "^5.48", + "datatables": "^1.10", + "jquery": "^3.4.1", + "mathjax": "^3", + "popper.js": "^1.15" } } diff --git a/perguntations/serve.py b/perguntations/serve.py index 60dff56..0d232b5 100644 --- a/perguntations/serve.py +++ b/perguntations/serve.py @@ -17,6 +17,7 @@ import ssl # user installed libraries import tornado.ioloop import tornado.web +# import tornado.websocket import tornado.httpserver # this project @@ -27,19 +28,6 @@ from perguntations.parser_markdown import md_to_html # ---------------------------------------------------------------------------- -# Decorator used to restrict access to the administrator -# ---------------------------------------------------------------------------- -def admin_only(func): - @functools.wraps(func) - async def wrapper(self, *args, **kwargs): - if self.current_user != '0': - raise tornado.web.HTTPError(403) # forbidden - else: - await func(self, *args, **kwargs) - return wrapper - - -# ---------------------------------------------------------------------------- # Web Application. Routes to handler classes. # ---------------------------------------------------------------------------- class WebApplication(tornado.web.Application): @@ -51,7 +39,8 @@ class WebApplication(tornado.web.Application): (r'/review', ReviewHandler), (r'/admin', AdminHandler), (r'/file', FileHandler), - # (r'/ws', AdminWebSocketHandler), + # (r'/root', MainHandler), # FIXME + # (r'/ws', AdminSocketHandler), (r'/', RootHandler), # TODO multiple tests ] @@ -69,6 +58,19 @@ class WebApplication(tornado.web.Application): # ---------------------------------------------------------------------------- +# Decorator used to restrict access to the administrator +# ---------------------------------------------------------------------------- +def admin_only(func): + @functools.wraps(func) + async def wrapper(self, *args, **kwargs): + if self.current_user != '0': + raise tornado.web.HTTPError(403) # forbidden + else: + await func(self, *args, **kwargs) + return wrapper + + +# ---------------------------------------------------------------------------- # Base handler. Other handlers will inherit this one. # ---------------------------------------------------------------------------- class BaseHandler(tornado.web.RequestHandler): @@ -83,6 +85,113 @@ class BaseHandler(tornado.web.RequestHandler): # ---------------------------------------------------------------------------- +# class MainHandler(BaseHandler): + +# @tornado.web.authenticated +# @admin_only +# def get(self): +# self.render("admin-ws.html", students=self.testapp.get_students_state()) + + +# # ---------------------------------------------------------------------------- +# class AdminSocketHandler(tornado.websocket.WebSocketHandler): +# waiters = set() +# # cache = [] + +# # def get_compression_options(self): +# # return {} # Non-None enables compression with default options. + +# # called when opening connection +# def open(self): +# logging.debug('[AdminSocketHandler.open]') +# AdminSocketHandler.waiters.add(self) + +# # called when closing connection +# def on_close(self): +# logging.debug('[AdminSocketHandler.on_close]') +# AdminSocketHandler.waiters.remove(self) + +# # @classmethod +# # def update_cache(cls, chat): +# # logging.debug(f'[AdminSocketHandler.update_cache] "{chat}"') +# # cls.cache.append(chat) + +# # @classmethod +# # def send_updates(cls, chat): +# # logging.info("sending message to %d waiters", len(cls.waiters)) +# # for waiter in cls.waiters: +# # try: +# # waiter.write_message(chat) +# # except Exception: +# # logging.error("Error sending message", exc_info=True) + +# # handle incomming messages +# def on_message(self, message): +# logging.info(f"[AdminSocketHandler.onmessage] got message {message}") +# parsed = tornado.escape.json_decode(message) +# print(parsed) +# chat = {"id": str(uuid.uuid4()), "body": parsed["body"]} +# print(chat) +# chat["html"] = tornado.escape.to_basestring( +# '
' + chat['body'] + '
' +# # self.render_string("message.html", message=chat) +# ) +# print(chat) + +# AdminSocketHandler.update_cache(chat) # store msgs +# AdminSocketHandler.send_updates(chat) # send to clients + + +# --- ADMIN ------------------------------------------------------------------ +class AdminHandler(BaseHandler): + SUPPORTED_METHODS = ['GET', 'POST'] + + @tornado.web.authenticated + @admin_only + async def get(self): + cmd = self.get_query_argument('cmd', default=None) + + if cmd == 'students_table': + data = {'data': self.testapp.get_students_state()} + self.write(json.dumps(data, default=str)) + elif cmd == 'test': # FIXME which test? + data = { + 'data': { + 'title': self.testapp.testfactory['title'], + 'ref': self.testapp.testfactory['ref'], + 'filename': self.testapp.testfactory['filename'], + 'database': self.testapp.testfactory['database'], + 'answers_dir': self.testapp.testfactory['answers_dir'], + } + } + self.write(json.dumps(data, default=str)) + else: + self.render('admin.html') + + @tornado.web.authenticated + @admin_only + async def post(self): + cmd = self.get_body_argument('cmd', None) + value = self.get_body_argument('value', None) + + if cmd == 'allow': + self.testapp.allow_student(value) + + elif cmd == 'deny': + self.testapp.deny_student(value) + + elif cmd == 'reset_password': + await self.testapp.update_student_password(uid=value, pw='') + + elif cmd == 'insert_student': + s = json.loads(value) + self.testapp.insert_new_student(uid=s['number'], name=s['name']) + + else: + logging.error(f'Unknown command: "{cmd}"') + + +# ---------------------------------------------------------------------------- # /login # ---------------------------------------------------------------------------- class LoginHandler(BaseHandler): @@ -281,55 +390,6 @@ class ReviewHandler(BaseHandler): templ=self._templates) -# --- ADMIN ------------------------------------------------------------------ -class AdminHandler(BaseHandler): - SUPPORTED_METHODS = ['GET', 'POST'] - - @tornado.web.authenticated - @admin_only - async def get(self): - cmd = self.get_query_argument('cmd', default=None) - - if cmd == 'students_table': - data = {'data': self.testapp.get_students_state()} - self.write(json.dumps(data, default=str)) - elif cmd == 'test': # FIXME which test? - data = { - 'data': { - 'title': self.testapp.testfactory['title'], - 'ref': self.testapp.testfactory['ref'], - 'filename': self.testapp.testfactory['filename'], - 'database': self.testapp.testfactory['database'], - 'answers_dir': self.testapp.testfactory['answers_dir'], - } - } - self.write(json.dumps(data, default=str)) - else: - self.render('admin.html') - - @tornado.web.authenticated - @admin_only - async def post(self): - cmd = self.get_body_argument('cmd', None) - value = self.get_body_argument('value', None) - - if cmd == 'allow': - self.testapp.allow_student(value) - - elif cmd == 'deny': - self.testapp.deny_student(value) - - elif cmd == 'reset_password': - await self.testapp.update_student_password(uid=value, pw='') - - elif cmd == 'insert_student': - s = json.loads(value) - self.testapp.insert_new_student(uid=s['number'], name=s['name']) - - else: - logging.error(f'Unknown command: "{cmd}"') - - # ---------------------------------------------------------------------------- def signal_handler(signal, frame): r = input(' --> Stop webserver? (yes/no) ') diff --git a/perguntations/templates/review-question.html b/perguntations/templates/review-question.html index f967e1f..3c66125 100644 --- a/perguntations/templates/review-question.html +++ b/perguntations/templates/review-question.html @@ -42,7 +42,7 @@ {{ q['comments'] }} {% if 'solution' in q %}
- {{ md('**Solução:** ' + q['solution']) }} + {{ md('**Solução:** \n\n' + q['solution']) }} {% end %}

{% else %} @@ -53,7 +53,7 @@ {{ q['comments'] }} {% if 'solution' in q %}
- {{ md('**Solução:** ' + q['solution']) }} + {{ md('**Solução:** \n\n' + q['solution']) }} {% end %}

{% end %} @@ -96,7 +96,7 @@ {{ q['comments'] }} {% if 'solution' in q %}
- {{ md('**Solução:** ' + q['solution']) }} + {{ md('**Solução:** \n\n' + q['solution']) }} {% end %}

diff --git a/perguntations/templates/review.html b/perguntations/templates/review.html index daf3596..44f4f02 100644 --- a/perguntations/templates/review.html +++ b/perguntations/templates/review.html @@ -6,15 +6,20 @@ - - + - @@ -69,7 +74,7 @@

{{ t['title'] }}

- +
{{ t.get('duration', chr(8734)) }}
diff --git a/perguntations/templates/test.html b/perguntations/templates/test.html index 607bffe..f00c44e 100644 --- a/perguntations/templates/test.html +++ b/perguntations/templates/test.html @@ -6,15 +6,20 @@ - - + - @@ -88,7 +93,7 @@
-
{{ str(t['duration'])+' min.' if t['duration'] > 0 else chr(8734) }}
+
{{ str(t['duration'])+' minutos' if t['duration'] > 0 else chr(8734) }}
-- libgit2 0.21.2