Commit efdbe121d0ec2d33eb16d4345b744a7c0c2a1c9a
1 parent
0a01fe13
Exists in
master
and in
1 other branch
- added database column "topic" in answers table.
- some code refactoring and cleanup in learnapp.
Showing
10 changed files
with
86 additions
and
80 deletions
Show diff stats
BUGS.md
@@ -3,6 +3,7 @@ BUGS: | @@ -3,6 +3,7 @@ BUGS: | ||
3 | 3 | ||
4 | - servidor http com redirect para https. | 4 | - servidor http com redirect para https. |
5 | - servir imagens/ficheiros. | 5 | - servir imagens/ficheiros. |
6 | +- codemirror em textarea. | ||
6 | - topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores. | 7 | - topicos virtuais nao deveriam aparecer. na construção da árvore os sucessores seriam ligados directamente aos predecessores. |
7 | 8 | ||
8 | 9 | ||
@@ -29,6 +30,7 @@ TODO: | @@ -29,6 +30,7 @@ TODO: | ||
29 | 30 | ||
30 | FIXED: | 31 | FIXED: |
31 | 32 | ||
33 | +- database: answers não tem referencia para o topico, so para question_ref | ||
32 | - melhorar markdown das tabelas. | 34 | - melhorar markdown das tabelas. |
33 | - gravar evolucao na bd no final de cada topico. | 35 | - gravar evolucao na bd no final de cada topico. |
34 | - submeter questoes radio, da erro se nao escolher nenhuma opção. | 36 | - submeter questoes radio, da erro se nao escolher nenhuma opção. |
initdb.py
@@ -19,11 +19,29 @@ def fix(name): | @@ -19,11 +19,29 @@ def fix(name): | ||
19 | 19 | ||
20 | # =========================================================================== | 20 | # =========================================================================== |
21 | # Parse command line options | 21 | # Parse command line options |
22 | -argparser = argparse.ArgumentParser(description='Create new database from a CSV file (SIIUE format)') | ||
23 | -argparser.add_argument('--db', default='students.db', type=str, help='database filename') | ||
24 | -argparser.add_argument('--demo', action='store_true', help='initialize database with a few fake students') | ||
25 | -argparser.add_argument('--pw', default='', type=str, help='default password') | ||
26 | -argparser.add_argument('csvfile', nargs='?', type=str, default='', help='CSV filename') | 22 | +argparser = argparse.ArgumentParser( |
23 | + description='Create new database from a CSV file (SIIUE format)') | ||
24 | + | ||
25 | +argparser.add_argument('--db', | ||
26 | + default='students.db', | ||
27 | + type=str, | ||
28 | + help='database filename') | ||
29 | + | ||
30 | +argparser.add_argument('--demo', | ||
31 | + action='store_true', | ||
32 | + help='initialize database with a few fake students') | ||
33 | + | ||
34 | +argparser.add_argument('--pw', | ||
35 | + default='', | ||
36 | + type=str, | ||
37 | + help='default password') | ||
38 | + | ||
39 | +argparser.add_argument('csvfile', | ||
40 | + nargs='?', | ||
41 | + type=str, | ||
42 | + default='', | ||
43 | + help='CSV filename') | ||
44 | + | ||
27 | args = argparser.parse_args() | 45 | args = argparser.parse_args() |
28 | 46 | ||
29 | # =======================================================x==================== | 47 | # =======================================================x==================== |
@@ -82,7 +100,8 @@ try: | @@ -82,7 +100,8 @@ try: | ||
82 | except Exception as e: | 100 | except Exception as e: |
83 | print(f'Error: Database "{args.db}" already exists?') | 101 | print(f'Error: Database "{args.db}" already exists?') |
84 | session.rollback() | 102 | session.rollback() |
85 | - exit(1) | 103 | + raise e |
104 | + # exit(1) | ||
86 | 105 | ||
87 | else: | 106 | else: |
88 | # --- end session --- | 107 | # --- end session --- |
knowledge.py
@@ -55,7 +55,7 @@ class StudentKnowledge(object): | @@ -55,7 +55,7 @@ class StudentKnowledge(object): | ||
55 | 'level': 0.0, # then unlock | 55 | 'level': 0.0, # then unlock |
56 | 'date': datetime.now() | 56 | 'date': datetime.now() |
57 | } | 57 | } |
58 | - logger.debug(f'unlocked {topic}') | 58 | + logger.debug(f'Unlocked "{topic}".') |
59 | 59 | ||
60 | 60 | ||
61 | # ------------------------------------------------------------------------ | 61 | # ------------------------------------------------------------------------ |
learnapp.py
@@ -10,7 +10,6 @@ import bcrypt | @@ -10,7 +10,6 @@ import bcrypt | ||
10 | from sqlalchemy import create_engine | 10 | from sqlalchemy import create_engine |
11 | from sqlalchemy.orm import sessionmaker | 11 | from sqlalchemy.orm import sessionmaker |
12 | import networkx as nx | 12 | import networkx as nx |
13 | -import yaml | ||
14 | 13 | ||
15 | # this project | 14 | # this project |
16 | from models import Student, Answer, Topic, StudentTopic | 15 | from models import Student, Answer, Topic, StudentTopic |
@@ -25,22 +24,25 @@ logger = logging.getLogger(__name__) | @@ -25,22 +24,25 @@ logger = logging.getLogger(__name__) | ||
25 | class LearnAppException(Exception): | 24 | class LearnAppException(Exception): |
26 | pass | 25 | pass |
27 | 26 | ||
27 | + | ||
28 | # ============================================================================ | 28 | # ============================================================================ |
29 | # LearnApp - application logic | 29 | # LearnApp - application logic |
30 | # ============================================================================ | 30 | # ============================================================================ |
31 | class LearnApp(object): | 31 | class LearnApp(object): |
32 | - def __init__(self, conffile): | 32 | + def __init__(self, config_file): |
33 | # state of online students | 33 | # state of online students |
34 | self.online = {} | 34 | self.online = {} |
35 | 35 | ||
36 | - # dependency graph shared by all students | ||
37 | - self.deps = build_dependency_graph(conffile) | 36 | + config = load_yaml(config_file) |
38 | 37 | ||
39 | # connect to database and checks for registered students | 38 | # connect to database and checks for registered students |
40 | - self.db_setup(self.deps.graph['database']) | 39 | + self.db_setup(config['database']) |
40 | + | ||
41 | + # dependency graph shared by all students | ||
42 | + self.deps = build_dependency_graph(config) | ||
41 | 43 | ||
42 | # add topics from dependency graph to the database, if missing | 44 | # add topics from dependency graph to the database, if missing |
43 | - self.db_add_topics() | 45 | + self.db_add_missing_topics(self.deps.nodes()) |
44 | 46 | ||
45 | # ------------------------------------------------------------------------ | 47 | # ------------------------------------------------------------------------ |
46 | # login | 48 | # login |
@@ -79,29 +81,6 @@ class LearnApp(object): | @@ -79,29 +81,6 @@ class LearnApp(object): | ||
79 | # logout | 81 | # logout |
80 | # ------------------------------------------------------------------------ | 82 | # ------------------------------------------------------------------------ |
81 | def logout(self, uid): | 83 | def logout(self, uid): |
82 | - # state = self.online[uid]['state'].state # dict {node:level,...} | ||
83 | - # # save topics state to database | ||
84 | - # with self.db_session(autoflush=False) as s: | ||
85 | - | ||
86 | - # # update existing associations and remove from state dict | ||
87 | - # for a in s.query(StudentTopic).filter_by(student_id=uid): | ||
88 | - # if a.topic_id in state: | ||
89 | - # d = state.pop(a.topic_id) | ||
90 | - # a.level = d['level'] #state.pop(a.topic_id) # update | ||
91 | - # a.date = str(d['date']) | ||
92 | - # s.add(a) | ||
93 | - | ||
94 | - # # insert the remaining ones | ||
95 | - # u = s.query(Student).get(uid) | ||
96 | - # for n,d in state.items(): | ||
97 | - # a = StudentTopic(level=d['level'], date=str(d['date'])) | ||
98 | - # t = s.query(Topic).get(n) | ||
99 | - # if t is None: # create if topic doesn't exist yet | ||
100 | - # t = Topic(id=n) | ||
101 | - # a.topic = t | ||
102 | - # u.topics.append(a) | ||
103 | - # s.add(a) | ||
104 | - | ||
105 | del self.online[uid] | 84 | del self.online[uid] |
106 | logger.info(f'User "{uid}" logged out') | 85 | logger.info(f'User "{uid}" logged out') |
107 | 86 | ||
@@ -126,23 +105,14 @@ class LearnApp(object): | @@ -126,23 +105,14 @@ class LearnApp(object): | ||
126 | knowledge = self.online[uid]['state'] | 105 | knowledge = self.online[uid]['state'] |
127 | grade = knowledge.check_answer(answer) | 106 | grade = knowledge.check_answer(answer) |
128 | 107 | ||
129 | - # if finished topic, save in database | ||
130 | if knowledge.get_current_question() is None: | 108 | if knowledge.get_current_question() is None: |
109 | + # finished topic, save into database | ||
131 | finished_topic = knowledge.get_current_topic() | 110 | finished_topic = knowledge.get_current_topic() |
132 | level = knowledge.get_topic_level(finished_topic) | 111 | level = knowledge.get_topic_level(finished_topic) |
133 | date = str(knowledge.get_topic_date(finished_topic)) | 112 | date = str(knowledge.get_topic_date(finished_topic)) |
113 | + finished_questions = knowledge.get_finished_questions() | ||
134 | 114 | ||
135 | with self.db_session(autoflush=False) as s: | 115 | with self.db_session(autoflush=False) as s: |
136 | - # save questions from finished_questions list | ||
137 | - s.add_all([ | ||
138 | - Answer( | ||
139 | - ref=q['ref'], | ||
140 | - grade=q['grade'], | ||
141 | - starttime=str(q['start_time']), | ||
142 | - finishtime=str(q['finish_time']), | ||
143 | - student_id=uid) | ||
144 | - for q in knowledge.get_finished_questions()]) | ||
145 | - | ||
146 | # save topic | 116 | # save topic |
147 | a = s.query(StudentTopic).filter_by(student_id=uid, topic_id=finished_topic).one_or_none() | 117 | a = s.query(StudentTopic).filter_by(student_id=uid, topic_id=finished_topic).one_or_none() |
148 | if a is None: | 118 | if a is None: |
@@ -152,12 +122,23 @@ class LearnApp(object): | @@ -152,12 +122,23 @@ class LearnApp(object): | ||
152 | t = s.query(Topic).get(finished_topic) | 122 | t = s.query(Topic).get(finished_topic) |
153 | a.topic = t | 123 | a.topic = t |
154 | u.topics.append(a) | 124 | u.topics.append(a) |
155 | - s.add(a) | ||
156 | else: | 125 | else: |
157 | # update studenttopic in database | 126 | # update studenttopic in database |
158 | a.level = level | 127 | a.level = level |
159 | a.date = date | 128 | a.date = date |
160 | - s.add(a) | 129 | + |
130 | + s.add(a) | ||
131 | + | ||
132 | + # save answered questions from finished_questions list | ||
133 | + s.add_all([ | ||
134 | + Answer( | ||
135 | + ref=q['ref'], | ||
136 | + grade=q['grade'], | ||
137 | + starttime=str(q['start_time']), | ||
138 | + finishtime=str(q['finish_time']), | ||
139 | + student_id=uid, | ||
140 | + topic_id=finished_topic) | ||
141 | + for q in finished_questions]) | ||
161 | 142 | ||
162 | return grade | 143 | return grade |
163 | 144 | ||
@@ -170,26 +151,33 @@ class LearnApp(object): | @@ -170,26 +151,33 @@ class LearnApp(object): | ||
170 | # ------------------------------------------------------------------------ | 151 | # ------------------------------------------------------------------------ |
171 | # Fill db table 'Topic' with topics from the graph if not already there. | 152 | # Fill db table 'Topic' with topics from the graph if not already there. |
172 | # ------------------------------------------------------------------------ | 153 | # ------------------------------------------------------------------------ |
173 | - def db_add_topics(self): | 154 | + def db_add_missing_topics(self, nn): |
174 | with self.db_session() as s: | 155 | with self.db_session() as s: |
175 | tt = [t[0] for t in s.query(Topic.id)] # db list of topics | 156 | tt = [t[0] for t in s.query(Topic.id)] # db list of topics |
176 | - nn = self.deps.nodes() # topics in the graph | ||
177 | - s.add_all([Topic(id=n) for n in nn if n not in tt]) | 157 | + missing_topics = [Topic(id=n) for n in nn if n not in tt] |
158 | + if missing_topics: | ||
159 | + s.add_all(missing_topics) | ||
160 | + logger.info(f'Added {len(missing_topics)} new topics to the database.') | ||
178 | 161 | ||
179 | # ------------------------------------------------------------------------ | 162 | # ------------------------------------------------------------------------ |
180 | # setup and check database | 163 | # setup and check database |
181 | # ------------------------------------------------------------------------ | 164 | # ------------------------------------------------------------------------ |
182 | def db_setup(self, db): | 165 | def db_setup(self, db): |
166 | + logger.info(f'Checking database "{db}":') | ||
183 | engine = create_engine(f'sqlite:///{db}', echo=False) | 167 | engine = create_engine(f'sqlite:///{db}', echo=False) |
184 | self.Session = sessionmaker(bind=engine) | 168 | self.Session = sessionmaker(bind=engine) |
185 | try: | 169 | try: |
186 | with self.db_session() as s: | 170 | with self.db_session() as s: |
187 | n = s.query(Student).count() | 171 | n = s.query(Student).count() |
172 | + m = s.query(Topic).count() | ||
173 | + q = s.query(Answer).count() | ||
188 | except Exception as e: | 174 | except Exception as e: |
189 | logger.critical(f'Database "{db}" not usable.') | 175 | logger.critical(f'Database "{db}" not usable.') |
190 | sys.exit(1) | 176 | sys.exit(1) |
191 | else: | 177 | else: |
192 | - logger.info(f'Database "{db}" has {n} students.') | 178 | + logger.info(f'{n:4} students.') |
179 | + logger.info(f'{m:4} topics.') | ||
180 | + logger.info(f'{q:4} questions answered.') | ||
193 | 181 | ||
194 | # ------------------------------------------------------------------------ | 182 | # ------------------------------------------------------------------------ |
195 | # helper to manage db sessions using the `with` statement, for example | 183 | # helper to manage db sessions using the `with` statement, for example |
@@ -255,7 +243,8 @@ class LearnApp(object): | @@ -255,7 +243,8 @@ class LearnApp(object): | ||
255 | 243 | ||
256 | 244 | ||
257 | # ============================================================================ | 245 | # ============================================================================ |
258 | -# Given configuration file, loads YAML on that file and builds a digraph. | 246 | +# Builds a digraph. |
247 | +# | ||
259 | # First, topics such as `computer/mips/exceptions` are added as nodes | 248 | # First, topics such as `computer/mips/exceptions` are added as nodes |
260 | # together with dependencies. Then, questions are loaded to a factory. | 249 | # together with dependencies. Then, questions are loaded to a factory. |
261 | # | 250 | # |
@@ -268,19 +257,8 @@ class LearnApp(object): | @@ -268,19 +257,8 @@ class LearnApp(object): | ||
268 | # g.node['my/topic']['questions'] list of question refs defined in YAML | 257 | # g.node['my/topic']['questions'] list of question refs defined in YAML |
269 | # g.node['my/topic']['factory'] dict with question factories | 258 | # g.node['my/topic']['factory'] dict with question factories |
270 | # ---------------------------------------------------------------------------- | 259 | # ---------------------------------------------------------------------------- |
271 | -def build_dependency_graph(config_file): | ||
272 | - # Load configuration file to a dict | ||
273 | - try: | ||
274 | - with open(config_file, 'r') as f: | ||
275 | - config = yaml.load(f) | ||
276 | - except FileNotFoundError: | ||
277 | - logger.critical(f'File not found: "{config_file}"') | ||
278 | - raise LearnAppException | ||
279 | - except yaml.scanner.ScannerError as err: | ||
280 | - logger.critical(f'Parsing YAML file "{config_file}": {err}') | ||
281 | - raise LearnAppException | ||
282 | - else: | ||
283 | - logger.info(f'Configuration file "{config_file}"') | 260 | +def build_dependency_graph(config={}): |
261 | + logger.info('Building topic dependency graph.') | ||
284 | 262 | ||
285 | # create graph | 263 | # create graph |
286 | prefix = config.get('path', '.') | 264 | prefix = config.get('path', '.') |
models.py
@@ -51,9 +51,11 @@ class Answer(Base): | @@ -51,9 +51,11 @@ class Answer(Base): | ||
51 | starttime = Column(String) | 51 | starttime = Column(String) |
52 | finishtime = Column(String) | 52 | finishtime = Column(String) |
53 | student_id = Column(String, ForeignKey('students.id')) | 53 | student_id = Column(String, ForeignKey('students.id')) |
54 | + topic_id = Column(String, ForeignKey('topics.id')) | ||
54 | 55 | ||
55 | # --- | 56 | # --- |
56 | student = relationship('Student', back_populates='answers') | 57 | student = relationship('Student', back_populates='answers') |
58 | + topic = relationship('Topic', back_populates='answers') | ||
57 | 59 | ||
58 | def __repr__(self): | 60 | def __repr__(self): |
59 | return '''Question: | 61 | return '''Question: |
@@ -73,6 +75,7 @@ class Topic(Base): | @@ -73,6 +75,7 @@ class Topic(Base): | ||
73 | 75 | ||
74 | # --- | 76 | # --- |
75 | students = relationship('StudentTopic', back_populates='topic') | 77 | students = relationship('StudentTopic', back_populates='topic') |
78 | + answers = relationship('Answer', back_populates='topic') | ||
76 | 79 | ||
77 | # def __init__(self, id): | 80 | # def __init__(self, id): |
78 | # self.id = id | 81 | # self.id = id |
serve.py
@@ -197,7 +197,7 @@ class QuestionHandler(BaseHandler): | @@ -197,7 +197,7 @@ class QuestionHandler(BaseHandler): | ||
197 | return { | 197 | return { |
198 | 'method': 'finished_topic', | 198 | 'method': 'finished_topic', |
199 | 'params': { # FIXME no html here please! | 199 | 'params': { # FIXME no html here please! |
200 | - 'question': f'<img src="/static/trophy.svg" alt="trophy" class="img-fluid mx-auto d-block" width="50%">' | 200 | + 'question': f'<img src="/static/trophy.svg" alt="trophy" class="img-fluid mx-auto d-block" width="35%">' |
201 | } | 201 | } |
202 | } | 202 | } |
203 | 203 |
static/trophy.png
163 KB
templates/maintopics.html
1 | {% autoescape %} | 1 | {% autoescape %} |
2 | 2 | ||
3 | -<!DOCTYPE html> | 3 | +<!doctype html> |
4 | <html lang="pt-PT"> | 4 | <html lang="pt-PT"> |
5 | <head> | 5 | <head> |
6 | <title>iLearn</title> | 6 | <title>iLearn</title> |
@@ -11,7 +11,7 @@ | @@ -11,7 +11,7 @@ | ||
11 | <meta name="author" content="Miguel Barão"> | 11 | <meta name="author" content="Miguel Barão"> |
12 | 12 | ||
13 | <!-- Bootstrap, Fontawesome --> | 13 | <!-- Bootstrap, Fontawesome --> |
14 | - <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> | 14 | + <link rel="stylesheet" href="/static/bootstrap/css/bootstrap-materia.min.css"> |
15 | <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> | 15 | <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> |
16 | 16 | ||
17 | <!-- Other --> | 17 | <!-- Other --> |
@@ -27,7 +27,7 @@ | @@ -27,7 +27,7 @@ | ||
27 | </head> | 27 | </head> |
28 | <!-- ===================================================================== --> | 28 | <!-- ===================================================================== --> |
29 | <body> | 29 | <body> |
30 | -<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark"> | 30 | +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary"> |
31 | <a class="navbar-brand" href="#"> | 31 | <a class="navbar-brand" href="#"> |
32 | <img src="/static/logo_horizontal.png" height="30" alt=""> | 32 | <img src="/static/logo_horizontal.png" height="30" alt=""> |
33 | </a> | 33 | </a> |
@@ -66,13 +66,13 @@ | @@ -66,13 +66,13 @@ | ||
66 | <div class="list-group my-3"> | 66 | <div class="list-group my-3"> |
67 | {% for t in state %} | 67 | {% for t in state %} |
68 | {% if t['level'] is None %} | 68 | {% if t['level'] is None %} |
69 | - <a class="list-group-item list-group-item-action bg-light disabled" href="#"> | 69 | + <a class="list-group-item list-group-item-action bg-light disabled"> |
70 | <div class="d-flex justify-content-start"> | 70 | <div class="d-flex justify-content-start"> |
71 | - <div class="p-2 font-italic"> | 71 | + <div class="p-2 font-italic text-muted"> |
72 | {{ t['name'] }} | 72 | {{ t['name'] }} |
73 | </div> | 73 | </div> |
74 | <div class="ml-auto p-2"> | 74 | <div class="ml-auto p-2"> |
75 | - <i class="fa fa-lock text-danger" aria-hidden="true"></i> | 75 | + <i class="fa fa-lock text-danger fa-lg" aria-hidden="true"></i> |
76 | </div> | 76 | </div> |
77 | </div> | 77 | </div> |
78 | </a> | 78 | </a> |
@@ -85,7 +85,7 @@ | @@ -85,7 +85,7 @@ | ||
85 | <div class="ml-auto p-2"> | 85 | <div class="ml-auto p-2"> |
86 | {% if t['level'] < 0.01 %} | 86 | {% if t['level'] < 0.01 %} |
87 | 87 | ||
88 | - <i class="fa fa-unlock text-success" aria-hidden="true"></i> | 88 | + <i class="fa fa-unlock text-success fa-lg" aria-hidden="true"></i> |
89 | {% else %} | 89 | {% else %} |
90 | <span class="text-nowrap"> | 90 | <span class="text-nowrap"> |
91 | {{ round(t['level']*5)*'<i class="fa fa-star text-warning" aria-hidden="true"></i>' + round(5-t['level']*5)*'<i class="fa fa-star-o text-muted" aria-hidden="true"></i>' }} | 91 | {{ round(t['level']*5)*'<i class="fa fa-star text-warning" aria-hidden="true"></i>' + round(5-t['level']*5)*'<i class="fa fa-star-o text-muted" aria-hidden="true"></i>' }} |
templates/topic.html
@@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
19 | <script type="text/javascript" src="/static/mathjax/MathJax.js?delayStartupUntil=onload&config=TeX-AMS_CHTML-full"></script> | 19 | <script type="text/javascript" src="/static/mathjax/MathJax.js?delayStartupUntil=onload&config=TeX-AMS_CHTML-full"></script> |
20 | 20 | ||
21 | <!-- Bootstrap --> | 21 | <!-- Bootstrap --> |
22 | - <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> | 22 | + <link rel="stylesheet" href="/static/bootstrap/css/bootstrap-materia.min.css"> |
23 | <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> | 23 | <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> |
24 | <link rel="stylesheet" href="/static/css/animate.min.css"> | 24 | <link rel="stylesheet" href="/static/css/animate.min.css"> |
25 | <link rel="stylesheet" href="/static/css/github.css"> | 25 | <link rel="stylesheet" href="/static/css/github.css"> |
@@ -58,7 +58,7 @@ | @@ -58,7 +58,7 @@ | ||
58 | <body> | 58 | <body> |
59 | 59 | ||
60 | <!-- Navbar --> | 60 | <!-- Navbar --> |
61 | -<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark"> | 61 | +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary"> |
62 | <a class="navbar-brand" href="#"> | 62 | <a class="navbar-brand" href="#"> |
63 | <img src="/static/logo_horizontal.png" height="30" alt=""> | 63 | <img src="/static/logo_horizontal.png" height="30" alt=""> |
64 | </a> | 64 | </a> |
@@ -96,8 +96,6 @@ | @@ -96,8 +96,6 @@ | ||
96 | 96 | ||
97 | <div id="notifications"></div> | 97 | <div id="notifications"></div> |
98 | 98 | ||
99 | - <!-- <img src="/static/trophy.svg" alt="trophy" class="img-fluid mx-auto d-block hidden" width="50%"> --> | ||
100 | - | ||
101 | <div class="my-5" id="content"> | 99 | <div class="my-5" id="content"> |
102 | <form action="/question" method="post" id="question_form" autocomplete="off"> | 100 | <form action="/question" method="post" id="question_form" autocomplete="off"> |
103 | {% module xsrf_form_html() %} | 101 | {% module xsrf_form_html() %} |
tools.py
@@ -135,8 +135,14 @@ def md_to_html(text, q=None): | @@ -135,8 +135,14 @@ def md_to_html(text, q=None): | ||
135 | def load_yaml(filename, default=None): | 135 | def load_yaml(filename, default=None): |
136 | try: | 136 | try: |
137 | f = open(path.expanduser(filename), 'r', encoding='utf-8') | 137 | f = open(path.expanduser(filename), 'r', encoding='utf-8') |
138 | + except FileNotFoundError: | ||
139 | + logger.error(f'Can\'t open "{script}": not found.') | ||
140 | + return default | ||
141 | + except PermissionError: | ||
142 | + logger.error(f'Can\'t open "{script}": no permission.') | ||
143 | + return default | ||
138 | except IOError: | 144 | except IOError: |
139 | - logger.error(f'Can\'t open file "{filename}"') | 145 | + logger.error(f'Can\'t open file "{filename}".') |
140 | return default | 146 | return default |
141 | else: | 147 | else: |
142 | with f: | 148 | with f: |