Commit 50a03cac86d6cf3d861a293fd909be2d3714acb4

Authored by Miguel Barão
1 parent 71adb971
Exists in master and in 1 other branch dev

- /admin review includes student identification.

- Fixed bug where textarea was being initialized with answer from
  previous submission from another student.
- Fixed get CSV with grades.
- Fixed many regressions from the upgrade to bootstrap 5.1.
- Changed menu in /admin.
- Removed dependency from fontawesome (but added bootstrap-icons).
- Removed jquery where not needed.
BUGS.md
... ... @@ -2,21 +2,22 @@
2 2  
3 3 ## BUGS
4 4  
5   -- review por nome e numero no cabecalho jumbotron
6   -- talvez a base de dados devesse ter como chave do teste um id que fosse único
7   - desse teste particular (não um auto counter, nem ref do teste)
8 5 - em caso de timeout na submissão (e.g. JOBE ou script nao responde) a correcção
9 6 não termina e o teste não é guardado.
10   -- reload do teste recomeça a contagem no inicio do tempo.
  7 +- modo --review nao implementado em testfactory.py
  8 +- talvez a base de dados devesse ter como chave do teste um id que fosse único
  9 + desse teste particular (não um auto counter, nem ref do teste)
11 10 - em admin, quando scale_max não é 20, as cores das barras continuam a reflectir
12 11 a escala 0,20. a tabela teste na DB não tem a escala desse teste.
  12 +- a revisao do teste não mostra as imagens que nao estejam ja em cache.
  13 +- reload do teste recomeça a contagem no inicio do tempo.
13 14 - mensagems de erro do assembler aparecem na mesma linha na correcao e nao
14   - fazerm rendering do `$t`, ver se servidor faz parse do markdown dessas
  15 + fazem rendering do `$t`, ver se servidor faz parse do markdown dessas
15 16 mensagens.
16   -- a revisao do teste não mostra as imagens que nao estejam ja em cache.
17 17  
18 18 ## TODO
19 19  
  20 +- pagina de login semelhante ao aprendizations
20 21 - QuestionTextArea falta reportar nos comments os vários erros que podem ocorrer
21 22 (timeout, etc)
22 23 - pergunta com varias partes.
... ... @@ -62,6 +63,12 @@
62 63 - se ocorrer um erro na correcçao avisar aluno para contactar o professor.
63 64 - abrir o teste numa janela maximizada e que nao permite que o aluno a
64 65 redimensione/mova? modo kiosk?
65   -- detectar scroll e enviar posição para servidor (analise de scroll para detectar copianço?)
  66 +- detectar scroll e enviar posição para servidor (analise de scroll para
  67 + detectar copianço?)
