diff --git a/BUGS.md b/BUGS.md
index 578c7a5..82e3df0 100644
--- a/BUGS.md
+++ b/BUGS.md
@@ -3,6 +3,7 @@ BUGS:
- servidor http com redirect para https.
- servir imagens/ficheiros.
+- codemirror em textarea.
- topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores.
@@ -29,6 +30,7 @@ TODO:
FIXED:
+- database: answers não tem referencia para o topico, so para question_ref
- melhorar markdown das tabelas.
- gravar evolucao na bd no final de cada topico.
- submeter questoes radio, da erro se nao escolher nenhuma opção.
diff --git a/initdb.py b/initdb.py
index 26b105c..9bffd43 100755
--- a/initdb.py
+++ b/initdb.py
@@ -19,11 +19,29 @@ def fix(name):
# ===========================================================================
# Parse command line options
-argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)')
-argparser.add_argument('--db', default='students.db', type=str, help='database filename')
-argparser.add_argument('--demo', action='store_true', help='initialize database with a few fake students')
-argparser.add_argument('--pw', default='', type=str, help='default password')
-argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename')
+argparser = argparse.ArgumentParser(
+ description='Create new database from a CSV file (SIIUE format)')
+
+argparser.add_argument('--db',
+ default='students.db',
+ type=str,
+ help='database filename')
+
+argparser.add_argument('--demo',
+ action='store_true',
+ help='initialize database with a few fake students')
+
+argparser.add_argument('--pw',
+ default='',
+ type=str,
+ help='default password')
+
+argparser.add_argument('csvfile',
+ nargs='?',
+ type=str,
+ default='',
+ help='CSV filename')
+
args = argparser.parse_args()
# =======================================================x====================
@@ -82,7 +100,8 @@ try:
except Exception as e:
print(f'Error: Database "{args.db}" already exists?')
session.rollback()
- exit(1)
+ raise e
+ # exit(1)
else:
# --- end session ---
diff --git a/knowledge.py b/knowledge.py
index 0da2651..a78fe93 100644
--- a/knowledge.py
+++ b/knowledge.py
@@ -55,7 +55,7 @@ class StudentKnowledge(object):
'level': 0.0, # then unlock
'date': datetime.now()
}
- logger.debug(f'unlocked {topic}')
+ logger.debug(f'Unlocked "{topic}".')
# ------------------------------------------------------------------------
diff --git a/learnapp.py b/learnapp.py
index 0c3413e..7631481 100644
--- a/learnapp.py
+++ b/learnapp.py
@@ -10,7 +10,6 @@ import bcrypt
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import networkx as nx
-import yaml
# this project
from models import Student, Answer, Topic, StudentTopic
@@ -25,22 +24,25 @@ logger = logging.getLogger(__name__)
class LearnAppException(Exception):
pass
+
# ============================================================================
# LearnApp - application logic
# ============================================================================
class LearnApp(object):
- def __init__(self, conffile):
+ def __init__(self, config_file):
# state of online students
self.online = {}
- # dependency graph shared by all students
- self.deps = build_dependency_graph(conffile)
+ config = load_yaml(config_file)
# connect to database and checks for registered students
- self.db_setup(self.deps.graph['database'])
+ self.db_setup(config['database'])
+
+ # dependency graph shared by all students
+ self.deps = build_dependency_graph(config)
# add topics from dependency graph to the database, if missing
- self.db_add_topics()
+ self.db_add_missing_topics(self.deps.nodes())
# ------------------------------------------------------------------------
# login
@@ -79,29 +81,6 @@ class LearnApp(object):
# logout
# ------------------------------------------------------------------------
def logout(self, uid):
- # state = self.online[uid]['state'].state # dict {node:level,...}
- # # save topics state to database
- # with self.db_session(autoflush=False) as s:
-
- # # update existing associations and remove from state dict
- # for a in s.query(StudentTopic).filter_by(student_id=uid):
- # if a.topic_id in state:
- # d = state.pop(a.topic_id)
- # a.level = d['level'] #state.pop(a.topic_id) # update
- # a.date = str(d['date'])
- # s.add(a)
-
- # # insert the remaining ones
- # u = s.query(Student).get(uid)
- # for n,d in state.items():
- # a = StudentTopic(level=d['level'], date=str(d['date']))
- # t = s.query(Topic).get(n)
- # if t is None: # create if topic doesn't exist yet
- # t = Topic(id=n)
- # a.topic = t
- # u.topics.append(a)
- # s.add(a)
-
del self.online[uid]
logger.info(f'User "{uid}" logged out')
@@ -126,23 +105,14 @@ class LearnApp(object):
knowledge = self.online[uid]['state']
grade = knowledge.check_answer(answer)
- # if finished topic, save in database
if knowledge.get_current_question() is None:
+ # finished topic, save into database
finished_topic = knowledge.get_current_topic()
level = knowledge.get_topic_level(finished_topic)
date = str(knowledge.get_topic_date(finished_topic))
+ finished_questions = knowledge.get_finished_questions()
with self.db_session(autoflush=False) as s:
- # save questions from finished_questions list
- s.add_all([
- Answer(
- ref=q['ref'],
- grade=q['grade'],
- starttime=str(q['start_time']),
- finishtime=str(q['finish_time']),
- student_id=uid)
- for q in knowledge.get_finished_questions()])
-
# save topic
a = s.query(StudentTopic).filter_by(student_id=uid, topic_id=finished_topic).one_or_none()
if a is None:
@@ -152,12 +122,23 @@ class LearnApp(object):
t = s.query(Topic).get(finished_topic)
a.topic = t
u.topics.append(a)
- s.add(a)
else:
# update studenttopic in database
a.level = level
a.date = date
- s.add(a)
+
+ s.add(a)
+
+ # save answered questions from finished_questions list
+ s.add_all([
+ Answer(
+ ref=q['ref'],
+ grade=q['grade'],
+ starttime=str(q['start_time']),
+ finishtime=str(q['finish_time']),
+ student_id=uid,
+ topic_id=finished_topic)
+ for q in finished_questions])
return grade
@@ -170,26 +151,33 @@ class LearnApp(object):
# ------------------------------------------------------------------------
# Fill db table 'Topic' with topics from the graph if not already there.
# ------------------------------------------------------------------------
- def db_add_topics(self):
+ def db_add_missing_topics(self, nn):
with self.db_session() as s:
tt = [t[0] for t in s.query(Topic.id)] # db list of topics
- nn = self.deps.nodes() # topics in the graph
- s.add_all([Topic(id=n) for n in nn if n not in tt])
+ missing_topics = [Topic(id=n) for n in nn if n not in tt]
+ if missing_topics:
+ s.add_all(missing_topics)
+ logger.info(f'Added {len(missing_topics)} new topics to the database.')
# ------------------------------------------------------------------------
# setup and check database
# ------------------------------------------------------------------------
def db_setup(self, db):
+ logger.info(f'Checking database "{db}":')
engine = create_engine(f'sqlite:///{db}', echo=False)
self.Session = sessionmaker(bind=engine)
try:
with self.db_session() as s:
n = s.query(Student).count()
+ m = s.query(Topic).count()
+ q = s.query(Answer).count()
except Exception as e:
logger.critical(f'Database "{db}" not usable.')
sys.exit(1)
else:
- logger.info(f'Database "{db}" has {n} students.')
+ logger.info(f'{n:4} students.')
+ logger.info(f'{m:4} topics.')
+ logger.info(f'{q:4} questions answered.')
# ------------------------------------------------------------------------
# helper to manage db sessions using the `with` statement, for example
@@ -255,7 +243,8 @@ class LearnApp(object):
# ============================================================================
-# Given configuration file, loads YAML on that file and builds a digraph.
+# Builds a digraph.
+#
# First, topics such as `computer/mips/exceptions` are added as nodes
# together with dependencies. Then, questions are loaded to a factory.
#
@@ -268,19 +257,8 @@ class LearnApp(object):
# g.node['my/topic']['questions'] list of question refs defined in YAML
# g.node['my/topic']['factory'] dict with question factories
# ----------------------------------------------------------------------------
-def build_dependency_graph(config_file):
- # Load configuration file to a dict
- try:
- with open(config_file, 'r') as f:
- config = yaml.load(f)
- except FileNotFoundError:
- logger.critical(f'File not found: "{config_file}"')
- raise LearnAppException
- except yaml.scanner.ScannerError as err:
- logger.critical(f'Parsing YAML file "{config_file}": {err}')
- raise LearnAppException
- else:
- logger.info(f'Configuration file "{config_file}"')
+def build_dependency_graph(config={}):
+ logger.info('Building topic dependency graph.')
# create graph
prefix = config.get('path', '.')
diff --git a/models.py b/models.py
index 99fe742..5dcd3a0 100644
--- a/models.py
+++ b/models.py
@@ -51,9 +51,11 @@ class Answer(Base):
starttime = Column(String)
finishtime = Column(String)
student_id = Column(String, ForeignKey('students.id'))
+ topic_id = Column(String, ForeignKey('topics.id'))
# ---
student = relationship('Student', back_populates='answers')
+ topic = relationship('Topic', back_populates='answers')
def __repr__(self):
return '''Question:
@@ -73,6 +75,7 @@ class Topic(Base):
# ---
students = relationship('StudentTopic', back_populates='topic')
+ answers = relationship('Answer', back_populates='topic')
# def __init__(self, id):
# self.id = id
diff --git a/serve.py b/serve.py
index a45ad27..bcd7470 100755
--- a/serve.py
+++ b/serve.py
@@ -197,7 +197,7 @@ class QuestionHandler(BaseHandler):
return {
'method': 'finished_topic',
'params': { # FIXME no html here please!
- 'question': f''
+ 'question': f'
'
}
}
diff --git a/static/trophy.png b/static/trophy.png
deleted file mode 100644
index 14df155..0000000
Binary files a/static/trophy.png and /dev/null differ
diff --git a/templates/maintopics.html b/templates/maintopics.html
index db0a8fd..1be0937 100644
--- a/templates/maintopics.html
+++ b/templates/maintopics.html
@@ -1,6 +1,6 @@
{% autoescape %}
-
+