diff --git a/BUGS.md b/BUGS.md
index 55a96a2..85d7255 100644
--- a/BUGS.md
+++ b/BUGS.md
@@ -1,6 +1,7 @@
# BUGS
+- testar se perguntas regex funcionam com yaml.safe_load.
- safari as vezes envia dois gets no inicio do topico. nesses casos, a segunda pergunta não é actualizada no browser... o topico tem de ser gerado qd se escolhe o topico em main_topics. O get nao deve alterar o estado.
- click numa opcao checkbox fora da checkbox+label não está a funcionar.
- shift-enter não está a funcionar
diff --git a/aprendizations/factory.py b/aprendizations/factory.py
index ea9178a..cbc3739 100644
--- a/aprendizations/factory.py
+++ b/aprendizations/factory.py
@@ -48,17 +48,17 @@ 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,
+ 'radio': QuestionRadio,
+ 'checkbox': QuestionCheckbox,
+ 'text': QuestionText,
+ 'text-regex': QuestionTextRegex,
'numeric-interval': QuestionNumericInterval,
- 'textarea': QuestionTextArea,
+ 'textarea': QuestionTextArea,
# -- informative panels --
- 'information': QuestionInformation,
- 'warning': QuestionInformation,
- 'alert': QuestionInformation,
- 'success': QuestionInformation,
+ 'information': QuestionInformation,
+ 'warning': QuestionInformation,
+ 'alert': QuestionInformation,
+ 'success': QuestionInformation,
}
def __init__(self, question_dict={}):
@@ -69,7 +69,7 @@ class QFactory(object):
# i.e. a question object (radio, checkbox, ...).
# -----------------------------------------------------------------------
# async def generate_async(self):
- # loop = asyncio.get_event_loop()
+ # loop = asyncio.get_running_loop()
# return await loop.run_in_executor(None, self.generate)
def generate(self):
diff --git a/aprendizations/knowledge.py b/aprendizations/knowledge.py
index 3f7d99a..1adc8f0 100644
--- a/aprendizations/knowledge.py
+++ b/aprendizations/knowledge.py
@@ -43,34 +43,33 @@ class StudentKnowledge(object):
now = datetime.now()
for tref, s in self.state.items():
dt = now - s['date']
- s['level'] *= 0.95 ** dt.days # forgetting factor 0.95 FIXME
+ s['level'] *= 0.98 ** dt.days # forgetting factor 0.95 FIXME
# ------------------------------------------------------------------------
# Unlock topics whose dependencies are satisfied (> min_level)
# ------------------------------------------------------------------------
def unlock_topics(self):
- # minimum level that the dependencies of a topic must have
- # for the topic to be unlocked.
- min_level = 0.01
-
for topic in self.deps.nodes():
if topic not in self.state: # if locked
pred = self.deps.predecessors(topic)
+ min_level = self.deps.node[topic]['min_level']
if all(d in self.state and self.state[d]['level'] > min_level
- for d in pred): # all dependencies are done
+ for d in pred): # all deps are greater than min_level
self.state[topic] = {
'level': 0.0, # unlocked
'date': datetime.now()
}
logger.debug(f'Unlocked "{topic}".')
+ # else: # lock this topic if deps do not satisfy min_level
+ # del self.state[topic]
# ------------------------------------------------------------------------
# Start a new topic.
# questions: list of generated questions to do in the topic
# current_question: the current question to be presented
# ------------------------------------------------------------------------
- # FIXME async mas nao tem awaits...
+ # FIXME async mas nao tem awaits... do not allow restart same topic
async def start_topic(self, topic):
logger.debug(f'StudentKnowledge.start_topic({topic})')
if self.current_topic == topic:
@@ -88,7 +87,7 @@ class StudentKnowledge(object):
t = self.deps.node[topic]
k = t['choose']
- if t['shuffle']:
+ if t['shuffle_questions']:
questions = random.sample(t['questions'], k=k)
else:
questions = t['questions'][:k]
@@ -131,11 +130,10 @@ class StudentKnowledge(object):
q = self.current_question
q['answer'] = answer
q['finish_time'] = datetime.now()
- grade = await q.correct_async()
-
- logger.debug(f'Grade {grade:.2} ({q["ref"]})')
+ await q.correct_async()
+ logger.debug(f'Grade {q["grade"]:.2} ({q["ref"]})')
- if grade > 0.999:
+ if q['grade'] > 0.999:
self.correct_answers += 1
self.next_question()
action = 'right'
diff --git a/aprendizations/learnapp.py b/aprendizations/learnapp.py
index dc193b8..2e24338 100644
--- a/aprendizations/learnapp.py
+++ b/aprendizations/learnapp.py
@@ -26,8 +26,7 @@ logger = logging.getLogger(__name__)
# helper functions
# ============================================================================
async def _bcrypt_hash(a, b):
- # loop = asyncio.get_running_loop() # FIXME python 3.7 only
- loop = asyncio.get_event_loop()
+ loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, bcrypt.hashpw,
a.encode('utf-8'), b)
@@ -248,11 +247,12 @@ class LearnApp(object):
# default attributes that apply to the topics
default_file = config.get('file', 'questions.yaml')
- default_shuffle = config.get('shuffle', True)
+ default_shuffle_questions = config.get('shuffle_questions', True)
default_choose = config.get('choose', 9999)
default_forgetting_factor = config.get('forgetting_factor', 1.0)
default_maxtries = config.get('max_tries', 3)
default_append_wrong = config.get('append_wrong', True)
+ default_min_level = config.get('min_level', 0.01) # to unlock topic
# iterate over topics and populate graph
topics = config.get('topics', {})
@@ -267,10 +267,11 @@ class LearnApp(object):
t['name'] = attr.get('name', tref)
t['path'] = path.join(g.graph['prefix'], tref) # prefix/topic
t['file'] = attr.get('file', default_file) # questions.yaml
- t['shuffle'] = attr.get('shuffle', default_shuffle)
+ t['shuffle_questions'] = attr.get('shuffle_questions', default_shuffle_questions)
t['max_tries'] = attr.get('max_tries', default_maxtries)
t['forgetting_factor'] = attr.get('forgetting_factor',
default_forgetting_factor)
+ t['min_level'] = attr.get('min_level', default_min_level)
t['choose'] = attr.get('choose', default_choose)
t['append_wrong'] = attr.get('append_wrong', default_append_wrong)
t['questions'] = attr.get('questions', [])
diff --git a/aprendizations/questions.py b/aprendizations/questions.py
index 5de9374..0bd37f7 100644
--- a/aprendizations/questions.py
+++ b/aprendizations/questions.py
@@ -43,17 +43,15 @@ class Question(dict):
'files': {},
})
- def correct(self) -> float:
+ def correct(self) -> None:
self['comments'] = ''
self['grade'] = 0.0
- return 0.0
- async def correct_async(self) -> float:
- loop = asyncio.get_running_loop() # FIXME python 3.7 only
- # loop = asyncio.get_event_loop() # python 3.6
- return await loop.run_in_executor(None, self.correct)
+ async def correct_async(self) -> None:
+ loop = asyncio.get_running_loop()
+ await loop.run_in_executor(None, self.correct)
- def set_defaults(self, d):
+ def set_defaults(self, d) -> None:
'Add k:v pairs from default dict d for nonexistent keys'
for k, v in d.items():
self.setdefault(k, v)
@@ -204,8 +202,6 @@ class QuestionCheckbox(Question):
self['grade'] = x / sum_abs
- return self['grade']
-
# ===========================================================================
class QuestionText(Question):
@@ -239,8 +235,6 @@ class QuestionText(Question):
if self['answer'] is not None:
self['grade'] = 1.0 if self['answer'] in self['correct'] else 0.0
- return self['grade']
-
# ===========================================================================
class QuestionTextRegex(Question):
@@ -271,8 +265,6 @@ class QuestionTextRegex(Question):
f'answer {self["answer"]}.')
self['grade'] = 1.0 if ok else 0.0
- return self['grade']
-
# ===========================================================================
class QuestionNumericInterval(Question):
@@ -308,8 +300,6 @@ class QuestionNumericInterval(Question):
else:
self['grade'] = 1.0 if lower <= answer <= upper else 0.0
- return self['grade']
-
# ===========================================================================
class QuestionTextArea(Question):
@@ -358,8 +348,6 @@ class QuestionTextArea(Question):
else:
self['grade'] = float(out)
- return self['grade']
-
# ===========================================================================
class QuestionInformation(Question):
@@ -375,4 +363,3 @@ class QuestionInformation(Question):
def correct(self):
super().correct()
self['grade'] = 1.0 # always "correct" but points should be zero!
- return self['grade']
diff --git a/aprendizations/serve.py b/aprendizations/serve.py
index 8b04785..cd7a7ec 100644
--- a/aprendizations/serve.py
+++ b/aprendizations/serve.py
@@ -95,7 +95,7 @@ class LoginHandler(BaseHandler):
self.render('login.html', error='')
async def post(self):
- uid = self.get_body_argument('uid')
+ uid = self.get_body_argument('uid').lstrip('l')
pw = self.get_body_argument('pw')
login_ok = await self.learn.login(uid, pw)
diff --git a/aprendizations/static/css/topic.css b/aprendizations/static/css/topic.css
index 90db3ed..01aae8e 100644
--- a/aprendizations/static/css/topic.css
+++ b/aprendizations/static/css/topic.css
@@ -23,6 +23,7 @@ html {
position: relative;
min-height: 100%;
}
-textarea {
- font-family: monospace;
+.CodeMirror {
+ border: 1px solid #eee;
+ height: auto;
}
diff --git a/aprendizations/templates/question-textarea.html b/aprendizations/templates/question-textarea.html
index 8a9c435..895dcbf 100644
--- a/aprendizations/templates/question-textarea.html
+++ b/aprendizations/templates/question-textarea.html
@@ -7,10 +7,9 @@
diff --git a/aprendizations/templates/topic.html b/aprendizations/templates/topic.html
index 9a47685..911b930 100644
--- a/aprendizations/templates/topic.html
+++ b/aprendizations/templates/topic.html
@@ -32,8 +32,8 @@
-
-
+
+
diff --git a/aprendizations/tools.py b/aprendizations/tools.py
index 9f10cc5..2d7d727 100644
--- a/aprendizations/tools.py
+++ b/aprendizations/tools.py
@@ -147,7 +147,7 @@ def load_yaml(filename, default=None):
else:
with f:
try:
- default = yaml.load(f)
+ default = yaml.safe_load(f) # FIXME check if supports all kinds of questions including regex
except yaml.YAMLError as e:
mark = e.problem_mark
logger.error(f'In file "{filename}" near line {mark.line}, '
@@ -187,7 +187,7 @@ def run_script(script, args=[], stdin='', timeout=5):
logger.error(f'Return code {p.returncode} running "{script}".')
else:
try:
- output = yaml.load(p.stdout)
+ output = yaml.safe_load(p.stdout)
except Exception:
logger.error(f'Error parsing yaml output of "{script}"')
else:
diff --git a/demo/demo.yaml b/demo/demo.yaml
index 2298e93..ec92143 100644
--- a/demo/demo.yaml
+++ b/demo/demo.yaml
@@ -5,12 +5,13 @@ database: students.db
# values applie to each topic, if undefined there
-# default values are: file=question.yaml, shuffle=True, choose: all
file: questions.yaml
-shuffle: false
+shuffle_questions: true
choose: 6
max_tries: 2
-forgetting_factor: 0.99
+forgetting_factor: 0.97
+min_level: 0.01
+append_wrong: true
# ----------------------------------------------------------------------------
topics:
--
libgit2 0.21.2