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.
@@ -2,21 +2,22 @@ @@ -2,21 +2,22 @@
2 2
3 ## BUGS 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 - em caso de timeout na submissão (e.g. JOBE ou script nao responde) a correcção 5 - em caso de timeout na submissão (e.g. JOBE ou script nao responde) a correcção
9 não termina e o teste não é guardado. 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 - em admin, quando scale_max não é 20, as cores das barras continuam a reflectir 10 - em admin, quando scale_max não é 20, as cores das barras continuam a reflectir
12 a escala 0,20. a tabela teste na DB não tem a escala desse teste. 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 - mensagems de erro do assembler aparecem na mesma linha na correcao e nao 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 mensagens. 16 mensagens.
16 -- a revisao do teste não mostra as imagens que nao estejam ja em cache.  
17 17
18 ## TODO 18 ## TODO
19 19
  20 +- pagina de login semelhante ao aprendizations
20 - QuestionTextArea falta reportar nos comments os vários erros que podem ocorrer 21 - QuestionTextArea falta reportar nos comments os vários erros que podem ocorrer
21 (timeout, etc) 22 (timeout, etc)
22 - pergunta com varias partes. 23 - pergunta com varias partes.
@@ -62,6 +63,12 @@ @@ -62,6 +63,12 @@
62 - se ocorrer um erro na correcçao avisar aluno para contactar o professor. 63 - se ocorrer um erro na correcçao avisar aluno para contactar o professor.
63 - abrir o teste numa janela maximizada e que nao permite que o aluno a 64 - abrir o teste numa janela maximizada e que nao permite que o aluno a
64 redimensione/mova? modo kiosk? 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 - criar perguntas de outros tipos, e.g. associação, ordenação. 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
@@ -2,7 +2,6 @@ @@ -2,7 +2,6 @@
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.15.3",  
6 "bootstrap": "^5.1.0", 5 "bootstrap": "^5.1.0",
7 "bootstrap-icons": "^1.7.2", 6 "bootstrap-icons": "^1.7.2",
8 "codemirror": "^5.61.1", 7 "codemirror": "^5.61.1",
perguntations/app.py
@@ -372,7 +372,7 @@ class App(): @@ -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 '''generates a CSV with the grades of the test currently running''' 376 '''generates a CSV with the grades of the test currently running'''
377 test_ref = self._testfactory['ref'] 377 test_ref = self._testfactory['ref']
378 with Session(self._engine, future=True) as session: 378 with Session(self._engine, future=True) as session:
perguntations/questions.py
@@ -604,16 +604,14 @@ def question_from(qdict: QDict) -> Question: @@ -604,16 +604,14 @@ def question_from(qdict: QDict) -> Question:
604 try: 604 try:
605 qclass = types[qdict['type']] 605 qclass = types[qdict['type']]
606 except KeyError: 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 raise 608 raise
610 609
611 # Create an instance of Question() of appropriate type 610 # Create an instance of Question() of appropriate type
612 try: 611 try:
613 - qinstance = qclass(QDict(qdict)) 612 + qinstance = qclass(qdict.copy())
614 except QuestionException: 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 raise 615 raise
618 616
619 return qinstance 617 return qinstance
@@ -625,11 +623,10 @@ class QFactory(): @@ -625,11 +623,10 @@ class QFactory():
625 QFactory is a class that can generate question instances, e.g. by shuffling 623 QFactory is a class that can generate question instances, e.g. by shuffling
626 options, running a script to generate the question, etc. 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 It returns a question instance of the correct class. 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 Example: 631 Example:
635 632
@@ -640,16 +637,13 @@ class QFactory(): @@ -640,16 +637,13 @@ class QFactory():
640 'options': ['a', 'b'] 637 'options': ['a', 'b']
641 }) 638 })
642 639
643 - # generate synchronously  
644 - question = qfactory.generate()  
645 -  
646 # generate asynchronously 640 # generate asynchronously
647 question = await qfactory.gen_async() 641 question = await qfactory.gen_async()
648 642
649 # answer one question and correct it 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 def __init__(self, qdict: QDict = QDict({})) -> None: 649 def __init__(self, qdict: QDict = QDict({})) -> None:
perguntations/templates/admin.html
@@ -21,8 +21,6 @@ @@ -21,8 +21,6 @@
21 21
22 <!-- Scripts --> 22 <!-- Scripts -->
23 <script src="/static/jquery/jquery.min.js"></script> 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 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script> 24 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
27 <script defer src="/static/datatables/js/jquery.dataTables.min.js"></script> 25 <script defer src="/static/datatables/js/jquery.dataTables.min.js"></script>
28 <script defer src="/static/underscore/underscore-min.js"></script> 26 <script defer src="/static/underscore/underscore-min.js"></script>
@@ -40,27 +38,34 @@ @@ -40,27 +38,34 @@
40 <span class="navbar-toggler-icon"></span> 38 <span class="navbar-toggler-icon"></span>
41 </button> 39 </button>
42 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 40 <div class="collapse navbar-collapse" id="navbarNavDropdown">
43 - <!-- left -->  
44 - <span class="navbar-text mr-auto"></span>  
45 -  
46 <!-- center --> 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 <!-- right --> 44 <!-- right -->
50 <ul class="navbar-nav"> 45 <ul class="navbar-nav">
51 <li class="nav-item dropdown"> 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 <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownAluno" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 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 </a> 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 <li><a class="dropdown-item" href="#" id="novo_aluno" data-bs-toggle="modal" data-bs-target="#novo_aluno_modal">Novo aluno...</a></li> 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 <li><a class="dropdown-item" href="#" id="reset_password_menu" data-bs-toggle="modal" data-bs-target="#reset_password_modal">Limpar password...</a></li> 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 <li><a class="dropdown-item" href="#" id="allow_all">Autorizar todos</a></li> 62 <li><a class="dropdown-item" href="#" id="allow_all">Autorizar todos</a></li>
59 <li><a class="dropdown-item" href="#" id="deny_all">Desautorizar todos</a></li> 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 </ul> 64 </ul>
63 </li> 65 </li>
  66 + <li class="nav-item">
  67 + <a class="nav-link" href="/logout">Sair</a>
  68 + </li>
