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# Main system APIs for querying and controlling the site.
5#
6# Be aware that you'll need to be an administrator to use most
7# of the APIs.
8
9import json
10import os
11import traceback
12import types
13import importlib
14import shutil
15import base64
16import random
17import uuid
18import hashlib
19
20from flask import Response
21
22from pafera.types import *
23from pafera.db import *
24
25from apps.system.user import *
26from apps.system.group import *
27from apps.system.page import *
28from apps.system.session import *
29from apps.system.file import *
30from apps.system.loginattempt import *
31
32# =====================================================================
34 """In order to enable caching for static pages, we place all session
35 and user specific information in this special handler. Using this
36 technique, we can use nginx to rapidly serve static files while this
37 function only has minimal work to do.
38 """
39 r = Response(f"""
40P.wallpaper = '{g.session['wallpaper']}';
41P.texttheme = '{g.session['texttheme']}';
42P.userid = '{ToShortCode(g.session.userid) if g.session.userid else ''}';
43P.groups = {g.session['groups'] if g.session.userid else []};
44P.lang = '{g.session.lang}';
45P.langcodes = {g.session.langcodes};
46P.languages = {g.siteconfig['languages']};
47
48let chooselanguageicon = E('.ChooseLanguageIcon');
49
50if (chooselanguageicon)
51{{
52 chooselanguageicon.innerHTML = `<img class="Square200" src="/files/flag-${{P.lang}}.png">`
53}}
54
55let systemusericon = E('.SystemUserIcon');
56
57if (systemusericon && P.userid)
58{{
59 systemusericon.innerHTML = `<img class="Square200" src="/system/headshots/${{P.CodeDir(P.userid)}}.webp">`
60}}
61""")
62
63 r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
64 r.headers["Pragma"] = "no-cache"
65 r.headers["Expires"] = "0"
66 r.headers['Cache-Control'] = 'public, max-age=0'
67
68 return r
69
70# =====================================================================
71def fileapi(g):
72 """Standard API to search through the files on the system and change
73 names or descriptions. Note that files are one of the few objects
74 where security is enabled by default, so we rely on the system
75 security protection to block unauthorized access.
76 """
77 results = {}
78
79 try:
80 d = json.loads(g.request.get_data())
81
82 command = StrV(d, 'command')
83
84 if command == 'search':
85 keyword = StrV(d, 'keyword')
86 filename = StrV(d, 'filename', keyword)
87 filetype = StrV(d, 'filetype')
88 modified = StrV(d, 'modified')
89 size = IntV(d, 'size')
90 start = IntV(d, 'start')
91 limit = IntV(d, 'limit', 1, 1000, 20)
92 orderby = StrV(d, 'orderby', 'filename')
93 filefilter = StrV(d, 'filter')
94 fileids = ArrayV(d, 'fileids')
95 showallfiles = IntV(d, 'showallfiles')
96
97 conds = []
98 params = []
99
100 if not showallfiles:
101 conds.append('dbowner = ?')
102 params.append(g.session.userid)
103
104 if fileids:
105 conds.append('rid IN (' + [', '.join(['?' for x in fileids])] + ')')
106 params.extend([FromShortCode(x) for x in fileids])
107 else:
108 if filename:
109 conds.append("filename LIKE ? OR descriptions LIKE ?")
110 params.append(EscapeSQL('%' + filename + '%'))
111 params.append(EscapeSQL('%' + filename + '%'))
112
113 if filetype:
114 if filetype in system_file.TYPES.keys():
115 filetype = system_file.TYPES[filetype]
116 conds.append("filetype = ?")
117 params.append(filetype)
118
119 if modified:
120 conds.append("modified LIKE ?")
121 params.append(EscapeSQL('%' + modified + '%'))
122 orderby = 'modified'
123
124 if size:
125 conds.append("size > ?")
126 params.append(size)
127 orderby = 'size'
128
129 conds = "WHERE " + (" AND ").join(conds)
130
131 files = g.db.Find(
132 system_file,
133 conds,
134 params,
135 start = start,
136 limit = limit,
137 orderby = orderby,
138 fields = 'rid, filename, extension, descriptions, modified, size, filetype, flags',
139 )
140
141 ls = []
142
143 for r in files:
144 o = r.ToJSON('filename, extension, modified, size, flags')
145 o['modified'] = o['modified'][:19]
146 o['description'] = BestTranslation(r.descriptions, g.session.langcodes)
147 o['idcode'] = ToShortCode(r.rid)
148 o['thumbnail'] = r.ThumbnailURL()
149 ls.append(o)
150
151 results['data'] = ls
152 results['count'] = len(files)
153 elif command == 'load':
154 idcode = StrV(d, 'idcode')
155
156 if not idcode:
157 raise Exception(g.T.missingparams)
158
159 r = g.db.Load(system_file, FromShortCode(idcode)).ToJSON()
160
161 o = r.ToJSON('filename, modified, size, flags')
162
163 o['modified'] = o['modified'][:19]
164 o['description'] = BestTranslation(r.descriptions, g.session.langcodes)
165 o['idcode'] = ToShortCode(r.rid)
166 o['thumbnail'] = r.ThumbnailURL()
167
168 results['data'] = o
169 elif command == 'save':
170 """The save API is only able to change filename and descriptions. The
171 rest of the statistics are automatically handled on upload.
172 """
173 newinfo = DictV(d, 'data')
174
175 if not V(newinfo, 'idcode'):
176 raise Exception(g.T.cantcreate)
177
178 s = g.db.Load(system_file, FromShortCode(newinfo['idcode']))
179
180 filename = StrV(newinfo, 'filename')
181 descriptions = DictV(newinfo, 'descriptions')
182
183 if filename or descriptions:
184 if filename:
185 s.Set(filename = filename)
186
187 if descriptions:
188 s.Set(descriptions = descriptions)
189
190 g.db.Save(s)
191
192 g.db.Commit()
193 elif command == 'delete':
194 ids = ArrayV(d, 'ids')
195
196 if not ids:
197 raise Exception(g.T.missingparams)
198
199 for r in g.db.LoadMany(system_file, [FromShortCode(x) for x in ids]):
200 filepath = r.FullPath()
201
202 g.db.Delete(r)
203
204 os.remove(filepath)
205
206 g.db.Commit()
207 else:
208 results['error'] = f'{g.T.cantunderstand} "{command}"'
209 except Exception as e:
210 traceback.print_exc()
211 results['error'] = str(e)
212
213 return results
214
215# =====================================================================
216def upload(g):
217 """Handles uploads for the whole system. Files in Pafera are stored
218 under their shortcodes, and can be private or public. Note that you'll
219 need ImageMagick and ffmpegthumbnailer installed on your system for
220 automatic thumbnail generation.
221 """
222
223 if len(g.urlargs) < 3:
224 errordict = {}
225 errordict[g.session.lang] = g.T.missingparams
226 return json.dumps(errordict)
227
228 fileid, filename, title = g.urlargs[:3]
229
230 security = 'public'
231
232 if len(g.urlargs) > 3:
233 security = g.urlargs[3]
234
235 if fileid == '_':
236 fileid = ''
237
238 g.db.RequireGroup('uploaders', g.T.cantchange)
239
240 bytesleft = int(g.request.headers.get('content-length'))
241 filesize = bytesleft
242
243 # The special headshot fileid is used to set user images
244 if fileid == 'headshot':
245 userid = FromShortCode(filename)
246
247 if (userid != g.session.userid
248 and not g.db.HasLink(system_user, g.session.userid, system_user, userid, USER_LINK_MANAGE)
249 ):
250 return json.dumps({'error': g.T.cantchange})
251
252 basename = 'static/system/headshots/' + CodeDir(filename)
253 jpgfile = basename + '.jpg'
254 webpfile = basename + '.webp'
255
256 with open(jpgfile, 'wb') as f:
257 while bytesleft > 0:
258 chunk = g.request.stream.read(65536)
259 f.write(chunk)
260 bytesleft -= len(chunk)
261
262 subprocess.call(['convert', jpgfile, '-quality', '70', '-resize', '512x512>', webpfile])
263
264 os.remove(jpgfile)
265
266 return '{}'
267
268 cachedir = '/srv/flasksite/private/uploads'
269 uploadedfile = cachedir + '/' + uuid.uuid4().hex
270
271 hasher = hashlib.sha1()
272
273 with open(uploadedfile, 'wb') as f:
274 while bytesleft > 0:
275 chunk = g.request.stream.read(65536)
276 f.write(chunk)
277 hasher.update(chunk)
278 bytesleft -= len(chunk)
279
280 finalhash = hasher.hexdigest()
281
282 # See if the file already exists on the system and is not private
283 f = g.db.Find(
284 system_file,
285 'WHERE hash = ? AND size = ? AND NOT flags & ?',
286 (hash, filesize, FILE_PRIVATE)
287 )
288
289 if f:
290 f = f[0]
291 os.remove(uploadedfile)
292 else:
293 # If it's a new file, then let the file class process statistics.
294 basename, extension = os.path.splitext(filename)
295
296 descriptions = {}
297
298 descriptions[g.session.lang] = title
299
300 f = system_file()
301
302 f.Set(
303 filename = filename,
304 extension = extension,
305 descriptions = descriptions,
306 hash = finalhash,
307 )
308
309 if security == 'protected':
310 f.Set(flags = FILE_PROTECTED)
311 elif security == 'private':
312 f.Set(flags = FILE_PRIVATE)
313 else:
314 f.Set(flags = FILE_PUBLIC)
315
316 g.db.Insert(f)
317
318 idcode = ToShortCode(f.rid)
319 fullpath = 'static/data/files' + CodeDir(idcode) + '.' + extension
320
321 os.rename(uploadedfile, fullpath)
322
323 f.Process()
324
325 g.db.Commit()
326
327 returninfo = {
328 'id': idcode,
329 'thumbnail': f.ThumbnailURL(),
330 }
331
332 return json.dumps(returninfo)
333
334# =====================================================================
335def userapi(g):
336 """Handles logins, registrations, approvals, and user information.
337 Also handles adding friends, following other users, and so forth.
338
339 Note that normal users cannot search for other users. You must be
340 an administrator or have managed users assigned to you in order to
341 see and change other users.
342 """
343 results = {}
344
345 try:
346 LoadTranslation(g, 'system', 'admin', g.session.lang)
347
348 d = json.loads(g.request.get_data())
349
350 command = StrV(d, 'command')
351
352 if command == 'login':
353 phonenumber = StrV(d, 'phonenumber').strip()
354 place = StrV(d, 'place').strip()
355 password = StrV(d, 'password').strip()
356
357 if not phonenumber or not place or not password:
358 raise Exception(g.T.missingparams)
359
360 # Limit login attempts to once every two seconds per IP. It's a
361 # crude but effective way to counter login spamming.
362 previousattempt = g.db.Find(
363 system_loginattempt,
364 'WHERE ip = ? ORDER BY eventtime DESC',
365 [g.session.ip]
366 )
367
368 currenttime = time.time()
369
370 if previousattempt and previousattempt[0].eventtime.timestamp() > currenttime - 2:
371 time.sleep(2)
372 raise Exception(g.T.nothingfound)
373
374 loginattempt = system_loginattempt()
375 loginattempt.Set(
376 ip = g.session.ip,
377 eventtime = currenttime,
378 )
379 g.db.Insert(loginattempt)
380 g.db.Commit()
381
382 u = g.db.Find(
383 system_user,
384 'WHERE phonenumber = ? AND place = ?',
385 [phonenumber, place]
386 )
387
388 if u:
389 u = u[0]
390 if u.CheckPassword('password', password):
391 g.session.Set(
392 userid = u.rid
393 )
394 g.session.data.update(u.settings)
395
396 groups = []
397 groupids = []
398
399 for group in g.db.Linked(u, system_group):
400 groups.append(group.groupname)
401 groupids.append(group.rid)
402
403 g.session['groups'] = groups
404 g.session['groupids'] = groupids
405
406 g.session.needupdate = 1
407 else:
408 time.sleep(2)
409 raise Exception(g.T.nothingfound)
410 else:
411 time.sleep(2)
412 raise Exception(g.T.nothingfound)
413 elif command == 'register':
414 phonenumber = StrV(d, 'phonenumber').strip()
415 place = StrV(d, 'place').strip()
416 password = StrV(d, 'password').strip()
417 password2 = StrV(d, 'password2').strip()
418 email = StrV(d, 'email').strip()
419 headshot = StrV(d, 'headshot').strip()
420 extrainfo = DictV(d, 'extrainfo')
421
422 if not phonenumber or not place or not password or not password2 or not headshot or len(headshot) < 60:
423 raise Exception(g.T.missingparams)
424
425 u = g.db.Find(
426 system_newuser,
427 'WHERE phonenumber = ? AND place = ?',
428 [phonenumber, place]
429 )
430
431 if u and g.session.userid != u[0].rid:
432 raise Exception(g.T.phonenumberalreadytaken)
433
434 imgdata = b64decodepad(headshot[23:])
435
436 if not imgdata:
437 raise Exception(g.T.wrongfileformat)
438
439 if u:
440 u = u[0]
441 else:
442 u = system_newuser()
443
444 u.Set(
445 phonenumber = phonenumber,
446 place = place,
447 email = email,
448 extrainfo = extrainfo
449 )
450
451 u.SetPassword('password', password)
452
453 g.db.Save(u)
454 g.db.Commit()
455
456 headshotdir = 'static/system/newheadshots/' + idcode[:3]
457
458 os.makedirs(headshotdir, exist_ok = True)
459
460 with open(headshotdir + '/' + idcode[3:] + '.webp', 'wb') as f:
461 f.write(imgdata)
462
463 results['registrationid'] = ToShortCode(u.rid)
464 elif command == 'listneedapprovals':
465 if 'admins' not in g.session['groups']:
466 raise Exception(g.T.cantview)
467
468 start = IntV(d, 'start', 0, 99999999, 0)
469 limit = IntV(d, 'limit', 10, 1000, 100)
470
471 results['data'] = [
472 x.ToJSON()
473 for x in g.db.Find(
474 system_newuser,
475 start = start,
476 limit = limit,
477 orderby = 'place, phonenumber'
478 )
479 ]
480 elif command == 'approve':
481 if 'admins' not in g.session['groups']:
482 raise Exception(g.T.cantchange)
483
484 userid = StrV(d, 'userid')
485 approve = IntV(d, 'approve')
486
487 userid = FromShortCode(userid)
488
489 u = g.db.Load(system_newuser, userid)
490
491 if approve:
492 user = system_user()
493 user.Set(**u.ToJSON())
494 user.Set(rid = 0)
495
496 g.db.Save(user)
497
498 newuserimage = 'static/system/newheadshots/' + CodeDir(ToShortCode(u.rid)) + '.webp'
499 userimage = 'static/system/headshots/' + CodeDir(ToShortCode(user.rid)) + '.webp'
500
501 os.makedirs(os.path.split(userimage)[0], exist_ok = True)
502
503 os.rename(newuserimage, userimage)
504 else:
505 pass
506
507 g.db.Delete(u)
508 g.db.Commit()
509 elif command == 'search':
510 keyword = StrV(d, 'keyword')
511 start = IntV(d, 'start', 0, 2147483647, 0)
512 limit = IntV(d, 'limit', 1, 2147483647, 100)
513
514 conds = []
515 params = []
516
517 if keyword:
518 conds.append('phonenumber LIKE ?')
519 params.append(EscapeSQL('%' + keyword + '%'))
520
521 conds.append('displayname LIKE ?')
522 params.append(EscapeSQL('%' + keyword + '%'))
523
524 if conds:
525 conds = ' OR '.join(conds)
526 else:
527 conds = ''
528
529 currentuser = g.db.Load(system_user, g.session.userid, 'rid, canmanage, managedby')
530
531 userids = ArrayV(d, 'userids')
532
533 if userids:
534 validuserids = []
535
536 for r in userids:
537 if r in currentuser.canmanage or r in currentuser.managedby:
538 validuserids.append(r)
539
540 if not validuserids:
541 return results
542
543 cond = 'WHERE rid IN (' + (', '.join(['?' for x in validuserids])) + ') '
544
545 if conds:
546 cond += ' AND (' + conds + ')'
547
548 params = [FromShortCode(x) for x in validuserids] + params
549
550 validusers = g.db.Find(
551 system_user,
552 cond,
553 params,
554 start = start,
555 limit = limit,
556 orderby = 'phonenumber',
557 fields = 'rid, phonenumber, place, displayname'
558 )
559
560 results['data'] = [
561 x.ToJSON('rid, phonenumber, place, displayname')
562 for x in validusers
563 ]
564 results['count'] = len(validusers)
565 return results
566
567 allusers = []
568
569 if 'admins' in g.session['groups']:
570 allusers = g.db.Find(
571 system_user,
572 'WHERE ' + conds if conds else '',
573 params,
574 start = start,
575 limit = limit,
576 orderby = 'phonenumber',
577 )
578
579 results['allusers'] = [
580 x.ToJSON('rid, phonenumber, place, displayname')
581 for x in allusers
582 ]
583 results['alluserscount'] = len(allusers)
584
585 if currentuser.canmanage:
586 cond = 'WHERE rid IN (' + (', '.join(['?' for x in currentuser.canmanage])) + ') '
587
588 if conds:
589 cond += ' AND (' + conds + ')'
590
591 params = [FromShortCode(x) for x in currentuser.canmanage] + params
592
593 managedusers = g.db.Find(
594 system_user,
595 cond,
596 params,
597 start = start,
598 limit = limit,
599 orderby = 'phonenumber',
600 fields = 'rid, phonenumber, place, displayname, expiredate, storagequota, numcanmanage, canmanage, managedby',
601 )
602 else:
603 managedusers = []
604
605 results['data'] = [
606 x.ToJSON('rid, phonenumber, place, displayname, expiredate, storagequota, numcanmanage, canmanage, managedby')
607 for x in managedusers
608 ]
609 results['count'] = len(managedusers)
610 elif command == 'save':
611 data = DictV(d, 'data')
612 userid = StrV(data, 'idcode')
613
614 if not userid:
615 raise Exception(g.T.missingparams)
616
617 useridcode = userid
618 userid = FromShortCode(userid)
619
620 u = g.db.Load(system_user, userid, 'rid, numcanmanage, canmanage, managedby, flags')
621
622 isadmin = 'admins' in g.session['groups']
623 canmanage = isadmin or ToShortCode(g.session.userid) in u.managedby
624
625 if userid != g.session.userid and not canmanage:
626 raise Exception(g.T.cantchange)
627
628 newvalues = {}
629
630 # If the user is not allowed to manage itself, then only the password
631 # can be changed
632 if not isadmin and userid == g.session.userid and not u.flags & USER_CAN_MANAGE_SELF:
633 newpassword = StrV(data, 'password')
634
635 if not newpassword:
636 return results
637
638 newvalues = {
639 'password': newpassword
640 }
641 else:
642 for r in [
643 'phonenumber',
644 'place',
645 'email',
646 'password',
647 ]:
648
649 value = StrV(data, r)
650
651 if value:
652 newvalues[r] = value
653
654 if isadmin:
655 expiredate = StrV(data, 'expiredate')
656
657 if expiredate:
658 newvalues['expiredate'] = expiredate
659
660 storagequota = IntV(data, 'storagequota')
661
662 if storagequota:
663 newvalues['storagequota'] = storagequota
664
665 numcanmanage = IntV(data, 'numcanmanage')
666
667 if numcanmanage:
668 newvalues['numcanmanage'] = numcanmanage
669
670 canmanagetoadd = []
671 canmanagetodelete = []
672
673 managedbytoadd = []
674 managedbytodelete = []
675
676 canmanage = ArrayV(data, 'canmanage')
677
678 if 'canmanage' in data:
679 oldcanmanage = set(u.canmanage)
680 newcanmanage = set(canmanage)
681
682 newvalues['canmanage'] = list(newcanmanage)
683
684 canmanagetoadd = list(newcanmanage - oldcanmanage)
685 canmanagetodelete = list(oldcanmanage - newcanmanage)
686
687 if not isadmin and len(canmanage) > u.numcanmanage:
688 raise Exception(g.T.reachedmanagelimit)
689
690 managedby = ArrayV(data, 'managedby')
691
692 if 'managedby' in data:
693 newvalues['managedby'] = managedby
694
695 oldmanagedby = set(u.managedby)
696 newmanagedby = set(managedby)
697
698 managedbytoadd = list(newmanagedby - oldmanagedby)
699 managedbytodelete = list(oldmanagedby - newmanagedby)
700
701 userstoedit = canmanagetoadd + canmanagetodelete + managedbytoadd + managedbytodelete
702
703 if userstoedit:
704 for r in g.db.Find(
705 system_user,
706 'WHERE rid IN (' + ', '.join(['?' for x in userstoedit]) + ')',
707 [FromShortCode(x) for x in userstoedit],
708 fields = 'rid, canmanage, managedby'
709 ):
710
711 thisuserid = ToShortCode(r.rid)
712
713 if thisuserid in canmanagetoadd:
714 r.managedby.append(useridcode)
715 r.UpdateFields('managedby')
716
717 if thisuserid in canmanagetodelete and useridcode in r.managedby:
718 r.managedby.remove(useridcode)
719 r.UpdateFields('managedby')
720
721 if thisuserid in managedbytoadd:
722 r.canmanage.append(useridcode)
723 r.UpdateFields('canmanage')
724
725 if thisuserid in managedbytodelete and useridcode in r.canmanage:
726 r.canmanage.remove(useridcode)
727 r.UpdateFields('canmanage')
728
729 g.db.Update(r)
730
731 u.Set(**newvalues)
732
733 if 'password' in data:
734 u.SetPassword('password', data['password'])
735
736 g.db.Save(u)
737 g.db.Commit()
738 elif command == 'delete':
739 ids = ArrayV(d, 'ids')
740
741 if not ids:
742 raise Exception(g.T.missingparams)
743
744 for r in ids:
745 userid = FromShortCode(r)
746
747 u = g.db.Load(system_user, userid, 'rid, managedby')
748
749 if 'admins' not in g.session['groups'] and ToShortCode(g.session.userid) not in u.managedby:
750 raise Exception(g.T.cantdelete)
751
752 g.db.Delete(u)
753
754 g.db.Commit()
755 elif command == 'load':
756 userid = StrV(d, 'userid')
757
758 if userid:
759 userid = FromShortCode(userid)
760 else:
761 userid = g.session.userid
762
763 u = g.db.Load(system_user, g.session.userid, 'rid, phonenumber, place, displayname, storageused, storagequota, numcanmanage, canmanage, managedby, accesstokens, flags')
764
765 canmanage = 'admins' in g.session['groups'] or ToShortCode(g.session.userid) in u.managedby
766
767 if userid != g.session.userid and not canmanage:
768 raise Exception(g.T.cantchange)
769
770 results['data'] = u.ToJSON('rid, phonenumber, place, displayname, storageused, storagequota, numcanmanage, canmanage, managedby, accesstokens, flags')
771 elif command == 'setlanguage':
772 newlang = StrV(d, 'data')
773
774 if newlang not in g.siteconfig['languages']:
775 raise Exception(newlang + ' is not supported.')
776
777 newlangcodes = [newlang]
778
779 [newlangcodes.append(x) for x in g.session.langcodes if x not in newlangcodes]
780
781 g.session.Set(
782 lang = newlang,
783 langcodes = newlangcodes,
784 )
785
786 g.db.Update(g.session)
787 g.db.Commit()
788 elif command == 'searchgroups':
789 keyword = StrV(d, 'keyword')
790 grouptype = StrV(d, 'grouptype', 'friends')
791 start = IntV(d, 'start', 0, 99999999, 0)
792 limit = IntV(d, 'limit', 10, 99999999, 100)
793
794 cond = []
795 params = []
796
797 if keyword:
798 conds.append('(phonenumber LIKE ? OR displayname LIKE ?)')
799 params.append(EscapeSQL('%' + keyword + '%'))
800 params.append(EscapeSQL('%' + keyword + '%'))
801
802 if cond:
803 cond = 'WHERE ' + ' AND '.join(conds)
804
805 objs = []
806 users = []
807
808 if 'admins' in g.session['groups']:
809 users = g.db.Find(
810 system_user,
811 cond,
812 params,
813 start = start,
814 limit = limit,
815 fields = 'rid, displayname, phonenumber'
816 )
817 else:
818 linktype = USER_FRIEND
819
820 if grouptype == 'friends':
821 pass
822 elif grouptype == 'acquaintances':
823 linktype = USER_ACQUAINTANCE
824 elif grouptype == 'follow':
825 linktype = USER_FOLLOW
826 elif grouptype == 'blocked':
827 linktype = USER_BLOCKED
828 else:
829 raise Exception(g.T.cantunderstand + grouptype)
830
831 users = g.db.LinkedToID(
832 system_user,
833 g.session.userid,
834 fields = 'rid, displayname, phonenumber'
835 )
836
837 results['count'] = len(users)
838
839 for r in users:
840 objs.append({
841 'idcode': ToShortCode(r.rid),
842 'displayname': BestTranslation(r.displayname, g.session.lang),
843 'phonenumber': r.phonenumber
844 })
845
846 results['data'] = objs
847 else:
848 results['error'] = f'{g.T.cantunderstand} "{command}"'
849 except Exception as e:
850 traceback.print_exc()
851 results['error'] = str(e)
852
853 return results
854
855# =====================================================================
857 """Configures settings for the whole site. You must be an
858 administrator in order to see and change settings.
859 """
860 results = {}
861
862 try:
863 if 'admins' not in g.session['groups']:
864 raise Exception(g.T.cantchange)
865
866 d = json.loads(g.request.get_data())
867
868 command = StrV(d, 'command')
869
870 if command == 'getinfo':
871 with open('private/system/pafera.cfg', 'r') as f:
872 results['data'] = json.load(f)
873 elif command == 'setinfo':
874 oldinfo = {}
875 newinfo = DictV(d, 'data')
876
877 if not V(newinfo, 'dbflags'):
878 newinfo['dbflags'] = 0
879
880 with open('private/system/pafera.cfg', 'r') as f:
881 oldinfo = json.load(f)
882
883 try:
884 newdb = DB(
885 connectionname = 'newdb',
886 dbhost = newinfo['dbhost'],
887 dbname = newinfo['dbname'],
888 dbtype = newinfo['dbtype'],
889 dbuser = newinfo['dbuser'],
890 dbpassword = newinfo['dbpassword'],
891 dbflags = newinfo['dbflags'],
892 )
893
894 import pafera.initsite
895 newg = types.SimpleNamespace()
896 newg.db = newdb
897 newg.T = g.T
899 except Exception as e:
900 traceback.print_exc()
901 raise Exception(g.T.cantconnect + newinfo['dbname'])
902
903 admin = g.db.Find(
904 system_user,
905 'WHERE phonenumber = ? AND place = ?',
906 ['admin', 'pafera']
907 )[0]
908
909 sitepassword = StrV(newinfo, 'sitepassword')
910
911 if not sitepassword and admin.CheckPassword('password', 'pafera'):
912 raise Exception(g.T.pleasesetnewpassword)
913 else:
914 admin.SetPassword('password', sitepassword)
915 g.db.Update(admin)
916 g.db.Commit()
917 newinfo.pop('sitepassword')
918
919 oldinfo.update(newinfo)
920
921 newinfo['sessiontimeout'] = IntV(newinfo, 'sessiontimeout', 600, 2678400, 604800)
922
923 with open('private/system/pafera.cfg', 'w') as f:
924 f.write(json.dumps(oldinfo, sort_keys = True, indent = 2))
925
926 if newinfo['production']:
927 subprocess.run(['/srv/flasksite/scripts/minifyall'])
928 else:
929 results['error'] = f'{g.T.cantunderstand} "{command}"'
930 except Exception as e:
931 results['error'] = str(e)
932
933 return results
934
935# =====================================================================
936def dbapi(g):
937 """Used by the web database management interface to handle objects
938 in the system. You must be an administrator to use this API.
939 """
940 results = {}
941
942 try:
943 if 'admins' not in g.session['groups']:
944 raise Exception(g.T.cantchange)
945
946 d = json.loads(g.request.get_data())
947
948 command = StrV(d, 'command')
949
950 if command == 'getmodels':
951 results['data'] = list(g.db.objtypes.keys())
952 elif command == 'getmodelinfo':
953 model = GetModel(StrV(d, 'model'))
954
955 if not model:
956 raise Exception(g.T.missingparams)
957
958 g.db.RegisterModel(model)
959
960 data = {}
961
962 # No need to specify validators
963 for k, v in model._dbfields.items():
964 data[k] = v[:2]
965
966 results['data'] = data
967 results['modelid'] = model._dbid
968 results['managejs'] = getattr(model, '_managejs', '')
969 results['links'] = getattr(model, '_dblinks', [])
970 results['displayfields'] = getattr(model, '_dbdisplay', model._dbid)
971 elif command == 'search':
972 model = GetModel(StrV(d, 'model'))
973 fields = ArrayV(d, 'fields')
974 cond = StrV(d, 'condition')
975 params = ArrayV(d, 'params')
976 start = IntV(d, 'start', 0, 99999999, 0)
977 limit = IntV(d, 'limit', 10, 99999999, 100)
978 orderby = StrV(d, 'orderby')
979
980 if not model:
981 raise Exception(g.T.missingparams)
982
983 if not fields:
984 fields = []
985
986 g.db.RegisterModel(model)
987
988 objs = g.db.Find(
989 model,
990 cond,
991 params,
992 start = start,
993 limit = limit,
994 fields = ', '.join(fields) if fields else '*',
995 orderby = orderby,
996 )
997
998 results['count'] = len(objs)
999
1000 fieldnames = ', '.join(fields)
1001
1002 results['data'] = [x.ToJSON(fieldnames) for x in objs]
1003 elif command == 'save':
1004 model = GetModel(StrV(d, 'model'))
1005 data = DictV(d, 'data')
1006
1007 if not model or not data:
1008 raise Exception(g.T.missingparams)
1009
1010 g.db.RegisterModel(model)
1011
1012 dbid = V(data, model._dbid)
1013
1014 if dbid == '0':
1015 raise Exception(g.T.missingparams)
1016
1017 if dbid:
1018 # Try to load the object if it already exists
1019 if 'INT' in model._dbfields[model._dbid][0]:
1020 dbid = int(dbid)
1021
1022 try:
1023 obj = g.db.Load(model, dbid)
1024 except Exception as e:
1025 print('dbapi.save():\tCould not load model ', model, dbid)
1026 obj = model()
1027 else:
1028 obj = model()
1029
1030 for k, v in model._dbfields.items():
1031 if v[0] == 'PASSWORD':
1032 if V(data, k):
1033 obj.SetPassword(k, data[k])
1034
1035 data.pop(k, None)
1036 elif v[0] == 'JSON':
1037 if not V(data, k):
1038 data[k] = {}
1039
1040 obj.Set(**data)
1041
1042 g.db.Save(obj)
1043
1044 g.db.Commit()
1045
1046 results['data'] = obj.ToJSON()
1047 elif command == 'load':
1048 model = GetModel(StrV(d, 'model'))
1049 modelid = V(d, 'modelid')
1050
1051 if not model or not modelid:
1052 raise Exception(g.T.missingparams)
1053
1054 g.db.RegisterModel(model)
1055
1056 obj = g.db.Load(model, modelid)
1057
1058 results['data'] = obj.ToJSON()
1059 elif command == 'delete':
1060 model = GetModel(StrV(d, 'model'))
1061 ids = ArrayV(d, 'data')
1062
1063 if not model:
1064 raise Exception(g.T.missingparams)
1065
1066 g.db.RegisterModel(model)
1067
1068 if not ids:
1069 results['error'] = 'No IDs provided to delete.'
1070 else:
1071 for objid in ids:
1072 if objid == '0':
1073 raise Exception(g.T.missingparams)
1074
1075 if not objid:
1076 continue
1077
1078 obj = g.db.Load(model, FromShortCode(objid))
1079
1080 g.db.Delete(obj)
1081
1082 g.db.Commit()
1083 elif command == 'linked':
1084 model1 = GetModel(StrV(d, 'model1'))
1085 model2 = GetModel(StrV(d, 'model2'))
1086 id1 = V(d, 'id1')
1087 linktype = IntV(d, 'type')
1088
1089 if not model1 or not model2 or not id1:
1090 raise Exception(g.T.missingparams)
1091
1092 g.db.RegisterModel(model1)
1093 g.db.RegisterModel(model2)
1094
1095 obj1 = g.db.Load(model1, id1)
1096
1097 displayfields = getattr(model2, '_dbdisplay', [])
1098
1099 if displayfields:
1100 displayfields = displayfields + [model2._dbid]
1101
1102 results['data'] = [x.ToJSON(', '.join(displayfields)) for x in g.db.Linked(obj1, model2, linktype)]
1103 elif command == 'link' or command == 'linkarray':
1104 model1 = GetModel(StrV(d, 'model1'))
1105 model2 = GetModel(StrV(d, 'model2'))
1106 id1 = V(d, 'id1')
1107 id2s = ArrayV(d, 'id2s')
1108 linktype = IntV(d, 'type')
1109
1110 if not model1 or not model2 or not id1:
1111 raise Exception(g.T.missingparams)
1112
1113 g.db.RegisterModel(model1)
1114 g.db.RegisterModel(model2)
1115
1116 obj1 = g.db.Load(model1, id1)
1117
1118 ls = []
1119
1120 for id in id2s:
1121 ls.append(g.db.Load(model2, id))
1122
1123 if len(ls) == 1 and command == 'link':
1124 g.db.Link(obj1, ls[0], linktype)
1125 else:
1126 if ls:
1127 g.db.LinkArray(obj1, ls, linktype)
1128 else:
1129 g.db.Unlink(obj1, model2, linktype)
1130
1131 g.db.Commit()
1132 elif command == 'cleanlinks':
1133 """Look for broken links where the original objects don't exist
1134 anymore and clean them from the system.
1135 """
1136 report = []
1137
1138 nummodels = 0
1139 numlinkscleaned = 0
1140
1141 for r in list(g.db.objtypes.keys()):
1142 m1 = GetModel(r)
1143
1144 nummodels += 1
1145
1146 report.append(f'Found model {r}')
1147
1148 for l in m1._dblinks:
1149 m2 = GetModel(l)
1150
1151 linktable = g.db.GetLinkTable(m1, m2)
1152
1153 linkobj1, linkobj2 = linktable.split('__')
1154
1155 if r == linkobj1:
1156 linkobj1id = m1._dbid
1157 linkobj2id = m2._dbid
1158 else:
1159 linkobj1id = m2._dbid
1160 linkobj2id = m1._dbid
1161
1162 report.append(f'\tFound link {linktable}')
1163
1164 if g.db.HasTable(linktable):
1165 brokenlinks = []
1166
1167 for a in g.db.Execute(f'''
1168 SELECT lt.linkaid, lt.linkbid
1169 FROM {linktable} AS lt
1170 LEFT JOIN {linkobj1} AS l1 ON lt.linkaid = l1.{linkobj1id}
1171 WHERE l1.{linkobj1id} IS NULL
1172 '''):
1173 report.append(f'''\t\tFound broken link {a[0]} -> {a[1]}''')
1174 brokenlinks.append((a[0], a[1]))
1175 numlinkscleaned += 1
1176
1177 for a in g.db.Execute(f'''
1178 SELECT lt.linkaid, lt.linkbid
1179 FROM {linktable} AS lt
1180 LEFT JOIN {linkobj2} AS l2 ON lt.linkbid = l2.{linkobj2id}
1181 WHERE l2.{linkobj2id} IS NULL
1182 '''):
1183 report.append(f'''\t\tFound broken link {a[0]} -> {a[1]}''')
1184 brokenlinks.append((a[0], a[1]))
1185 numlinkscleaned += 1
1186
1187 if brokenlinks:
1188 g.db.ExecuteMany(f'''
1189 DELETE FROM {linktable}
1190 WHERE linkaid = ? AND linkbid = ?
1191 ''',
1192 brokenlinks
1193 )
1194
1195 g.db.Commit()
1196
1197 report.append(f'''Cleaned {numlinkscleaned} broken links from {nummodels} models''')
1198
1199 results['report'] = '\n'.join(report)
1200 else:
1201 results['error'] = f'{g.T.cantunderstand} "{command}"'
1202 except Exception as e:
1203 traceback.print_exc()
1204 results['error'] = str(e)
1205
1206 return results
1207
1208# =====================================================================
1210 """Handles frequently used translations for the system. You must be
1211 an administrator or be part of the translators group in order to use
1212 this API.
1213 """
1214 results = {}
1215
1216 try:
1217 if ('admins' not in g.session['groups']
1218 and 'translators' not in g.session['groups']
1219 ):
1220 raise Exception(g.T.cantchange)
1221
1222 d = json.loads(g.request.get_data())
1223
1224 command = StrV(d, 'command')
1225
1226 if command == 'list':
1227 apps = {}
1228
1229 for f in os.listdir('static'):
1230 apppath = os.path.join('static', f)
1231
1232 if os.path.isdir(apppath):
1233 translations = {}
1234
1235 for l in g.siteconfig['languages']:
1236 translationspath = os.path.join(apppath, 'translations', l)
1237
1238 if os.path.exists(translationspath):
1239 for t in os.listdir(translationspath):
1240 translations[os.path.splitext(t)[0]] = 1
1241
1242 apps[f] = list(translations.keys())
1243
1244 results['data'] = apps
1245 elif command == 'load':
1246 app = StrV(d, 'app')
1247 translation = StrV(d, 'translation')
1248 languages = ArrayV(d, 'languages')
1249
1250 if not app or not translation or not languages:
1251 raise Exception(g.T.missingparams)
1252
1253 translations = {}
1254
1255 for lang in languages:
1256 try:
1257 T = types.SimpleNamespace()
1258
1259 with open('static/' + app + '/translations/' + lang + '/' + translation + '.js', 'r') as f:
1260 exec(f.read())
1261
1262 translations[lang] = T.__dict__
1263 except Exception as e:
1264 pass
1265
1266 results['data'] = translations
1267 elif command == 'save':
1268 app = StrV(d, 'app')
1269 translation = StrV(d, 'translation')
1270 data = DictV(d, 'data')
1271
1272 if not app or not translation:
1273 raise Exception(g.T.missingparams)
1274
1275 # Create blank files for new translations
1276 if not data:
1277 for l in g.siteconfig['languages']:
1278 data[l] = {}
1279
1280 results['error'] = '';
1281
1282 for lang, translations in data.items():
1283 try:
1284 dirname = 'static/' + app + '/translations/' + lang
1285
1286 os.makedirs(dirname, 0o777, True)
1287
1288 with open(dirname + '/' + translation + '.js', 'w') as f:
1289 for k, v in data[lang].items():
1290 f.write(f'T.{k}={json.dumps(v)}\n')
1291
1292 f.write('\n')
1293 except Exception as e:
1294 traceback.print_exc()
1295 results['error'] += str(e) + '\n'
1296 elif command == 'rename' or command == 'copy':
1297 app = StrV(d, 'app')
1298 newname = StrV(d, 'newname')
1299 oldname = StrV(d, 'oldname')
1300
1301 if not app or not newname or not oldname:
1302 raise Exception(g.T.missingparams)
1303
1304 apppath = os.path.join('static', app)
1305
1306 for l in g.siteconfig['languages']:
1307 translationsdir = os.path.join(apppath, 'translations', l)
1308
1309 translationsfile = os.path.join(translationsdir, oldname + '.js')
1310
1311 if os.path.exists(translationsfile):
1312 if command == 'rename':
1313 os.replace(translationsfile, os.path.join(translationsdir, newname + '.js'))
1314 else:
1315 shutil.copy(translationsfile, os.path.join(translationsdir, newname + '.js'))
1316 elif command == 'delete':
1317 app = StrV(d, 'app')
1318 translation = StrV(d, 'translation')
1319
1320 for l in g.siteconfig['languages']:
1321 translationsfile = os.path.join('static', app, 'translations', l, translation + '.js')
1322
1323 if os.path.exists(translationsfile):
1324 os.remove(translationsfile)
1325 else:
1326 results['error'] = f'{g.T.cantunderstand} "{command}"'
1327 except Exception as e:
1328 traceback.print_exc()
1329 results['error'] = str(e)
1330
1331 return results
1332
1333# =====================================================================
1335 """Sets security for database objects. Note that the DB_SECURE flag
1336 must be set on both the database and the model in order to apply.
1337 """
1338 results = {}
1339
1340 try:
1341 d = json.loads(g.request.get_data())
1342
1343 command = StrV(d, 'command')
1344
1345 if command == 'load':
1346 modelname = StrV(d, 'model')
1347 model = GetModel(modelname)
1348 objid = StrV(d, 'objid')
1349
1350 if not model or not objid:
1351 raise Exception(g.T.missingparams)
1352
1353 g.db.RegisterModel(model)
1354
1355 if not model._dbflags & DB_SECURE:
1356 raise Exception(g.T.cantchange + modelname)
1357
1358 obj = g.db.Load(model, FromShortCode(objid), model._dbid)
1359
1360 results['dbowner'] = ToShortCode(obj.dbowner)
1361 results['dbaccess'] = obj.dbaccess
1362 results['dbgroup'] = ToShortCode(obj.dbgroup) if obj.dbgroup else ''
1363 results['dbgroupaccess'] = obj.dbgroupaccess
1364
1365 acl = obj.GetACL(g.db)
1366
1367 acljson = {
1368 'users': {},
1369 'groups': {}
1370 }
1371
1372 if not acl:
1373 results['dbacl'] = acljson
1374 else:
1375 for k, v in acl['users'].items():
1376 userobj = {
1377 'displayname': '',
1378 'access': v
1379 }
1380
1381 if 'admins' in g.session['groups']:
1382 try:
1383 userobj['displayname'] = BestTranslation(
1384 g.db.Load(system_user, k, 'displayname').displayname,
1385 g.session.langcodes
1386 )
1387 except Exception as e:
1388 userobj['displayname'] = f'Error: {e}'
1389
1390 acljson['users'][k] = userobj
1391
1392 for k, v in acl['groups'].items():
1393 groupobj = {
1394 'displayname': '',
1395 'access': v
1396 }
1397
1398 if 'admins' in g.session['groups']:
1399 try:
1400 groupobj['displayname'] = BestTranslation(
1401 g.db.Load(system_group, k, 'displayname').displayname,
1402 g.session.langcodes
1403 )
1404 except Exception as e:
1405 groupobj['displayname'] = f'Error: {e}'
1406
1407 acljson['groups'][k] = groupobj
1408
1409 results['dbacl'] = acljson
1410
1411 if 'admins' in g.session['groups']:
1412 try:
1413 results['ownername'] = BestTranslation(
1414 g.db.Load(system_user, obj.dbowner, 'displayname').displayname,
1415 g.session.langcodes
1416 )
1417 except Exception as e:
1418 results['ownername'] = f'Error: {e}'
1419 else:
1420 results['ownername'] = results['dbowner']
1421
1422 if obj.dbgroup:
1423 try:
1424 group = g.db.Load(system_group, obj.dbgroup, 'displayname')
1425
1426 results['groupname'] = group.groupname
1427 results['groupdisplayname'] = BestTranslation(
1428 group.displayname,
1429 g.session.langcodes
1430 )
1431 except Exception as e:
1432 results['groupname'] = ''
1433 results['groupdisplayname'] = f'Error: {e}'
1434 else:
1435 results['groupname'] = ''
1436 results['groupdisplayname'] = '[None]'
1437 elif command == 'searchgroups':
1438 keyword = StrV(d, 'keyword')
1439 start = IntV(d, 'start', 0, 99999999, 0)
1440 limit = IntV(d, 'limit', 10, 99999999, 100)
1441
1442 cond = []
1443 params = []
1444
1445 if keyword:
1446 conds.append('(groupname LIKE ? OR displayname LIKE ?)')
1447 params.append(EscapeSQL('%' + keyword + '%'))
1448 params.append(EscapeSQL('%' + keyword + '%'))
1449
1450 if cond:
1451 cond = 'WHERE ' + ' AND '.join(conds)
1452
1453 objs = []
1454 groups = []
1455
1456 if 'admins' in g.session['groups']:
1457 groups = g.db.Find(
1458 system_group,
1459 cond,
1460 params,
1461 start = start,
1462 limit = limit,
1463 fields = 'rid, groupname, displayname'
1464 )
1465 else:
1466 groups = g.db.Linked(
1467 g.db.Load(system_user, g.session.userid, 'rid'),
1468 cond = cond,
1469 params = params,
1470 start = start,
1471 limit = limit,
1472 fields = 'rid, groupname, displayname'
1473 )
1474
1475 for r in groups:
1476 objs.append({
1477 'idcode': ToShortCode(r.rid),
1478 'displayname': BestTranslation(r.displayname, g.session.langcodes),
1479 'groupname': r.groupname
1480 })
1481
1482 results['count'] = len(groups)
1483 results['data'] = objs
1484 elif command == 'save':
1485 modelname = StrV(d, 'model')
1486 model = GetModel(modelname)
1487 objid = StrV(d, 'objid')
1488 dbowner = StrV(d, 'dbowner')
1489 dbaccess = IntV(d, 'dbaccess')
1490 dbgroup = StrV(d, 'dbgroup')
1491 dbgroupaccess = StrV(d, 'dbgroupaccess')
1492 dbacl = DictV(d, 'dbacl')
1493
1494 if not model or not objid or not dbowner:
1495 raise Exception(g.T.missingparams)
1496
1497 g.db.RegisterModel(model)
1498
1499 if not model._dbflags & DB_SECURE:
1500 raise Exception(g.T.cantchange + modelname)
1501
1502 obj = g.db.Load(model, FromShortCode(objid), model._dbid)
1503
1504 acl = obj.GetACL(g.db)
1505
1506 acljson = {
1507 'users': {},
1508 'groups': {}
1509 }
1510
1511 if not acl:
1512 results['dbacl'] = acljson
1513 else:
1514 for k, v in acl['users'].items():
1515 userobj = {
1516 'displayname': '',
1517 'access': v
1518 }
1519
1520 if 'admins' in g.session['groups']:
1521 try:
1522 userobj['displayname'] = BestTranslation(
1523 g.db.Load(system_user, k, 'displayname').displayname,
1524 g.session.langcodes
1525 )
1526 except Exception as e:
1527 userobj['displayname'] = f'Error: {e}'
1528
1529 acljson['users'][k] = userobj
1530
1531 for k, v in acl['groups'].items():
1532 groupobj = {
1533 'displayname': '',
1534 'access': v
1535 }
1536
1537 if 'admins' in g.session['groups']:
1538 try:
1539 groupobj['displayname'] = BestTranslation(
1540 g.db.Load(system_group, k, 'displayname').displayname,
1541 g.session.langcodes
1542 )
1543 except Exception as e:
1544 groupobj['displayname'] = f'Error: {e}'
1545
1546 acljson['groups'][k] = groupobj
1547
1548 results['dbacl'] = acljson
1549
1550 if 'admins' in g.session['groups']:
1551 try:
1552 results['ownername'] = BestTranslation(
1553 g.db.Load(system_user, obj.dbowner, 'displayname').displayname,
1554 g.session.langcodes
1555 )
1556 except Exception as e:
1557 results['ownername'] = f'Error: {e}'
1558 else:
1559 results['ownername'] = results['dbowner']
1560
1561 if obj.dbgroup:
1562 try:
1563 group = g.db.Load(system_group, obj.dbgroup, 'displayname')
1564
1565 results['groupname'] = group.groupname
1566 results['groupdisplayname'] = BestTranslation(
1567 group.displayname,
1568 g.session.langcodes
1569 )
1570 except Exception as e:
1571 results['groupname'] = ''
1572 results['groupdisplayname'] = f'Error: {e}'
1573 else:
1574 results['groupname'] = ''
1575 results['groupdisplayname'] = '[None]'
1576 else:
1577 results['error'] = f'{g.T.cantunderstand} "{command}"'
1578 except Exception as e:
1579 traceback.print_exc()
1580 results['error'] = str(e)
1581
1582 return results
1583
1584# *********************************************************************
1585ROUTES = {
1586 'fileapi': fileapi,
1587 'upload': upload,
1588 'controlpanelapi': controlpanelapi,
1589 'dbapi': dbapi,
1590 'userapi': userapi,
1591 'sessionvars': sessionvars,
1592 'translationsapi': translationsapi,
1593 'securityapi': securityapi,
1594}
The main database object.
Definition: db.py:163
Definition: db.py:1
def InitDB(g)
Setups the initial database users, groups, and pages.
Definition: initsite.py:59
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 GetModel(model)
Returns the model object from the correct module.
Definition: utils.py:155
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 b64decodepad(s)
Helper function to ensure that a b64 string is correctly padded to four spaces.
Definition: utils.py:174
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
def translationsapi(g)
Handles frequently used translations for the system.
Definition: __init__.py:1209
def controlpanelapi(g)
Configures settings for the whole site.
Definition: __init__.py:856
def dbapi(g)
Used by the web database management interface to handle objects in the system.
Definition: __init__.py:936
def fileapi(g)
Standard API to search through the files on the system and change names or descriptions.
Definition: __init__.py:71
def upload(g)
Handles uploads for the whole system.
Definition: __init__.py:216
def securityapi(g)
Sets security for database objects.
Definition: __init__.py:1334
def sessionvars(g)
In order to enable caching for static pages, we place all session and user specific information in th...
Definition: __init__.py:33
def userapi(g)
Handles logins, registrations, approvals, and user information.
Definition: __init__.py:335