66 68 - criar perguntas de outros tipos, e.g. associação, ordenação.
67   -- stress tests. use https://locust.io
  69 +- stress tests. use [locust](https://locust.io)
  70 +
  71 +## FIXED
  72 +
  73 +- textarea vem inicializado com a resposta de outros alunos!!!
  74 +- App has no attribute get_grades_csv
... ...
package.json
... ... @@ -2,7 +2,6 @@
2 2 "description": "Javascript libraries required to run the server",
3 3 "email": "mjsb@uevora.pt",
4 4 "dependencies": {
5   - "@fortawesome/fontawesome-free": "^5.15.3",
6 5 "bootstrap": "^5.1.0",
7 6 "bootstrap-icons": "^1.7.2",
8 7 "codemirror": "^5.61.1",
... ...
perguntations/app.py
... ... @@ -372,7 +372,7 @@ class App():
372 372 }
373 373  
374 374 # ------------------------------------------------------------------------
375   - def get_test_csv(self):
  375 + def get_grades_csv(self):
376 376 '''generates a CSV with the grades of the test currently running'''
377 377 test_ref = self._testfactory['ref']
378 378 with Session(self._engine, future=True) as session:
... ...
perguntations/questions.py
... ... @@ -604,16 +604,14 @@ def question_from(qdict: QDict) -> Question:
604 604 try:
605 605 qclass = types[qdict['type']]
606 606 except KeyError:
607   - logger.error('Invalid type "%s" in "%s"',
608   - qdict['type'], qdict['ref'])
  607 + logger.error('Invalid type "%s" in "%s"', qdict['type'], qdict['ref'])
609 608 raise
610 609  
611 610 # Create an instance of Question() of appropriate type
612 611 try:
613   - qinstance = qclass(QDict(qdict))
  612 + qinstance = qclass(qdict.copy())
614 613 except QuestionException:
615   - logger.error('Error generating "%s" in %s/%s',
616   - qdict['ref'], qdict['path'], qdict['filename'])
  614 + logger.error('Generating "%s" in %s', qdict['ref'], qdict['filename'])
617 615 raise
618 616  
619 617 return qinstance
... ... @@ -625,11 +623,10 @@ class QFactory():
625 623 QFactory is a class that can generate question instances, e.g. by shuffling
626 624 options, running a script to generate the question, etc.
627 625  
628   - To generate an instance of a question we use the method generate().
  626 + To generate an instance of a question we use the method gen_async().
629 627 It returns a question instance of the correct class.
630   - There is also an asynchronous version called gen_async(). This version is
631   - synchronous for all question types (radio, checkbox, etc) except for
632   - generator types which run asynchronously.
  628 + The method is async but it only awaits on generator questions. The others
  629 + are run until completion.
633 630  
634 631 Example:
635 632  
... ... @@ -640,16 +637,13 @@ class QFactory():
640 637 'options': ['a', 'b']
641 638 })
642 639  
643   - # generate synchronously
644   - question = qfactory.generate()
645   -
646 640 # generate asynchronously
647 641 question = await qfactory.gen_async()
648 642  
649 643 # answer one question and correct it
650   - question['answer'] = 42 # set answer
651   - question.correct() # correct answer
652   - grade = question['grade'] # get grade
  644 + question.set_answer(42) # set answer
  645 + question.correct() # correct answer
  646 + grade = question['grade'] # get grade
