Commit c68097b2c536277f8f68a362aed8ea909ec77931

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

- /admin functional but table sorting is unfinished.

- /review almost there, total_points still undefined.
- images not working.
app.py
... ... @@ -43,17 +43,17 @@ class App(object):
43 43  
44 44 # connect to database and check registered students
45 45 dbfile = path.expanduser(self.testfactory['database'])
46   - engine = create_engine('sqlite:///{}'.format(dbfile), echo=False)
  46 + engine = create_engine(f'sqlite:///{dbfile}', echo=False)
47 47 self.Session = scoped_session(sessionmaker(bind=engine))
48 48  
49 49 try:
50 50 with self.db_session() as s:
51 51 n = s.query(Student).filter(Student.id != '0').count()
52 52 except Exception as e:
53   - logger.critical('Database not usable {}.'.format(self.testfactory['database']))
  53 + logger.critical(f'Database not usable {self.testfactory["database"]}.')
54 54 raise e
55 55 else:
56   - logger.info('Database has {} students registered.'.format(n))
  56 + logger.info(f'Database has {n} students registered.')
57 57  
58 58 # command line option --allow-all
59 59 if conf['allow_all']:
... ... @@ -64,7 +64,8 @@ class App(object):
64 64 # -----------------------------------------------------------------------
65 65 def exit(self):
66 66 if len(self.online) > 1:
67   - logger.warning('Students still online: {}'.format(', '.join(self.online)))
  67 + online_students = ', '.join(self.online)
  68 + logger.warning(f'Students still online: {online_students}')
68 69 logger.critical('----------- !!! Server terminated !!! -----------')
69 70  
70 71 # -----------------------------------------------------------------------
... ... @@ -81,7 +82,7 @@ class App(object):
81 82 def login(self, uid, try_pw):
82 83 if uid not in self.allowed and uid != '0':
83 84 # not allowed
84   - logger.warning('Student {}: not allowed to login.'.format(uid))
  85 + logger.warning(f'Student {uid}: not allowed to login.')
85 86 return False
86 87  
87 88 with self.db_session() as s:
... ... @@ -89,7 +90,7 @@ class App(object):
89 90  
90 91 if student is None:
91 92 # not found
92   - logger.warning('Student {}: not found in database.'.format(uid))
  93 + logger.warning(f'Student {uid}: not found in database.')
93 94 return False
94 95  
95 96 if student.password == '':
... ... @@ -97,20 +98,20 @@ class App(object):
97 98 hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt())
98 99 student.password = hashed_pw
99 100 s.commit()
100   - logger.info('Student {}: first login, password updated.'.format(uid))
  101 + logger.info(f'Student {uid}: first login, password updated.')
101 102  
102 103 elif bcrypt.hashpw(try_pw.encode('utf-8'), student.password) != student.password:
103 104 # wrong password
104   - logger.info('Student {}: wrong password.'.format(uid))
  105 + logger.info(f'Student {uid}: wrong password.')
105 106 return False
106 107  
107 108 # success
108 109 self.allowed.discard(uid)
109 110 if uid in self.online:
110   - logger.warning('Student {}: already logged in.'.format(uid))
  111 + logger.warning(f'Student {uid}: already logged in.')
111 112 else:
112 113 self.online[uid] = {'student': {'name': student.name, 'number': uid}}
113   - logger.info('Student {}: logged in.'.format(uid))
  114 + logger.info(f'Student {uid}: logged in.')
114 115  
115 116 return True
116 117  
... ... @@ -118,17 +119,17 @@ class App(object):
118 119 def logout(self, uid):
119 120 self.online.pop(uid, None) # remove from dict if exists
120 121 # del self.online[uid]
121   - logger.info('Student {}: logged out.'.format(uid))
  122 + logger.info(f'Student {uid}: logged out.')
122 123  
123 124 # -----------------------------------------------------------------------
124 125 def generate_test(self, uid):
125 126 if uid in self.online:
126   - logger.info('Student {}: generating new test.'.format(uid))
  127 + logger.info(f'Student {uid}: generating new test.')
127 128 student_id = self.online[uid]['student']
128 129 self.online[uid]['test'] = self.testfactory.generate(student_id)
129 130 return self.online[uid]['test']
130 131 else:
131   - logger.error('Student {}: offline, can''t generate test'.format(uid))
  132 + logger.error(f'Student {uid}: offline, can\'t generate test')
132 133 return None
133 134  
134 135 # -----------------------------------------------------------------------
... ... @@ -165,7 +166,7 @@ class App(object):
165 166 test_id=t['ref']) for q in t['questions'] if 'grade' in q])
166 167 s.commit()
167 168  
168   - logger.info('Student {0}: finished test.'.format(uid))
  169 + logger.info(f'Student {uid}: finished test.')
169 170 return grade
170 171  
171 172 # -----------------------------------------------------------------------
... ... @@ -194,7 +195,7 @@ class App(object):
194 195 comment=''))
195 196 s.commit()
196 197  
197   - logger.info('Student {0}: gave up.'.format(uid))
  198 + logger.info(f'Student {uid}: gave up.')