64 </ul> 69 </ul>
65 </div> 70 </div>
66 </div> 71 </div>
@@ -71,28 +76,25 @@ @@ -71,28 +76,25 @@
71 <div class="bg-light p-3"> 76 <div class="bg-light p-3">
72 <h3 id="title"></h3> 77 <h3 id="title"></h3>
73 <ul> 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 </ul> 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 </div> 84 </div>
84 <br> 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 <tr> 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 </tr> 95 </tr>
95 </thead> 96 </thead>
  97 + <tbody> </tbody>
96 </table> 98 </table>
97 99
98 </div> <!-- container --> 100 </div> <!-- container -->
perguntations/templates/grade.html
1 -<!DOCTYPE html> 1 +<!doctype html>
2 <html lang="pt-PT"> 2 <html lang="pt-PT">
3 <head> 3 <head>
4 - <title>Teste</title> 4 + <title>Prova de avaliação</title>
5 <meta charset="utf-8"> 5 <meta charset="utf-8">
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">
@@ -35,9 +35,7 @@ @@ -35,9 +35,7 @@
35 <div class="container"> 35 <div class="container">
36 <div class="bg-light p-3"> 36 <div class="bg-light p-3">
37 {% if t['state'] == 'CORRECTED' %} 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 {% elif t['state'] == 'SUBMITTED' %} 39 {% elif t['state'] == 'SUBMITTED' %}
42 <h3>A prova foi submetida com sucesso. Vai ser corrigida mais tarde.</h3> 40 <h3>A prova foi submetida com sucesso. Vai ser corrigida mais tarde.</h3>
43 {% elif t['state'] == 'QUIT' %} 41 {% elif t['state'] == 'QUIT' %}
perguntations/templates/review-question-checkbox.html
@@ -10,45 +10,35 @@ @@ -10,45 +10,35 @@
10 {% if q['answer'] is not None and str(n) in q['answer'] %} 10 {% if q['answer'] is not None and str(n) in q['answer'] %}
11 <div class="p-2"> 11 <div class="p-2">
12 <i class="bi bi-check-square"></i> 12 <i class="bi bi-check-square"></i>
13 - <!-- <i class="far fa-check-square" aria-hidden="true"></i> -->  
14 </div> 13 </div>
15 <div class="p-2"> 14 <div class="p-2">
16 {{ md(opt) }} 15 {{ md(opt) }}
17 </div> 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 </div> 25 </div>
31 26
32 {% else %} 27 {% else %}
33 <div class="p-2"> 28 <div class="p-2">
34 <i class="bi bi-square"></i> 29 <i class="bi bi-square"></i>
35 - <!-- <i class="far fa-square" aria-hidden="true"></i> -->  
36 </div> 30 </div>
37 <div class="p-2"> 31 <div class="p-2">
38 {{ md(opt) }} 32 {{ md(opt) }}
39 </div> 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 </div> 42 </div>
53 {% end %} 43 {% end %}
54 </div> 44 </div>
perguntations/templates/review-question-radio.html
@@ -10,39 +10,28 @@ @@ -10,39 +10,28 @@
10 {% if q['answer'] is not None and str(n)==q['answer'] %} 10 {% if q['answer'] is not None and str(n)==q['answer'] %}
11 <div class="p-2"> 11 <div class="p-2">
12 <i class="bi bi-record-circle"></i> 12 <i class="bi bi-record-circle"></i>
13 - <!-- <i class="fas fa-dot-circle" aria-hidden="true"></i> -->  
14 </div> 13 </div>
15 <div class="p-2"> 14 <div class="p-2">
16 {{ md(opt) }} 15 {{ md(opt) }}
17 </div> 16 </div>
18 - <div class="ml-auto p-2"> 17 + <div class="ms-auto p-2">
19 {% if q['correct'][n] > 0 %} 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 {% else %} 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 {% end %} 22 {% end %}
30 </div> 23 </div>
31 24
32 {% else %} 25 {% else %}
33 <div class="p-2"> 26 <div class="p-2">
34 <i class="bi bi-circle"></i> 27 <i class="bi bi-circle"></i>
35 - <!-- <i class="far fa-circle" aria-hidden="true"></i> -->  
36 </div> 28 </div>
37 <div class="p-2"> 29 <div class="p-2">
38 {{ md(opt) }} 30 {{ md(opt) }}
39 </div> 31 </div>
40 - <div class="ml-auto p-2"> 32 + <div class="ms-auto p-2">
41 {% if q['correct'][n] > 0 %} 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 {% end %} 35 {% end %}
47 </div> 36 </div>
48 {% end %} 37 {% end %}
perguntations/templates/review-question.html
@@ -6,10 +6,9 @@ @@ -6,10 +6,9 @@
6 <div class="card border-dark mb-3"> 6 <div class="card border-dark mb-3">
7 <h5 class="card-header text-white bg-dark"> 7 <h5 class="card-header text-white bg-dark">
8 {{ q['number'] }}. {{ q['title'] }} 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 </div> 12 </div>
14 </h5> <!-- card-header --> 13 </h5> <!-- card-header -->
15 14
@@ -38,14 +37,12 @@ @@ -38,14 +37,12 @@
38 {% if q['grade'] > 0.999 %} 37 {% if q['grade'] > 0.999 %}
39 <h1 class="text-success"><i class="bi bi-hand-thumbs-up"></i></h1> 38 <h1 class="text-success"><i class="bi bi-hand-thumbs-up"></i></h1>
40 <p class="text-success"> 39 <p class="text-success">
41 - <!-- <i class="far fa-thumbs-up fa-3x" aria-hidden="true"></i> -->  
42 {{ round(q['grade'] * q['points'], 2) }} pontos 40 {{ round(q['grade'] * q['points'], 2) }} pontos
43 </p> 41 </p>
44 <p class="text-success">{{ md(q['comments']) }}</p> 42 <p class="text-success">{{ md(q['comments']) }}</p>
45 {% elif q['grade'] >= 0.5 %} 43 {% elif q['grade'] >= 0.5 %}
46 <h1 class="text-warning"><i class="bi bi-exclamation-triangle"></i></h1> 44 <h1 class="text-warning"><i class="bi bi-exclamation-triangle"></i></h1>
47 <p class="text-warning"> 45 <p class="text-warning">
48 - <!-- <i class="fas fa-exclamation-triangle fa-3x" aria-hidden="true"></i> -->  
49 {{ round(q['grade'] * q['points'], 2) }} pontos 46 {{ round(q['grade'] * q['points'], 2) }} pontos
50 </p> 47 </p>
51 <p class="text-warning">{{ md(q['comments']) }}</p> 48 <p class="text-warning">{{ md(q['comments']) }}</p>
@@ -56,7 +53,6 @@ @@ -56,7 +53,6 @@
56 {% else %} 53 {% else %}
57 <h1 class="text-danger"><i class="bi bi-hand-thumbs-down"></i></h1> 54 <h1 class="text-danger"><i class="bi bi-hand-thumbs-down"></i></h1>
58 <p class="text-danger"> 55 <p class="text-danger">
59 - <!-- <i class="far fa-thumbs-down fa-3x" aria-hidden="true"></i> -->  
60 {{ round(q['grade'] * q['points'], 2) }} pontos 56 {{ round(q['grade'] * q['points'], 2) }} pontos
61 </p> 57 </p>
62 <p class="text-danger">{{ md(q['comments']) }}</p> 58 <p class="text-danger">{{ md(q['comments']) }}</p>
@@ -80,10 +76,9 @@ @@ -80,10 +76,9 @@
80 <div class="card border-secondary mb-3"> 76 <div class="card border-secondary mb-3">
81 <h5 class="card-header text-white bg-secondary"> 77 <h5 class="card-header text-white bg-secondary">
82 {{ q['number'] }}. {{ q['title'] }} 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 </div> 82 </div>
88 </h5> <!-- card-header --> 83 </h5> <!-- card-header -->
89 84
@@ -102,16 +97,10 @@ @@ -102,16 +97,10 @@
102 </div> <!-- card-body --> 97 </div> <!-- card-body -->
103 98
104 <div class="card-footer"> 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 {% if debug %} 105 {% if debug %}
117 <hr> 106 <hr>
perguntations/templates/review.html
@@ -22,14 +22,12 @@ @@ -22,14 +22,12 @@
22 22
23 <!-- Styles --> 23 <!-- Styles -->
24 <link rel="stylesheet" type="text/css" href="/static/bootstrap/css/bootstrap.min.css"> 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 <link rel="stylesheet" type="text/css" href="/static/css/github.css"> <!-- syntax highlight --> 26 <link rel="stylesheet" type="text/css" href="/static/css/github.css"> <!-- syntax highlight -->
26 <link rel="stylesheet" type="text/css" href="/static/css/test.css"> 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 <!-- Scripts --> 29 <!-- Scripts -->
30 <script src="/static/jquery/jquery.min.js"></script> 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 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script> 31 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
34 </head> 32 </head>
35 <!-- ===================================================================== --> 33 <!-- ===================================================================== -->
@@ -58,10 +56,8 @@ @@ -58,10 +56,8 @@
58 <ul class="nav navbar-nav"> 56 <ul class="nav navbar-nav">
59 <li class="nav-item"> 57 <li class="nav-item">
60 <span class="navbar-text"> 58 <span class="navbar-text">
61 - <!-- <i class="fas fa-user" aria-hidden="true"></i> -->  
62 <span id="name">{{ escape(name) }}</span> 59 <span id="name">{{ escape(name) }}</span>
63 (<span id="number">{{ escape(uid) }}</span>) 60 (<span id="number">{{ escape(uid) }}</span>)
64 - <!-- <span class="caret"></span> -->  
65 </span> 61 </span>
66 </li> 62 </li>
67 </ul> 63 </ul>
@@ -73,46 +69,47 @@ @@ -73,46 +69,47 @@
73 <div class="container"> 69 <div class="container">
74 <div class="bg-light p-3"> 70 <div class="bg-light p-3">
75 <h1 class="display-5">{{ t['title'] }}</h1> 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 <hr> 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 </div> 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 </div> 105 </div>
95 - </h5>  
96 - <h3> 106 + </div>
  107 + {% if t['comment'] != '' %}