653 647 '''
654 648  
655 649 def __init__(self, qdict: QDict = QDict({})) -> None:
... ...
perguntations/templates/admin.html
... ... @@ -21,8 +21,6 @@
21 21  
22 22 <!-- Scripts -->
23 23 <script src="/static/jquery/jquery.min.js"></script>
24   - <!-- <script defer src="/static/popper.js/popper.min.js"></script> -->
25   - <!-- <script defer src="/static/fontawesome-free/js/all.min.js"></script> -->
26 24 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
27 25 <script defer src="/static/datatables/js/jquery.dataTables.min.js"></script>
28 26 <script defer src="/static/underscore/underscore-min.js"></script>
... ... @@ -40,27 +38,34 @@
40 38 <span class="navbar-toggler-icon"></span>
41 39 </button>
42 40 <div class="collapse navbar-collapse" id="navbarNavDropdown">
43   - <!-- left -->
44   - <span class="navbar-text mr-auto"></span>
45   -
46 41 <!-- center -->
47   - <span class="navbar-text mr-auto"><span id="clock"> --:-- </span></span>
  42 + <span class="navbar-text mx-auto" id="clock"> --:-- </span>
48 43  
49 44 <!-- right -->
50 45 <ul class="navbar-nav">
51 46 <li class="nav-item dropdown">
  47 + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownNotas" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  48 + Notas
  49 + </a>
  50 + <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownNotas">
  51 + <li><a class="dropdown-item" href="/adminwebservice?cmd=testcsv">Obter CSV notas finais...</a></li>
  52 + <li><a class="dropdown-item" href="/adminwebservice?cmd=questionscsv">Obter CSV detalhado...</a></li>
  53 + </ul>
  54 + </li>
  55 + <li class="nav-item dropdown">
52 56 <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownAluno" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
53   - Acções
  57 + Alunos
54 58 </a>
55   - <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownAluno">
  59 + <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownAluno">
56 60 <li><a class="dropdown-item" href="#" id="novo_aluno" data-bs-toggle="modal" data-bs-target="#novo_aluno_modal">Novo aluno...</a></li>
57 61 <li><a class="dropdown-item" href="#" id="reset_password_menu" data-bs-toggle="modal" data-bs-target="#reset_password_modal">Limpar password...</a></li>
58 62 <li><a class="dropdown-item" href="#" id="allow_all">Autorizar todos</a></li>
59 63 <li><a class="dropdown-item" href="#" id="deny_all">Desautorizar todos</a></li>
60   - <li><hr class="dropdown-divider"></hr></li>
61   - <li><a class="dropdown-item" href="/logout">Sair</a></li>
62 64 </ul>
63 65 </li>
  66 + <li class="nav-item">
  67 + <a class="nav-link" href="/logout">Sair</a>
  68 + </li>
64 69 </ul>
65 70 </div>
66 71 </div>
... ... @@ -71,28 +76,25 @@
71 76 <div class="bg-light p-3">
72 77 <h3 id="title"></h3>
73 78 <ul>
74   - <li>Referência: <code id="ref">--</code><br></li>
75   - <li>Ficheiro de configuração: <code id="filename">--</code><br></li>
76   - <li>Directório com os testes entregues: <code id="answers_dir">--</code><br></li>
77   - <li>Base de dados: <code id="database">--</code><br></li>
  79 + <li>Referência: <code id="ref">--</code></li>
  80 + <li>Ficheiro de configuração: <code id="filename">--</code></li>
  81 + <li>Directório com os testes entregues: <code id="answers_dir">--</code></li>
  82 + <li>Base de dados: <code id="database">--</code></li>
78 83 </ul>
79   - <p>
80   - <a href="/adminwebservice?cmd=testcsv" class="btn btn-primary">Obter CSV das notas</a>
81   - <a href="/adminwebservice?cmd=questionscsv" class="btn btn-primary">Obter CSV detalhado</a>
82   - </p>
83 84 </div>
84 85 <br>
85   - <table class="table table-sm table-striped" style="width:100%" id="students_table">
86   - <thead class="thead thead-light">
  86 + <table class="table table-borderless table-striped" id="students_table">
  87 + <thead>
87 88 <tr>
88   - <th>#</th>
89   - <th>Autoriz.</th>
90   - <th>Número</th>
91   - <th>Nome</th>
92   - <th>Estado</th>
93   - <th>Nota</th>
  89 + <th scope="col">#</th>
  90 + <th scope="col">Autoriz.</th>
  91 + <th scope="col">Número</th>
  92 + <th scope="col">Nome</th>
  93 + <th scope="col">Estado</th>
  94 + <th scope="col">Nota</th>
94 95 </tr>
95 96 </thead>
  97 + <tbody> </tbody>
96 98 </table>
97 99  
98 100 </div> <!-- container -->
... ...
perguntations/templates/grade.html
1   -<!DOCTYPE html>
  1 +<!doctype html>
2 2 <html lang="pt-PT">
3 3 <head>
4   - <title>Teste</title>
  4 + <title>Prova de avaliação</title>
5 5 <meta charset="utf-8">
6 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7 7 <link rel="icon" href="/static/favicon.ico">
... ... @@ -35,9 +35,7 @@
35 35 <div class="container">
36 36 <div class="bg-light p-3">
37 37 {% if t['state'] == 'CORRECTED' %}
38   - <h1>
39   - Resultado: <strong>{{ f'{round(t["grade"], 3)}' }}</strong> valores
40   - </h1>
  38 + <h1>Resultado: <strong>{{ f'{round(t["grade"], 3)}' }}</strong> valores</h1>
41 39 {% elif t['state'] == 'SUBMITTED' %}
42 40 <h3>A prova foi submetida com sucesso. Vai ser corrigida mais tarde.</h3>
43 41 {% elif t['state'] == 'QUIT' %}
... ...
perguntations/templates/review-question-checkbox.html
... ... @@ -10,45 +10,35 @@
10 10 {% if q['answer'] is not None and str(n) in q['answer'] %}
11 11 <div class="p-2">
12 12 <i class="bi bi-check-square"></i>
13   - <!-- <i class="far fa-check-square" aria-hidden="true"></i> -->
14 13 </div>
15 14 <div class="p-2">
16 15 {{ md(opt) }}
17 16 </div>
18   - <div class="ml-auto p-2">
19   - {% if q['correct'][n] > 0 %}
20   - <div class="text-right text-success">
21   - <i class="bi bi-check-lg"></i>
22   - <!-- <i class="fas fa-check" aria-hidden="true"></i> -->
23   - </div>
24   - {% else %}
25   - <div class="text-right text-danger">
26   - <i class="bi bi-x-lg"></i>
27   - <!-- <i class="fas fa-times" aria-hidden="true"></i> -->
28   - </div>
29   - {% end %}
  17 + <div class="ms-auto p-2">
  18 + <div class="text-end">
  19 + {% if q['correct'][n] > 0 %}
  20 + <i class="bi bi-check-lg text-success"></i>
  21 + {% else %}
  22 + <i class="bi bi-x-lg text-danger"></i>
  23 + {% end %}
  24 + </div>
30 25 </div>
31 26  
32 27 {% else %}
33 28 <div class="p-2">
34 29 <i class="bi bi-square"></i>
35   - <!-- <i class="far fa-square" aria-hidden="true"></i> -->
36 30 </div>
37 31 <div class="p-2">
38 32 {{ md(opt) }}
39 33 </div>
40   - <div class="ml-auto p-2">
41   - {% if q['correct'][n] > 0 %}
42   - <div class="text-right text-danger">
43   - <i class="bi bi-x-lg"></i>
44   - <!-- <i class="fas fa-times" aria-hidden="true"></i> -->
45   - </div>
46   - {% else %}
47   - <div class="text-right text-success">
48   - <i class="bi bi-check-lg"></i>
49   - <!-- <i class="fas fa-check" aria-hidden="true"></i> -->
50   - </div>
51   - {% end %}
  34 + <div class="ms-auto p-2">
  35 + <div class="text-end">
  36 + {% if q['correct'][n] > 0 %}
  37 + <i class="bi bi-x-lg text-danger"></i>
  38 + {% else %}
  39 + <i class="bi bi-check-lg text-success"></i>
  40 + {% end %}
  41 + </div>
52 42 </div>
53 43 {% end %}
54 44 </div>
... ...
perguntations/templates/review-question-radio.html
... ... @@ -10,39 +10,28 @@
10 10 {% if q['answer'] is not None and str(n)==q['answer'] %}
11 11 <div class="p-2">
12 12 <i class="bi bi-record-circle"></i>
13   - <!-- <i class="fas fa-dot-circle" aria-hidden="true"></i> -->
14 13 </div>
15 14 <div class="p-2">
16 15 {{ md(opt) }}
17 16 </div>
18   - <div class="ml-auto p-2">
  17 + <div class="ms-auto p-2">
19 18 {% if q['correct'][n] > 0 %}
20   - <div class="text-right text-success">
21   - <i class="bi bi-check-lg"></i>
22   - <!-- <i class="fas fa-check" aria-hidden="true"></i> -->
23   - </div>
  19 + <i class="bi bi-check-lg text-success"></i>
24 20 {% else %}
25   - <div class="text-right text-danger">
26   - <i class="bi bi-x-lg"></i>
27   - <!-- <i class="fas fa-times" aria-hidden="true"></i> -->
28   - </div>
  21 + <i class="bi bi-x-lg text-danger"></i>
29 22 {% end %}
30 23 </div>
31 24  
32 25 {% else %}
33 26 <div class="p-2">
34 27 <i class="bi bi-circle"></i>
35   - <!-- <i class="far fa-circle" aria-hidden="true"></i> -->
36 28 </div>
37 29 <div class="p-2">
38 30 {{ md(opt) }}
39 31 </div>
40   - <div class="ml-auto p-2">
  32 + <div class="ms-auto p-2">
41 33 {% if q['correct'][n] > 0 %}
42   - <div class="text-right text-info">
43   - <i class="bi bi-arrow-left"></i>
44   - <!-- <i class="fas fa-dot-circle" aria-hidden="true"></i> -->
45   - </div>
  34 + <i class="bi bi-arrow-left text-info"></i>
46 35 {% end %}
47 36 </div>
48 37 {% end %}
... ...
perguntations/templates/review-question.html
... ... @@ -6,10 +6,9 @@
6 6 <div class="card border-dark mb-3">
7 7 <h5 class="card-header text-white bg-dark">
8 8 {{ q['number'] }}. {{ q['title'] }}
9   - <div class="float-right">
10   - <small>Classificar&nbsp;</small>
11   - <i class="bi bi-check-square"></i>
12   - <!-- <i class="far fa-check-square" aria-hidden="true"></i> -->
  9 + <div class="float-end">
  10 + <small>Classificada&nbsp;</small>
  11 + <i class="bi bi-toggle2-on"></i>
13 12 </div>
14 13 </h5> <!-- card-header -->
15 14  
... ... @@ -38,14 +37,12 @@
38 37 {% if q['grade'] > 0.999 %}
39 38 <h1 class="text-success"><i class="bi bi-hand-thumbs-up"></i></h1>
40 39 <p class="text-success">
41   - <!-- <i class="far fa-thumbs-up fa-3x" aria-hidden="true"></i> -->
42 40 {{ round(q['grade'] * q['points'], 2) }} pontos
43 41 </p>
44 42 <p class="text-success">{{ md(q['comments']) }}</p>
45 43 {% elif q['grade'] >= 0.5 %}
46 44 <h1 class="text-warning"><i class="bi bi-exclamation-triangle"></i></h1>
47 45 <p class="text-warning">
48   - <!-- <i class="fas fa-exclamation-triangle fa-3x" aria-hidden="true"></i> -->
49 46 {{ round(q['grade'] * q['points'], 2) }} pontos
50 47 </p>
51 48 <p class="text-warning">{{ md(q['comments']) }}</p>
... ... @@ -56,7 +53,6 @@
56 53 {% else %}
57 54 <h1 class="text-danger"><i class="bi bi-hand-thumbs-down"></i></h1>
58 55 <p class="text-danger">
59   - <!-- <i class="far fa-thumbs-down fa-3x" aria-hidden="true"></i> -->
60 56 {{ round(q['grade'] * q['points'], 2) }} pontos
61 57 </p>
62 58 <p class="text-danger">{{ md(q['comments']) }}</p>
... ... @@ -80,10 +76,9 @@
80 76 <div class="card border-secondary mb-3">
81 77 <h5 class="card-header text-white bg-secondary">
82 78 {{ q['number'] }}. {{ q['title'] }}
83   - <div class="float-right">
84   - <small>Classificar&nbsp;</small>
85   - <i class="bi bi-square"></i>
86   - <!-- <i class="far fa-square" aria-hidden="true"></i> -->
  79 + <div class="float-end">
  80 + <small>Não classificada</small>
  81 + <i class="bi bi-toggle2-off"></i>
87 82 </div>
88 83 </h5> <!-- card-header -->
89 84  
... ... @@ -102,16 +97,10 @@
102 97 </div> <!-- card-body -->
103 98  
104 99 <div class="card-footer">
105   - <h1 class="text-secondary"><i class="bi bi-dash-circle"></i></h1>
106   - <p class="text-secondary">
107   - Não respondeu
108   - <!-- <i class="fas fa-ban fa-3x" aria-hidden="true"></i> -->
109   - {{ md(q['comments']) }}
110   - {% if q['solution'] %}
111   - <hr>
112   - {{ md('**Solução:** \n\n' + q['solution']) }}
113   - {% end %}
114   - </p>
  100 + {{ md(q['comments']) }}
  101 + {% if q['solution'] %}
  102 + {{ md('**Solução:** \n\n' + q['solution']) }}
  103 + {% end %}
115 104  
116 105 {% if debug %}
117 106 <hr>
... ...
perguntations/templates/review.html
... ... @@ -22,14 +22,12 @@
22 22  
23 23 <!-- Styles -->
24 24 <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css">
  25 + <link rel="stylesheet" type="text/css" href="/static/bootstrap-icons/font/bootstrap-icons.css">
25 26 <link rel="stylesheet" type="text/css" href="/static/css/github.css"> <!-- syntax highlight -->
26 27 <link rel="stylesheet" type="text/css" href="/static/css/test.css">
27   - <link rel="stylesheet" type="text/css" href="/static/bootstrap-icons/font/bootstrap-icons.css">
28 28  
29 29 <!-- Scripts -->
30 30 <script src="/static/jquery/jquery.min.js"></script>
31   - <!-- <script defer src="/static/popper.js/popper.min.js"></script> -->
32   - <!-- <script defer src="/static/fontawesome-free/js/all.min.js"></script> -->
33 31 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
34 32 </head>
35 33 <!-- ===================================================================== -->
... ... @@ -58,10 +56,8 @@
58 56 <ul class="nav navbar-nav">
59 57 <li class="nav-item">
60 58 <span class="navbar-text">
61   - <!-- <i class="fas fa-user" aria-hidden="true"></i> -->
62 59 <span id="name">{{ escape(name) }}</span>
63 60 (<span id="number">{{ escape(uid) }}</span>)
64   - <!-- <span class="caret"></span> -->
65 61 </span>
66 62 </li>
67 63 </ul>
... ... @@ -73,46 +69,47 @@
73 69 <div class="container">
74 70 <div class="bg-light p-3">
75 71 <h1 class="display-5">{{ t['title'] }}</h1>
76   - <h5>
77   - <div class="row">
78   - <label for="duracao" class="col-sm-2">Duração:</label>
79   - <div class="col-sm-10" id="duracao">
80   - {{ str(t.get('duration', 0)) + ' minutos' if t.get('duration', 0) > 0 else '+'+chr(8734) }}
81   - </div>
82   - </div>
83   - </h5>
84 72 <hr>
85   -
86   - <h5>
87   - <div class="row">
88   - <label for="inicio" class="col-sm-2">Início:</label>
89   - <div class="col-sm-10" id="inicio">{{t['start_time'][:19]}}</div>
  73 + <div class="row">
  74 + <label for="nome" class="col-sm-2">Nome:</label>
  75 + <div class="col-sm-9" id="nome">{{ escape(name) }}</div>
  76 + </div>
  77 + <div class="row">
  78 + <label for="numero" class="col-sm-2">Número:</label>
  79 + <div class="col-sm-9" id="numero">{{ escape(uid) }}</div>
  80 + </div>
  81 + <div class="row">
  82 + <label for="duracao" class="col-sm-2">Duração:</label>
  83 + <div class="col-sm-10" id="duracao">
  84 + {{ str(t.get('duration', 0)) + ' minutos' if t.get('duration', 0) > 0 else '+'+chr(8734) }}
90 85 </div>
91   - <div class="row">
92   - <label for="fim" class="col-sm-2">Fim:</label>
93   - <div class="col-sm-10" id="fim">{{t['finish_time'][:19]}}</div>
  86 + </div>
  87 + <div class="row">
  88 + <label for="inicio" class="col-sm-2">Início:</label>
  89 + <div class="col-sm-10" id="inicio">{{t['start_time'][:19]}}</div>
  90 + </div>
  91 + <div class="row">
  92 + <label for="fim" class="col-sm-2">Fim:</label>
  93 + <div class="col-sm-10" id="fim">{{t['finish_time'][:19]}}</div>
  94 + </div>
  95 + <div class="row">
  96 + <label for="nota" class="col-sm-2">Nota:</label>
  97 + <div class="col-sm-10" id="nota">
  98 + {% if t['state'] == 'CORRECTED' %}
  99 + <strong>{{ round(t['grade'], 2) }}</strong> valores
  100 + {% elif t['state'] == 'SUBMITTED' %}
  101 + (não corrigido)
  102 + {% elif t['state'] == 'QUIT' %}
  103 + (DESISTIU)
  104 + {% end %}
94 105 </div>
95   - </h5>
96   - <h3>
  106 + </div>
  107 + {% if t['comment'] != '' %}
97 108 <div class="row">
98   - <label for="nota" class="col-sm-2">Nota:</label>
99   - <div class="col-sm-10" id="nota">
100   - {% if t['state'] == 'CORRECTED' %}
101   - <span class="badge badge-primary">{{ round(t['grade'], 2) }}</span> valores
102   - {% elif t['state'] == 'SUBMITTED' %}
103   - (não corrigido)
104   - {% elif t['state'] == 'QUIT' %}
105   - (DESISTIU)
106   - {% end %}
107   - </div>
  109 + <label for="comentario" class="col-sm-2">Comentário:</label>
  110 + <div class="col-sm-10" id="comentario">{{ t['comment'] }}</div>
108 111 </div>
109   - {% if t['comment'] != '' %}
110   - <div class="row">
111   - <label for="comentario" class="col-sm-2">Comentário:</label>
112   - <div class="col-sm-10" id="comentario">{{ t['comment'] }}</div>
113   - </div>
114   - {% end %}
115   - </h3>
  112 + {% end %}
116 113 </div>
117 114  
118 115 {% for i, q in enumerate(t['questions']) %}
... ...
perguntations/templates/test.html
1   -<!DOCTYPE html>
  1 +<!doctype html>
2 2 <html lang="pt-PT">
3 3 <head>
4   - <title>Teste</title>
5   - <meta charset="UTF-8">
6   - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  4 + <title>Prova de avalliação</title>
  5 + <meta charset="utf-8">
  6 + <meta name="viewport" content="width=device-width, initial-scale=1">
7 7 <link rel="icon" href="/static/favicon.ico">
8 8  
  9 +<!-- Styles -->
  10 + <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css">
  11 + <link rel="stylesheet" type="text/css" href="/static/codemirror/lib/codemirror.css">
  12 + <link rel="stylesheet" type="text/css" href="/static/codemirror/theme/darcula.css">
  13 + <link rel="stylesheet" type="text/css" href="/static/css/github.css"> <!-- syntax highlight -->
  14 + <link rel="stylesheet" type="text/css" href="/static/css/test.css">
  15 +
9 16 <!-- MathJax3 -->
10 17 <script>
11 18 MathJax = {
... ... @@ -21,21 +28,12 @@
21 28  
22 29 <!-- Scripts -->
23 30 <script src="/static/jquery/jquery.min.js"></script>
24   - <!-- <script defer src="/static/popper.js/popper.min.js"></script> -->
25   - <!-- <script defer src="/static/fontawesome-free/js/all.min.js"></script> -->
26 31 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
27 32 <script defer src="/static/underscore/underscore-min.js"></script>
28 33 <script src="/static/codemirror/lib/codemirror.js"></script>
29 34 <script src="/static/codemirror/addon/selection/active-line.js"></script>
30 35 <script src="/static/codemirror/addon/edit/matchbrackets.js"></script>
31 36  
32   -<!-- Styles -->
33   - <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css">
34   - <link rel="stylesheet" type="text/css" href="/static/codemirror/lib/codemirror.css">
35   - <link rel="stylesheet" type="text/css" href="/static/codemirror/theme/darcula.css">
36   - <link rel="stylesheet" type="text/css" href="/static/css/github.css"> <!-- syntax highlight -->
37   - <link rel="stylesheet" type="text/css" href="/static/css/test.css">
38   -
39 37 <!-- My scripts -->
40 38 <script defer src="/static/js/question_disabler.js"></script>
41 39 <script defer src="/static/js/prevent_enter_submit.js"></script>
... ... @@ -73,10 +71,8 @@
73 71 <ul class="nav navbar-nav">
74 72 <li class="nav-item">
75 73 <span class="navbar-text">
76   - <!-- <i class="fas fa-user" aria-hidden="true"></i> -->
77 74 <span id="name">{{ escape(name) }}</span>
78 75 (<span id="number">{{ escape(uid) }}</span>)
79   - <!-- <span class="caret"></span> -->
80 76 </span>
81 77 </li>
82 78 </ul>
... ... @@ -136,17 +132,19 @@
136 132 <h4 class="modal-title">Deseja submeter o teste?</h4>
137 133 </div>
138 134 <div class="modal-body">
139   - O teste será enviado para classificação e já não poderá voltar atrás.
140   - Antes de submeter, verifique se respondeu a todas as questões.
141   - Desactive as perguntas que não pretende classificar para evitar
142   - penalizações.
  135 + <ul>
  136 + <li> O teste será enviado para classificação e já não poderá voltar atrás.</li>
  137 + <li> Antes de submeter, verifique se respondeu a todas as questões.</li>
  138 + <li> Para evitar penalizações, esconda as perguntas que não
  139 + respondeu desactivando a checkbox "classificar".</li>
  140 + </ul>
143 141 </div>
144 142 <div class="modal-footer">
145 143 <button form="test" type="submit" class="btn btn-success btn-lg">
146 144 Sim, submeter...
147 145 </button>
148 146 <button type="button" class="btn btn-danger btn-lg" data-bs-dismiss="modal">
149   - Oops, NÃO!!!
  147 + Ainda NÃO!!!
150 148 </button>
151 149 </div>
152 150 </div>
... ... @@ -157,7 +155,6 @@
157 155 $("textarea").each(function(i, ta) {
158 156 CodeMirror.fromTextArea(ta, {
159 157 lineNumbers: true,
160   - // theme: "darcula",
161 158 viewportMargin: Infinity,
162 159 matchBrackets: true,
163 160 styleActiveLine: true,
... ...
perguntations/testfactory.py
... ... @@ -270,6 +270,7 @@ class TestFactory(dict):
270 270  
271 271 if question['type'] == 'textarea':
272 272 _runtests_textarea(qref, question)
  273 +
273 274 # ------------------------------------------------------------------------
274 275 async def generate(self):
275 276 '''
... ...