198 199 return t
199 200  
200 201 # -----------------------------------------------------------------------
... ... @@ -271,17 +272,17 @@ class App(object):
271 272 # --- helpers (change state)
272 273 def allow_student(self, uid):
273 274 self.allowed.add(uid)
274   - logger.info('Student {}: allowed to login.'.format(uid))
  275 + logger.info(f'Student {uid}: allowed to login.')
275 276  
276 277 def deny_student(self, uid):
277 278 self.allowed.discard(uid)
278   - logger.info('Student {}: denied to login'.format(uid))
  279 + logger.info(f'Student {uid}: denied to login')
279 280  
280 281 def reset_password(self, uid):
281 282 with self.db_session() as s:
282 283 u = s.query(Student).filter(Student.id == uid).update({'password': ''})
283 284 s.commit()
284   - logger.info('Student {}: password reset to ""'.format(uid))
  285 + logger.info(f'Student {uid}: password reset to ""')
285 286  
286 287 def set_user_agent(self, uid, user_agent=''):
287 288 self.online[uid]['student']['user_agent'] = user_agent
... ... @@ -295,9 +296,9 @@ class App(object):
295 296 s.add(Student(id=uid, name=name, password=''))
296 297 s.commit()
297 298 except Exception:
298   - logger.error('Insert failed: student {} already exists.'.format(uid))
  299 + logger.error(f'Insert failed: student {uid} already exists.')
299 300 else:
300   - logger.info('New student inserted into database: {}, {}'.format(uid, name))
  301 + logger.info(f'New student inserted: {uid}, {name}')
301 302  
302 303 def set_student_focus(self, uid, value):
303 304 self.online[uid]['student']['focus'] = value
... ...
serve.py
... ... @@ -16,7 +16,7 @@ import tornado.httpserver
16 16 from tornado import template, gen
17 17  
18 18 # project
19   -from tools import load_yaml, md_to_html
  19 +from tools import load_yaml, md_to_html, md_to_html_review
20 20 from app import App, AppException
21 21  
22 22  
... ... @@ -165,6 +165,20 @@ class TestHandler(BaseHandler):
165 165  
166 166 # --- REVIEW -------------------------------------------------------------
167 167 class ReviewHandler(BaseHandler):
  168 + templates = {
  169 + 'radio': 'review-question-radio.html',
  170 + 'checkbox': 'review-question-checkbox.html',
  171 + 'text': 'review-question-text.html',
  172 + 'text-regex': 'review-question-text.html',
  173 + 'text-numeric': 'review-question-text.html',
  174 + 'textarea': 'review-question-text.html',
  175 + # -- information panels --
  176 + 'info': 'question-information.html',
  177 + 'warn': 'question-warning.html',
  178 + 'alert': 'question-alert.html',
  179 + 'success': 'question-success.html',
  180 + }
  181 +
168 182 @tornado.web.authenticated
169 183 def get(self):
170 184 test_id=45 # FIXME
... ... @@ -176,14 +190,14 @@ class ReviewHandler(BaseHandler):
176 190 fname = self.testapp.get_json_filename_of_test(test_id)
177 191 try:
178 192 f = open(path.expanduser(fname))
179   - except FileNotFoundError:
  193 + except FileNotFoundError: # FIXME EnvironmentError?
180 194 logging.error(f'Cannot find "{fname}" for review.')
181 195 except Exception as e:
182 196 raise e
183 197 else:
184 198 with f:
185 199 t = json.load(f)
186   - self.render('review.html', t=t)
  200 + self.render('review.html', t=t, md=md_to_html_review, templ=self.templates)
