Commit f865b05a08c5e9acbe0ab92f4b0dad1d776f789f
1 parent
f2ee3414
Exists in
master
and in
1 other branch
allow admin to reload topic and regenerate QFactory
Showing
2 changed files
with
63 additions
and
48 deletions
Show diff stats
aprendizations/learnapp.py
| @@ -78,7 +78,6 @@ class LearnApp(object): | @@ -78,7 +78,6 @@ class LearnApp(object): | ||
| 78 | logger.info(f'{len(t):>6} topics in {courses}') | 78 | logger.info(f'{len(t):>6} topics in {courses}') |
| 79 | for f in config.get('topics_from', []): | 79 | for f in config.get('topics_from', []): |
| 80 | c = load_yaml(f) # course configuration | 80 | c = load_yaml(f) # course configuration |
| 81 | - | ||
| 82 | # FIXME set defaults?? | 81 | # FIXME set defaults?? |
| 83 | logger.info(f'{len(c["topics"]):>6} topics imported from {f}') | 82 | logger.info(f'{len(c["topics"]):>6} topics imported from {f}') |
| 84 | self.populate_graph(c) | 83 | self.populate_graph(c) |
| @@ -302,6 +301,10 @@ class LearnApp(object): | @@ -302,6 +301,10 @@ class LearnApp(object): | ||
| 302 | # ------------------------------------------------------------------------ | 301 | # ------------------------------------------------------------------------ |
| 303 | async def start_topic(self, uid: str, topic: str) -> None: | 302 | async def start_topic(self, uid: str, topic: str) -> None: |
| 304 | student = self.online[uid]['state'] | 303 | student = self.online[uid]['state'] |
| 304 | + if uid == '0': | ||
| 305 | + logger.warning(f'Reloading "{topic}"') | ||
| 306 | + self.factory.update(self.factory_for(topic)) | ||
| 307 | + | ||
| 305 | try: | 308 | try: |
| 306 | await student.start_topic(topic) | 309 | await student.start_topic(topic) |
| 307 | except Exception as e: | 310 | except Exception as e: |
| @@ -393,66 +396,78 @@ class LearnApp(object): | @@ -393,66 +396,78 @@ class LearnApp(object): | ||
| 393 | 396 | ||
| 394 | # ------------------------------------------------------------------------ | 397 | # ------------------------------------------------------------------------ |
| 395 | # Buils dictionary of question factories | 398 | # Buils dictionary of question factories |
| 399 | + # - visits each topic in the graph, | ||
| 400 | + # - adds factory for each topic. | ||
| 396 | # ------------------------------------------------------------------------ | 401 | # ------------------------------------------------------------------------ |
| 397 | def make_factory(self) -> Dict[str, QFactory]: | 402 | def make_factory(self) -> Dict[str, QFactory]: |
| 398 | 403 | ||
| 399 | logger.info('Building questions factory:') | 404 | logger.info('Building questions factory:') |
| 400 | - factory = {} | 405 | + factory = dict() |
| 401 | g = self.deps | 406 | g = self.deps |
| 402 | for tref in g.nodes(): | 407 | for tref in g.nodes(): |
| 403 | - t = g.nodes[tref] | 408 | + factory.update(self.factory_for(tref)) |
| 404 | 409 | ||
| 405 | - # load questions as list of dicts | ||
| 406 | - topicpath: str = path.join(g.graph['prefix'], tref) | ||
| 407 | - try: | ||
| 408 | - fullpath: str = path.join(topicpath, t['file']) | ||
| 409 | - except Exception: | ||
| 410 | - msg1 = f'Invalid topic "{tref}"' | ||
| 411 | - msg2 = f'Check dependencies of {", ".join(g.successors(tref))}' | ||
| 412 | - raise LearnException(f'{msg1}. {msg2}') | 410 | + logger.info(f'Factory has {len(factory)} questions') |
| 411 | + return factory | ||
| 413 | 412 | ||
| 414 | - logger.debug(f' Loading {fullpath}') | ||
| 415 | - try: | ||
| 416 | - questions: List[QDict] = load_yaml(fullpath) | ||
| 417 | - except Exception: | ||
| 418 | - msg = f'Failed to load "{fullpath}"' | ||
| 419 | - logger.error(msg) | ||
| 420 | - raise LearnException(msg) | 413 | + # ------------------------------------------------------------------------ |
| 414 | + # makes factory for a single topic | ||
| 415 | + # ------------------------------------------------------------------------ | ||
| 416 | + def factory_for(self, tref: str) -> Dict[str, QFactory]: | ||
| 417 | + factory = {} | ||
| 418 | + g = self.deps | ||
| 419 | + t = g.nodes[tref] # get node | ||
| 421 | 420 | ||
| 422 | - if not isinstance(questions, list): | ||
| 423 | - msg = f'File "{fullpath}" must be a list of questions' | ||
| 424 | - raise LearnException(msg) | 421 | + # load questions as list of dicts |
| 422 | + topicpath: str = path.join(g.graph['prefix'], tref) | ||
| 423 | + try: | ||
| 424 | + fullpath: str = path.join(topicpath, t['file']) | ||
| 425 | + except Exception: | ||
| 426 | + msg1 = f'Invalid topic "{tref}"' | ||
| 427 | + msg2 = f'Check dependencies of {", ".join(g.successors(tref))}' | ||
| 428 | + raise LearnException(f'{msg1}. {msg2}') | ||
| 425 | 429 | ||
| 426 | - # update refs to include topic as prefix. | ||
| 427 | - # refs are required to be unique only within the file. | ||
| 428 | - # undefined are set to topic:n, where n is the question number | ||
| 429 | - # within the file | ||
| 430 | - localrefs: Set[str] = set() # refs in current file | ||
| 431 | - for i, q in enumerate(questions): | ||
| 432 | - qref = q.get('ref', str(i)) # ref or number | ||
| 433 | - if qref in localrefs: | ||
| 434 | - msg = f'Duplicate ref "{qref}" in "{topicpath}"' | ||
| 435 | - raise LearnException(msg) | ||
| 436 | - localrefs.add(qref) | 430 | + logger.debug(f' Loading {fullpath}') |
| 431 | + try: | ||
| 432 | + questions: List[QDict] = load_yaml(fullpath) | ||
| 433 | + except Exception: | ||
| 434 | + msg = f'Failed to load "{fullpath}"' | ||
| 435 | + logger.error(msg) | ||
| 436 | + raise LearnException(msg) | ||
| 437 | + | ||
| 438 | + if not isinstance(questions, list): | ||
| 439 | + msg = f'File "{fullpath}" must be a list of questions' | ||
| 440 | + raise LearnException(msg) | ||
| 441 | + | ||
| 442 | + # update refs to include topic as prefix. | ||
| 443 | + # refs are required to be unique only within the file. | ||
| 444 | + # undefined are set to topic:n, where n is the question number | ||
| 445 | + # within the file | ||
| 446 | + localrefs: Set[str] = set() # refs in current file | ||
| 447 | + for i, q in enumerate(questions): | ||
| 448 | + qref = q.get('ref', str(i)) # ref or number | ||
| 449 | + if qref in localrefs: | ||
| 450 | + msg = f'Duplicate ref "{qref}" in "{topicpath}"' | ||
| 451 | + raise LearnException(msg) | ||
| 452 | + localrefs.add(qref) | ||
| 437 | 453 | ||
| 438 | - q['ref'] = f'{tref}:{qref}' | ||
| 439 | - q['path'] = topicpath | ||
| 440 | - q.setdefault('append_wrong', t['append_wrong']) | 454 | + q['ref'] = f'{tref}:{qref}' |
| 455 | + q['path'] = topicpath | ||
| 456 | + q.setdefault('append_wrong', t['append_wrong']) | ||
| 441 | 457 | ||
| 442 | - # if questions are left undefined, include all. | ||
| 443 | - if not t['questions']: | ||
| 444 | - t['questions'] = [q['ref'] for q in questions] | 458 | + # if questions are left undefined, include all. |
| 459 | + if not t['questions']: | ||
| 460 | + t['questions'] = [q['ref'] for q in questions] | ||
| 445 | 461 | ||
| 446 | - t['choose'] = min(t['choose'], len(t['questions'])) | 462 | + t['choose'] = min(t['choose'], len(t['questions'])) |
| 447 | 463 | ||
| 448 | - for q in questions: | ||
| 449 | - if q['ref'] in t['questions']: | ||
| 450 | - factory[q['ref']] = QFactory(q) | ||
| 451 | - logger.debug(f' + {q["ref"]}') | 464 | + for q in questions: |
| 465 | + if q['ref'] in t['questions']: | ||
| 466 | + factory[q['ref']] = QFactory(q) | ||
| 467 | + logger.debug(f' + {q["ref"]}') | ||
| 452 | 468 | ||
| 453 | - logger.info(f'{len(t["questions"]):6} questions in {tref}') | 469 | + logger.info(f'{len(t["questions"]):6} questions in {tref}') |
| 454 | 470 | ||
| 455 | - logger.info(f'Factory has {len(factory)} questions') | ||
| 456 | return factory | 471 | return factory |
| 457 | 472 | ||
| 458 | # ------------------------------------------------------------------------ | 473 | # ------------------------------------------------------------------------ |
| @@ -501,7 +516,7 @@ class LearnApp(object): | @@ -501,7 +516,7 @@ class LearnApp(object): | ||
| 501 | 516 | ||
| 502 | # ------------------------------------------------------------------------ | 517 | # ------------------------------------------------------------------------ |
| 503 | def get_current_public_dir(self, uid: str) -> str: | 518 | def get_current_public_dir(self, uid: str) -> str: |
| 504 | - topic: str = self.online[uid]['state'].get_current_topic() # FIXME returns None if its the last question in the topic | 519 | + topic: str = self.online[uid]['state'].get_current_topic() |
| 505 | prefix: str = self.deps.graph['prefix'] | 520 | prefix: str = self.deps.graph['prefix'] |
| 506 | return path.join(prefix, topic, 'public') | 521 | return path.join(prefix, topic, 'public') |
| 507 | 522 |
aprendizations/student.py
| @@ -75,12 +75,12 @@ class StudentState(object): | @@ -75,12 +75,12 @@ class StudentState(object): | ||
| 75 | logger.debug(f'start topic "{topic}"') | 75 | logger.debug(f'start topic "{topic}"') |
| 76 | 76 | ||
| 77 | # avoid regenerating questions in the middle of the current topic | 77 | # avoid regenerating questions in the middle of the current topic |
| 78 | - if self.current_topic == topic: | 78 | + if self.current_topic == topic and self.uid != '0': |
| 79 | logger.info('Restarting current topic is not allowed.') | 79 | logger.info('Restarting current topic is not allowed.') |
| 80 | return | 80 | return |
| 81 | 81 | ||
| 82 | # do not allow locked topics | 82 | # do not allow locked topics |
| 83 | - if self.is_locked(topic): | 83 | + if self.is_locked(topic) and self.uid != '0': |
| 84 | logger.debug(f'is locked "{topic}"') | 84 | logger.debug(f'is locked "{topic}"') |
| 85 | return | 85 | return |
| 86 | 86 |