Commit 778c815e58fb217dbdd3986ff662fc43c06f779e
1 parent
870abe3c
Exists in
master
and in
1 other branch
- fixed debug loglevel when command line option --debug is active.
- fixed giveup() not logging out stude - insert new student on /admin
Showing
8 changed files
with
66 additions
and
29 deletions
Show diff stats
BUGS.md
| @@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
| 3 | 3 | ||
| 4 | - usar thread.Lock para aceder a variaveis de estado? | 4 | - usar thread.Lock para aceder a variaveis de estado? |
| 5 | - permitir adicionar imagens nas perguntas. | 5 | - permitir adicionar imagens nas perguntas. |
| 6 | -- debug mode: log levels not working | 6 | +- detect_unfocus.js so funciona se estiver inline no html. porquê??? |
| 7 | 7 | ||
| 8 | # TODO | 8 | # TODO |
| 9 | 9 | ||
| @@ -24,6 +24,10 @@ | @@ -24,6 +24,10 @@ | ||
| 24 | 24 | ||
| 25 | # FIXED | 25 | # FIXED |
| 26 | 26 | ||
| 27 | +- inserir novo aluno /admin não fecha. | ||
| 28 | +- se aluno desistir, ainda fica marcado como online | ||
| 29 | +- give dá None em vez de 0.0 | ||
| 30 | +- debug mode: log levels not working | ||
| 27 | - Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. | 31 | - Se aluno fizer logout, o teste não é gravado e ficamos sem registo do teste que o aluno viu. |
| 28 | - criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. | 32 | - criar sqlalchemy sessions dentro de app de modo a estarem associadas a requests. ver se é facil usar with db:(...) para criar e fechar sessão. |
| 29 | - sqlalchemy queixa-se de threads. | 33 | - sqlalchemy queixa-se de threads. |
app.py
| @@ -158,11 +158,12 @@ class App(object): | @@ -158,11 +158,12 @@ class App(object): | ||
| 158 | def giveup_test(self, uid): | 158 | def giveup_test(self, uid): |
| 159 | logger.info('Student {0}: gave up.'.format(uid)) | 159 | logger.info('Student {0}: gave up.'.format(uid)) |
| 160 | t = self.online[uid]['test'] | 160 | t = self.online[uid]['test'] |
| 161 | - t.giveup() | 161 | + grade = t.giveup() |
| 162 | if t['save_answers']: | 162 | if t['save_answers']: |
| 163 | fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' | 163 | fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' |
| 164 | fpath = path.abspath(path.join(t['answers_dir'], fname)) | 164 | fpath = path.abspath(path.join(t['answers_dir'], fname)) |
| 165 | t.save_json(fpath) | 165 | t.save_json(fpath) |
| 166 | + return grade | ||
| 166 | 167 | ||
| 167 | # ----------------------------------------------------------------------- | 168 | # ----------------------------------------------------------------------- |
| 168 | 169 | ||
| @@ -245,3 +246,13 @@ class App(object): | @@ -245,3 +246,13 @@ class App(object): | ||
| 245 | 246 | ||
| 246 | def set_user_ip(self, uid, ipaddress=''): | 247 | def set_user_ip(self, uid, ipaddress=''): |
| 247 | self.online[uid]['student']['ip_address'] = ipaddress | 248 | self.online[uid]['student']['ip_address'] = ipaddress |
| 249 | + | ||
| 250 | + def insert_new_student(self, uid, name): | ||
| 251 | + try: | ||
| 252 | + with self.db_session() as s: | ||
| 253 | + s.add(Student(id=uid, name=name, password='')) | ||
| 254 | + s.commit() | ||
| 255 | + except Exception: | ||
| 256 | + logger.error('Insert failed: student {} already exists.'.format(uid)) | ||
| 257 | + else: | ||
| 258 | + logger.info('New student inserted into database: {}, {}'.format(uid, name)) |
config/logger-debug.yaml
| @@ -9,19 +9,19 @@ formatters: | @@ -9,19 +9,19 @@ formatters: | ||
| 9 | 9 | ||
| 10 | handlers: | 10 | handlers: |
| 11 | default: | 11 | default: |
| 12 | - level: 'INFO' | 12 | + level: 'DEBUG' |
| 13 | class: 'logging.StreamHandler' | 13 | class: 'logging.StreamHandler' |
| 14 | formatter: 'standard' | 14 | formatter: 'standard' |
| 15 | stream: 'ext://sys.stdout' | 15 | stream: 'ext://sys.stdout' |
| 16 | 16 | ||
| 17 | cherrypy_console: | 17 | cherrypy_console: |
| 18 | - level: 'INFO' | 18 | + level: 'DEBUG' |
| 19 | class: 'logging.StreamHandler' | 19 | class: 'logging.StreamHandler' |
| 20 | formatter: 'standard' | 20 | formatter: 'standard' |
| 21 | stream: 'ext://sys.stdout' | 21 | stream: 'ext://sys.stdout' |
| 22 | 22 | ||
| 23 | cherrypy_access: | 23 | cherrypy_access: |
| 24 | - level: 'INFO' | 24 | + level: 'DEBUG' |
| 25 | class: 'logging.handlers.RotatingFileHandler' | 25 | class: 'logging.handlers.RotatingFileHandler' |
| 26 | formatter: 'void' | 26 | formatter: 'void' |
| 27 | filename: 'logs/access.log' | 27 | filename: 'logs/access.log' |
| @@ -30,7 +30,7 @@ handlers: | @@ -30,7 +30,7 @@ handlers: | ||
| 30 | encoding: 'utf8' | 30 | encoding: 'utf8' |
| 31 | 31 | ||
| 32 | cherrypy_error: | 32 | cherrypy_error: |
| 33 | - level: 'INFO' | 33 | + level: 'DEBUG' |
| 34 | class: 'logging.handlers.RotatingFileHandler' | 34 | class: 'logging.handlers.RotatingFileHandler' |
| 35 | formatter: 'void' | 35 | formatter: 'void' |
| 36 | filename: 'logs/errors.log' | 36 | filename: 'logs/errors.log' |
serve.py
| @@ -99,12 +99,11 @@ class AdminWebService(object): | @@ -99,12 +99,11 @@ class AdminWebService(object): | ||
| 99 | elif args['cmd'] == 'reset': | 99 | elif args['cmd'] == 'reset': |
| 100 | return self.app.reset_password(args['name']) | 100 | return self.app.reset_password(args['name']) |
| 101 | 101 | ||
| 102 | + elif args['cmd'] == 'insert': | ||
| 103 | + return self.app.insert_new_student(uid=args['number'], name=args['name']) | ||
| 104 | + | ||
| 102 | else: | 105 | else: |
| 103 | print(args) | 106 | print(args) |
| 104 | - # if args['cmd'] == 'focus': | ||
| 105 | - # print('FOCUS', args['name']) | ||
| 106 | - # elif args['cmd'] == 'blur': | ||
| 107 | - # print('FOCUS', args['name']) | ||
| 108 | 107 | ||
| 109 | # ============================================================================ | 108 | # ============================================================================ |
| 110 | # Webserver root | 109 | # Webserver root |
| @@ -177,7 +176,7 @@ class Root(object): | @@ -177,7 +176,7 @@ class Root(object): | ||
| 177 | # radio - all off -> no key, 1 on -> string '0' | 176 | # radio - all off -> no key, 1 on -> string '0' |
| 178 | # text - always returns string. no answer '', otherwise 'dskdjs' | 177 | # text - always returns string. no answer '', otherwise 'dskdjs' |
| 179 | uid = cherrypy.session.get(SESSION_KEY) | 178 | uid = cherrypy.session.get(SESSION_KEY) |
| 180 | - student_name = self.app.get_student_name(uid) | 179 | + name = self.app.get_student_name(uid) |
| 181 | title = self.app.get_test(uid)['title'] | 180 | title = self.app.get_test(uid)['title'] |
| 182 | qq = self.app.get_test_qtypes(uid) # {'q1_ref': 'checkbox', ...} | 181 | qq = self.app.get_test_qtypes(uid) # {'q1_ref': 'checkbox', ...} |
| 183 | 182 | ||
| @@ -204,7 +203,7 @@ class Root(object): | @@ -204,7 +203,7 @@ class Root(object): | ||
| 204 | # --- Show result to student | 203 | # --- Show result to student |
| 205 | return self.template['grade'].render( | 204 | return self.template['grade'].render( |
| 206 | title=title, | 205 | title=title, |
| 207 | - student_id=uid + ' - ' + student_name, | 206 | + student_id=uid + ' - ' + name, |
| 208 | grade=grade, | 207 | grade=grade, |
| 209 | allgrades=self.app.get_student_grades_from_all_tests(uid) | 208 | allgrades=self.app.get_student_grades_from_all_tests(uid) |
| 210 | ) | 209 | ) |
| @@ -214,7 +213,11 @@ class Root(object): | @@ -214,7 +213,11 @@ class Root(object): | ||
| 214 | @require() | 213 | @require() |
| 215 | def giveup(self): | 214 | def giveup(self): |
| 216 | uid = cherrypy.session.get(SESSION_KEY) | 215 | uid = cherrypy.session.get(SESSION_KEY) |
| 216 | + name = self.app.get_student_name(uid) | ||
| 217 | + title = self.app.get_test(uid)['title'] | ||
| 218 | + | ||
| 217 | grade = self.app.giveup_test(uid) | 219 | grade = self.app.giveup_test(uid) |
| 220 | + self.app.logout(uid) | ||
| 218 | 221 | ||
| 219 | # --- Expire session | 222 | # --- Expire session |
| 220 | cherrypy.lib.sessions.expire() # session coockie expires client side | 223 | cherrypy.lib.sessions.expire() # session coockie expires client side |
| @@ -222,8 +225,8 @@ class Root(object): | @@ -222,8 +225,8 @@ class Root(object): | ||
| 222 | 225 | ||
| 223 | # --- Show result to student | 226 | # --- Show result to student |
| 224 | return self.template['grade'].render( | 227 | return self.template['grade'].render( |
| 225 | - title='title', | ||
| 226 | - student_id=uid, | 228 | + title=title, |
| 229 | + student_id=uid + ' - ' + name, | ||
| 227 | grade=grade, | 230 | grade=grade, |
| 228 | allgrades=self.app.get_student_grades_from_all_tests(uid) | 231 | allgrades=self.app.get_student_grades_from_all_tests(uid) |
| 229 | ) | 232 | ) |
static/js/admin.js
| @@ -21,9 +21,17 @@ $(document).ready(function() { | @@ -21,9 +21,17 @@ $(document).ready(function() { | ||
| 21 | }); | 21 | }); |
| 22 | } | 22 | } |
| 23 | ); | 23 | ); |
| 24 | - $("#novo_aluno").click( | 24 | + $("#inserir_novo_aluno").click( |
| 25 | function () { | 25 | function () { |
| 26 | - alert('Não implementado!'); | 26 | + $.ajax({ |
| 27 | + type: "POST", | ||
| 28 | + url: "/adminwebservice", | ||
| 29 | + data: { | ||
| 30 | + "cmd": "insert", | ||
| 31 | + "number": $("#novo_numero").val(), | ||
| 32 | + "name": $("#novo_nome").val() | ||
| 33 | + } | ||
| 34 | + }); | ||
| 27 | } | 35 | } |
| 28 | ); | 36 | ); |
| 29 | } | 37 | } |
| @@ -158,5 +166,5 @@ $(document).ready(function() { | @@ -158,5 +166,5 @@ $(document).ready(function() { | ||
| 158 | 166 | ||
| 159 | populate(); // run once when the page is loaded | 167 | populate(); // run once when the page is loaded |
| 160 | define_buttons_handlers(); | 168 | define_buttons_handlers(); |
| 161 | - setInterval(populate, 5000); // poll server on 5s interval | 169 | + setInterval(populate, 5000); // poll server on 5s interval |
| 162 | }); | 170 | }); |
templates/admin.html
| @@ -130,20 +130,39 @@ | @@ -130,20 +130,39 @@ | ||
| 130 | <button id="deny_all" class="btn btn-xs btn-danger">Nenhum</button> | 130 | <button id="deny_all" class="btn btn-xs btn-danger">Nenhum</button> |
| 131 | </div> | 131 | </div> |
| 132 | <div class="col-sm-4"> | 132 | <div class="col-sm-4"> |
| 133 | - <button id="novo_aluno" class="btn btn-xs btn-danger">Inserir novo aluno</button> | 133 | + <button id="novo_aluno" class="btn btn-xs btn-danger" data-toggle="modal" data-target="#novo_aluno_modal">Inserir novo aluno</button> |
| 134 | </div> | 134 | </div> |
| 135 | <div class="col-sm-4"> | 135 | <div class="col-sm-4"> |
| 136 | <div class="input-group input-group-sm"> | 136 | <div class="input-group input-group-sm"> |
| 137 | <input id="reset_number" type="text" class="form-control" placeholder="Número"> | 137 | <input id="reset_number" type="text" class="form-control" placeholder="Número"> |
| 138 | <span class="input-group-btn"> | 138 | <span class="input-group-btn"> |
| 139 | - <button id="reset_password" class="btn btn-danger" type="button">Reset password!</button> | 139 | + <button id="reset_password" class="btn btn-danger" type="button">Reset password!</button> |
| 140 | </span> | 140 | </span> |
| 141 | - </div><!-- /input-group --> | 141 | + </div> |
| 142 | </div> | 142 | </div> |
| 143 | </div> | 143 | </div> |
| 144 | </div> | 144 | </div> |
| 145 | </div> | 145 | </div> |
| 146 | 146 | ||
| 147 | + <div class="modal fade" id="novo_aluno_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> | ||
| 148 | + <div class="modal-dialog"> | ||
| 149 | + <div class="modal-content"> | ||
| 150 | + <div class="modal-header"> | ||
| 151 | + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | ||
| 152 | + <h4 class="modal-title">Inserir novo aluno</h4> | ||
| 153 | + </div> | ||
| 154 | + <div class="modal-body"> | ||
| 155 | + Número: <input id="novo_numero" type="text"> | ||
| 156 | + Nome: <input id="novo_nome" type="text"> | ||
| 157 | + </div> | ||
| 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> | ||
| 162 | + </div> | ||
| 163 | + </div> | ||
| 164 | + </div> | ||
| 165 | + | ||
| 147 | </div> <!-- container --> | 166 | </div> <!-- container --> |
| 148 | </body> | 167 | </body> |
| 149 | </html> | 168 | </html> |
templates/test.html
| @@ -29,8 +29,8 @@ | @@ -29,8 +29,8 @@ | ||
| 29 | <!-- My javascripts --> | 29 | <!-- My javascripts --> |
| 30 | <script src="/static/js/question_disabler.js"></script> | 30 | <script src="/static/js/question_disabler.js"></script> |
| 31 | <script src="/static/js/prevent_enter_submit.js"></script> | 31 | <script src="/static/js/prevent_enter_submit.js"></script> |
| 32 | - <script src="/static/js/detect_unfocus.js"></script> | ||
| 33 | <script src="/static/js/tabkey_in_textarea.js"></script> | 32 | <script src="/static/js/tabkey_in_textarea.js"></script> |
| 33 | + <script src="/static/js/detect_unfocus.js"></script> | ||
| 34 | 34 | ||
| 35 | <style> | 35 | <style> |
| 36 | /* Fixes navigation panel overlaying content */ | 36 | /* Fixes navigation panel overlaying content */ |
| @@ -333,7 +333,5 @@ | @@ -333,7 +333,5 @@ | ||
| 333 | </div> | 333 | </div> |
| 334 | </div> | 334 | </div> |
| 335 | </div> | 335 | </div> |
| 336 | - | ||
| 337 | - | ||
| 338 | </body> | 336 | </body> |
| 339 | </html> | 337 | </html> |
test.py
| @@ -249,9 +249,3 @@ class Test(dict): | @@ -249,9 +249,3 @@ class Test(dict): | ||
| 249 | json.dump(self, f, indent=2, default=str) | 249 | json.dump(self, f, indent=2, default=str) |
| 250 | # HACK default=str is required for datetime objects | 250 | # HACK default=str is required for datetime objects |
| 251 | logger.info('Student {}: saved JSON file.'.format(self['student']['number'])) | 251 | logger.info('Student {}: saved JSON file.'.format(self['student']['number'])) |
| 252 | - | ||
| 253 | - # ----------------------------------------------------------------------- | ||
| 254 | - # def generate_html(self): | ||
| 255 | - # for q in self['questions']: | ||
| 256 | - # q['text_html'] = markdown.markdown(q['text']) | ||
| 257 | - |