187 201  
188 202  
189 203 # @cherrypy.expose
... ... @@ -231,7 +245,6 @@ class AdminHandler(BaseHandler):
231 245  
232 246 @tornado.web.authenticated
233 247 def post(self):
234   - print('admin post')
235 248 if self.current_user != '0':
236 249 self.redirect('/')
237 250  
... ...
static/js/admin.js
1 1 $(document).ready(function() {
  2 + var sorting_direction = -1;
  3 +
2 4 // button handlers (runs once)
3 5 function button_handlers() {
  6 + function show_chevron(col, dir) {
  7 + var chevron;
  8 + if (dir > 0)
  9 + chevron = '<i class="fa fa-chevron-up" aria-hidden="true"></i>';
  10 + else
  11 + chevron = '<i class="fa fa-chevron-down" aria-hidden="true"></i>';
  12 +
  13 + $("#col-numero-dir").html(col == "col-numero" ? chevron : "");
  14 + $("#col-nome-dir").html(col == "col-nome" ? chevron : "");
  15 + $("#col-estado-dir").html(col == "col-estado" ? chevron : "");
  16 + $("#col-nota-dir").html(col == "col-nota" ? chevron : "");
  17 + }
  18 +
  19 + $("#col-numero, #col-nome, #col-estado, #col-nota").click(
  20 + function() {
  21 + sorting_direction = -sorting_direction;
  22 + show_chevron(this.id, sorting_direction);
  23 + }
  24 + );
  25 +
4 26 $("#allow_all").click(
5 27 function() {
6 28 $(":checkbox").prop("checked", true).trigger('change');
... ... @@ -43,11 +65,6 @@ $(document).ready(function() {
43 65 // ----------------------------------------------------------------------
44 66 // checkbox handler to allow/deny students individually
45 67 function autorizeStudent(e) {
46   - // $.ajax({
47   - // type: "POST",
48   - // url: "/admin",
49   - // data: {"cmd": "allow", "name": this.name, "value": this.checked}
50   - // });
51 68 if (this.checked) {
52 69 $(this).parent().parent().addClass("active");
53 70 $.ajax({
... ... @@ -95,44 +112,43 @@ $(document).ready(function() {
95 112 // ----------------------------------------------------------------------
96 113 function generate_grade_bar(grade) {
97 114 var barcolor;
98   - if (grade < 10) {
  115 + if (grade < 10)
99 116 barcolor = 'bg-danger';
100   - }
101   - else if (grade < 15) {
  117 + else if (grade < 15)
102 118 barcolor = 'bg-warning';
103   - }
104   - else {
  119 + else
105 120 barcolor = 'bg-success';
106   - }
107 121  
108   - var bar = '<div class="progress"><div class="progress-bar ' + barcolor + '" role="progressbar" aria-valuenow="' + grade + '" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ' + (5*grade) + '%;">' + grade + '</div></div>';
109   - return bar
  122 + return '<div class="progress"><div class="progress-bar ' + barcolor
  123 + + '" role="progressbar" aria-valuenow="' + grade
  124 + + '" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: '
  125 + + (5*grade) + '%;">' + grade + '</div></div>';
110 126 }
111 127  
112 128 // ----------------------------------------------------------------------
113 129 function populateStudentsTable(students) {
114   - var n = students.length;
115   - $("#students-header").html(n)
116 130 var rows = "";
  131 +
117 132 $.each(students, function(i, d) {
118 133 var uid = d['uid'];
  134 + var checked = d['allowed'] ? 'checked' : '';
  135 + var password_defined = d['password_defined'] ? ' <span class="badge badge-secondary"><i class="fa fa-key" aria-hidden="true"></i></span>' : '';
  136 + var hora_inicio = d['start_time'] ? ' <span class="badge badge-success">' + d['start_time'].slice(11,19) + '</span>': '';
  137 + var estado = password_defined + hora_inicio;
119 138  
120 139 if (d['start_time'] != '') // test
121   - rows += '<tr id="' + uid + '" + class="success">';
  140 + rows += '<tr id="' + uid + '" + class="table-success">';
122 141 else if (d['online']) // online
123   - rows += '<tr id="' + uid + '" + class="warning">';
  142 + rows += '<tr id="' + uid + '" + class="table-warning">';
124 143 else if (d['allowed']) // allowed
125   - rows += '<tr id="' + uid + '" + class="active">';
  144 + rows += '<tr id="' + uid + '" + class="table-active">';
126 145 else // offline
127 146 rows += '<tr id="' + uid + '" + class="">';
128 147  
129   - rows += '\
130   - <td><input type="checkbox" name="' + uid + '" value="true"' + (d['allowed'] ? 'checked' : '') + '>' +
131   - (d['start_time']=='' ? '' : ' <span class="label label-success">teste</span>') +
132   - // (d['online'] ? '<span class="label label-warning">online</span>' : '') +
133   - '</td>\
134   - <td>' + uid + (d['password_defined'] ? ' <i class="fa fa-key" aria-hidden="true"></i>' : '') + '</td>\
  148 + rows += '<td><input type="checkbox" name="' + uid + '" value="true"'
  149 + + checked + '> ' + uid + '</td>\
135 150 <td>' + d['name'] + '</td>\
  151 + <td>' + estado + '</td>\
136 152 <td>';
137 153 var g = d['grades'];
138 154 var glength = g.length;
... ... @@ -141,8 +157,10 @@ $(document).ready(function() {
141 157 }
142 158 rows += '</td></tr>';
143 159 });
  160 +
144 161 $("#students").html(rows);
145   - $('[data-toggle="tooltip"]').tooltip();
  162 + // $('[data-toggle="tooltip"]').tooltip(); FIXME
  163 + $("#students-header").html(students.length);
146 164 }
147 165  
148 166 // ----------------------------------------------------------------------
... ... @@ -161,7 +179,7 @@ $(document).ready(function() {
161 179 $("#answers_dir").html(data['test']['answers_dir']);
162 180  
163 181 // fill online and student tables
164   - populateOnlineTable(data["students"]);
  182 + // populateOnlineTable(data["students"]);
165 183 populateStudentsTable(data["students"])
166 184  
167 185 // add event handlers
... ...
templates/admin.html
... ... @@ -6,163 +6,157 @@
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">
8 8  
9   - <!-- Bootstrap -->
  9 + <!-- styles -->
10 10 <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
11   -
12   - <!-- optional -->
13 11 <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css">
14   - <!-- <link rel="stylesheet" href="/static/css/test.css"> -->
15   -
16 12 <style>
17   - body {
18   - padding-top: 100px;
19   - }
  13 + html {
  14 + font-size: 13px;
  15 + }
  16 + body {
  17 + padding-top: 100px;
  18 + }
20 19 </style>
21 20 </head>
22 21 <!-- ===================================================================== -->
23 22 <body>
24   -<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark">
25   - <a class="navbar-brand" href="#">
26   - <!-- <i class="fa fa-clock-o" aria-hidden="true"></i> -->
27   - <span id="clock"> --:-- </span>
28   - </a>
29   -
  23 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary">
  24 + <a class="navbar-brand" href="#">Admin</a>
30 25 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
31 26 <span class="navbar-toggler-icon"></span>
32 27 </button>
33 28  
34   -<!-- <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
35   - <span class="navbar-toggler-icon"></span>
36   - </button>
37   -
38   - -->
39 29 <div class="collapse navbar-collapse" id="navbarNavDropdown">
  30 + <!-- left -->
  31 + <span class="navbar-text mr-auto"></span>
  32 +
  33 + <!-- center -->
  34 + <span class="navbar-text mr-auto"><span class="alert alert-info" id="clock"> --:-- </span></span>
  35 +
  36 + <!-- right -->
40 37 <ul class="navbar-nav">
41   -<!-- <li class="nav-item">
42   - <a class="nav-link" href="#">Features</a>
43   - </li> -->
44 38 <li class="nav-item dropdown">
45 39 <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownAluno" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
46 40 Aluno
47 41 </a>
48   - <div class="dropdown-menu" aria-labelledby="navbarDropdownAluno">
49   - <a class="dropdown-item" href="#" id="inserir_novo_aluno">Inserir novo...</a>
50   - <a class="dropdown-item" href="#" id="reset_password">Reset password...</a>
  42 + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownAluno">
  43 + <a class="dropdown-item" href="#" id="novo_aluno" data-toggle="modal" data-target="#novo_aluno_modal">Inserir novo...</a>
  44 + <a class="dropdown-item" href="#" id="reset_password" data-toggle="modal" data-target="#reset_password_modal">Reset password...</a>
51 45 <a class="dropdown-item" href="#" id="allow_all">Autorizar todos</a>
52 46 <a class="dropdown-item" href="#" id="deny_all">Desautorizar todos</a>
53 47 </div>
54 48 </li>
55 49 </ul>
56   - <div class="navbar-nav"></div>
57   -<!-- <span class="navbar-text">
58   - <i class="fa fa-user" aria-hidden="true"></i>
59   - <span id="name">Admin</span>
60   - <span class="caret"></span>
61   - </span> -->
62 50 </div>
63 51 </nav>
64 52 <!-- ===================================================================== -->
65 53 <div class="container-fluid">
66 54 <!-- ===================================================================== -->
67   - <div class="jumbotron">
68   - <h3 id="title"></h3>
69   - Ref: <span id="ref"></span><br>
70   - Enunciado: <span id="filename"></span><br>
71   - Base de dados: <span id="database"></span><br>
72   - Testes submetidos: <span id="answers_dir"></span><br>
73   - <span class="badge badge-secondary"><span id="students-header"></span></span> inscritos<br>
74   - <span class="badge badge-success"><span id="online-header"></span></span> online
  55 + <div class="jumbotron">
  56 + <h3 id="title"></h3>
  57 + Ref: <span id="ref"></span><br>
  58 + Enunciado: <span id="filename"></span><br>
  59 + Base de dados: <span id="database"></span><br>
  60 + Testes submetidos: <span id="answers_dir"></span><br>
  61 + <span class="badge badge-secondary"><span id="students-header">?</span></span> inscritos<br>
  62 + <span class="badge badge-success"><span id="online-header">?</span></span> online
75 63  
76   - </div> <!-- jumbotron -->
  64 + </div> <!-- jumbotron -->
77 65  
78 66 <!-- ===================================================================== -->
79   - <!-- <div class="card border-dark"> -->
80   - <!-- <div class="card-body"> -->
81   - <table class="table table-condensed noleftmargin">
82   - <thead class="thead-default">
83   - <tr>
84   - <th>Número</th>
85   - <th>Nome</th>
86   - <!-- <th>Data de início</th> -->
87   - <th>Início</th>
88   - <!-- <th>IP</th> -->
89   - <th>Estado</th>
90   - </tr>
91   - </thead>
92   - <tbody id="online_students">
93   - <!-- to be populated -->
94   - </tbody>
95   - </table>
96   - <!-- </div> card-body -->
97   - <!-- </div> card -->
98   -
  67 + <!-- <table class="table table-condensed noleftmargin">
  68 + <thead class="thead-default">
  69 + <tr>
  70 + <th>Número</th>
  71 + <th>Nome</th>
  72 + <th>Início</th>
  73 + <th>Estado</th>
  74 + </tr>
  75 + </thead>
  76 + <tbody id="online_students">
  77 + to be populated
  78 + </tbody>
  79 + </table>
  80 + -->
99 81 <!-- ===================================================================== -->
100   - <!-- <div class="card border-dark"> -->
101   - <!-- <div class="card-body"> -->
102   - <table class="table noleftmargin">
103   - <thead class="thead-default">
104   - <tr>
105   - <th>Perm.</th>
106   - <th>Número</th>
107   - <th>Nome</th>
108   - <!-- <th>Estado</th> -->
109   - <th>Nota</th>
110   - </tr>
111   - </thead>
112   - <tbody id="students">
113   - <!-- to be populated -->
114   - </tbody>
115   - </table>
116   - <!-- </div> card-body -->
117   -
118   - <!-- <div class="card-footer"> -->
119   - <div class="row">
120   - <div class="col-sm-4">
121   - <button id="novo_aluno" class="btn btn-xs btn-primary" data-toggle="modal" data-target="#novo_aluno_modal">Inserir novo aluno</button>
122   - </div>
123   - <div class="col-sm-4">
124   - <div class="input-group input-group-sm">
125   - <input id="reset_number" type="text" class="form-control" placeholder="Número">
126   - <span class="input-group-btn">
127   - <button id="reset_password" class="btn btn-primary" type="button">Reset password!</button>
128   - </span>
129   - </div>
130   - </div>
131   - </div> <!-- row -->
132   - <!-- </div> card-footer -->
133   - <!-- </div> card -->
  82 + <table class="table table-sm noleftmargin">
  83 + <thead class="thead-inverse">
  84 + <tr>
  85 + <!-- <th>Perm.</th> -->
  86 + <th class="col-md-2" id="col-numero">Número <span id="col-numero-dir"></span></th>
  87 + <th class="col-md-6" id="col-nome">Nome <span id="col-nome-dir"></span></th>
  88 + <th class="col-md-2" id="col-estado">Estado <span id="col-estado-dir"></span></th>
  89 + <th class="col-md-2" id="col-nota">Nota <span id="col-nota-dir"></span></th>
  90 + </tr>
  91 + </thead>
  92 + <tbody id="students">
  93 + <!-- to be populated -->
  94 + </tbody>
  95 + </table>
134 96 <!-- ===================================================================== -->
135 97 </div> <!-- container -->
136 98 <!-- ===================================================================== -->
137 99  
  100 +<!-- modal: inserir novo aluno -->
  101 +<div class="modal" id="novo_aluno_modal">
  102 + <div class="modal-dialog" role="document">
  103 + <div class="modal-content">
138 104  
  105 + <div class="modal-header">
  106 + <h5 class="modal-title">Inserir novo aluno</h5>
  107 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  108 + </div>
139 109  
140   - <div class="modal" id="novo_aluno_modal">
141   - <div class="modal-dialog" role="document">
142   - <div class="modal-content">
143   -
144   - <div class="modal-header">
145   - <h5 class="modal-title">Inserir novo aluno</h5>
146   - <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  110 + <div class="modal-body">
  111 +<!-- <p>Número: <input id="novo_numero" type="text"></p>
  112 + <p>Nome: <input id="novo_nome" type="text"></p>
  113 + -->
  114 + <div class="form-group row">
  115 + <label for="novo_numero" class="col-sm-2 col-form-label">Número</label>
  116 + <div class="col-sm-10">
  117 + <input type="text" class="form-control" id="novo_numero" value="">
147 118 </div>
148   -
149   - <div class="modal-body">
150   - <p>
151   - Número: <input id="novo_numero" type="text">
152   - </p>
153   - <p>
154   - Nome: <input id="novo_nome" type="text">
155   - </p>
  119 + </div>
  120 + <div class="form-group row">
  121 + <label for="novo_nome" class="col-sm-2 col-form-label">Nome</label>
  122 + <div class="col-sm-10">
  123 + <input type="text" class="form-control" id="novo_nome" value="">
156 124 </div>
  125 + </div>
157 126  
158   - <div class="modal-footer">
159   - <button type="button" class="btn btn-danger" data-dismiss="modal">Cancelar</button>
160   - <button id="inserir_novo_aluno" class="btn btn-danger" role="button" data-dismiss="modal">Inserir</button>
161   - </div>
  127 + </div>
  128 +
  129 + <div class="modal-footer">
  130 + <!-- <button type="button" class="btn btn-danger" data-dismiss="modal">Cancelar</button> -->
  131 + <button id="inserir_novo_aluno" class="btn btn-primary" role="button" data-dismiss="modal">Inserir</button>
  132 + </div>
  133 +
  134 + </div>
  135 + </div>
  136 +</div>
  137 +
  138 +<!-- modal: reset password -->
  139 +<div class="modal" id="reset_password_modal">
  140 + <div class="modal-dialog" role="document">
  141 + <div class="modal-content">
  142 +
  143 + <div class="modal-header">
  144 + <h5 class="modal-title">Reset password</h5>
  145 + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  146 + </div>
162 147  
  148 + <div class="modal-body">
  149 + <div class="input-group input-group-sm">
  150 + <input id="reset_number" type="text" class="form-control" placeholder="Número">
  151 + <span class="input-group-btn">
  152 + <button id="reset_password" class="btn btn-primary" type="button">Reset password!</button>
  153 + </span>
163 154 </div>
164 155 </div>
165 156 </div>
  157 + </div>
  158 +</div>
  159 +
166 160  
167 161 <!-- ===================================================================== -->
168 162  
... ...
templates/question.html
... ... @@ -2,7 +2,7 @@
2 2  
3 3 {% block question %}
4 4 <div class="card border-dark mb-3">
5   - <h5 class="card-header text-white bg-dark ">
  5 + <h5 class="card-header text-white bg-dark">
6 6 {{ i+1 }}. {{ question['title'] }}
7 7 <div class="pull-right">
8 8 <small>Classificar&nbsp;</small>
... ...
templates/review-question-checkbox.html 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +{% extends "review-question.html" %}
  2 +{% autoescape %}
  3 +
  4 +{% block answer %}
  5 +<fieldset data-role="controlgroup">
  6 + <ul class="list-group">
  7 + {% for n, opt in enumerate(q['options']) %}
  8 + <li class="list-group-item">
  9 + {% if q['answer'] is not None and str(n) in q['answer'] %}
  10 + {{ md('<i class="fa fa-check-square-o" aria-hidden="true"></i> ' + opt, q)}}
  11 + {% if q['correct'][n] > 0 %}
  12 + <div class="text-right text-success">
  13 + <i class="fa fa-check" aria-hidden="true"></i>
  14 + </div>
  15 + {% else %}
  16 + <div class="text-right text-danger">
  17 + <i class="fa fa-close" aria-hidden="true"></i>
  18 + </div>
  19 + {% end %}
  20 + {% else %}
  21 + {{ md('<i class="fa fa-square-o" aria-hidden="true"></i> ' + opt, q) }}
  22 +
  23 + {% if q['correct'][n] > 0 %}
  24 + <div class="text-right text-info">
  25 + <i class="fa fa-close" aria-hidden="true"></i>
  26 + </div>
  27 + {% elif q['correct'][n] < 0 %}
  28 + <div class="text-right text-success">
  29 + <i class="fa fa-check" aria-hidden="true"></i>
  30 + </div>
  31 + {% end %}
  32 + {% end %}
  33 + </li>
  34 + {% end %}
  35 + </ul>
  36 +</fieldset>
  37 +{% end %}
0 38 \ No newline at end of file
... ...
templates/review-question-radio.html 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +{% extends "review-question.html" %}
  2 +{% autoescape %}
  3 +
  4 +{% block answer %}
  5 +<fieldset data-role="controlgroup">
  6 + <ul class="list-group">
  7 + {% for n, opt in enumerate(q['options']) %}
  8 + <li class="list-group-item">
  9 + {% if q['answer'] is not None and str(n)==q['answer'] %}
  10 + {{ md('<i class="fa fa-dot-circle-o" aria-hidden="true"></i> ' + opt, q)}}
  11 + {% if q['correct'][n] > 0 %}
  12 + <div class="text-right text-success">
  13 + <i class="fa fa-check" aria-hidden="true"></i>
  14 + </div>
  15 + {% else %}
  16 + <div class="text-right text-danger">
  17 + <i class="fa fa-close" aria-hidden="true"></i>
  18 + </div>
  19 + {% end %}
  20 + {% else %}
  21 + {{ md('<i class="fa fa-circle-o" aria-hidden="true"></i> ' + opt, q) }}
  22 +
  23 + {% if q['correct'][n] > 0 %}
  24 + <div class="text-right text-info">
  25 + <i class="fa fa-circle" aria-hidden="true"></i>
  26 + </div>
  27 + {% end %}
  28 + {% end %}
  29 + </li>
  30 + {% end %}
  31 + </ul>
  32 +</fieldset>
  33 +{% end %}
0 34 \ No newline at end of file
... ...
templates/review-question-text.html 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +{% extends "review-question.html" %}
  2 +{% autoescape %}
  3 +
  4 +{% block answer %}
  5 + <pre>{{ q['answer'] if q['answer'] is not None else '' }}</pre>
  6 +{% end %}
0 7 \ No newline at end of file
... ...
templates/review-question.html 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +{% autoescape %}
  2 +
  3 +{% block question %}
  4 +<div class="card border-dark mb-3">
  5 +
  6 + <h5 class="card-header text-white bg-dark">
  7 + {{ i+1 }}. {{ q['title'] }}
  8 + <div class="pull-right">
  9 + <small>Classificar&nbsp;</small>
  10 + {% if q['answer'] is not None %}
  11 + <i class="fa fa-check-square-o" aria-hidden="true"></i>
  12 + {% else %}
  13 + <i class="fa fa-square-o" aria-hidden="true"></i>
  14 + {% end %}
  15 + </div>
  16 + </h5>
  17 +
  18 + <div class="card-body">
  19 + <div id="text">
  20 + {{ q['text'] }}
  21 + </div>
  22 +
  23 + {% block answer %}{% end %}
  24 +
  25 + {% if t['show_points'] %}
  26 + <p class="text-right">
  27 + <small>
  28 + (Cotação: {{ q['points'] }} pontos não normalizados)
  29 + </small>
  30 + </p>
  31 + {% end %}
  32 +
  33 + {% if t['state'] == 'FINISHED' %}
  34 + <div class="card-footer">
  35 + {% if q['grade'] > 0.99 %}
  36 + <p class="text-success">
  37 + <i class="fa fa-thumbs-o-up" aria-hidden="true"></i>
  38 + {{ round(q['grade'] * q['points'] / total_points * 20.0, 2)}}
  39 + pontos<br>
  40 + {{ q['comments'] }}
  41 + </p>
  42 + {% elif q['grade'] > 0.49 %}
  43 + <p class="text-warning">
  44 + <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
  45 + {{ round(q['grade'] * q['points'] / total_points * 20.0, 2) }}
  46 + pontos<br>
  47 + {{ q['comments'] }}
  48 + </p>
  49 + {% else %}
  50 + <p class="text-danger">
  51 + <i class="fa fa-thumbs-o-down" aria-hidden="true"></i>
  52 + {{ round(q['grade'] * q['points'] / total_points * 20.0, 2) }}
  53 + pontos<br>
  54 + {{ q['comments'] }}
  55 + </p>
  56 + {% end %}
  57 + </div>
  58 + {% end %}
  59 + </div>
  60 +
  61 +</div>
  62 +{% end %}
0 63 \ No newline at end of file
... ...
templates/review.html
... ... @@ -27,7 +27,7 @@
27 27 </head>
28 28 <!-- ===================================================================== -->
29 29 <body>
30   -<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark">
  30 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary">
31 31 <a class="navbar-brand" href="#">Revisão de prova</a>
32 32 </nav>
33 33 <!-- ===================================================================== -->
... ... @@ -52,183 +52,10 @@
52 52 {% end %}
53 53 </dl>
54 54 </big>
55   - <small>
56   - <dl class="dl-horizontal">
57   - <dt>Referência:</dt><dd>{{t['ref']}}</dd>
58   - <dt>IP:</dt><dd>{{t['student']['ip_address']}}</dd>
59   - <dt>Browser:</dt><dd>{{t['student']['user_agent']}}</dd>
60   - </dl>
61   - </small>
62 55 </div>
63   -<!-- ===================================================================== -->
64   - {% for i,q in enumerate(t['questions']) %}
65   -
66   - {% if q['type'] == 'information' %}
67   - <div class="card card-info">
68   - <div class="panel-heading clearfix">
69   - <h4 class="panel-title pull-left">
70   - <i class="fa fa-info-circle" aria-hidden="true"></i>
71   - ${q['title']}
72   - </h4>
73   - </div>
74   - <div class="panel-body">
75   - ${md_to_html_review(q['text'], q)}
76   - </div>
77   - </div>
78   - % elif q['type'] == 'warning':
79   - <div class="alert alert-warning drop-shadow" role="alert">
80   - <h4>
81   - <i class="fa fa-question-circle" aria-hidden="true"></i>
82   - ${q['title']}
83   - </h4>
84   - <p>
85   - ${md_to_html_review(q['text'], q)}
86   - </p>
87   - </div>
88   - % elif q['type'] == 'alert':
89   - <div class="alert alert-danger drop-shadow" role="alert">
90   - <h4>
91   - <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
92   - ${q['title']}
93   - </h4>
94   - <p>
95   - ${md_to_html_review(q['text'], q)}
96   - </p>
97   - </div>
98   - % else:
99   - <div class="panel panel-primary drop-shadow">
100   - <div class="panel-heading clearfix">
101   - <h4 class="panel-title pull-left">
102   - ${q['title']}
103   - </h4>
104   - <div class="pull-right">
105   - Classificar&nbsp;
106   - % if q['answer'] is not None:
107   - <i class="fa fa-check-square-o" aria-hidden="true"></i>
108   - % else:
109   - <i class="fa fa-square-o" aria-hidden="true"></i>
110   - % endif
111   - </div>
112   - </div>
113   - <div class="panel-body" id="example${i}">
114   - <div class="question">
115   - ${md_to_html_review(q['text'], q)}
116   - </div>
117   -
118   - % if q['type'] == 'radio':
119   - <ul class="list-group">
120   - % for opt in q['options']:
121   - <li class="list-group-item">
122   - % if q['answer'] is not None and str(loop.index) == q['answer']:
123   - ${md_to_html_review('<i class="fa fa-dot-circle-o" aria-hidden="true"></i> ' + opt, q)}
124   -
125   - % if q['correct'][loop.index] > 0:
126   - <div class="text-right text-success">
127   - <i class="fa fa-check" aria-hidden="true"></i>
128   - </div>
129   - % else:
130   - <div class="text-right text-danger">
131   - <i class="fa fa-close" aria-hidden="true"></i>
132   - </div>
133   - % endif
134   -
135   - % else:
136   - ${md_to_html_review('<i class="fa fa-circle-o" aria-hidden="true"></i> ' + opt, q)}
137   -
138   - % if q['correct'][loop.index] > 0:
139   - <div class="text-right text-info">
140   - <i class="fa fa-circle" aria-hidden="true"></i>
141   - </div>
142   - % endif
143   - % endif
144   - </li>
145   - % endfor
146   - </ul>
147   - % elif q['type'] == 'checkbox':
148   - <ul class="list-group">
149   - % for opt in q['options']:
150   - <li class="list-group-item">
151   - % if q['answer'] is not None and str(loop.index) in q['answer']:
152   - ${md_to_html_review('<i class="fa fa-check-square-o" aria-hidden="true"></i> ' + opt, q)}
153   -
154   - % if q['correct'][loop.index] > 0:
155   - <div class="text-right text-success">
156   - <i class="fa fa-check" aria-hidden="true"></i>
157   - </div>
158   - % elif q['correct'][loop.index] < 0:
159   - <div class="text-right text-danger">
160   - <i class="fa fa-close" aria-hidden="true"></i>
161   - </div>
162   - % endif
163   -
164   - % else:
165   - ${md_to_html_review('<i class="fa fa-square-o" aria-hidden="true"></i> ' + opt, q)}
166   -
167   - % if q['correct'][loop.index] > 0:
168   - <div class="text-right text-danger">
169   - <i class="fa fa-close" aria-hidden="true"></i>
170   - </div>
171   - % elif q['correct'][loop.index] < 0:
172   - <div class="text-right text-success">
173   - <i class="fa fa-check" aria-hidden="true"></i>
174   - </div>
175   - % endif
176   -
177   - % endif
178   - </li>
179   - % endfor
180   - </ul>
181   - % elif q['type'] in ('text', 'text_regex', 'text_numeric', 'textarea'):
182   - <pre>${q['answer'] if q['answer'] is not None else ''}</pre>
183   - % endif
184   -
185   - % if t['show_hints']:
186   - % if 'hint' in q:
187   - <button class="btn btn-sm btn-warning" type="button" data-toggle="collapse" data-target="#hint-${q['ref']}" aria-expanded="false" aria-controls="hint-${q['ref']}">
188   - Ajuda
189   - </button>
190   - <div class="collapse" id="hint-${q['ref']}">
191   - <div class="well">
192   - ${md_to_html_review(q['hint'], q)}
193   - </div>
194   - </div>
195   - % endif # hint
196   - % endif
197   -
198   - % if t['show_points']:
199   - <p class="text-right">
200   - <small>(Cotação: ${round(q['points'] / total_points * 20.0, 2)} pontos)</small>
201   - <p>
202   - % endif
203   -
204   - </div> <!-- panel-body -->
205   -
206   - % if t['state'] == 'FINISHED':
207   - <div class="panel-footer">
208   - % if q['grade'] > 0.99:
209   - <p class="text-success">
210   - <i class="fa fa-thumbs-o-up" aria-hidden="true"></i>
211   - ${round(q['grade'] * q['points'] / total_points * 20.0, 2)} pontos<br>
212   - ${q['comments']}
213   - </p>
214   - % elif q['grade'] > 0.49:
215   - <p class="text-warning">
216   - <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
217   - ${round(q['grade'] * q['points'] / total_points * 20.0, 2)} pontos<br>
218   - ${q['comments']}
219   - </p>
220   - % else:
221   - <p class="text-danger">
222   - <i class="fa fa-thumbs-o-down" aria-hidden="true"></i>
223   - ${round(q['grade'] * q['points'] / total_points * 20.0, 2)} pontos<br>
224   - ${q['comments']}
225   - </p>
226   - % endif
227   - </div>
228   - % endif
229 56  
230   - </div> <!-- panel -->
231   - % endif # question type
  57 + {% for i, q in enumerate(t['questions']) %}
  58 + {% module Template(templ[q['type']], i=i, q=q, md=md, t=t) %}
232 59 {% end %}
233 60 </div> <!-- container -->
234 61  
... ...
templates/test.html
... ... @@ -78,9 +78,9 @@
78 78 <div class="col-9">
79 79 <button type="button" class="btn btn-success btn-lg btn-block" data-toggle="modal" data-target="#confirmar" id="form-button-submit">Submeter teste</button>
80 80 </div>
81   - <div class="col-3">
  81 +<!-- <div class="col-3">
82 82 <button type="button" class="btn btn-danger btn-lg btn-block" data-toggle="modal" data-target="#sair" id="form-button-sair">Desisto</button>
83   - </div>
  83 + </div> -->
84 84 </div>
85 85 </form>
86 86 <hr>
... ...
test.py
... ... @@ -195,7 +195,7 @@ class Test(dict):
195 195 self['finish_time'] = None
196 196 self['state'] = 'ONGOING'
197 197 self['comment'] = ''
198   - logger.info(f'Student {self["student"]["number"]}: starting test.')
  198 + logger.info(f'Student {self["student"]["number"]}: starting test.')
199 199  
200 200 # -----------------------------------------------------------------------
201 201 # Removes all answers from the test (clean)
... ...