97 <div class="row"> 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 </div> 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 </div> 113 </div>
117 114
118 {% for i, q in enumerate(t['questions']) %} 115 {% for i, q in enumerate(t['questions']) %}
perguntations/templates/test.html
1 -<!DOCTYPE html> 1 +<!doctype html>
2 <html lang="pt-PT"> 2 <html lang="pt-PT">
3 <head> 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 <link rel="icon" href="/static/favicon.ico"> 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 <!-- MathJax3 --> 16 <!-- MathJax3 -->
10 <script> 17 <script>
11 MathJax = { 18 MathJax = {
@@ -21,21 +28,12 @@ @@ -21,21 +28,12 @@
21 28
22 <!-- Scripts --> 29 <!-- Scripts -->
23 <script src="/static/jquery/jquery.min.js"></script> 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 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script> 31 <script defer src="/static/bootstrap/js/bootstrap.bundle.min.js"></script>
27 <script defer src="/static/underscore/underscore-min.js"></script> 32 <script defer src="/static/underscore/underscore-min.js"></script>
28 <script src="/static/codemirror/lib/codemirror.js"></script> 33 <script src="/static/codemirror/lib/codemirror.js"></script>
29 <script src="/static/codemirror/addon/selection/active-line.js"></script> 34 <script src="/static/codemirror/addon/selection/active-line.js"></script>
30 <script src="/static/codemirror/addon/edit/matchbrackets.js"></script> 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 <!-- My scripts --> 37 <!-- My scripts -->
40 <script defer src="/static/js/question_disabler.js"></script> 38 <script defer src="/static/js/question_disabler.js"></script>
41 <script defer src="/static/js/prevent_enter_submit.js"></script> 39 <script defer src="/static/js/prevent_enter_submit.js"></script>
@@ -73,10 +71,8 @@ @@ -73,10 +71,8 @@
73 <ul class="nav navbar-nav"> 71 <ul class="nav navbar-nav">
74 <li class="nav-item"> 72 <li class="nav-item">
75 <span class="navbar-text"> 73 <span class="navbar-text">
76 - <!-- <i class="fas fa-user" aria-hidden="true"></i> -->  
77 <span id="name">{{ escape(name) }}</span> 74 <span id="name">{{ escape(name) }}</span>
78 (<span id="number">{{ escape(uid) }}</span>) 75 (<span id="number">{{ escape(uid) }}</span>)
79 - <!-- <span class="caret"></span> -->  
80 </span> 76 </span>
81 </li> 77 </li>
82 </ul> 78 </ul>
@@ -136,17 +132,19 @@ @@ -136,17 +132,19 @@
136 <h4 class="modal-title">Deseja submeter o teste?</h4> 132 <h4 class="modal-title">Deseja submeter o teste?</h4>
137 </div> 133 </div>
138 <div class="modal-body"> 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 </div> 141 </div>
144 <div class="modal-footer"> 142 <div class="modal-footer">
145 <button form="test" type="submit" class="btn btn-success btn-lg"> 143 <button form="test" type="submit" class="btn btn-success btn-lg">
146 Sim, submeter... 144 Sim, submeter...
147 </button> 145 </button>
148 <button type="button" class="btn btn-danger btn-lg" data-bs-dismiss="modal"> 146 <button type="button" class="btn btn-danger btn-lg" data-bs-dismiss="modal">
149 - Oops, NÃO!!! 147 + Ainda NÃO!!!
150 </button> 148 </button>
151 </div> 149 </div>
152 </div> 150 </div>
@@ -157,7 +155,6 @@ @@ -157,7 +155,6 @@
157 $("textarea").each(function(i, ta) { 155 $("textarea").each(function(i, ta) {
158 CodeMirror.fromTextArea(ta, { 156 CodeMirror.fromTextArea(ta, {
159 lineNumbers: true, 157 lineNumbers: true,
160 - // theme: "darcula",  
161 viewportMargin: Infinity, 158 viewportMargin: Infinity,
162 matchBrackets: true, 159 matchBrackets: true,
163 styleActiveLine: true, 160 styleActiveLine: true,
perguntations/testfactory.py
@@ -270,6 +270,7 @@ class TestFactory(dict): @@ -270,6 +270,7 @@ class TestFactory(dict):
270 270
271 if question['type'] == 'textarea': 271 if question['type'] == 'textarea':
272 _runtests_textarea(qref, question) 272 _runtests_textarea(qref, question)
  273 +
273 # ------------------------------------------------------------------------ 274 # ------------------------------------------------------------------------
274 async def generate(self): 275 async def generate(self):
275 ''' 276 '''