PaferaPy Async 0.1
ASGI framework focused on simplicity and efficiency
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Pafera app for our version of a learning management system.
5# You'll need to be in the teacher or student groups for most of
6# these APIs to work.
7#
8# The focus is on having instant feedback, training good study
9# habits, and being able to run and access the system from
10# any internet connected device.
11
12import importlib
13import json
14import os
15import os.path
16import random
17import re
18import shutil
19import time
20import traceback
21import types
22import uuid
23import datetime
24
25from pprint import pprint
26
27import dateutil.parser
28
29from flask import Response, send_file
30from lockbydir.lockbydir import *
31
32import markdown
33import dateutil.parser
34
35from pafera.types import *
36from pafera.utils import *
37from pafera.db import *
38
39from apps.system.user import *
40from apps.system.group import *
41from apps.system.page import *
42
43from apps.learn.initapp import *
44from apps.learn.card import *
45from apps.learn.schoolclass import *
46from apps.learn.problem import *
47from apps.learn.answer import *
50from apps.learn.school import *
51from apps.learn.teacher import *
52from apps.learn.student import *
53from apps.learn.challenge import *
55from apps.learn.studylist import *
56from apps.learn.classgrade import *
57
58# Prices for items purchasable by students
59PRICES = {
60 'chair': 1,
61 'sofa': 2,
62 'desk': 3,
63 'TV': 4,
64 'door': 5,
65 'window': 6,
66 'table': 7,
67 'bed': 8,
68}
69
70# Convenience names to work with problems
71PROBLEM_CARDFIELDS = [
72 'problemid',
73 'answerid',
74 'explanationid',
75 'choice1id',
76 'choice2id',
77 'choice3id',
78 'choice4id',
79 'choice5id',
80 'choice6id',
81 'choice7id',
82]
83
84# =====================================================================
85def cardapi(g):
86 """Handles card management including setting child cards and problems.
87 """
88 results = {}
89
90 try:
91 LoadTranslation(g, 'learn', 'default', g.session.lang)
92
93 d = json.loads(g.request.get_data())
94
95 command = StrV(d, 'command')
96
97 if command == 'search':
98 g.db.RequireGroup(['teachers', 'students'], g.T.cantview)
99
100 keyword = StrV(d, 'keyword')
101 title = StrV(d, 'title')
102 content = StrV(d, 'content')
103 flags = IntV(d, 'flags')
104 cardtype = StrV(d, 'type')
105 schoolid = StrV(d, 'schoolid')
106 classid = StrV(d, 'classid')
107 start = IntV(d, 'start', 0, 99999999, 0)
108 limit = IntV(d, 'limit', 10, 1000, 100)
109
110 conds = []
111 params = []
112
113 if keyword:
114 escapedkeyword = EscapeSQL('%' + keyword + '%')
115
116 conds.append("title LIKE ? OR description LIKE ? OR content LIKE ?")
117 params.append(escapedkeyword)
118 params.append(escapedkeyword)
119 params.append(escapedkeyword)
120
121 if title:
122 conds.append("title LIKE ?")
123 params.append(EscapeSQL('%' + title + '%'))
124
125 if content:
126 conds.append("content LIKE ?")
127 params.append(EscapeSQL('%' + content + '%'))
128
129 if flags:
130 conds.append("flags & ?")
131 params.append(flags)
132
133 if cardtype:
134 if cardtype == 'course':
135 conds.append("flags & ?")
136 params.append(CARD_COURSE)
137
138 if conds:
139 conds = "WHERE " + (" AND ").join(conds)
140 else:
141 conds = ""
142
143 chosencards = []
144
145 if schoolid or classid:
146 if classid:
147 c = g.db.Load(learn_schoolclass, FromShortCode(classid), fields = 'rid')
148 else:
149 c = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
150
151 for r in g.db.Linked(c,
152 learn_card,
153 fields = 'rid, image, sound, video, title, description',
154 start = start,
155 limit = limit
156 ):
157 chosencards.append(r.ToJSON('rid, image, sound, video, title, description, content'))
158
159 cardobjs = []
160
161 cards = g.db.Find(
162 learn_card,
163 conds,
164 params,
165 start = start,
166 limit = limit,
167 orderby = 'title, content',
168 fields = 'rid, image, sound, video, title, description, content'
169 )
170
171 for r in cards:
172 cardobjs.append(r.ToJSON('rid, image, sound, video, title, description, content'))
173
174 results['data'] = cardobjs
175 results['count'] = len(cards)
176 results['chosen'] = chosencards
177 elif command == 'linked':
178 g.db.RequireGroup(['teachers', 'students'], g.T.cantview)
179
180 cardidcode = StrV(d, 'cardidcode')
181 schoolid = StrV(d, 'schoolid')
182 classid = StrV(d, 'classid')
183 studentid = StrV(d, 'studentid')
184 linktype = IntV(d, 'linktype')
185
186 c = 0
187
188 if classid:
189 classid = FromShortCode(classid)
190
191 if (g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
192 or g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
193 ):
194 c = g.db.Load(learn_schoolclass, classid, 'rid')
195 else:
196 raise Exception(g.T.cantview)
197 elif schoolid:
198 schoolid = FromShortCode(schoolid)
199
200 if g.db.HasLink(learn_school, schoolid, learn_schooladministrator, g.session.userid):
201 c = g.db.Load(learn_school, schoolid, 'rid')
202 else:
203 raise Exception(g.T.cantview)
204 elif studentid:
205 if FromShortCode(studentid) == g.session.userid:
206 c = g.db.Load(learn_student, g.session.userid, 'userid')
207 else:
208 raise Exception(g.T.cantview)
209 elif cardidcode:
210 c = g.db.Load(learn_card, FromShortCode(cardidcode), 'rid')
211
212 linktype = CARD_CHILD
213 else:
214 c = g.db.Load(learn_teacher, g.session.userid, 'userid')
215
216 ls = []
217
218 for r in g.db.Linked(c, learn_card, linktype):
219 ls.append(r.ToJSON())
220
221 results['data'] = ls
222 results['count'] = len(ls)
223 elif command == 'save':
224 g.db.RequireGroup(['teachers'], g.T.cantchange)
225
226 data = DictV(d, 'data')
227 idcode = StrV(data, 'idcode')
228
229 if not data:
230 raise Exception(g.T.missingparams)
231
232 data['cardtype'] = IntV(data, 'cardtype')
233 data['title'] = StrV(data, 'title')
234 data['description'] = StrV(data, 'description')
235 data['image'] = StrV(data, 'image')
236 data['sound'] = StrV(data, 'sound')
237 data['video'] = StrV(data, 'video')
238 data['markdown'] = StrV(data, 'markdown')
239
240 if data['markdown']:
241 data['html'] = markdown.markdown(data['markdown'])
242 else:
243 data['html'] = StrV(data, 'html')
244
245 card = 0
246
247 if idcode:
248 card = g.db.Load(learn_card, FromShortCode(idcode))
249 else:
250 # Since answer cards are repeated so often, especially in math,
251 # we save space by not have 1000 cards that simply say "3" over
252 # and over again.
253 if IntV(data, 'flags') & CARD_PROBLEM and not data['markdown'] and not data['html'] and not data['image'] and not data['sound'] and not data['video']:
254 card = g.db.Find(
255 learn_card,
256 'WHERE content = ?',
257 [
258 data['content'],
259 ]
260 )
261
262 if card:
263 card = card[0]
264
265 if not card:
266 card = learn_card()
267
268 data['ownerid'] = card.ownerid if card.ownerid else g.session.userid
269 data['editors'] = card.editors if card.editors else []
270
271 card.Set(**data)
272
273 # Only allow owners and editors to edit an existing card.
274 # Otherwise, we essentially do copy-on-write to save to a different card.
275 if idcode:
276 currentusercode = ToShortCode(g.session.userid)
277
278 if g.session.userid != card.ownerid and currentusercode not in card.editors:
279 card.ownerid = g.session.userid
280 card.rid = 0;
281
282 g.db.Save(card)
283 g.db.Commit()
284
285 results['data'] = card.ToJSON()
286 elif command == 'setchildcards':
287 teacher = g.db.Load(learn_teacher, g.session.userid, fields = 'userid, allowedcards')
288
289 cardid = StrV(d, 'cardid')
290 childcardids = ArrayV(d, 'childcardids')
291
292 if not cardid:
293 raise Exception(g.T.missingparams)
294
295 if cardid not in teacher.allowedcards:
296 raise Exception(g.T.cantchange)
297
298 card = g.db.Load(learn_card, FromShortCode(cardid), 'rid, flags')
299
300 if card.flags & CARD_LESSON:
301 raise Exception(g.T.lessonscanthavechildcards)
302
303 cardobjs = []
304 jsonobjs = []
305
306 for cid in childcardids:
307 childcard = g.db.Load(learn_card, FromShortCode(cid), 'rid, image, title, description')
308 cardobjs.append(childcard)
309
310 o = childcard.ToJSON('image, title, description')
311 o['idcode'] = cid
312 jsonobjs.append(o)
313
314 if childcardids:
315 g.db.LinkArray(card, cardobjs, CARD_CHILD)
316
317 teacher.Set(
318 allowedcards = teacher.allowedcards + childcardids
319 )
320 else:
321 g.db.Unlink(card, learn_card, CARD_CHILD)
322
323 # Update all students to view the new child
324 UpdateAllowedCards(g, teacher)
325
326 for c in g.db.Linked(teacher, learn_schoolclass, 'userid'):
327 for student in g.db.Linked(c, learn_student, 'userid, allowedcards'):
328 if cardid in student.allowedcards:
329 UpdateAllowedCards(g, student)
330
331 g.db.Commit()
332
333 results['data'] = jsonobjs
334 elif command == 'setproblems':
335 teacher = g.db.Load(learn_teacher, g.session.userid, fields = 'userid, allowedcards')
336
337 cardid = StrV(d, 'cardid')
338 problemids = ArrayV(d, 'problemids')
339
340 if not cardid:
341 raise Exception(g.T.missingparams)
342
343 if cardid not in teacher.allowedcards:
344 raise Exception(g.T.cantchange)
345
346 card = g.db.Load(learn_card, FromShortCode(cardid), 'rid, flags')
347
348 if not (card.flags & CARD_LESSON):
349 raise Exception(g.T.onlylessonscancontainproblems)
350
351 problemobjs = []
352
353 for cid in problemids:
354 problem = g.db.Load(learn_problem, FromShortCode(cid), 'rid')
355 problemobjs.append(problem)
356
357 if problemids:
358 g.db.LinkArray(card, problemobjs, CARD_LINK_PROBLEM)
359 else:
360 g.db.Unlink(card, learn_problem, CARD_LINK_PROBLEM)
361
362 g.db.Commit()
363
364 results['data'] = GetAllProblems(g, card.rid, CARD_LINK_PROBLEM)
365 elif command == 'delete':
366 idcodes = ArrayV(d, 'ids')
367
368 cards = g.db.LoadMany(learn_card, [FromShortCode(x) for x in idcodes], 'rid, ownerid, editors')
369
370 for c in cards:
371 if 'admins' in g.session['groups'] or g.session.userid == c.ownerid or ToShortCode(g.session.userid) in c.editors:
372 g.db.Delete(c)
373
374 g.db.Commit()
375 else:
376 results['error'] = f'{g.T.cantunderstand} "{command}"'
377 except Exception as e:
378 traceback.print_exc()
379 results['error'] = str(e)
380
381 return results
382
383# =====================================================================
385 """Manages problems, which are essentially a collection of cards.
386
387 Note that you need to be a teacher in order to create or change
388 problems. Normal students can only view problems.
389 """
390 results = {}
391
392 try:
393 d = json.loads(g.request.get_data())
394
395 command = StrV(d, 'command')
396
397 if command == 'search':
398 g.db.RequireGroup(['teachers'], g.T.cantview)
399
400 keyword = StrV(d, 'keyword')
401 flags = IntV(d, 'flags')
402 start = IntV(d, 'start', 0, 99999999, 0)
403 limit = IntV(d, 'limit', 10, 1000, 100)
404
405 conds = []
406 params = []
407
408 if keyword:
409 escapedkeyword = EscapeSQL('%' + keyword + '%')
410
411 conds.append("content LIKE ?")
412 params.append(escapedkeyword)
413
414 if flags:
415 conds.append("flags & ?")
416 params.append(flags)
417
418 if conds:
419 conds = "WHERE " + (" AND ").join(conds)
420 else:
421 conds = ""
422
423 problemobjs = []
424 problemcount = 0
425
426 cards = {}
427
428 for r in g.db.Find(
429 learn_card,
430 conds,
431 params,
432 limit = 1000,
433 orderby = 'title, content',
434 fields = 'rid, image, sound, video, content',
435 ):
436 cards[r.rid] = r.ToJSON('rid, image, sound, video, content')
437
438 if cards:
439 cardids = list(cards.keys())
440
441 questionmarks = ['?' for x in cardids]
442
443 problems = []
444
445 questionmarks = ', '.join(questionmarks)
446
447 problems = g.db.Find(
448 learn_problem,
449 'WHERE problemid IN (' + questionmarks
450 + ') OR answerid IN (' + questionmarks + ')',
451 cardids + cardids,
452 start = start,
453 limit = limit,
454 )
455
456 problemcount = len(problems)
457
458 if problemcount:
459 additionalcardids = []
460
461 for r in problems:
462 if r.problemid and not cards.get(r.problemid):
463 additionalcardids.append(r.problemid)
464
465 if r.answerid and not cards.get(r.answerid):
466 additionalcardids.append(r.answerid)
467
468 for r in g.db.LoadMany(
469 learn_card,
470 additionalcardids,
471 'rid, image, sound, video, content'
472 ):
473 cards[r.rid] = r.ToJSON('rid, image, sound, video, content')
474
475 for p in problems:
476 o = {}
477
478 if not p.problemid in cards or not p.answerid in cards:
479 continue
480
481 problemobj = cards[p.problemid]
482 problemobj['idcode'] = ToShortCode(p.problemid)
483
484 answerobj = cards[p.answerid]
485 answerobj['idcode'] = ToShortCode(p.answerid)
486
487 o['problem'] = problemobj
488 o['answer'] = answerobj
489 o['idcode'] = ToShortCode(p.rid)
490
491 problemobjs.append(o)
492
493 results['data'] = problemobjs
494 results['count'] = problemcount
495 elif command == 'save':
496 g.db.RequireGroup(['teachers'], g.T.cantchange)
497
498 data = DictV(d, 'data')
499 idcode = StrV(data, 'idcode')
500
501 if not data:
502 raise Exception(g.T.missingparams)
503
504 if idcode:
505 p = g.db.Load(learn_problem, FromShortCode(idcode))
506 else:
507 p = learn_problem()
508 p.Set(ownerid = g.session.userid)
509
510 # Convert short codes to IDs
511 for r in PROBLEM_CARDFIELDS:
512 v = StrV(data, r)
513
514 if v:
515 data[r] = FromShortCode(v)
516 else:
517 data[r] = 0
518
519 data['timelimit'] = IntV(data, 'timelimit') if IntV(data, 'timelimit') else 0
520 data['points'] = IntV(data, 'points') if IntV(data, 'points') else 0
521 data['flags'] = IntV(data, 'flags') if IntV(data, 'flags') else 0
522
523 if not data['problemid'] or not data['answerid']:
524 raise Exception(g.T.missingparams)
525
526 # Only allow owners and editors to edit an existing problems.
527 # Otherwise, we essentially do copy-on-write to save to a different problem.
528 if idcode:
529 currentusercode = ToShortCode(g.session.userid)
530
531 if g.session.userid != p.ownerid and currentusercode not in p.editors and not p.HasSameValues(data):
532 data['ownerid'] = g.session.userid
533 data['rid'] = 0
534
535 p.Set(**data)
536
537 g.db.Save(p)
538 g.db.Commit()
539
540 objjson = {
541 'idcode': ToShortCode(p.rid),
542 }
543
544 for r in PROBLEM_CARDFIELDS:
545 v = getattr(p, r, None)
546
547 if v:
548 objjson[r] = ToShortCode(v)
549 else:
550 objjson[r] = ''
551
552 results['data'] = objjson
553 elif command == 'delete':
554 idcodes = ArrayV(d, 'ids')
555
556 problems = g.db.LoadMany(learn_problem, [FromShortCode(x) for x in idcodes], 'rid, ownerid, editors')
557
558 for c in problems:
559 if 'admins' in g.session['groups'] or g.session.userid == c.ownerid or ToShortCode(g.session.userid) in c.editors:
560 g.db.Delete(c)
561 else:
562 raise Exception(g.T.cantdelete)
563
564 g.db.Commit()
565 elif command == 'cardproblems':
566 allowedcards = []
567
568 # An user can be a teacher for one class but a student for another class, so we load all allowed cards
569 try:
570 allowedcards = g.db.Load(learn_teacher, g.session.userid, fields = 'allowedcards').allowedcards
571 caneditcards = 1
572 allowedcards.extend(g.db.Load(learn_student, g.session.userid, fields = 'allowedcards').allowedcards)
573 except Exception as e:
574 allowedcards = g.db.Load(learn_student, g.session.userid, fields = 'allowedcards').allowedcards
575
576 cardidcodes = ArrayV(d, 'cardidcodes')
577
578 for r in cardidcodes:
579 if r not in allowedcards:
580 raise Exception(g.T.cantview)
581
582 problemtype = IntV(d, 'problemtype', 0, 99999999, CARD_LINK_PROBLEM)
583
584 cardids = [FromShortCode(x) for x in cardidcodes]
585
586 results['data'] = GetAllProblems(g, cardids, problemtype)
587 else:
588 results['error'] = f'{g.T.cantunderstand} "{command}"'
589 except Exception as e:
590 traceback.print_exc()
591 results['error'] = str(e)
592
593 return results
594
595# =====================================================================
597 """Used to view and submit answers for a class, which can be viewed
598 in the teacher's classroom web interface. Functionality for putting
599 students into groups, purchasing items, or setting prompts for the
600 class are also included.
601 """
602 T = g.T
603 results = {}
604
605 try:
606 LoadTranslation(g, 'learn', 'challenges', g.session.lang)
607
608 d = json.loads(g.request.get_data())
609
610 command = StrV(d, 'command')
611
612 if command == 'changescores':
613 classid = StrV(d, 'classid')
614 students = ArrayV(d, 'students')
615
616 if not classid or not students:
617 raise Exception(g.T.missingparams)
618
619 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
620 raise Exception(g.T.cantchange)
621
622 for r in g.db.Find(
623 learn_answer,
624 'WHERE userid IN (' + ', '.join(['?' for x in students]) + ')',
625 [FromShortCode(x['idcode']) for x in students],
626 ):
627
628 idcode = ToShortCode(r.userid)
629 student = [x for x in students if x['idcode'] == idcode][0]
630
631 right = IntV(student, 'right')
632 wrong = IntV(student, 'wrong')
633 strikes = IntV(student, 'strikes')
634 bonus = IntV(student, 'bonus')
635
636 if not right and not wrong and not strikes and not bonus:
637 raise Exception(T.missingparams)
638
639 if right:
640 r.Set(numright = r.numright + right)
641
642 if wrong:
643 r.Set(numwrong = r.numwrong + wrong)
644
645 if strikes:
646 r.Set(numstrikes = r.numstrikes + strikes)
647
648 if bonus:
649 r.Set(bonusscore = r.bonusscore + bonus)
650
651 r.UpdateScore()
652 g.db.Update(r);
653
654 g.db.Commit()
655 elif command == 'clearscoreadded':
656 classid = StrV(d, 'classid')
657
658 if not classid:
659 raise Exception(g.T.missingparams)
660
661 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
662 raise Exception(g.T.cantchange)
663
664 g.db.Execute('UPDATE learn_answer SET scoreadded = 0 WHERE classid = ?', FromShortCode(classid))
665 g.db.Commit()
666 elif command == 'save':
667 classid = StrV(d, 'classid')
668
669 if not classid:
670 raise Exception(g.T.missingparams)
671
672 classid = FromShortCode(classid)
673
674 if (not g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
675 and not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
676 ):
677 raise Exception(g.T.cantchange)
678
679 answer = StrV(d, 'answer').strip()[:4096]
680
681 o = g.db.Find(
682 learn_answer,
683 'WHERE userid = ? and classid = ?',
684 [g.session.userid, classid],
685 )
686
687 if o:
688 o = o[0]
689 else:
690 o = learn_answer()
691
692 u = g.db.Load(learn_student, g.session.userid)
693
694 flags = ANSWER_ENABLE_COPY_PASTE
695
696 if 'teachers' in g.session['groups']:
697 flags |= ANSWER_TEACHER
698
699 o.Set(
700 displayname = u.displayname,
701 flags = flags,
702 classid = classid,
703 userid = g.session.userid,
704 )
705 o.AddHomeworkScores(g)
706
707 if not o.flags & ANSWER_LOCKED:
708 o.Set(
709 classid = classid,
710 userid = g.session.userid,
711 answer = answer,
712 coderesult = {},
713 )
714 g.db.Save(o)
715 g.db.Commit()
716 elif command == 'saveimage':
717 classid = StrV(d, 'classid')
718 dataurl = StrV(d, 'dataurl')
719
720 if not classid or not dataurl:
721 raise Exception(g.T.missingparams)
722
723 classid = FromShortCode(classid)
724
725 if (not g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
726 and not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
727 ):
728 raise Exception(g.T.cantchange)
729
730 useridcode = ToShortCode(g.session.userid)
731
732 imageformat, imagedata = dataurl.split(';base64,')
733
734 imagedata = base64.b64decode(imagedata)
735
736 imagefilebase = 'static/learn/drawings/' + CodeDir(useridcode)
737 jpgfile = imagefilebase + '.jpg'
738 webpfile = imagefilebase + '.webp'
739
740 os.makedirs(os.path.split(imagefilebase)[0], exist_ok = True)
741
742 with open(jpgfile, 'wb') as f:
743 f.write(imagedata)
744
745 subprocess.call(['convert', jpgfile, '-quality', '70', '-resize', '640x640>', webpfile])
746
747 os.remove(jpgfile)
748 elif command == 'get':
749 classid = StrV(d, 'classid')
750
751 if not classid:
752 raise Exception(g.T.missingparams)
753
754 o = g.db.Find(
755 learn_answer,
756 'WHERE userid = ? and classid = ?',
757 [g.session.userid, FromShortCode(classid)]
758 )
759
760 if o:
761 results['data'] = o[0].answer
762 else:
763 results['data'] = ''
764 elif command == 'list':
765 classid = StrV(d, 'classid')
766
767 if not classid:
768 raise Exception(g.T.missingparams)
769
770 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
771 raise Exception(g.T.cantchange)
772
773 ls = []
774
775 for r in g.db.Find(learn_answer, 'WHERE classid = ?', FromShortCode(classid)):
776 ls.append(r.ToJSON())
777
778 results['data'] = ls
779 elif command == 'newclass':
780 classid = StrV(d, 'classid')
781
782 if not classid:
783 raise Exception(g.T.missingparams)
784
785 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
786 raise Exception(g.T.cantchange)
787
788 g.db.Delete(learn_answer, 'WHERE classid = ?', FromShortCode(classid))
789 g.db.Commit()
790 elif command == 'setgroup':
791 classid = StrV(d, 'classid')
792
793 if not classid:
794 raise Exception(g.T.missingparams)
795
796 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
797 raise Exception(g.T.cantchange)
798
799 idcode = StrV(d, 'id')
800 groupnum = IntV(d, 'groupnum')
801
802 rows = g.db.Find(
803 learn_answer,
804 'WHERE userid = ? AND classid = ?',
805 [FromShortCode(idcode), FromShortCode(classid)]
806 )
807
808 if rows:
809 o = rows[0]
810
811 o.Set(groupnum = groupnum)
812
813 g.db.Update(o);
814
815 g.db.Commit()
816 elif command == 'setgroups':
817 classid = StrV(d, 'classid')
818
819 if not classid:
820 raise Exception(g.T.missingparams)
821
822 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
823 raise Exception(g.T.cantchange)
824
825 students = DictV(d, 'students')
826
827 for r in g.db.Find(
828 learn_answer,
829 'WHERE classid = ? AND userid IN (' + ', '.join(['?' for x in students.keys()]) + ')',
830 [FromShortCode(classid)] + [FromShortCode(x) for x in students.keys()]
831 ):
832
833 r.Set(groupnum = students[ToShortCode(r.userid)])
834
835 g.db.Update(r)
836
837 g.db.Commit()
838 elif command == 'cleargroups':
839 classid = StrV(d, 'classid')
840
841 if not classid:
842 raise Exception(g.T.missingparams)
843
844 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
845 raise Exception(g.T.cantchange)
846
847 g.db.Execute('UPDATE learn_answer SET groupnum = 0 WHERE classid = ?', FromShortCode(classid))
848 g.db.Commit()
849 elif command == 'resetscores':
850 classid = StrV(d, 'classid')
851
852 if not classid:
853 raise Exception(g.T.missingparams)
854
855 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
856 raise Exception(g.T.cantchange)
857
858 g.db.Execute("""
859 UPDATE learn_answer
860 SET numright = 0,
861 numwrong = 0,
862 numstrikes = 0,
863 bonusscore = 0,
864 scoreadded = 0,
865 groupnum = 0,
866 score = 0
867 WHERE classid = ?
868 """,
869 FromShortCode(classid)
870 )
871 g.db.Commit()
872 elif command == 'getprompt':
873 classid = StrV(d, 'classid')
874
875 if not classid:
876 raise Exception(g.T.missingparams)
877
878 classid = FromShortCode(classid)
879
880 if (not g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
881 and not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
882 ):
883 raise Exception(g.T.cantview)
884
885 o = g.db.Find(
886 learn_answer,
887 'WHERE classid = ? AND userid = ?',
888 [classid, g.session.userid],
889 )
890
891 if o:
892 o = o[0]
893
894 results['prompt'] = o.prompt
895 results['promptdata'] = o.promptdata
896 results['coderesult'] = o.coderesult
897 results['items'] = o.items
898 results['score'] = o.score
899 results['flags'] = o.flags
900 else:
901 o = learn_answer()
902
903 nameobj = 0
904
905 if g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
906 o.Set(
907 displayname = g.db.Load(learn_teacher, g.session.userid, 'displayname').displayname,
908 flags = ANSWER_TEACHER | ANSWER_ENABLE_COPY_PASTE,
909 );
910 else:
911 o.Set(
912 displayname = g.db.Load(learn_student, g.session.userid, 'displayname').displayname
913 );
914
915 o.Set(
916 classid = classid,
917 userid = g.session.userid,
918 )
919
920 o.AddHomeworkScores(g)
921
922 g.db.Insert(o)
923 g.db.Commit()
924
925 results['prompt'] = ''
926 results['promptdata'] = {}
927 results['coderesult'] = ''
928 results['items'] = {}
929 results['score'] = 0
930 results['flags'] = 0
931
932 results['challenges'] = GetCurrentChallenges(g, classid, CHALLENGE_CLASSWORK)
933 elif command == 'buyitem':
934 classid = StrV(d, 'classid')
935
936 if not classid:
937 raise Exception(g.T.missingparams)
938
939 if (not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_student, g.session.userid)
940 and not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid)
941 ):
942 raise Exception(g.T.cantview)
943
944 itemname = StrV(d, 'data')
945 quantity = IntV(d, 'quantity')
946
947 if itemname not in PRICES.keys():
948 raise Exception(g.T.nothingfound)
949
950 price = PRICES[itemname]
951
952 o = g.db.Find(
953 learn_answer,
954 'WHERE classid = ? AND userid = ?',
955 [FromShortCode(classid), g.session.userid],
956 )
957
958 if o:
959 o = o[0]
960
961 # purchasing item
962 if quantity > 0:
963 total = price * quantity
964
965 if o.score < total:
966 raise Exception(g.T.notenoughmoney)
967
968 if not o.items:
969 o.items = {}
970
971 if itemname not in o.items:
972 o.items[itemname] = quantity
973 else:
974 o.items[itemname] += quantity
975
976 if o.bonusscore > total:
977 o.Set(
978 bonusscore = o.bonusscore - total,
979 items = o.items,
980 )
981 else:
982 o.Set(
983 numright = o.numright - total,
984 items = o.items,
985 )
986
987 o.Set(score = o.score - total)
988 # Using an item deletes it from the item store, and is controllable only
989 # by teachers
990 elif quantity < 0:
991 g.db.RequireGroup('teachers', g.T.cantchange)
992
993 o = g.db.Find(learn_answer, 'WHERE userid = ?', FromShortCode(StrV(d, 'useridcode')))
994
995 if not o:
996 raise Exception(g.T.nothingfound)
997
998 o = o[0]
999
1000 if itemname not in o.items or o.items[itemname] + quantity < 0:
1001 raise Exception(g.T.nothingfound)
1002
1003 o.items[itemname] += quantity
1004 o.Set(items = o.items)
1005
1006 g.db.Update(o)
1007 g.db.Commit()
1008
1009 results['prompt'] = o.prompt
1010 results['promptdata'] = o.promptdata
1011 results['coderesult'] = o.coderesult
1012 results['items'] = o.items
1013 results['score'] = o.score
1014 else:
1015 results['prompt'] = ''
1016 results['promptdata'] = {}
1017 results['coderesult'] = ''
1018 results['items'] = {}
1019 results['score'] = 0
1020 elif command == 'setprompt':
1021 classid = StrV(d, 'classid')
1022
1023 if not classid:
1024 raise Exception(g.T.missingparams)
1025
1026 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
1027 raise Exception(g.T.cantchange)
1028
1029 prompt = StrV(d, 'prompt')
1030 promptdata = DictV(d, 'promptdata')
1031
1032 g.db.Execute("""
1033 UPDATE learn_answer
1034 SET prompt = ?
1035 WHERE classid = ?
1036 """,
1037 [prompt, FromShortCode(classid)]
1038 )
1039
1040 if promptdata:
1041 g.db.Execute("""
1042 UPDATE learn_answer
1043 SET promptdata = ?
1044 WHERE classid = ?
1045 """,
1046 [json.dumps(promptdata), FromShortCode(classid)]
1047 )
1048
1049 g.db.Commit()
1050 elif command == 'setcoderesult':
1051 classid = StrV(d, 'classid')
1052
1053 if not classid:
1054 raise Exception(g.T.missingparams)
1055
1056 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
1057 raise Exception(g.T.cantchange)
1058
1059 studentid = StrV(d, 'studentid')
1060 coderesult = DictV(d, 'coderesult')
1061
1062 if not studentid:
1063 raise Exception(T.missingparams)
1064
1065 g.db.Execute("""
1066 UPDATE learn_answer
1067 SET coderesult = ?
1068 WHERE userid = ? AND classid = ?
1069 """,
1070 [
1071 json.dumps(coderesult),
1072 FromShortCode(studentid),
1073 FromShortCode(classid)
1074 ]
1075 )
1076
1077 g.db.Commit()
1078 elif command == 'setflags':
1079 classid = StrV(d, 'classid')
1080
1081 if not classid:
1082 raise Exception(g.T.missingparams)
1083
1084 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
1085 raise Exception(g.T.cantchange)
1086
1087 studentid = StrV(d, 'studentid')
1088 flags = IntV(d, 'flags')
1089
1090 if not studentid:
1091 raise Exception(T.missingparams)
1092
1093 g.db.Execute("""
1094 UPDATE learn_answer
1095 SET flags = ?
1096 WHERE userid = ? AND classid = ?
1097 """,
1098 [
1099 flags,
1100 FromShortCode(studentid),
1101 FromShortCode(classid),
1102 ]
1103 )
1104
1105 g.db.Commit()
1106 elif command == 'setpoints':
1107 challengeid = StrV(d, 'challengeid')
1108 studentid = StrV(d, 'studentid')
1109 points = IntV(d, 'points')
1110
1111 if not challengeid:
1112 raise Exception(g.T.missingparams)
1113
1114 challengeid = FromShortCode(challengeid)
1115
1116 challengeobj = g.db.Load(learn_challenge, challengeid)
1117
1118 if not g.db.HasLink(learn_schoolclass, challengeobj.classid, learn_teacher, g.session.userid):
1119 raise Exception(g.T.cantchange)
1120
1121 if studentid not in challengeobj.results:
1122 studentobj = g.db.Load(learn_student, FromShortCode(studentid))
1123 challengeobj.results[studentid] = [studentobj.displayname, 0, 0, 0, 0]
1124
1125 challengeobj.results[studentid][4] = points
1126
1127 challengeobj.UpdateFields('results')
1128
1129 g.db.Update(challengeobj)
1130
1131 cls = g.db.Load(learn_schoolclass, challengeobj.classid)
1132
1133 cls.UpdateGrades(g, FromShortCode(studentid))
1134
1135 g.db.Commit()
1136 elif command == 'delete':
1137 classid = StrV(d, 'classid')
1138 studentid = StrV(d, 'studentid')
1139
1140 if not classid or not studentid:
1141 raise Exception(g.T.missingparams)
1142
1143 classid = FromShortCode(classid)
1144 studentid = FromShortCode(studentid)
1145
1146 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1147 raise Exception(g.T.cantchange)
1148
1149 g.db.Execute("""
1150 DELETE FROM learn_answer
1151 WHERE userid = ? AND classid = ?
1152 """,
1153 [
1154 studentid,
1155 classid,
1156 ]
1157 )
1158
1159 g.db.Commit()
1160 else:
1161 results['error'] = f'{g.T.cantunderstand} "{command}"'
1162 except Exception as e:
1163 traceback.print_exc()
1164 results['error'] = str(e)
1165
1166 return results
1167
1168# =====================================================================
1170 """Used to create and save challenge results. Note that only teachers
1171 can create challenges, and students can only save challenges which
1172 have already been started in /learn/challenges.html.
1173
1174 Also allows the user to view activity logs and statistics.
1175 """
1176 results = {}
1177
1178 try:
1179 LoadTranslation(g, 'learn', 'default', g.session.lang)
1180 LoadTranslation(g, 'learn', 'challenges', g.session.lang)
1181
1182 d = json.loads(g.request.get_data())
1183
1184 command = StrV(d, 'command')
1185
1186 if command == 'getgrades':
1187 classid = StrV(d, 'classid')
1188
1189 if not classid:
1190 raise Exception(g.T.missingparams)
1191
1192 classid = FromShortCode(classid)
1193
1194 isstudent = g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
1195
1196 if not isstudent:
1197 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1198 raise Exception(g.T.cantview)
1199
1200 results['data'] = [
1201 r.ToJSON()
1202 for r in g.db.Find(
1203 learn_classgrade,
1204 'WHERE classid = ?',
1205 classid,
1206 orderby = 'finalscore DESC, classpercent DESC, reviewpercent DESC, persistencepercent DESC'
1207 )
1208 ]
1209 elif command == 'getactivitylog' or command == 'getchallengeresults':
1210 classid = StrV(d, 'classid')
1211 studentid = StrV(d, 'studentid')
1212 challengetype = IntV(d, 'challengetype')
1213 starttime = dateutil.parser.parse(StrV(d, 'starttime')).timestamp()
1214 endtime = dateutil.parser.parse(StrV(d, 'endtime')).timestamp()
1215
1216 if not studentid and not classid:
1217 raise Exception(g.T.missingparams)
1218
1219 classid = FromShortCode(classid)
1220 studentid = FromShortCode(studentid) if studentid else ''
1221
1222 isstudent = g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid)
1223
1224 if not isstudent:
1225 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1226 raise Exception(g.T.cantview)
1227
1228 if not challengetype:
1229 challengetype = CHALLENGE_HOMEWORK
1230
1231 if command == 'getchallengeresults':
1232 homeworks = []
1233
1234 for r in g.db.Find(
1235 learn_challenge,
1236 'WHERE classid = ? AND starttime >= ? AND starttime <= ? AND challengetype = ?',
1237 [
1238 classid,
1239 starttime,
1240 endtime,
1241 challengetype,
1242 ],
1243 orderby = 'endtime DESC',
1244 fields = 'rid, title, results',
1245 ):
1246 homeworks.append(r.ToJSON('rid, title, results'))
1247
1248 results['data'] = homeworks
1249 return results
1250
1251 conds = ['learn_challengeresult.classid = ?', 'learn_challengeresult.starttime >= ?', 'learn_challengeresult.starttime <= ?']
1252 params = [classid, starttime, endtime]
1253
1254 if studentid:
1255 conds.append('userid = ?')
1256 params.append(studentid)
1257
1258 if challengetype:
1259 conds.append('challengetype = ?')
1260 params.append(challengetype)
1261
1262 entries = g.db.Find(
1263 learn_challengeresult,
1264 '''JOIN learn_challenge ON learn_challengeresult.challengeid = learn_challenge.rid
1265 WHERE ''' + ' AND '.join(conds),
1266 params,
1267 orderby = 'starttime DESC',
1268 fields = 'learn_challengeresult.rid AS rid, learn_challengeresult.userid AS userid, learn_challengeresult.starttime AS starttime, learn_challenge.challenge AS challenge, learn_challenge.lessonids AS lessonids, score, percentright'
1269 )
1270
1271 logresults = []
1272
1273 for r in entries:
1274 logresults.append({
1275 'studentid': ToShortCode(r.userid),
1276 'starttime': r.starttime,
1277 'challenge': r.challenge,
1278 'lessons': '',
1279 'lessonids': r.lessonids,
1280 'score': r.score,
1281 'percentright': r.percentright,
1282 })
1283
1284 lessonids = set()
1285
1286 for r in logresults:
1287 if r['lessonids']:
1288 for s in r['lessonids']:
1289 lessonids.add(FromShortCode(s))
1290
1291 if lessonids:
1292 lessontitles = GetCardTitles(g, list(lessonids))
1293
1294 for r in logresults:
1295 if r['lessonids']:
1296 lessonnames = []
1297
1298 for s in r['lessonids']:
1299 lessonnames.append(lessontitles[FromShortCode(s)])
1300
1301 r['lessons'] = ', '.join(lessonnames)
1302
1303 results['data'] = logresults
1304 elif command == 'search':
1305 keyword = StrV(d, 'keyword')
1306 challenge = StrV(d, 'challenge', keyword)
1307 title = StrV(d, 'title', keyword)
1308 lessons = StrV(d, 'lessons', keyword)
1309 classid = StrV(d, 'classid')
1310 start = IntV(d, 'start', 0, 99999999, 0)
1311 limit = IntV(d, 'limit', 10, 1000, 100)
1312
1313 if not classid:
1314 raise Exception(g.T.missingparams)
1315
1316 classid = FromShortCode(classid)
1317
1318 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1319 raise Exception(g.T.cantview)
1320
1321 conds = []
1322 params = [classid]
1323
1324 if challenge:
1325 conds.append("challenge LIKE ?")
1326 params.append(EscapeSQL('%' + challenge + '%'))
1327
1328 if title:
1329 conds.append("title LIKE ?")
1330 params.append(EscapeSQL('%' + json.dumps(title) + '%'))
1331
1332 if lessons:
1333 conds.append("lessontitles LIKE ?")
1334 params.append(EscapeSQL('%' + lessons+ '%'))
1335
1336 if conds:
1337 conds = "WHERE classid = ? AND (" + (" OR ").join(conds) + ')'
1338 else:
1339 conds = "WHERE classid = ? "
1340
1341 challenges = g.db.Find(
1342 learn_challenge,
1343 conds,
1344 params,
1345 start = start,
1346 limit = limit,
1347 orderby = 'starttime DESC',
1348 fields = 'rid, starttime, endtime, challenge, title, instructions, question, image, sound, video, files, lessonids, problemids, averagescore, averageright, averagepercent, percenttried, percentcomplete, minscore, minpercentage, numpoints, didnttrypenalty, numtries, numproblems, timelimit, challengetype, flags'
1349 )
1350
1351 challengeobjs = []
1352
1353 timeoffset = int(g.request.cookies.get('timeoffset'))
1354
1355 for r in challenges:
1356 r.OffsetTime(-timeoffset * 60)
1357
1358 o = r.ToJSON()
1359
1360 if o['lessonids']:
1361 o['lessons'] = [
1362 {
1363 'idcode': ToShortCode(k),
1364 'title': v,
1365 }
1366 for k, v in GetCardTitles(g, [FromShortCode(x) for x in o['lessonids']]).items()
1367 ]
1368 else:
1369 o['lessons'] = ''
1370
1371 if o['problemids']:
1372 o['problems'] = GetAllProblems(g, [], CARD_LINK_PROBLEM, [FromShortCode(x) for x in o['problemids']])
1373 else:
1374 o['problems'] = ''
1375
1376 challengeobjs.append(o)
1377
1378 results['data'] = challengeobjs
1379 results['count'] = len(challenges)
1380 elif command == 'save':
1381 data = DictV(d, 'data')
1382
1383 challengetype = IntV(data, 'challengetype')
1384 idcode = StrV(data, 'idcode')
1385 classid = StrV(data, 'classid')
1386 title = StrV(data, 'title')
1387 instructions = StrV(data, 'instructions')
1388 question = StrV(data, 'question')
1389 image = StrV(data, 'image')
1390 sound = StrV(data, 'sound')
1391 video = StrV(data, 'video')
1392 files = ArrayV(data, 'files')
1393 challenge = StrV(data, 'challenge')
1394 starttime = StrV(data, 'starttime')
1395 endtime = StrV(data, 'endtime')
1396 minscore = IntV(data, 'minscore')
1397 minpercentage = IntV(data, 'minpercentage')
1398 minpoints = IntV(data, 'minpoints')
1399 lessonids = ArrayV(data, 'lessonids')
1400 problemids = ArrayV(data, 'problemids')
1401 numtries = IntV(data, 'numtries', 1, 9999, 3)
1402 numpoints = IntV(data, 'numpoints', 0, 100, 50)
1403 didnttrypenalty = IntV(data, 'didnttrypenalty', 0, 100, 50)
1404 numproblems = IntV(data, 'numproblems', 1, 20, 6)
1405 timelimit = IntV(data, 'timelimit')
1406 flags = IntV(data, 'flags')
1407
1408 if not classid or not challenge or (starttime > endtime):
1409 raise Exception(g.T.missingparams)
1410
1411 classid = FromShortCode(classid)
1412
1413 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1414 raise Exception(g.T.cantchange)
1415
1416 if idcode:
1417 obj = g.db.Load(learn_challenge, FromShortCode(idcode))
1418 else:
1419 obj = learn_challenge()
1420
1421 cls = g.db.Load(learn_schoolclass, classid)
1422
1423 studentresults = {
1424 ToShortCode(x.userid): [x.displayname, CHALLENGE_DIDNT_TRY, 0, 0, didnttrypenalty] for x in g.db.Linked(cls, learn_student, fields = 'userid, displayname')
1425 }
1426
1427 obj.Set(results = studentresults)
1428
1429 currenttime = time.time()
1430 endtimestamp = dateutil.parser.parse(endtime).timestamp()
1431
1432 flags |= CHALLENGE_NEEDS_ANALYSIS
1433
1434 if not challengetype:
1435 challengetype = CHALLENGE_HOMEWORK
1436
1437 if challengetype == CHALLENGE_CLASSPARTICIPATION:
1438 challenge = 'Class Participation'
1439
1440 if not title:
1441 title = {}
1442
1443 if flags & CHALLENGE_USE_STUDYLIST:
1444 title[g.session.lang] = g.T.studylist
1445 elif lessonids:
1446 title[g.session.lang] = ', '.join(GetCardTitles(g, lessonids).keys())
1447 else:
1448 title[g.session.lang] = getattr(g.T, challenge.tolower().replace(' ', ''), g.T.challenge)
1449
1450 obj.Set(
1451 classid = classid,
1452 challengetype = challengetype,
1453 title = title,
1454 instructions = instructions,
1455 question = question,
1456 image = image,
1457 sound = sound,
1458 video = video,
1459 files = files,
1460 challenge = challenge,
1461 starttime = starttime,
1462 endtime = endtime,
1463 minscore = minscore,
1464 minpercentage = minpercentage,
1465 minpoints = minpoints,
1466 lessonids = lessonids,
1467 problemids = problemids,
1468 numtries = numtries,
1469 numpoints = numpoints,
1470 didnttrypenalty = didnttrypenalty,
1471 numproblems = numproblems,
1472 timelimit = timelimit,
1473 flags = flags,
1474 )
1475
1476 timeoffset = int(g.request.cookies.get('timeoffset'))
1477
1478 obj.OffsetTime(timeoffset * 60)
1479
1480 if idcode:
1481 g.db.Update(obj)
1482 else:
1483 g.db.Insert(obj)
1484
1485 g.db.Commit()
1486
1487 results['data'] = obj.ToJSON()
1488 elif command == 'saveresult' or command == 'saveunfinishedresult':
1489 data = DictV(d, 'data')
1490
1491 challengeresultid = StrV(data, 'challengeresultid')
1492 problemresults = ArrayV(data, 'problemresults')
1493 score = IntV(data, 'score')
1494 maximumscore = IntV(data, 'maximumscore')
1495 percentright = IntV(data, 'percentright')
1496
1497 if not challengeresultid or not problemresults or not maximumscore:
1498 raise Exception(g.T.missingparams)
1499
1500 challengeresult = g.db.Load(learn_challengeresult, FromShortCode(challengeresultid))
1501
1502 if challengeresult.userid != g.session.userid:
1503 raise Exception(g.T.missingparams)
1504
1505 isteacher = g.db.HasLink(learn_schoolclass, challengeresult.classid, learn_teacher, g.session.userid)
1506
1507 challengeobj = 0
1508
1509 previousresults = []
1510
1511 if len(challengeresult.lessonids) == 1:
1512 previousresults = g.db.Find(
1513 learn_challengeresult,
1514 'WHERE userid = ? AND challenge = ? AND lessonids = ?',
1515 [
1516 challengeresult.userid,
1517 challengeresult.challenge,
1518 challengeresult.lessonids[0],
1519 ],
1520 fields = 'score',
1521 )
1522 elif challengeresult.challengeid:
1523 previousresults = g.db.Find(
1524 learn_challengeresult,
1525 'WHERE userid = ? AND challengeid = ?',
1526 [
1527 g.session.userid,
1528 challengeresult.challengeid
1529 ],
1530 fields = 'score',
1531 )
1532
1533 isnewhighscore = 1
1534 previoushighscore = 0
1535
1536 if previousresults:
1537 for r in previousresults:
1538 if r.score >= score:
1539 isnewhighscore = 0
1540
1541 if r.score > previoushighscore:
1542 previoushighscore = r.score
1543
1544 results['isnewhighscore'] = data['score'] != 0 and isnewhighscore
1545 results['previoushighscore'] = previoushighscore
1546
1547 flags = challengeresult.flags
1548
1549 if command == 'saveunfinishedresult':
1550 flags &= CHALLENGERESULT_UNFINISHED
1551 else:
1552 flags = (flags & (~CHALLENGERESULT_UNFINISHED)) | CHALLENGERESULT_FINISHED
1553
1554 currenttime = datetime.datetime.now()
1555
1556 results['passed'] = 0
1557 results['remainingattempts'] = 0
1558
1559 if challengeresult.challengeid:
1560 challengeobj = g.db.Load(
1561 learn_challenge,
1562 challengeresult.challengeid,
1563 'rid, classid, challenge, challengetype, lessonids, starttime, endtime, minscore, minpercentage, numtries, numpoints, results, flags'
1564 )
1565
1566 if currenttime < challengeobj.starttime:
1567 raise Exception(g.T.challengenotstarted)
1568
1569 if currenttime > challengeobj.endtime:
1570 raise Exception(g.T.challengealreadyover)
1571
1572 results['remainingattempts'] = challengeobj.GetNumRemainingAttempts(g)
1573
1574 currentid = ToShortCode(g.session.userid)
1575
1576 if challengeobj.challenge == 'Write Answer':
1577 challengeresult.Set(
1578 problemresults = problemresults,
1579 endtime = currenttime,
1580 )
1581 g.db.Update(challengeresult)
1582 g.db.Commit()
1583 return results
1584
1585 results['bonuspoints'] = 0
1586
1587 answerobj = 0
1588
1589 if challengeobj.challengetype == CHALLENGE_CLASSWORK and challengeobj.challenge == 'Typing':
1590 answerobj = g.db.Find(
1591 learn_answer,
1592 'WHERE classid = ? AND userid = ?',
1593 [challengeobj.classid, g.session.userid]
1594 )
1595
1596 if answerobj:
1597 answerobj = answerobj[0]
1598
1599 answerobj.Set(bonusscore = answerobj.bonusscore + score)
1600
1601 answerobj.UpdateScore()
1602
1603 g.db.Update(answerobj)
1604
1605 currentname = 0
1606
1607 if currentid in challengeobj.results:
1608 currentname = challengeobj.results[currentid][0]
1609 else:
1610 if isteacher:
1611 currentname = g.db.Load(learn_teacher, g.session.userid, 'displayname').displayname
1612 else:
1613 currentname = g.db.Load(learn_student, g.session.userid, 'displayname').displayname
1614
1615 if ((challengeobj.minscore and score >= challengeobj.minscore)
1616 or (challengeobj.minpercentage and percentright >= challengeobj.minpercentage)
1617 ):
1618 flags = (flags & (~CHALLENGERESULT_UNFINISHED)) | CHALLENGERESULT_FINISHED | CHALLENGERESULT_PASSED
1619
1620 results['bonuspoints'] = challengeobj.numpoints
1621
1622 alreadypassed = 0
1623
1624 for r in challengeobj.previousresults:
1625 if ((challengeobj.minscore and r.score > challengeobj.minscore)
1626 or (challengeobj.minpercentage and r.percentright > challengeobj.minpercentage)
1627 ):
1628 alreadypassed = 1
1629 break;
1630
1631 if not alreadypassed and answerobj:
1632 answerobj.Set(
1633 bonusscore = answerobj.bonusscore + challengeobj.numpoints
1634 )
1635
1636 answerobj.UpdateScore()
1637
1638 g.db.Update(answerobj)
1639
1640 results['passed'] = 1
1641
1642 challengeresult.Set(scoreadded = challengeobj.numpoints)
1643
1644 challengeobj.results[currentid] = [currentname, CHALLENGE_COMPLETED, score, percentright, challengeobj.numpoints]
1645 else:
1646 challengeobj.results[currentid] = [currentname, CHALLENGE_FAILED, score, percentright, 0]
1647
1648 challengeobj.Set(results = challengeobj.results)
1649
1650 g.db.Update(challengeobj)
1651
1652 challengeresult.Set(lessonids = challengeobj.lessonids)
1653
1654 challengeresult.Set(
1655 problemresults = problemresults,
1656 endtime = currenttime,
1657 timeused = (currenttime - challengeresult.starttime).total_seconds(),
1658 score = score,
1659 maximumscore = maximumscore,
1660 percentright = percentright,
1661 flags = flags,
1662 )
1663
1664 g.db.Update(challengeresult)
1665
1666 if challengeobj and problemresults:
1667 for r in problemresults:
1668 if isinstance(r, dict) and 'id' in r:
1669 entry = g.db.Find(
1670 learn_studylist,
1671 'WHERE classid = ? AND userid = ? AND problemid = ?',
1672 [
1673 challengeobj.classid,
1674 g.session.userid,
1675 FromShortCode(r['id']),
1676 ]
1677 )
1678
1679 if entry:
1680 entry = entry[0]
1681 else:
1682 entry = learn_studylist()
1683 entry.Set(
1684 classid = challengeobj.classid,
1685 userid = g.session.userid,
1686 problemid = FromShortCode(r['id']),
1687 )
1688
1689 scorediff = -10
1690
1691 if r['status'] == 'right':
1692 scorediff = r['timeremaining']
1693
1694 if scorediff > 20:
1695 scorediff = 20
1696
1697 if not entry.results:
1698 entry.results = {}
1699
1700 entry.results[int(currenttime.timestamp())] = scorediff
1701
1702 entry.Set(
1703 score = entry.score + scorediff,
1704 results = entry.results,
1705 )
1706
1707 g.db.Save(entry)
1708
1709 g.db.Commit()
1710
1711 if challengeobj:
1712 challengeobj.Analyze(g)
1713
1714 thisclass = g.db.Load(learn_schoolclass, challengeobj.classid)
1715 thisclass.UpdateGrades(g, g.session.userid)
1716
1717 elif command == 'analyze':
1718 challengeid = StrV(d, 'challengeid')
1719 classid = StrV(d, 'classid')
1720
1721 if not challengeid or not classid:
1722 raise Exception(g.T.missingparams)
1723
1724 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
1725 raise Exception(g.T.cantchange)
1726
1727 challenge = g.db.Load(learn_challenge, FromShortCode(challengeid))
1728
1729 if challenge.flags & CHALLENGE_NEEDS_ANALYSIS:
1730 challenge.Analyze(g)
1731 g.db.Commit()
1732
1733 results['data'] = challenge.ToJSON()
1734 elif command == 'getanswers':
1735 challengeid = StrV(d, 'challengeid')
1736 start = IntV(d, 'start', 0, 99999999, 0)
1737 limit = IntV(d, 'limit', 10, 1000, 100)
1738
1739 if not challengeid:
1740 raise Exception(g.T.missingparams)
1741
1742 challengeid = FromShortCode(challengeid)
1743
1744 challengeobj = g.db.Load(learn_challenge, challengeid, 'rid, classid, results')
1745
1746 if not g.db.HasLink(learn_schoolclass, challengeobj.classid, learn_teacher, g.session.userid):
1747 raise Exception(g.T.cantview)
1748
1749 answers = {}
1750
1751 for r in g.db.Find(
1752 learn_challengeresult,
1753 'WHERE challengeid = ?',
1754 challengeid,
1755 fields = 'rid, userid, problemresults',
1756 orderby = 'endtime',
1757 ):
1758 studentcode = ToShortCode(r.userid)
1759 answer = r.problemresults
1760 points = challengeobj.results[studentcode][4] if studentcode in challengeobj.results else 0
1761
1762 answers[studentcode] = {
1763 'points': points,
1764 }
1765
1766 if isinstance(answer, list) and len(answer) == 5:
1767 answers[studentcode]['answer'] = {
1768 'idcode': studentcode,
1769 'content': answer[0],
1770 'image': answer[1],
1771 'sound': answer[2],
1772 'video': answer[3],
1773 'files': answer[4],
1774 }
1775 else:
1776 answers[studentcode]['answer'] = {
1777 'idcode': studentcode,
1778 'content': '',
1779 'image': '',
1780 'sound': '',
1781 'video': '',
1782 'files': '',
1783 }
1784
1785 for r in g.db.Find(
1786 learn_challengeresult,
1787 'WHERE challengeid = ?',
1788 challengeid,
1789 fields = 'rid, problemresults',
1790 orderby = 'endtime'
1791 ):
1792
1793 if r.problemresults and 'answer' in r.problemresults:
1794 answers[ToShortCode(r.userid)]['answer'] = r.problemresults['answer']
1795
1796 results['data'] = answers
1797 elif command == 'delete':
1798 idcodes = ArrayV(d, 'ids')
1799 classid = StrV(d, 'classid')
1800
1801 if not g.db.HasLink(learn_schoolclass, FromShortCode(classid), learn_teacher, g.session.userid):
1802 raise Exception(g.T.cantdelete)
1803
1804 g.db.Delete(
1805 learn_challenge,
1806 'WHERE classid = ? AND rid IN (' + ', '.join(['?' for x in idcodes]) + ')',
1807 [FromShortCode(classid)] + [FromShortCode(x) for x in idcodes],
1808 )
1809
1810 g.db.Commit()
1811 else:
1812 results['error'] = f'{g.T.cantunderstand} "{command}"'
1813
1814 except Exception as e:
1815 traceback.print_exc()
1816 results['error'] = str(e)
1817
1818 return results
1819
1820
1821# =====================================================================
1823 """Lets the user view study list entries, results, and delete unneeded
1824 problems.
1825
1826 Note that in most cases, problems are automatically added to the study
1827 list when a teacher updates a class, so deleting a problem that is
1828 in the study list for that class will only take effect until the next
1829 time the teacher changes the class.
1830 """
1831
1832 results = {}
1833
1834 try:
1835 d = json.loads(g.request.get_data())
1836
1837 command = StrV(d, 'command')
1838
1839 if command == 'search':
1840 studentid = StrV(d, 'studentid')
1841 classid = StrV(d, 'classid')
1842 start = IntV(d, 'start', 0, 99999999, 0)
1843 limit = IntV(d, 'limit', 10, 1000, 100)
1844
1845 if not classid:
1846 raise Exception(g.T.missingparams)
1847
1848 classid = FromShortCode(classid)
1849
1850 if studentid:
1851 studentid = FromShortCode(studentid)
1852
1853 if studentid and studentid != g.session.userid and not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
1854 raise Exception(g.T.cantview)
1855
1856 conds = ['classid = ?', "userid = ?"]
1857 params = [classid]
1858
1859 if studentid:
1860 params.append(studentid)
1861 else:
1862 params.append(0)
1863
1864 conds = "WHERE " + (" AND ").join(conds)
1865
1866 entries = g.db.Find(
1867 learn_studylist,
1868 conds,
1869 params,
1870 start = start if studentid else 0,
1871 limit = limit if studentid else 1,
1872 orderby = 'score, problemid',
1873 fields = 'rid, problemid, score, results'
1874 )
1875
1876 if not studentid:
1877 if not entries:
1878 entry = learn_studylist()
1879 entry.Set(
1880 classid = classid,
1881 userid = 0,
1882 problemid = 0,
1883 results = {},
1884 )
1885 g.db.Save(entry)
1886 g.db.Commit()
1887 else:
1888 entry = entries[0]
1889
1890 if 'problemids' in entry.results:
1891 entryobjs = [
1892 {
1893 'idcode': x,
1894 'problemid': FromShortCode(x),
1895 'score': 0,
1896 'results': {}
1897 }
1898 for x in entry.results['problemids']
1899 ]
1900
1901 results['cardids'] = entry.results['problemids']
1902 else:
1903 entryobjs = []
1904
1905 else:
1906 results['count'] = len(entries)
1907
1908 entryobjs = [x.ToJSON('problemid, score, results') for x in entries]
1909
1910 for x in entryobjs:
1911 x['idcode'] = ToShortCode(x['problemid'])
1912
1913
1914 if entryobjs:
1915 problems = GetAllProblems(g, [], CARD_LINK_PROBLEM, [x['problemid'] for x in entryobjs])
1916
1917 for p in problems:
1918 problemid = FromShortCode(p['idcode'])
1919
1920 for e in entryobjs:
1921 if problemid == e['problemid']:
1922 e['problem'] = p
1923 break
1924
1925 # Clear any deleted problems
1926 entryobjs = list(filter(lambda x: 'problem' in x, entryobjs))
1927
1928 results['data'] = entryobjs if studentid else entryobjs[start:start + limit]
1929
1930 if not studentid:
1931 results['count'] = len(entryobjs)
1932
1933 elif command == 'save':
1934 classid = StrV(d, 'classid')
1935 studentid = StrV(d, 'studentid')
1936 cardids = ArrayV(d, 'cardids')
1937 problemids = ArrayV(d, 'problemids')
1938
1939 if not classid:
1940 raise Exception(g.T.missingparams)
1941
1942 classid = FromShortCode(classid)
1943
1944 if studentid:
1945 studentid = FromShortCode(studentid)
1946
1947 isteacher = g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
1948
1949 if (studentid and studentid != g.session.userid) or not isteacher:
1950 raise Exception(g.T.cantchange)
1951
1952 if cardids:
1953 cardids = list(set(cardids))
1954
1955 for r in GetAllProblems(g, [FromShortCode(x) for x in cardids], CARD_LINK_PROBLEM):
1956 problemids.append(r['idcode'])
1957
1958 if problemids:
1959 cardids = list(set(problemids))
1960
1961 if studentid:
1962 existingproblems = [
1963 ToShortCode(x.problemid)
1964 for x in g.db.Find(
1965 learn_studylist,
1966 'WHERE classid = ? AND userid = ?',
1967 [
1968 classid,
1969 studentid,
1970 ],
1971 fields = 'problemid'
1972 )
1973 ]
1974
1975 newproblems = filter(lambda x: x not in existingproblems, problemids)
1976
1977 for s in newproblems:
1978 newentry = learn_studylist()
1979 newentry.Set(
1980 classid = classid,
1981 userid = studentid,
1982 problemid = FromShortCode(s),
1983 results = {},
1984 )
1985 g.db.Save(newentry)
1986 else:
1987 entry = g.db.Find(
1988 learn_studylist,
1989 'WHERE classid = ? AND userid = 0',
1990 classid,
1991 )
1992
1993 if not entry:
1994 entry = learn_studylist()
1995 entry.Set(
1996 classid = classid,
1997 userid = 0,
1998 results = {}
1999 )
2000 else:
2001 entry = entry[0]
2002
2003 if 'problemids' in entry.results:
2004 problemids.extend(entry.results['problemids'])
2005
2006 problemids = list(set(problemids))
2007
2008 entry.Set(
2009 problemid = 0,
2010 results = {
2011 'cardids': cardids,
2012 'problemids': problemids,
2013 }
2014 )
2015
2016 g.db.Save(entry)
2017
2018 cls = g.db.Load(learn_schoolclass, classid, 'rid')
2019
2020 for r in g.db.Linked(cls, learn_student, fields = 'userid'):
2021 existingproblems = [
2022 ToShortCode(x.problemid)
2023 for x in g.db.Find(
2024 learn_studylist,
2025 'WHERE classid = ? AND userid = ?',
2026 [
2027 classid,
2028 r.userid,
2029 ],
2030 fields = 'problemid'
2031 )
2032 ]
2033
2034 newproblems = filter(lambda x: x not in existingproblems, problemids)
2035
2036 for s in newproblems:
2037 newentry = learn_studylist()
2038 newentry.Set(
2039 classid = classid,
2040 userid = r.userid,
2041 problemid = FromShortCode(s),
2042 results = {}
2043 )
2044 g.db.Save(newentry)
2045
2046 g.db.Commit()
2047 elif command == 'delete':
2048 idcodes = ArrayV(d, 'ids')
2049 classid = StrV(d, 'classid')
2050 studentid = StrV(d, 'studentid')
2051
2052 if studentid:
2053 studentid = FromShortCode(studentid)
2054
2055 classid = FromShortCode(classid)
2056
2057 isteacher = g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
2058
2059 if (
2060 (studentid and studentid != g.session.userid and not isteacher)
2061 or (not studentid and not isteacher)
2062 ):
2063 raise Exception(g.T.cantdelete)
2064
2065 if studentid:
2066
2067 g.db.Delete(
2068 learn_studylist,
2069 'WHERE classid = ? AND userid = ? AND problemid IN (' + ', '.join(['?' for x in idcodes]) + ')',
2070 [classid] + [studentid] + [FromShortCode(x) for x in idcodes],
2071 )
2072
2073 g.db.Commit()
2074 else:
2075 entry = g.db.Find(
2076 learn_studylist,
2077 'WHERE classid = ? AND userid = 0',
2078 classid,
2079 )
2080
2081 if entry and 'problemids' in entry[0].results:
2082 entry = entry[0]
2083
2084 for r in idcodes:
2085 entry.results['problemids'].remove(r)
2086
2087 entry.Set(results = entry.results)
2088
2089 g.db.Save(entry)
2090
2091 g.db.Delete(
2092 learn_studylist,
2093 'WHERE classid = ? AND problemid IN (' + ', '.join(['?' for x in idcodes]) + ')',
2094 [classid] + [FromShortCode(x) for x in idcodes],
2095 )
2096
2097 g.db.Commit()
2098
2099 else:
2100 results['error'] = f'{g.T.cantunderstand} "{command}"'
2101
2102 except Exception as e:
2103 traceback.print_exc()
2104 results['error'] = str(e)
2105
2106 return results
2107
2108# =====================================================================
2110 """Lets new students find a school that is right for them and
2111 handles general school management. You'll need to be a school
2112 administrator or school system administrator in order to create or
2113 change school information.
2114 """
2115 results = {}
2116
2117 try:
2118 d = json.loads(g.request.get_data())
2119
2120 command = StrV(d, 'command')
2121
2122 if command == 'search':
2123 keyword = StrV(d, 'keyword')
2124 displayname = StrV(d, 'displayname', keyword)
2125 description = StrV(d, 'description', keyword)
2126 address = StrV(d, 'address', keyword)
2127 phone = StrV(d, 'phone', keyword)
2128 email = StrV(d, 'email', keyword)
2129 contactinfo = StrV(d, 'contactinfo', keyword)
2130 start = IntV(d, 'start', 0, 99999999, 0)
2131 limit = IntV(d, 'limit', 10, 1000, 20)
2132
2133 conds = []
2134 params = []
2135
2136 if description:
2137 conds.append("description LIKE ?")
2138 params.append(EscapeSQL('%' + description + '%'))
2139
2140 if address:
2141 conds.append("address LIKE ?")
2142 params.append(EscapeSQL('%' + address + '%'))
2143
2144 if phone:
2145 conds.append("phone LIKE ?")
2146 params.append(EscapeSQL('%' + phone + '%'))
2147
2148 if email:
2149 conds.append("email LIKE ?")
2150 params.append(EscapeSQL('%' + email + '%'))
2151
2152 if contactinfo:
2153 conds.append("contactinfo LIKE ?")
2154 params.append(EscapeSQL('%' + contactinfo + '%'))
2155
2156 if conds:
2157 conds = "WHERE " + (" AND ").join(conds)
2158 else:
2159 conds = ""
2160
2161 ls = []
2162
2163 cards = g.db.Find(
2164 learn_school,
2165 conds,
2166 params,
2167 start = start,
2168 limit = limit,
2169 orderby = 'displayname'
2170 )
2171
2172 for c in cards:
2173 ls.append(c.ToJSON())
2174
2175 results['data'] = ls
2176 results['count'] = len(ls)
2177 elif command == 'load':
2178 schoolid = StrV(d, 'schoolid')
2179
2180 if not schoolid:
2181 raise Exception(T.missingparams)
2182
2183 school = g.db.Load(learn_school, FromShortCode(schoolid))
2184
2185 if not g.db.HasLink(school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
2186 raise Exception(T.cantview)
2187
2188 results['data'] = o.ToJSON()
2189 elif command == 'getteachers':
2190 schoolid = StrV(d, 'schoolid')
2191 if not schoolid:
2192 raise Exception(T.missingparams)
2193
2194 school = g.db.Load(learn_school, FromShortCode(schoolid))
2195
2196 if not g.db.HasLink(school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
2197 raise Exception(T.cantview)
2198
2199 keyword = StrV(d, 'keyword')
2200 displayname = StrV(d, 'displayname', keyword)
2201 profile = StrV(d, 'profile', keyword)
2202 phonenumber = StrV(d, 'phonenumber', keyword)
2203 email = StrV(d, 'email', keyword)
2204 start = IntV(d, 'start', 0, 99999999, 0)
2205 limit = IntV(d, 'limit', 10, 1000, 100)
2206
2207 conds = []
2208 params = []
2209
2210 if displayname:
2211 conds.append("displayname LIKE ?")
2212 params.append(EscapeSQL('%' + displayname + '%'))
2213
2214 if profile:
2215 conds.append("profile LIKE ?")
2216 params.append(EscapeSQL('%' + profile + '%'))
2217
2218 if phonenumber:
2219 conds.append("phonenumber LIKE ?")
2220 params.append(EscapeSQL('%' + phonenumber + '%'))
2221
2222 if email:
2223 conds.append("email LIKE ?")
2224 params.append(EscapeSQL('%' + email + '%'))
2225
2226 conds.append("schoolid = ?")
2227 params.append(FromShortCode(schoolid))
2228
2229 if conds:
2230 conds = "WHERE " + (" AND ").join(conds)
2231 else:
2232 conds = ""
2233
2234 ls = []
2235
2236 for r in g.db.Linked(school,
2237 learn_teacher,
2238 fields = 'userid, displayname, phonenumber',
2239 cond = conds,
2240 params = params,
2241 start = start,
2242 limit = limit,
2243 ):
2244 o = r.ToJSON('displayname, phonenumber')
2245 o['idcode'] = ToShortCode(r.userid)
2246 ls.append(o)
2247
2248 results['data'] = ls
2249 elif command == 'save':
2250 newinfo = d['data']
2251
2252 if not V('newinfo', 'rid') and not g.db.HasGroup('school.system.administrators'):
2253 raise Exception(g.T.cantcreate)
2254
2255 g.db.RequireGroup('school.administrators', g.T.cantchange)
2256
2257 s = learn_school()
2258 s.Set(**(d['data']))
2259 g.db.Save(s)
2260
2261 try:
2262 a = g.db.Load(learn_schooladministrator, g.session.userid)
2263 except Exception as e:
2264 a = learn_administrator()
2265 a.Set(userid = g.session.userid)
2266 g.db.Insert(a)
2267
2268 g.db.Link(s, a)
2269
2270 if V('newinfo', 'rid'):
2271 for c in g.db.Linked(s, learn_schoolclass):
2272 c.Set(displayname = s.displayname)
2273
2274 g.db.Commit()
2275
2276 results['data'] = s.rid
2277 elif command == 'delete':
2278 idcodes = ArrayV(d, 'id')
2279
2280 if not idcodes:
2281 raise Exception(g.T.missingparams)
2282
2283 g.db.RequireGroup('school.system.administrators', g.T.cantdelete)
2284
2285 for idcode in idcodes:
2286 s = g.db.Load(learn_school, FromShortCode(idcode))
2287
2288 for a in g.db.Linked(s, learn_schoolclass):
2289 g.db.Unlink(s, a)
2290
2291 for a in g.db.Linked(s, learn_schooladministrator):
2292 g.db.Unlink(s, a)
2293
2294 for a in g.db.Linked(s, learn_schoolsystemadministrator):
2295 g.db.Unlink(s, a)
2296
2297 for a in g.db.Linked(s, learn_teacher):
2298 g.db.Unlink(s, a)
2299
2300 for a in g.db.Linked(s, learn_student):
2301 g.db.Unlink(s, a)
2302
2303 g.db.Delete(s)
2304
2305 g.db.Commit()
2306 elif command == 'getwaitingstudents':
2307 g.db.RequireGroup('school.administrators', g.T.cantview)
2308
2309 schoolid = StrV(d, 'schoolid')
2310
2311 if not schoolid:
2312 raise Exception(g.T.missingparams)
2313
2314 s = g.db.Load(learn_school, FromShortCode(schoolid))
2315
2316 waitingstudents = []
2317
2318 for r in g.db.Linked(s, learn_student, STUDENT_APPLIED, fields = 'userid, displayname, phonenumber'):
2319 o = r.ToJSON()
2320 o['idcode'] = ToShortCode(r.userid)
2321 waitingstudents.append(o)
2322
2323 results['data'] = waitingstudents
2324 results['count'] = len(waitingstudents)
2325 elif command == 'approvestudent':
2326 g.db.RequireGroup('school.administrators', g.T.cantview)
2327
2328 schoolid = StrV(d, 'schoolid')
2329 studentid = StrV(d, 'studentid')
2330 accept = IntV(d, 'accept')
2331
2332 if not schoolid or not studentid:
2333 raise Exception(g.T.missingparams)
2334
2335 school = g.db.Load(learn_school, FromShortCode(schoolid))
2336 student = g.db.Load(learn_student, FromShortCode(studentid))
2337
2338 g.db.Unlink(school, student, STUDENT_APPLIED)
2339
2340 if accept:
2341 g.db.Link(school, student)
2342
2343 g.db.Commit()
2344 elif command == 'getwaitingteachers':
2345 g.db.RequireGroup('school.administrators', g.T.cantview)
2346
2347 schoolid = StrV(d, 'schoolid')
2348
2349 if not schoolid:
2350 raise Exception(g.T.missingparams)
2351
2352 s = g.db.Load(learn_school, FromShortCode(schoolid))
2353
2354 waitingstudents = []
2355
2356 for r in g.db.Linked(s, learn_teacher, TEACHER_APPLIED, fields = 'userid, displayname, phonenumber'):
2357 o = r.ToJSON()
2358 o['idcode'] = ToShortCode(r.userid)
2359 waitingstudents.append(o)
2360
2361 results['data'] = waitingstudents
2362 results['count'] = len(waitingstudents)
2363 elif command == 'approveteacher':
2364 g.db.RequireGroup('school.administrators', g.T.cantview)
2365
2366 schoolid = StrV(d, 'schoolid')
2367 teacherid = StrV(d, 'teacherid')
2368 accept = IntV(d, 'accept')
2369
2370 if not schoolid or not teacherid:
2371 raise Exception(g.T.missingparams)
2372
2373 school = g.db.Load(learn_school, FromShortCode(schoolid))
2374 teacher = g.db.Load(learn_teacher, FromShortCode(teacherid))
2375
2376 g.db.Unlink(school, teacherid, TEACHER_APPLIED)
2377
2378 if accept:
2379 g.db.Link(school, teacherid)
2380
2381 g.db.Commit()
2382 elif command == 'getlinked':
2383 admin = g.db.Load(learn_schooladministrator, g.session.userid)
2384
2385 results['data'] = [
2386 x.ToJSON('rid, icon, displayname, description, phone')
2387 for x in g.db.Linked(
2388 admin,
2389 learn_school,
2390 fields = 'rid, icon, displayname, description, phone'
2391 )
2392 ]
2393 results['count'] = len(results['data'])
2394 elif command == 'addusers':
2395 admin = g.db.Load(learn_schooladministrator, g.session.userid)
2396
2397 schoolid = StrV(d, 'schoolid')
2398 studentids = ArrayV(d, 'studentids')
2399 teacherids = ArrayV(d, 'teacherids')
2400 administratorids = ArrayV(d, 'administratorids')
2401
2402 if not schoolid or (not studentids and not teacherids and not administratorids):
2403 raise Exception(g.T.missingparams)
2404
2405 school = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
2406
2407 administratorgroup = g.db.Find(system_group, 'WHERE groupname = ?', 'school.administrators')[0]
2408
2409 if studentids:
2410 studentgroup = g.db.Find(system_group, 'WHERE groupname = ?', 'students')[0]
2411
2412 newusers = g.db.Find(
2413 system_user,
2414 'WHERE rid IN (' + ', '.join(['?' for x in studentids]) + ')',
2415 [
2416 FromShortCode(x) for x in studentids
2417 ],
2418 fields = 'rid, displayname'
2419 )
2420
2421 for r in newusers:
2422 g.db.Link(r, studentgroup)
2423
2424 existingstudents = list(
2425 g.db.Find(
2426 learn_student,
2427 'WHERE userid IN (' + ', '.join(['?' for x in newusers]) + ')',
2428 [
2429 x.rid for x in newusers
2430 ],
2431 fields = 'rid'
2432 )
2433 )
2434
2435 newuserids = set([x.rid for x in newusers])
2436 existingstudentids = set([x.rid for x in existingstudents])
2437
2438 studentstocreate = list(newuserids - existingstudentids)
2439
2440 for r in studentstocreate:
2441 for s in newusers:
2442 if s.rid == r:
2443 newuser = learn_student()
2444 newuser.Set(
2445 userid = s.rid,
2446 displayname = s.displayname,
2447 )
2448 g.db.Save(newuser)
2449
2450 existingstudents.append(newuser)
2451 break
2452
2453 for r in existingstudents:
2454 g.db.Link(r, school)
2455
2456 if teacherids:
2457 teachergroup = g.db.Find(system_group, 'WHERE groupname = ?', 'teachers')[0]
2458
2459 newusers = g.db.Find(
2460 system_user,
2461 'WHERE rid IN (' + ', '.join(['?' for x in teacherids]) + ')',
2462 [
2463 FromShortCode(x) for x in teacherids
2464 ],
2465 fields = 'rid, displayname'
2466 )
2467
2468 for r in newusers:
2469 g.db.Link(r, teachergroup)
2470
2471 existingteachers = list(
2472 g.db.Find(
2473 learn_teacher,
2474 'WHERE userid IN (' + ', '.join(['?' for x in newusers]) + ')',
2475 [
2476 x.rid for x in newusers
2477 ],
2478 fields = 'rid'
2479 )
2480 )
2481
2482 newuserids = set([x.rid for x in newusers])
2483 existingteacherids = set([x.rid for x in existingteachers])
2484
2485 teacherstocreate = list(newuserids - existingteacherids)
2486
2487 for r in teacherstocreate:
2488 for s in newusers:
2489 if s.rid == r:
2490 newuser = learn_teacher()
2491 newuser.Set(
2492 userid = s.rid,
2493 displayname = s.displayname,
2494 )
2495 g.db.Save(newuser)
2496
2497 existingteachers.append(newuser)
2498 break
2499
2500 for r in existingteachers:
2501 g.db.Link(r, school)
2502
2503 if administratorids:
2504 administratorgroup = g.db.Find(system_group, 'WHERE groupname = ?', 'school.administrators')[0]
2505
2506 newusers = g.db.Find(
2507 system_user,
2508 'WHERE rid IN (' + ', '.join(['?' for x in administratorids]) + ')',
2509 [
2510 FromShortCode(x) for x in administratorids
2511 ],
2512 fields = 'rid, displayname'
2513 )
2514
2515 for r in newusers:
2516 g.db.Link(r, administratorgroup)
2517
2518 existingadministrators = list(
2519 g.db.Find(
2520 learn_schooladministrator,
2521 'WHERE userid IN (' + ', '.join(['?' for x in newusers]) + ')',
2522 [
2523 x.rid for x in newusers
2524 ],
2525 fields = 'rid'
2526 )
2527 )
2528
2529 newuserids = set([x.rid for x in newusers])
2530 existingadministratorids = set([x.rid for x in existingadministrators])
2531
2532 administratorstocreate = list(newuserids - existingadministratorids)
2533
2534 for r in administratorstocreate:
2535 for s in newusers:
2536 if s.rid == r:
2537 newuser = learn_schooladministrator()
2538 newuser.Set(
2539 userid = s.rid,
2540 displayname = s.displayname,
2541 )
2542 g.db.Save(newuser)
2543
2544 existingadministrators.append(newuser)
2545 break
2546
2547 for r in existingadministrators:
2548 g.db.Link(r, school)
2549
2550 g.db.Commit()
2551 else:
2552 results['error'] = f'{g.T.cantunderstand} "{command}"'
2553 except Exception as e:
2554 traceback.print_exc()
2555 results['error'] = str(e)
2556
2557 return results
2558
2559# =====================================================================
2561 """Handles the management of classes for a school. You'll need to be
2562 an administrator for that school in order to make any changes to the
2563 class information. If you're a teacher, you can change autohomework
2564 and other class settings here.
2565 """
2566 results = {}
2567
2568 try:
2569 d = json.loads(g.request.get_data())
2570
2571 command = StrV(d, 'command')
2572
2573 if command == 'search':
2574 keyword = StrV(d, 'keyword')
2575 coursenum = StrV(d, 'coursenum', keyword)
2576 displayname = StrV(d, 'displayname', keyword)
2577 description = StrV(d, 'description', keyword)
2578 schedule = StrV(d, 'schedule', keyword)
2579 teachers = StrV(d, 'teachers', keyword)
2580 schoolid = StrV(d, 'schoolid')
2581 start = IntV(d, 'start', 0, 99999999, 0)
2582 limit = IntV(d, 'limit', 10, 1000, 100)
2583
2584 conds = []
2585 params = []
2586
2587 if coursenum:
2588 conds.append("coursenum LIKE ?")
2589 params.append(EscapeSQL('%' + coursenum + '%'))
2590
2591 if displayname:
2592 conds.append("displayname LIKE ?")
2593 params.append(EscapeSQL('%' + displayname + '%'))
2594
2595 if description:
2596 conds.append("description LIKE ?")
2597 params.append(EscapeSQL('%' + description + '%'))
2598
2599 if description:
2600 conds.append("description LIKE ?")
2601 params.append(EscapeSQL('%' + description + '%'))
2602
2603 if teachers:
2604 conds.append("teachers LIKE ?")
2605 params.append(EscapeSQL('%' + teachers + '%'))
2606
2607 if schoolid:
2608 conds.append("schoolid = ?")
2609 params.append(FromShortCode(schoolid))
2610
2611 if conds:
2612 conds = "WHERE " + (" AND ").join(conds)
2613 else:
2614 conds = ""
2615
2616 ls = []
2617
2618 cards = g.db.Find(
2619 learn_schoolclass,
2620 conds,
2621 params,
2622 start = start,
2623 limit = limit,
2624 orderby = 'coursenum, schedule, teachers'
2625 )
2626
2627 for c in cards:
2628 ls.append(c.ToJSON())
2629
2630 results['data'] = ls
2631 results['count'] = len(ls)
2632 elif command == 'load':
2633 classid = StrV(d, 'classid')
2634
2635 r = g.db.Load(learn_schoolclass, FromShortCode(schoolid))
2636 results['data'] = r.ToJSON()
2637 elif command == 'get':
2638 g.db.RequireGroup(['teachers', 'students'], g.T.cantview)
2639
2640 try:
2641 if g.db.HasGroup('teachers'):
2642 u = g.db.Load(learn_teacher, g.session.userid)
2643 else:
2644 u = g.db.Load(learn_student, g.session.userid)
2645
2646 ls = []
2647
2648 for r in g.db.Linked(u, learn_schoolclass):
2649 ls.append(r.ToJSON())
2650
2651 results['data'] = ls
2652 except Exception as e:
2653 results['data'] = []
2654 elif command == 'save':
2655 newinfo = d['data']
2656 schoolid = StrV(d, 'schoolid')
2657
2658 if not schoolid or not newinfo:
2659 raise Exception(T.missingparams)
2660
2661 if (not V('newinfo', 'rid')
2662 and not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid)
2663 ):
2664 raise Exception(g.T.cantcreate)
2665
2666 s = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid, displayname')
2667
2668 c = learn_schoolclass()
2669 c.Set(**newinfo)
2670 c.Set(
2671 schoolid = s.rid,
2672 displayname = s.displayname,
2673 )
2674 g.db.Save(c)
2675
2676 g.db.Link(s, c)
2677
2678 g.db.Commit()
2679
2680 results['data'] = s.rid
2681 elif command == 'delete':
2682 idcodes = ArrayV(d, 'ids')
2683
2684 if not idcodes:
2685 raise Exception(g.T.missingparams)
2686
2687 schooladministrator = g.db.Load(learn_schooladministrator, g.session.userid)
2688
2689 administeredschools = [x.rid for x in g.db.Linked(schooladministrator, learn_school, fields = 'rid')]
2690
2691 for idcode in idcodes:
2692 s = g.db.Load(learn_schoolclass, FromShortCode(idcode))
2693
2694 if s.schoolid not in administeredschools:
2695 continue
2696
2697 for a in g.db.Linked(s, learn_teacher):
2698 g.db.Unlink(s, a)
2699
2700 for a in g.db.Linked(s, learn_student):
2701 g.db.Unlink(s, a)
2702
2703 for a in g.db.Linked(s, learn_school):
2704 g.db.Unlink(s, a)
2705
2706 g.db.Delete(s)
2707
2708 g.db.Commit()
2709 elif command == 'setteachers':
2710 schoolid = StrV(d, 'schoolid')
2711 classid = StrV(d, 'classid')
2712 teachers = ArrayV(d, 'teachers')
2713
2714 if not classid or not teachers or not schoolid:
2715 raise Exception(g.T.missingparams)
2716
2717 if not g.session.userid:
2718 raise Exception(g.T.needlogin)
2719
2720 if not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
2721 raise Exception(g.T.cantview)
2722
2723 c = g.db.Load(learn_schoolclass, FromShortCode(classid))
2724
2725 displaynames = {}
2726
2727 teacherobjs = list(
2728 g.db.LoadMany(
2729 learn_teacher,
2730 [FromShortCode(x) for x in teachers],
2731 'userid, displayname',
2732 )
2733 )
2734
2735 for r in teacherobjs:
2736 for k, v in r.displayname.items():
2737 if k in displaynames:
2738 displaynames[k] = displaynames[k] + ', ' + v
2739 else:
2740 displaynames[k] = v
2741
2742 c.Set(teachers = displaynames)
2743
2744 g.db.LinkArray(c, teacherobjs)
2745
2746 g.db.Commit()
2747 elif command == 'setcurriculum':
2748 schoolid = StrV(d, 'schoolid')
2749 classid = StrV(d, 'classid')
2750 cardids = ArrayV(d, 'cardids')
2751
2752 if not classid or not cardids:
2753 raise Exception(g.T.missingparams)
2754
2755 if not schoolid:
2756 raise Exception(g.T.missingparams)
2757
2758 if not g.session.userid:
2759 raise Exception(g.T.needlogin)
2760
2761 if not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_teacher, g.session.userid):
2762 raise Exception(g.T.cantview)
2763
2764 c = g.db.Load(learn_schoolclass, FromShortCode(classid))
2765
2766 students = g.db.Linked(c, learn_student, fields = 'userid')
2767 teachers = g.db.Linked(c, learn_teacher, fields = 'userid')
2768
2769 previouscards = g.db.Linked(c, learn_card, fields = 'rid')
2770 previouscardids = [x.rid for x in previouscards]
2771
2772 for p in previouscards:
2773 if ToShortCode(p.rid) not in cardids:
2774 for student in students:
2775 g.db.Unlink(p, student)
2776
2777 for teacher in teachers:
2778 g.db.Unlink(p, teacher)
2779
2780 cardobjs = []
2781
2782 for cid in cardids:
2783
2784 card = g.db.Load(learn_card, FromShortCode(cid), 'rid')
2785
2786 cardobjs.append(card)
2787
2788 if card.rid not in previouscardids:
2789 for student in students:
2790 g.db.Link(card, student)
2791
2792 for teacher in teachers:
2793 g.db.Link(card, teacher)
2794
2795 g.db.LinkArray(c, cardobjs)
2796
2797 # Update access for all affected students and teachers
2798 updatecardobjs = list(students) + list(teachers)
2799
2800 if updatecardobjs:
2801 for r in updatecardobjs:
2802 UpdateAllowedCards(g, r)
2803
2804 g.db.Commit()
2805 elif command == 'setstudents':
2806 schoolid = StrV(d, 'schoolid')
2807 classid = StrV(d, 'classid')
2808 students = ArrayV(d, 'students')
2809
2810 if not classid or not students:
2811 raise Exception(g.T.missingparams)
2812
2813 if not schoolid:
2814 raise Exception(g.T.missingparams)
2815
2816 if not g.session.userid:
2817 raise Exception(g.T.needlogin)
2818
2819 if not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
2820 raise Exception(g.T.cantview)
2821
2822 c = g.db.Load(learn_schoolclass, FromShortCode(classid))
2823
2824 studentobjs = list(
2825 g.db.LoadMany(
2826 learn_student,
2827 [FromShortCode(x) for x in students],
2828 'userid',
2829 )
2830 )
2831
2832 g.db.LinkArray(c, studentobjs)
2833
2834 c.Set(numstudents = len(studentobjs))
2835
2836 g.db.Update(c)
2837
2838 courses = g.db.Linked(c, learn_card, 'rid')
2839
2840 for r in studentobjs:
2841 for s in courses:
2842 g.db.Link(r, s)
2843
2844 for r in studentobjs:
2845 UpdateAllowedCards(g, r)
2846
2847 g.db.Commit()
2848 elif command == 'setautohomework':
2849 classid = StrV(d, 'classid')
2850 challengetypes = ArrayV(d, 'challengetypes')
2851 lessonids = ArrayV(d, 'lessonids')
2852 problemids = ArrayV(d, 'problemids')
2853 minscore = IntV(d, 'minscore')
2854 minpercentage = IntV(d, 'minpercentage')
2855 numpoints = IntV(d, 'numpoints')
2856 problemselect = StrV(d, 'problemselect')
2857 numtries = IntV(d, 'numtries')
2858 numproblems = IntV(d, 'numproblems')
2859 timelimit = IntV(d, 'timelimit')
2860 didnttrypenalty = IntV(d, 'didnttrypenalty')
2861 usehomeworkmanager = IntV(d, 'usehomeworkmanager')
2862 usestudylist = IntV(d, 'usestudylist')
2863 timezone = IntV(d, 'timezone')
2864
2865 if not classid:
2866 raise Exception(g.T.missingparams)
2867
2868 classid = FromShortCode(classid)
2869
2870 if not g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid):
2871 raise Exception(g.T.cantchange)
2872
2873 c = g.db.Load(learn_schoolclass, classid, 'rid, autohomework, flags')
2874
2875 if usehomeworkmanager:
2876 c.flags = c.flags | CLASS_AUTO_HOMEWORK
2877 else:
2878 c.flags = c.flags & (~CLASS_AUTO_HOMEWORK)
2879
2880 if usestudylist:
2881 c.flags = c.flags | CLASS_REVIEW_STUDYLIST
2882 else:
2883 c.flags = c.flags & (~CLASS_REVIEW_STUDYLIST)
2884
2885 lessons = [];
2886
2887 if lessonids:
2888 for k, v in GetCardTitles(g, [FromShortCode(x) for x in lessonids]).items():
2889 lessons.append({
2890 'idcode': ToShortCode(k),
2891 'title': v,
2892 })
2893
2894 problems = []
2895
2896 if problemids:
2897 problems = GetAllProblems(g, [], CARD_LINK_PROBLEM, [FromShortCode(x) for x in problemids])
2898
2899 c.Set(
2900 resettime = timezone,
2901 flags = c.flags,
2902 autohomework = {
2903 'challengetypes': challengetypes,
2904 'lessonids': lessonids,
2905 'problemids': problemids,
2906 'lessons': lessons,
2907 'problems': problems,
2908 'minscore': minscore,
2909 'minpercentage': minpercentage,
2910 'numpoints': numpoints,
2911 'problemselect': problemselect,
2912 'numtries': numtries,
2913 'numproblems': numproblems,
2914 'timelimit': timelimit,
2915 'didnttrypenalty': didnttrypenalty,
2916 },
2917 )
2918
2919 g.db.Update(c);
2920
2921 g.db.Commit()
2922
2923 results['data'] = c.ToJSON('flags, resettime, autohomework')
2924 else:
2925 results['error'] = f'{g.T.cantunderstand} "{command}"'
2926 except Exception as e:
2927 traceback.print_exc()
2928 results['error'] = str(e)
2929
2930 return results
2931
2932# =====================================================================
2934 """Handles student management. Only a school administrator can use
2935 this API.
2936 """
2937 results = {}
2938
2939 try:
2940 d = json.loads(g.request.get_data())
2941
2942 command = StrV(d, 'command')
2943
2944 schoolid = StrV(d, 'schoolid')
2945
2946 if not schoolid:
2947 raise Exception(g.T.missingparams)
2948
2949 if not g.session.userid:
2950 raise Exception(g.T.needlogin)
2951
2952 if not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
2953 raise Exception(g.T.cantview)
2954
2955 if command == 'search':
2956 displayname = StrV(d, 'displayname')
2957 phonenumber = StrV(d, 'phonenumber')
2958 classid = StrV(d, 'classid')
2959 start = IntV(d, 'start', 0, 99999999, 0)
2960 limit = IntV(d, 'limit', 10, 1000, 100)
2961
2962 conds = []
2963 params = []
2964
2965 if displayname:
2966 conds.append("displayname LIKE ?")
2967 params.append(EscapeSQL('%' + displayname + '%'))
2968
2969 if phonenumber:
2970 conds.append("phonenumber LIKE ?")
2971 params.append(EscapeSQL('%' + phonenumber + '%'))
2972
2973 if conds:
2974 conds = "WHERE " + (" AND ").join(conds)
2975 else:
2976 conds = ""
2977
2978 school = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
2979
2980 studentobjs = []
2981 chosenobjs = []
2982 chosenids = []
2983
2984 if classid:
2985 ls = []
2986
2987 c = g.db.Load(learn_schoolclass, FromShortCode(classid))
2988
2989 for r in g.db.Linked(c, learn_student, fields = 'userid, displayname, phonenumber, grade, coins, items'):
2990 o = r.ToJSON('displayname, phonenumber, grade, coins, items')
2991 o['idcode'] = ToShortCode(r.userid)
2992 chosenobjs.append(o)
2993 chosenids.append(r.userid)
2994
2995 students = g.db.Linked(
2996 school,
2997 learn_student,
2998 0,
2999 start,
3000 limit,
3001 'userid, displayname, phonenumber, grade, coins, items',
3002 'phonenumber',
3003 conds,
3004 params,
3005 )
3006
3007 for r in students:
3008 if r.userid in chosenids:
3009 continue
3010
3011 o = r.ToJSON('displayname, phonenumber, grade, coins, items')
3012 o['headshot'] = ToShortCode(r.userid)
3013 o['idcode'] = o['headshot']
3014 studentobjs.append(o)
3015
3016 results['data'] = studentobjs
3017 results['count'] = len(students)
3018 results['chosen'] = chosenobjs
3019 elif command == 'expel':
3020 idcode = StrV(d, 'id')
3021
3022 if not idcode:
3023 raise Exception(g.T.missingparams)
3024
3025 school = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
3026 student = g.db.Load(learn_student, FromShortCode(idcode), fields = 'userid')
3027
3028 g.db.Unlink(school, student)
3029 g.db.Commit()
3030 else:
3031 results['error'] = f'{g.T.cantunderstand} "{command}"'
3032 except Exception as e:
3033 traceback.print_exc()
3034 results['error'] = str(e)
3035
3036 return results
3037
3038# =====================================================================
3040 """Handles teacher management. Only a school administrator can use
3041 this API.
3042 """
3043 results = {}
3044
3045 try:
3046 d = json.loads(g.request.get_data())
3047
3048 command = StrV(d, 'command')
3049
3050 schoolid = StrV(d, 'schoolid')
3051
3052 if not schoolid:
3053 raise Exception(g.T.missingparams)
3054
3055 if not g.session.userid:
3056 raise Exception(g.T.needlogin)
3057
3058 if not g.db.HasLink(learn_school, FromShortCode(schoolid), learn_schooladministrator, g.session.userid):
3059 raise Exception(g.T.cantview)
3060
3061 if command == 'search':
3062 keyword = StrV(d, 'keyword')
3063 displayname = StrV(d, 'displayname', keyword)
3064 phonenumber = StrV(d, 'phonenumber', keyword)
3065 email = StrV(d, 'email', keyword)
3066 classid = StrV(d, 'classid')
3067 start = IntV(d, 'start', 0, 99999999, 0)
3068 limit = IntV(d, 'limit', 10, 1000, 100)
3069
3070 conds = []
3071 params = []
3072
3073 if displayname:
3074 conds.append("displayname LIKE ?")
3075 params.append(EscapeSQL('%' + displayname + '%'))
3076
3077 if phonenumber:
3078 conds.append("phonenumber LIKE ?")
3079 params.append(EscapeSQL('%' + phonenumber + '%'))
3080
3081 if email:
3082 conds.append("phonenumber LIKE ?")
3083 params.append(EscapeSQL('%' + phonenumber + '%'))
3084
3085 if conds:
3086 conds = "WHERE " + (" AND ").join(conds)
3087 else:
3088 conds = ""
3089
3090 school = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
3091
3092 teacherobjs = []
3093 chosenobjs = []
3094 chosenids = []
3095
3096 if classid:
3097 ls = []
3098
3099 c = g.db.Load(learn_schoolclass, FromShortCode(classid))
3100
3101 for r in g.db.Linked(c, learn_teacher, fields = 'userid, displayname, phonenumber, email'):
3102 o = r.ToJSON('displayname, phonenumber, email')
3103 o['idcode'] = ToShortCode(r.userid)
3104 chosenobjs.append(o)
3105 chosenids.append(r.userid)
3106
3107 teachers = g.db.Linked(
3108 school,
3109 learn_teacher,
3110 0,
3111 start,
3112 limit,
3113 'userid, displayname, phonenumber',
3114 'phonenumber',
3115 conds,
3116 params,
3117 )
3118
3119 for r in teachers:
3120 if r.userid in chosenids:
3121 continue
3122
3123 o = r.ToJSON('displayname, phonenumber')
3124 o['headshot'] = ToShortCode(r.userid)
3125 o['idcode'] = o['headshot']
3126 teacherobjs.append(o)
3127
3128 results['data'] = teacherobjs
3129 results['count'] = len(teachers)
3130 results['chosen'] = chosenobjs
3131 elif command == 'expel':
3132 idcode = StrV(d, 'id')
3133
3134 if not idcode:
3135 raise Exception(g.T.missingparams)
3136
3137 school = g.db.Load(learn_school, FromShortCode(schoolid), fields = 'rid')
3138 teacher = g.db.Load(learn_teacher, FromShortCode(idcode), fields = 'userid')
3139
3140 g.db.Unlink(school, teacher)
3141 g.db.Commit()
3142 else:
3143 results['error'] = f'{g.T.cantunderstand} "{command}"'
3144 except Exception as e:
3145 traceback.print_exc()
3146 results['error'] = str(e)
3147
3148 return results
3149
3150# =====================================================================
3152 """A simple text to speech API for students and teachers to play
3153 around with. You'll need to install the festival speech synthesis
3154 program on your server to use this.
3155 """
3156 if not g.urlargs:
3157 abort(500)
3158
3159 if ('students' not in g.session['groups']
3160 and 'teachers' not in g.session['groups']
3161 ):
3162 raise Exception(g.T.cantview)
3163
3164 text = g.urlargs[0]
3165
3166 filename = re.sub(r'\W', '_', text)
3167
3168 cachedir = '/srv/flasksite/libs/texttomp3'
3169
3170 textfile = cachedir + '/' + filename + '.txt'
3171 mp3file = cachedir + '/' + filename + '.mp3'
3172
3173 if os.path.exists(mp3file):
3174 return send_file(mp3file)
3175
3176 os.makedirs(cachedir, exist_ok = True)
3177
3178 with open(textfile, 'w') as f:
3179 f.write(g.urlargs[0])
3180
3181 p1 = subprocess.Popen(['text2wave', textfile], stdout = subprocess.PIPE)
3182
3183 output = subprocess.check_output(['lame', '-', mp3file], stdin = p1.stdout)
3184
3185 p1.wait()
3186
3187 return send_file(mp3file)
3188
3189# =====================================================================
3191 """Speech recognition API using the Kaldi project. Of course, it would
3192 be nice if we could simply use Google's speech recognition, but since
3193 Google servers are blocked in China, we have to use our own solution.
3194
3195 Note that speech recognition is a very expensive and memory intensive
3196 operation, so we use the DLock API to run only a single instance at
3197 a time. If you have a faster server, feel free to change the
3198 numprocesses variable to the number of processor cores that you have.
3199 """
3200 results = {};
3201
3202 try:
3203 if ('students' not in g.session['groups']
3204 and 'teachers' not in g.session['groups']
3205 ):
3206 raise Exception(g.T.cantview)
3207
3208 data = g.request.get_data()
3209
3210 contentlength = g.request.headers.get('content-length')
3211
3212 if contentlength:
3213 contentlength = int(contentlength)
3214 else:
3215 raise Exception(g.T.nodatauploaded)
3216
3217 if contentlength > (1024 * 64):
3218 raise Exception(g.T.toobig)
3219
3220 filename = uuid.uuid4().hex + '.ogg'
3221
3222 cachedir = '/srv/flasksite/static/learn/opus'
3223
3224 os.makedirs(cachedir, exist_ok = True)
3225
3226 uploadedfile = cachedir + '/' + filename
3227
3228 # Clean older recordings
3229 currenttime = time.time()
3230
3231 for r in os.listdir(cachedir):
3232 fullpath = cachedir + '/' + r
3233
3234 if os.stat(fullpath).st_mtime + 3600 < currenttime:
3235 os.remove(fullpath)
3236
3237 with open(uploadedfile, 'wb') as f:
3238 f.write(g.request.get_data())
3239
3240 results['uploadedfile'] = '/learn/opus/' + filename
3241
3242 # Really simple load balancer for the expensive speech recognition
3243 # operation. However, this implementation is cross-platform and works well
3244 # enough for low powered devices
3245 numprocesses = 1
3246 lock = DLock('opustotext' + str(random.randint(1, numprocesses)))
3247
3248 acquired = lock.LoopWhileLocked_ThenLocking()
3249
3250 if acquired:
3251 output = subprocess.check_output(
3252 [
3253 '/srv/flasksite/libs/vosk/recognizespeech.py',
3254 uploadedfile
3255 ],
3256 text = True
3257 )
3258
3259 if not output:
3260 results['error'] = g.T.nothingfound
3261 else:
3262 results['output'] = output
3263 else:
3264 results['error'] = g.T.serverbusy
3265 except Exception as e:
3266 results['error'] = f"Error processing request: {e}"
3267
3268 return results
3269
3270# =====================================================================
3272 """The helper function for /learn/courses.html.
3273
3274 This loads the main card, child cards, problems, and challenges
3275 for the JavaScript renderer.
3276 """
3277 result = ''
3278
3279 try:
3280 if not g.urlargs:
3281 return '''
3282 <div class="Error">
3283 No course ID given.
3284 </div>
3285 '''
3286 else:
3287 classid = g.urlargs.pop(0)
3288
3289 if not classid:
3290 raise Exception(g.T.missingparams)
3291
3292 stackleft = len(g.urlargs)
3293 cardstack = []
3294 childstacks = []
3295 breadcrumbs = []
3296 siblingcards = []
3297 childcards = []
3298 childcardids = []
3299 parentcard = 0
3300 c = 0
3301 problems = 0
3302 caneditcards = 0
3303 allowedcards = []
3304
3305 # An user can be a teacher for one class but a student for another class, so we load all allowed cards
3306 try:
3307 allowedcards = g.db.Load(learn_teacher, g.session.userid, fields = 'allowedcards').allowedcards
3308 caneditcards = 1
3309 allowedcards.extend(g.db.Load(learn_student, g.session.userid, fields = 'allowedcards').allowedcards)
3310 except Exception as e:
3311 allowedcards = g.db.Load(learn_student, g.session.userid, fields = 'allowedcards').allowedcards
3312
3313 ls = []
3314
3315 for arg in g.urlargs:
3316 if arg not in allowedcards:
3317 raise Exception(g.T.cantview)
3318
3319 if c:
3320 parentcard = c
3321
3322 c = g.db.Load(learn_card, FromShortCode(arg), 'rid, title, description, content, html, image, sound, video, flags')
3323
3324 breadcrumbs.append(arg)
3325
3326 siblingcards = childcards
3327
3328 childcards = g.db.Linked(c, learn_card, CARD_CHILD, fields = 'rid, title, description, image')
3329
3330 stackleft -= 1
3331
3332 childcardids = [ToShortCode(x.rid) for x in childcards]
3333
3334 card = c.ToJSON('title, description, content, html, image, sound, video, flags')
3335
3336 card['idcode'] = ToShortCode(c.rid)
3337
3338 cardstack.append(card)
3339
3340 childcardobjs = []
3341
3342 for x in childcards:
3343 childcardobj = x.ToJSON('title, description, image')
3344
3345 childcardobj['idcode'] = ToShortCode(x.rid)
3346
3347 childcardobjs.append(childcardobj)
3348
3349 childstacks.append(childcardobjs)
3350
3351 if not stackleft and c.flags & CARD_LESSON:
3352 problems = GetAllProblems(g, [c.rid], CARD_LINK_PROBLEM)
3353
3354 ls.append(f'''
3355 <script>
3356
3357 G.classid = '{classid}';
3358 G.cardstack = {json.dumps(cardstack)};
3359 G.childstacks = {json.dumps(childstacks)};
3360 G.problems = {json.dumps(problems)};
3361 G.caneditcards = {1 if caneditcards else 0};
3362
3363 </script>
3364
3365 <div class="ButtonBar CourseNavBar"></div>
3366 <div class="MainCard"></div>
3367 <div class="ButtonBar MainCardBar"></div>
3368 <div class="ChildCards FlexGrid16"></div>
3369 <div class="ButtonBar ChildCardBar"></div>
3370 <div class="ProblemToggles"></div>
3371 <div class="ProblemCards FlexGrid16"></div>
3372 <div class="ButtonBar ProblemBar"></div>
3373 <div class="ChallengeCards FlexGrid16"></div>
3374 <div class="SiblingNav Pad50"></div>
3375 ''')
3376
3377 return '\n'.join(ls)
3378 except Exception as e:
3379 traceback.print_exc()
3380 result = '<div class="Error">Error processing: ' + str(e) + '</div>'
3381
3382 return result
3383
3384# =====================================================================
3386 """The helper function for /learn/home.html.
3387
3388 This loads the classes and challenges for the JavaScript renderer
3389 in addition to generating autohomework challenges and doing analyses
3390 on challenges which have ended.
3391 """
3392 LoadTranslation(g, 'learn', 'default', g.session.lang)
3393 LoadTranslation(g, 'learn', 'challenges', g.session.lang)
3394
3395 isadministrator = 0
3396
3397 studentjson = {}
3398 teacherjson = {}
3399
3400 teacherclasses = []
3401 studentclasses = []
3402
3403 currenttime = time.time()
3404
3405 if 'school.administrators' in g.session['groups']:
3406 isadministrator = 1
3407
3408 if 'teachers' in g.session['groups']:
3409 try:
3410 teacher = g.db.Load(learn_teacher, g.session.userid, fields = 'userid, displayname')
3411
3412 teacherjson = teacher.ToJSON()
3413 teacherjson['idcode'] = ToShortCode(teacher.userid)
3414
3415 for r in g.db.Linked(teacher, learn_schoolclass):
3416 r.GenerateAutoHomework(g)
3417
3418 o = r.ToJSON()
3419
3420 o['courses'] = [
3421 x.ToJSON('rid, title')
3422 for x in g.db.Linked(r, learn_card, fields = 'rid, title')
3423 ]
3424
3425 o['students'] = {
3426 ToShortCode(x.userid): x.displayname
3427 for x in g.db.Linked(r, learn_student, fields = 'userid, displayname')
3428 }
3429
3430 teacherclasses.append(o)
3431 except Exception as e:
3432 traceback.print_exc()
3433 #pass
3434
3435 student = g.db.Load(
3436 learn_student,
3437 g.session.userid,
3438 'userid, displayname, coins, items'
3439 )
3440
3441 studentjson = student.ToJSON()
3442 studentjson['idcode'] = ToShortCode(student.userid)
3443
3444 for r in g.db.Linked(student, learn_schoolclass):
3445 r.GenerateAutoHomework(g)
3446
3447 o = r.ToJSON()
3448
3449 o['courses'] = [
3450 x.ToJSON('rid, title')
3451 for x in g.db.Linked(r, learn_card, fields = 'rid, title')
3452 ]
3453
3454 o['students'] = {
3455 ToShortCode(x.userid): x.displayname
3456 for x in g.db.Linked(r, learn_student, fields = 'userid, displayname')
3457 }
3458
3459 o['challenges'] = GetCurrentChallenges(g, r.rid, CHALLENGE_HOMEWORK)
3460
3461 r.UpdateChallenges(g)
3462
3463 studentclasses.append(o)
3464
3465 return f'''
3466 <script>
3467
3468 G.isadministrator = {isadministrator};
3469 G.teacher = {json.dumps(teacherjson)};
3470 G.teacherclasses = {json.dumps(teacherclasses)};
3471 G.student = {json.dumps(studentjson)};
3472 G.studentclasses = {json.dumps(studentclasses)};
3473
3474 </script>
3475
3476 <div class="PageLoadContent"></div>
3477 '''
3478
3479# =====================================================================
3480def challengesfunc(g):
3481 """The helper function for /learn/challenges.html.
3482
3483 This loads the challenge information for the JavaScript renderer.
3484 """
3485 args = g.request.args
3486
3487 classid = args.get('classid', '')
3488 challenge = args.get('challenge', '')
3489 challengeid = args.get('challengeid', '')
3490 lessonids = args.get('lessonids', '')
3491 problemids = args.get('problemids', '')
3492
3493 if lessonids:
3494 lessonids = lessonids.split(',')
3495 else:
3496 lessonids = []
3497
3498 if problemids:
3499 problemids = problemids.split(',')
3500 else:
3501 problemids = []
3502
3503 LoadTranslation(g, 'learn', 'challenges', g.session.lang)
3504
3505 if not classid or (not challenge and not challengeid) or (not challengeid and not lessonids and not problemids):
3506 raise Exception(g.T.missingparams)
3507
3508 classid = FromShortCode(classid)
3509
3510 challengeid = FromShortCode(challengeid) if challengeid else 0
3511
3512 isteacher = g.db.HasLink(learn_schoolclass, classid, learn_teacher, g.session.userid)
3513
3514 # Only teachers can select random challenges
3515 if challenge and not isteacher:
3516 raise Exception(g.T.cantview)
3517
3518 # Limit challenges to students enrolled in this class
3519 if not isteacher and not g.db.HasLink(learn_schoolclass, classid, learn_student, g.session.userid):
3520 raise Exception(g.T.cantview)
3521
3522 problems = []
3523
3524 challengeresult = learn_challengeresult()
3525 previousanswer = []
3526
3527 challengeobj = 0
3528 remainingattempts = 0
3529
3530 if challengeid:
3531 challengeobj = g.db.Load(
3532 learn_challenge,
3533 challengeid
3534 )
3535
3536 currenttime = time.time()
3537
3538 if not isteacher and currenttime < challengeobj.starttime.timestamp():
3539 diff = challengeobj.starttime - datetime.datetime.fromtimestamp(currenttime)
3540
3541 return f'''<h1>{g.T.challengenotstarted}</h1>
3542
3543<p class="Error Pad50">
3544 {g.T.pleasecomebackat} {diff.days * 24 + int((diff.seconds % 86400) / 3600)}:{int((diff.seconds % 3600) / 60):02}:{int(diff.seconds % 60):02}
3545</p>
3546
3547<div class="ButtonBar">
3548 <a class="Color1">
3549 {g.T.back}
3550 </a>
3551</div>
3552'''
3553
3554 if not isteacher and currenttime > challengeobj.endtime.timestamp():
3555 return f'''<h1>{g.T.challengealreadyover}</h1>
3556
3557<p class="Error Pad50">
3558 {g.T.betterlucknexttime}
3559<p>
3560
3561<div class="ButtonBar">
3562 <a class="Color1">
3563 {g.T.back}
3564 </a>
3565</div>
3566'''
3567
3568 challenge = challengeobj.challenge.split(' ')
3569 challenge = '.'.join(challenge).lower()
3570
3571 remainingattempts = challengeobj.GetNumRemainingAttempts(g)
3572
3573 if remainingattempts == 0 and not isteacher:
3574 return f'''<h1>{g.T.nomoretriesavailable}</h1>
3575
3576<p class="Error Pad50">
3577 {g.T.nomoretriesavailableexplanation}
3578</p>
3579
3580<div class="ButtonBar">
3581 <a class="Color1">
3582 {g.T.back}
3583 </a>
3584</div>
3585'''
3586 previousanswer = ['', '', '', '', []]
3587
3588 if (challengeobj.challenge == 'Write Answer'
3589 and challengeobj.previousresults
3590 ):
3591 challengeresult = challengeobj.previousresults[0]
3592 previousanswer = challengeresult.problemresults
3593
3594 if challengeobj.flags & CHALLENGE_USE_STUDYLIST:
3595 studylist = learn_studylist()
3596 studylist.Set(
3597 classid = classid,
3598 userid = g.session.userid,
3599 )
3600 problems.extend(studylist.GetProblems(g, challengeobj.numproblems))
3601 else:
3602 problems.extend(
3603 GetAllProblems(
3604 g,
3605 [FromShortCode(x) for x in challengeobj.lessonids],
3606 CARD_LINK_PROBLEM,
3607 [FromShortCode(x) for x in challengeobj.problemids],
3608 )
3609 )
3610 elif lessonids or problemids:
3611 problems.extend(
3612 GetAllProblems(
3613 g,
3614 [FromShortCode(x) for x in lessonids],
3615 CARD_LINK_PROBLEM,
3616 [FromShortCode(x) for x in problemids],
3617 )
3618 )
3619
3620 if challengeid and problems and challengeobj.numproblems:
3621 problems = random.sample(problems, challengeobj.numproblems)
3622
3623 challengeresult.Set(
3624 userid = g.session.userid,
3625 starttime = datetime.datetime.now(),
3626 endtime = 0,
3627 timeused = 0,
3628 classid = classid,
3629 challenge = challenge,
3630 challengeid = challengeid,
3631 lessonids = ','.join(sorted(lessonids)),
3632 problemids = ','.join(sorted(problemids)),
3633 flags = CHALLENGERESULT_UNFINISHED,
3634 problemresults = [],
3635 score = 0,
3636 maximumscore = 0,
3637 percentright = 0,
3638 );
3639
3640 g.db.Save(challengeresult)
3641 g.db.Commit()
3642
3643 challengejson = {}
3644
3645 if challengeobj:
3646 challengejson = challengeobj.ToJSON('rid, title, instructions, image, sound, video, files, numtries, minscore, minpercentage, timelimit, flags')
3647 challengejson['question'] = BestTranslation(challengeobj.question, g.session.lang)
3648
3649 if challengeobj.challenge == 'Write Answer':
3650 challengejson['title'] = {'en': g.T.questionanswers}
3651
3652 return f'''
3653<script>
3654
3655G.classid = '{classid}';
3656G.challenge = '{challenge}';
3657G.challengeid = '{challengeid}';
3658G.challengeresultid = '{ToShortCode(challengeresult.rid)}';
3659
3660G.remainingattempts = {remainingattempts};
3661G.challengeobj = {json.dumps(challengejson)};
3662G.lessonids = {json.dumps(lessonids)};
3663G.problemids = {json.dumps(problemids)};
3664
3665G.problems = {json.dumps(problems)};
3666G.previousanswer = {json.dumps(previousanswer)};
3667
3668</script>
3669'''
3670
3671# =====================================================================
3672def cleanup(g):
3673 """The standard Pafera app cleanup handler.
3674
3675 Since Pafera is focused on speed and fault tolerance rather than
3676 pure correctness, we don't use foreign keys and check constraints
3677 in the database layer. This function checks for broken links and
3678 rows and deletes them from the database.
3679 """
3680 g.db.RequireGroup('admins', g.T.cantchange)
3681
3682 ls = ['<pre>']
3683
3684 ls.append('Cleaning up cards')
3685
3686 for r in g.db.Find(learn_card, fields = 'rid, title, content'):
3687 s = g.db.Execute('''
3688 SELECT rid
3689 FROM learn_problem
3690 WHERE problemid = ?
3691 OR answerid = ?
3692 OR explanationid = ?
3693 OR choice1id = ?
3694 OR choice2id = ?
3695 OR choice3id = ?
3696 OR choice4id = ?
3697 OR choice5id = ?
3698 OR choice6id = ?
3699 OR choice7id = ?
3700 LIMIT 1
3701 ''',
3702 [
3703 r.rid,
3704 r.rid,
3705 r.rid,
3706 r.rid,
3707 r.rid,
3708 r.rid,
3709 r.rid,
3710 r.rid,
3711 r.rid,
3712 r.rid,
3713 ]
3714 )
3715
3716 if not s:
3717 ls.append(f'''\tFound orphaned card {r.rid}\t{r.title}\t{r.content}''')
3718
3719 ls.append('Cleaning up problems')
3720
3721 for r in g.db.Find(learn_problem, fields = 'rid, problemid, answerid'):
3722 s = g.db.Execute('''
3723 SELECT rid
3724 FROM learn_card
3725 WHERE rid IN (?, ?)
3726 ''',
3727 [
3728 r.problemid,
3729 r.answerid,
3730 ]
3731 )
3732
3733 if len(s) != 2:
3734 ls.append(f'''\tFound broken problem {r.rid}\t{r.problemid}\t{r.answerid}''')
3735
3736 ls.append('Cleaning up study list')
3737
3738 idstodelete = []
3739
3740 for r in g.db.Execute('''
3741 SELECT COUNT(*) as count, classid, userid, problemid
3742 FROM learn_studylist
3743 GROUP BY classid, userid, problemid
3744 HAVING count > 1
3745 '''):
3746
3747 ls.append(f'''{r[0]}\t{r[1]}\t{r[2]}\tr{r[3]}''')
3748
3749 numtodelete = r[0] - 1
3750 numdeleted = 0
3751 currentnum = 1
3752
3753 for s in g.db.Execute('''
3754 SELECT rid, results
3755 FROM learn_studylist
3756 WHERE classid = ?
3757 AND userid = ?
3758 AND problemid = ?
3759 ''',
3760 [
3761 r[1],
3762 r[2],
3763 r[3],
3764 ]
3765 ):
3766
3767 if numtodelete != numdeleted and (s[1] == '{}' or currentnum == r[0]):
3768 ls.append(f'''\tDeleting rid {s[0]}''')
3769 idstodelete.append(s[0])
3770 numdeleted += 1
3771
3772 currentnum += 1
3773
3774 if idstodelete:
3775 print(f'Cleaned up {len(idstodelete)} duplicate entries')
3776
3777 g.db.Execute(f'''
3778 DELETE FROM learn_studylist
3779 WHERE rid IN ({', '.join(['?' for x in idstodelete])})
3780 ''',
3781 idstodelete
3782 )
3783
3784 ls.append('</pre>')
3785
3786 g.db.Commit()
3787
3788 return '\n'.join(ls)
3789
3790# *********************************************************************
3791ROUTES = {
3792 'cardapi': cardapi,
3793 'problemapi': problemapi,
3794 'answersapi': answersapi,
3795 'schoolapi': schoolapi,
3796 'classapi': classapi,
3797 'studentapi': studentapi,
3798 'teacherapi': teacherapi,
3799 'challengeapi': challengeapi,
3800 'studylistapi': studylistapi,
3801 'texttomp3': texttomp3,
3802 'opustotext': opustotext,
3803 'coursesfunc': coursesfunc,
3804 'homefunc': homefunc,
3805 'challengesfunc': challengesfunc,
3806 'cleanup': cleanup,
3807}
3808
def cardapi(g)
Handles card management including setting child cards and problems.
Definition: __init__.py:85
def teacherapi(g)
Handles teacher management.
Definition: __init__.py:3039
def homefunc(g)
The helper function for /learn/home.html.
Definition: __init__.py:3385
def opustotext(g)
Speech recognition API using the Kaldi project.
Definition: __init__.py:3190
def answersapi(g)
Used to view and submit answers for a class, which can be viewed in the teacher's classroom web inter...
Definition: __init__.py:596
def classapi(g)
Handles the management of classes for a school.
Definition: __init__.py:2560
def coursesfunc(g)
The helper function for /learn/courses.html.
Definition: __init__.py:3271
def schoolapi(g)
Lets new students find a school that is right for them and handles general school management.
Definition: __init__.py:2109
def studentapi(g)
Handles student management.
Definition: __init__.py:2933
def problemapi(g)
Manages problems, which are essentially a collection of cards.
Definition: __init__.py:384
def studylistapi(g)
Lets the user view study list entries, results, and delete unneeded problems.
Definition: __init__.py:1822
def challengeapi(g)
Used to create and save challenge results.
Definition: __init__.py:1169
def texttomp3(g)
A simple text to speech API for students and teachers to play around with.
Definition: __init__.py:3151
Definition: db.py:1
def StrV(d, k, default='')
Utility function to get a string from a dict or object given its name.
Definition: types.py:221
def DictV(d, k, default={})
Utility function to get a dict from a dict or object given its name.
Definition: types.py:264
def ArrayV(d, k, default=[])
Utility function to get an array from a dict or object given its name.
Definition: types.py:241
def IntV(d, k, min=None, max=None, default=0)
Utility function to get an int from a dict or object given its name.
Definition: types.py:169
def V(d, k, default=None)
Utility function to get a value from a dict or object given its name.
Definition: types.py:151
def EscapeSQL(s)
Escapes special characters to be used in a SQL query string.
Definition: utils.py:190
def FromShortCode(code, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a six character alphanumeric code into a 32-bit value.
Definition: utils.py:64
def ToShortCode(val, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a 32-bit value into a six character alphanumeric code.
Definition: utils.py:36
def BestTranslation(translations, languages, defaultlang='en')
Returns the best translation found in a dict of different translations.
Definition: utils.py:104
def CodeDir(code)
Separates a filename into three character segments with the directory separator '/' in between.
Definition: utils.py:87
def LoadTranslation(g, app, translation, languages)
Load the translation into the global language dict in g.T.
Definition: utils.py:129