Commit db04e658cdb955cc98823f2c1520a76c4076d104

Authored by Miguel Barão
1 parent c288f8c6
Exists in master and in 1 other branch dev

- added option to select which questions go for a given topic instead of all of them.

Showing 3 changed files with 37 additions and 22 deletions   Show diff stats
1 1
2 BUGS: 2 BUGS:
3 3
4 -- level depender do numero de respostas correctas 4 +- nao permite perguntas repetidas. iterar questions da configuracao em vez das do ficheiro. ver app.py linha 223.
5 - pymips: activar/desactivar instruções 5 - pymips: activar/desactivar instruções
6 - tabs em textarea nao funcionam correctamente (insere 1 espaco em vez de 4) 6 - tabs em textarea nao funcionam correctamente (insere 1 espaco em vez de 4)
7 - reportar comentarios após submeter. 7 - reportar comentarios após submeter.
@@ -27,6 +27,7 @@ TODO: @@ -27,6 +27,7 @@ TODO:
27 27
28 FIXED: 28 FIXED:
29 29
  30 +- level depender do numero de respostas correctas
30 - pymips a funcionar 31 - pymips a funcionar
31 - logs mostram que está a gerar cada pergunta 2 vezes...?? 32 - logs mostram que está a gerar cada pergunta 2 vezes...??
32 - letsencrypt.org 33 - letsencrypt.org
@@ -72,7 +72,7 @@ First we need to create a database: @@ -72,7 +72,7 @@ First we need to create a database:
72 ### SSL Certificates 72 ### SSL Certificates
73 73
74 We need certificates for https. Certificates can be self-signed or certificates validated by a trusted authority. 74 We need certificates for https. Certificates can be self-signed or certificates validated by a trusted authority.
75 -Self-signed can be used for development, but browsers will complain. 75 +Self-signed can be used for development, but browsers will complain.
76 LetsEncrypt issues trusted and free certificates, but the served must have a fixed IP and a domain name (not dynamic). 76 LetsEncrypt issues trusted and free certificates, but the served must have a fixed IP and a domain name (not dynamic).
77 77
78 #### Selfsigned 78 #### Selfsigned
@@ -140,24 +140,26 @@ Reboot. @@ -140,24 +140,26 @@ Reboot.
140 140
141 The server should not generate this error, but when using external scripts to generate questions or to correct, these scripts can print unicode strings to stdout. If the terminal does not support unicode, python will generate this exception. 141 The server should not generate this error, but when using external scripts to generate questions or to correct, these scripts can print unicode strings to stdout. If the terminal does not support unicode, python will generate this exception.
142 142
143 -To correct in FreeBSD, edit `~/.login_conf` to use UTF-8, for example: 143 +- FreeBSD fix: edit `~/.login_conf` to use UTF-8, for example:
144 144
145 me:\ 145 me:\
146 :charset=UTF-8:\ 146 :charset=UTF-8:\
147 :lang=en_US.UTF-8: 147 :lang=en_US.UTF-8:
148 148
  149 +- Debian fix: check `locale`...
  150 +
