diff --git a/BUGS.md b/BUGS.md index 3b06cbe..c1089a6 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,7 +1,7 @@ # BUGS -- nao esta a usar points das perguntas +- codigo `hello world` nao esta a preservar o whitespace. O renderer de markdown gera a tag que não preserva whitespace. Necessario adicionar
.
 - teste nao esta a mostrar imagens.
 - se houver erros a abrir ficheiros .yaml de perguntas, depois dos testes diz "No errors found".
 - dizer quanto desconta em cada pergunta de escolha multipla
@@ -19,6 +19,7 @@ ou usar push (websockets?)
 
 # TODO
 
+- nao esta a usar points das perguntas
 - test: mostrar duração do teste com progressbar no navbar.
 - submissao fazer um post ajax?
 - adicionar opcao para eliminar um teste em curso.
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/__init__.py b/perguntations/__init__.py
index f38c675..bf956b7 100644
--- a/perguntations/__init__.py
+++ b/perguntations/__init__.py
@@ -32,7 +32,7 @@ proof of submission and for review.
 '''
 
 APP_NAME = 'perguntations'
-APP_VERSION = '2019.06.dev1'
+APP_VERSION = '2019.09.dev1'
 APP_DESCRIPTION = __doc__
 
 __author__ = 'Miguel Barão'
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) }}
diff --git a/setup.py b/setup.py index 3e5d7ce..155c8ed 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ setup( url="https:USERNAME//bitbucket.org/USERNAME/perguntations.git", packages=find_packages(), include_package_data=True, # install files from MANIFEST.in - python_requires='>=3.6.*', + python_requires='>=3.7.*', install_requires=[ 'tornado', 'mistune', 'pyyaml', 'pygments', 'sqlalchemy', 'bcrypt'], entry_points={ -- libgit2 0.21.2