From 2826a6019e2b26af5a9263b97aaba28ff5f54667 Mon Sep 17 00:00:00 2001 From: Miguel Barao Date: Tue, 19 Mar 2019 16:23:45 +0000 Subject: [PATCH] Moved question factory to questions.yaml Moved menu item "topicos" to menu bar. --- aprendizations/factory.py | 99 --------------------------------------------------------------------------------------------------- aprendizations/learnapp.py | 2 +- aprendizations/questions.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- aprendizations/serve.py | 23 ++++++++++++----------- aprendizations/templates/maintopics-table.html | 6 +++++- aprendizations/templates/topic.html | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------- 6 files changed, 189 insertions(+), 201 deletions(-) delete mode 100644 aprendizations/factory.py diff --git a/aprendizations/factory.py b/aprendizations/factory.py deleted file mode 100644 index 0f8bd03..0000000 --- a/aprendizations/factory.py +++ /dev/null @@ -1,99 +0,0 @@ -# QFactory is a class that can generate question instances, e.g. by shuffling -# options, running a script to generate the question, etc. -# -# To generate an instance of a question we use the method generate() where -# the argument is the reference of the question we wish to produce. -# -# Example: -# -# # read question from file -# qdict = tools.load_yaml(filename) -# qfactory = QFactory(qdict) -# question = qfactory.generate() -# -# # experiment answering one question and correct it -# question.updateAnswer('42') # insert answer -# grade = question.correct() # correct answer - -# An instance of an actual question is an object that inherits from Question() -# -# Question - base class inherited by other classes -# QuestionInformation - not a question, just a box with content -# QuestionRadio - single choice from a list of options -# QuestionCheckbox - multiple choice, equivalent to multiple true/false -# QuestionText - line of text compared to a list of acceptable answers -# QuestionTextRegex - line of text matched against a regular expression -# QuestionTextArea - corrected by an external program -# QuestionNumericInterval - line of text parsed as a float - -# base -from os import path -import logging - -# project -from aprendizations.tools import run_script -from aprendizations.questions import (QuestionInformation, QuestionRadio, - QuestionCheckbox, QuestionText, - QuestionTextRegex, QuestionTextArea, - QuestionNumericInterval, - QuestionException) - -# setup logger for this module -logger = logging.getLogger(__name__) - - -# =========================================================================== -# Question Factory -# =========================================================================== -class QFactory(object): - # Depending on the type of question, a different question class will be - # instantiated. All these classes derive from the base class `Question`. - _types = { - 'radio': QuestionRadio, - 'checkbox': QuestionCheckbox, - 'text': QuestionText, - 'text-regex': QuestionTextRegex, - 'numeric-interval': QuestionNumericInterval, - 'textarea': QuestionTextArea, - # -- informative panels -- - 'information': QuestionInformation, - 'warning': QuestionInformation, - 'alert': QuestionInformation, - 'success': QuestionInformation, - } - - def __init__(self, question_dict={}): - self.question = question_dict - - # ----------------------------------------------------------------------- - # Given a ref returns an instance of a descendent of Question(), - # i.e. a question object (radio, checkbox, ...). - # ----------------------------------------------------------------------- - def generate(self): - logger.debug(f'Generating "{self.question["ref"]}"...') - # Shallow copy so that script generated questions will not replace - # the original generators - q = self.question.copy() - - # If question is of generator type, an external program will be run - # which will print a valid question in yaml format to stdout. This - # output is then yaml parsed into a dictionary `q`. - if q['type'] == 'generator': - logger.debug(f' \\_ Running "{q["script"]}".') - q.setdefault('args', []) - q.setdefault('stdin', '') - script = path.join(q['path'], q['script']) - out = run_script(script=script, args=q['args'], stdin=q['stdin']) - q.update(out) - - # Finally we create an instance of Question() - try: - qinstance = self._types[q['type']](q) # instance matching class - except QuestionException as e: - logger.error(e) - raise e - except KeyError: - logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') - raise - else: - return qinstance diff --git a/aprendizations/learnapp.py b/aprendizations/learnapp.py index ed30ce8..6cd0565 100644 --- a/aprendizations/learnapp.py +++ b/aprendizations/learnapp.py @@ -15,7 +15,7 @@ import networkx as nx # this project from aprendizations.models import Student, Answer, Topic, StudentTopic from aprendizations.knowledge import StudentKnowledge -from aprendizations.factory import QFactory +from aprendizations.questions import QFactory from aprendizations.tools import load_yaml # setup logger for this module diff --git a/aprendizations/questions.py b/aprendizations/questions.py index c201618..86b2095 100644 --- a/aprendizations/questions.py +++ b/aprendizations/questions.py @@ -30,7 +30,7 @@ class Question(dict): def __init__(self, q): super().__init__(q) - # add these if missing + # add required keys if missing self.set_defaults({ 'title': '', 'answer': None, @@ -368,3 +368,81 @@ class QuestionInformation(Question): def correct(self): super().correct() self['grade'] = 1.0 # always "correct" but points should be zero! + + +# =========================================================================== +# QFactory is a class that can generate question instances, e.g. by shuffling +# options, running a script to generate the question, etc. +# +# To generate an instance of a question we use the method generate() where +# the argument is the reference of the question we wish to produce. +# The generate() method returns a question instance of the correct class. +# +# Example: +# +# # generate a question instance from a dictionary +# qdict = { +# 'type': 'radio', +# 'text': 'Choose one', +# 'options': ['a', 'b'] +# } +# qfactory = QFactory(qdict) +# question = qfactory.generate() +# +# # answer one question and correct it +# question['answer'] = 42 # set answer +# question.correct() # correct answer +# print(question['grade']) # print grade +# =========================================================================== +class QFactory(object): + # Depending on the type of question, a different question class will be + # instantiated. All these classes derive from the base class `Question`. + _types = { + 'radio': QuestionRadio, + 'checkbox': QuestionCheckbox, + 'text': QuestionText, + 'text-regex': QuestionTextRegex, + 'numeric-interval': QuestionNumericInterval, + 'textarea': QuestionTextArea, + # -- informative panels -- + 'information': QuestionInformation, + 'warning': QuestionInformation, + 'alert': QuestionInformation, + 'success': QuestionInformation, + } + + def __init__(self, question_dict={}): + self.question = question_dict + + # ----------------------------------------------------------------------- + # Given a ref returns an instance of a descendent of Question(), + # i.e. a question object (radio, checkbox, ...). + # ----------------------------------------------------------------------- + def generate(self): + logger.debug(f'Generating "{self.question["ref"]}"...') + # Shallow copy so that script generated questions will not replace + # the original generators + q = self.question.copy() + + # If question is of generator type, an external program will be run + # which will print a valid question in yaml format to stdout. This + # output is then yaml parsed into a dictionary `q`. + if q['type'] == 'generator': + logger.debug(f' \\_ Running "{q["script"]}".') + q.setdefault('args', []) + q.setdefault('stdin', '') + script = path.join(q['path'], q['script']) + out = run_script(script=script, args=q['args'], stdin=q['stdin']) + q.update(out) + + # Finally we create an instance of Question() + try: + qinstance = self._types[q['type']](q) # instance matching class + except QuestionException as e: + logger.error(e) + raise e + except KeyError: + logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') + raise + else: + return qinstance diff --git a/aprendizations/serve.py b/aprendizations/serve.py index c039339..1a72c00 100644 --- a/aprendizations/serve.py +++ b/aprendizations/serve.py @@ -454,20 +454,21 @@ def main(): logging.info('====================== Start Logging ======================') # --- start application - logging.info('Starting App') + logging.info('Starting App...') try: - learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db, check=arg.check) - except Exception as e: - logging.critical('Failed to start application') - raise e + learnapp = LearnApp(arg.conffile, prefix=arg.prefix, db=arg.db, + check=arg.check) + except Exception: + logging.critical('Failed to start application.') + sys.exit(1) # --- create web application - logging.info('Starting Web App (tornado)') + logging.info('Starting Web App (tornado)...') try: webapp = WebApplication(learnapp, debug=arg.debug) - except Exception as e: + except Exception: logging.critical('Failed to start web application.') - raise e + sys.exit(1) # --- get SSL certificates if 'XDG_DATA_HOME' in os.environ: @@ -481,21 +482,21 @@ def main(): path.join(certs_dir, 'privkey.pem')) except FileNotFoundError: logging.critical(f'SSL certificates missing in {certs_dir}') - sys.exit(-1) + sys.exit(1) # --- create webserver try: httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl_ctx) except ValueError: logging.critical('Certificates cert.pem and privkey.pem not found') - sys.exit(-1) + sys.exit(1) httpserver.listen(arg.port) logging.info(f'Listening on port {arg.port}.') # --- run webserver - logging.info('Webserver running... (Ctrl-C to stop)') signal.signal(signal.SIGINT, signal_handler) + logging.info('Webserver running. (Ctrl-C to stop)') try: tornado.ioloop.IOLoop.current().start() # running... diff --git a/aprendizations/templates/maintopics-table.html b/aprendizations/templates/maintopics-table.html index f5985a0..6f03f75 100644 --- a/aprendizations/templates/maintopics-table.html +++ b/aprendizations/templates/maintopics-table.html @@ -33,7 +33,11 @@