149 ## Useful sqlite3 queries 151 ## Useful sqlite3 queries
150 152
151 -Which students have already done at least one topic? 153 +Which students did at least one topic?
152 154
153 sqlite3 students.db "select distinct student_id from studenttopic" 155 sqlite3 students.db "select distinct student_id from studenttopic"
154 156
155 157
156 -How many topics have done each student? 158 +How many topics had each student done?
157 159
158 sqlite3 students.db "select student_id, count(topic_id) from studenttopic group by student_id order by count(topic_id) desc" 160 sqlite3 students.db "select student_id, count(topic_id) from studenttopic group by student_id order by count(topic_id) desc"
159 161
160 162
161 -What questions have more wrong answers? 163 +Which questions have more wrong answers?
162 164
163 sqlite3 students.db "select count(ref), ref from answers where grade<1.0 group by ref order by count(ref) desc" 165 sqlite3 students.db "select count(ref), ref from answers where grade<1.0 group by ref order by count(ref) desc"
@@ -146,7 +146,7 @@ class LearnApp(object): @@ -146,7 +146,7 @@ class LearnApp(object):
146 # return self.online[uid]['state'].current_question 146 # return self.online[uid]['state'].current_question
147 147
148 # ------------------------------------------------------------------------ 148 # ------------------------------------------------------------------------
149 - # check answer and if correct returns new question, otherise returns None 149 + # check answer and if correct returns new question, otherwise returns None
150 def check_answer(self, uid, answer): 150 def check_answer(self, uid, answer):
151 knowledge = self.online[uid]['state'] 151 knowledge = self.online[uid]['state']
152 current_question = knowledge.check_answer(answer) 152 current_question = knowledge.check_answer(answer)
@@ -168,7 +168,6 @@ class LearnApp(object): @@ -168,7 +168,6 @@ class LearnApp(object):
168 # Receives a set of topics (strings like "math/algebra"), 168 # Receives a set of topics (strings like "math/algebra"),
169 # and recursively adds dependencies to the dependency graph 169 # and recursively adds dependencies to the dependency graph
170 def build_dependency_graph(self, config_file): 170 def build_dependency_graph(self, config_file):
171 - logger.debug(f'LearnApp.build_dependency_graph("{config_file}")')  
172 171
173 # Load configuration file 172 # Load configuration file
174 try: 173 try:
@@ -189,36 +188,49 @@ class LearnApp(object): @@ -189,36 +188,49 @@ class LearnApp(object):
189 topics = config.get('topics', {}) 188 topics = config.get('topics', {})
190 for ref,attr in topics.items(): 189 for ref,attr in topics.items():
191 g.add_node(ref) 190 g.add_node(ref)
192 - if isinstance(attr, list):  
193 - # if prop is a list, we assume it's just a list of dependencies  
194 - g.add_edges_from((d,ref) for d in attr)  
195 -  
196 - elif isinstance(attr, dict): 191 + if isinstance(attr, dict):
197 g.node[ref]['name'] = attr.get('name', ref) 192 g.node[ref]['name'] = attr.get('name', ref)
  193 + g.node[ref]['questions'] = attr.get('questions', [])
198 g.add_edges_from((d,ref) for d in attr.get('deps', [])) 194 g.add_edges_from((d,ref) for d in attr.get('deps', []))
199 195
200 - elif isinstance(attr, str): 196 + elif isinstance(attr, list):
  197 + # if prop is a list, we assume it's just a list of dependencies
  198 + g.add_edges_from((d,ref) for d in attr)
  199 +
  200 + elif isinstance(attr, str): # FIXME is this really useful??
201 g.node[ref]['name'] = attr 201 g.node[ref]['name'] = attr
202 202
203 # iterate over topics and create question factories 203 # iterate over topics and create question factories
204 logger.info('Loading:') 204 logger.info('Loading:')
205 for ref in g.nodes_iter(): 205 for ref in g.nodes_iter():
206 g.node[ref].setdefault('name', ref) 206 g.node[ref].setdefault('name', ref)
207 - fullpath = path.expanduser(path.join(prefix, ref)) 207 + prefix_ref = path.join(prefix, ref)
  208 + fullpath = path.expanduser(prefix_ref)
208 if path.isdir(fullpath): 209 if path.isdir(fullpath):
209 filename = path.join(fullpath, "questions.yaml") 210 filename = path.join(fullpath, "questions.yaml")
210 else: 211 else:
211 - logger.error(f'build_dependency_graph: "{fullpath}" is not a directory') 212 + logger.error(f'Cant find directory "{prefix_ref}", ignored...')
  213 + continue
212 214
213 if path.isfile(filename): 215 if path.isfile(filename):
214 - questions = load_yaml(filename, default=[])  
215 - logger.info(f' {len(questions)} questions from "{ref}"')  
216 - for q in questions:  
217 - q['path'] = fullpath  
218 - g.node[ref]['factory'] = [QFactory(q) for q in questions] 216 + loaded_questions = load_yaml(filename, default=[])
  217 +
  218 + # if 'questions' is not provided in configuration, load all
  219 + if not g.node[ref]['questions']:
  220 + g.node[ref]['questions'] = [q['ref'] for q in loaded_questions]
  221 +
  222 +
  223 + # FIXME nao permite perguntas repetidas. iterar questions da configuracao em vez das do ficheiro.
  224 + g.node[ref]['factory'] = []
  225 + for q in loaded_questions:
  226 + if q['ref'] in g.node[ref]['questions']:
  227 + q['path'] = fullpath
  228 + g.node[ref]['factory'].append(QFactory(q))
  229 + logger.info(f' {len(g.node[ref]["factory"])} questions from "{ref}"')
  230 +
219 else: 231 else:
220 g.node[ref]['factory'] = [] 232 g.node[ref]['factory'] = []
221 - logger.error(f'build_dependency_graph: "{filename}" does not exist') 233 + logger.error(f'Cant load "{filename}"')
222 234
223 self.depgraph = g 235 self.depgraph = g
224 return g 236 return g