Commit c68097b2c536277f8f68a362aed8ea909ec77931
1 parent
bd7832e0
Exists in
master
and in
1 other branch
- /admin functional but table sorting is unfinished.
- /review almost there, total_points still undefined. - images not working.
Showing
12 changed files
with
334 additions
and
343 deletions
Show diff stats
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">×</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">×</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">×</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 </small> | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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 </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 | |
| 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) | ... | ... |