13from pprint
import pprint
27DB_CANNOT_DELETE = 0x20
32DB_CAN_CHANGE_PERMISSIONS = 0x400
33DB_CANNOT_CHANGE_PERMISSIONS = 0x800
34DB_CAN_VIEW_PROTECTED = 0x1000
35DB_CANNOT_VIEW_PROTECTED = 0x2000
42DB_TRACK_CHANGES = 0x02
50 """Bootstrap class for the database.
52 This class keep tracks of every database model sent to
53 db.RegisterModel()
and enables them to show up
in the web database
58 'typename': (
'VARCHAR(255)',
'NOT NULL PRIMARY KEY',),
59 'access': (
'INT',
'NOT NULL DEFAULT 0',),
60 'aclid': (
'INT',
'NOT NULL DEFAULT 0',),
61 'flags': (
'INT',
'NOT NULL DEFAULT 0',),
63 _dbdisplay = [
'typename']
69 """Bootstrap class for the database.
71 This class keeps track of ACL rules and exists to save space so that
72 rules do
not need to be stored
in every object
with security enabled.
76 'id': (
'INTEGER PRIMARY KEY AUTOINCREMENT',
'NOT NULL',),
77 'rules': (
'DICT',
"NOT NULL",),
79 _dbdisplay = [
'rules']
85 """Bootstrap class for the database.
87 This class allows the database object itself to be used like a DB2
90 g.db[
'John'] =
'Cleese'
96 'dbkey': (
'VARCHAR(255)',
'NOT NULL PRIMARY KEY',),
97 'dbvalue': (
'SINGLETEXT',
'NOT NULL',),
99 _dbdisplay = [
'dbkey',
'dbvalue']
105 """Bootstrap class for the database.
107 This class keeps track of all changed values if you have enabled
108 tracking
in dbflags. Future plans involve using this
class to enabled
109 replication
and rollbacks like the PHP version.
119 'id': (
'INT PRIMARY KEY',
'',),
120 'objtype': (
'INT',
'NOT NULL',),
133 'userid': (
'INT',
'NOT NULL',),
134 'eventtime': (
'BIGINT',
'NOT NULL',),
135 'changed': (
'DICT',
'NOT NULL',),
136 'ids': (
'NEWLINELIST',
'NOT NULL',),
138 _dbdisplay = [
'objtype',
'eventtype',
'eventtime',
'changed']
144 """Bootstrap class for the database.
146 Tagging system for every object
in the system. Useful
if you enjoy
147 creating blogs
or galleries.
151 'id': (
'INT PRIMARY KEY',
'',),
152 'languageid': (
'INT',
'NOT NULL',),
153 'tagname': (
'SINGLETEXT',
'NOT NULL',),
156 (
'unique', (
'languageid',
'tagname',)),
158 _dbdisplay = [
'tagname']
164 """The main database object. Note that Pafera allows you to create as
165 many database objects as you want, so you can keep one part of your
166 logic
in a MySQL database, one part
in an Sqlite database,
and so
169 The current access
is saved
in db.userid
and db.groups. Root access
170 can be enabled at any time by calling db.Sudo(),
while debugging
171 is enabled through db.Debug().
175 1: ('en',
'English',
'English'),
176 2: (
'zh',
'Chinese',
'中文'),
187 DEFAULTFILE =
"default.db"
191 connectionname = None,
193 dbhost = 'localhost',
197 dbflags = DB_SECURE | DB_TRACK_CHANGES | DB_TRACK_VALUES,
200 """Basic database constructor.
202 Note that security must be enabled on *both* the database and the model to take effect.
204 Using closeondelete means that the object will automatically close the database
205 connection when it goes out of scope.
223 if not connectionname:
231 if dbtype ==
'sqlite':
233 self.
dbconn = sqlite3.connect(dbname, 30)
234 self.
dbconn.row_factory = sqlite3.Row
235 self.
dbconn.execute(
"PRAGMA busy_timeout = 15000")
236 elif dbtype ==
'mysql':
237 import mysql.connector
as mysql
239 self.
dbconn = mysql.connect(
246 self.
dbconn.set_charset_collation(
'utf8mb4',
'utf8mb4_unicode_520_ci')
247 elif dbtype ==
'postgresql':
248 import psycopg2
as postgresql
250 self.
dbconn = postgresql.connect(
253 password = dbpassword,
257 raise Exception(f
'Database type {dbtype} not supported')
261 DB.CONNECTIONS[connectionname] = (dbname, dbtype, self)
263 if not DB.DEFAULTCONN:
264 DB.DEFAULTCONN = self
268 for r
in self.
Execute(
"SELECT typename, access, aclid, flags FROM system_objtype"):
269 self.
objtypes[ r[0] ] = (r[1], r[2], r[3])
272 except Exception
as e:
281 for r
in self.
Execute(
"SELECT typename, access, aclid, flags FROM system_objtype"):
282 self.
objtypes[ r[0] ] = (r[1], r[2], r[3])
286 """Standard destructor to automatically close the database connection
293 """Close the database connection. You should normally never need to call this since the
294 object will automatically close the connectino when it goes out of scope
302 except Exception
as e:
303 traceback.print_exc()
307 """Begins a transaction. Useful if you don't use auto-commit mode.
310 raise Exception(
"DB.Begin: Already in a transaction!")
314 if self.
dbtype ==
'mysql':
315 self.
dbconn.start_transaction()
317 self.
cursor.execute(
"BEGIN TRANSACTION")
321 """Commits the current transaction. Be sure to call this at the end of your script or none
322 of your changes will take effect.
332 """Rolls back the current transaction. This will also automatically be done if you end
333 the script without calling db.Commit()
342 def Execute(self, query, params = [], many = 0, returndict = 0):
343 """Executes a query upon the database. Use db.Query() if you need a version which
344 automatically wraps the returned values into a dict. Use db.Debug() to see what is
345 being done behind the scenes.
348 if not (isinstance(params, list)
or isinstance(params, tuple)):
351 query = query.strip()
353 if self.
dbtype ==
'mysql':
354 query = query.replace(
'?',
'%s')
357 print(
'DB: Executing query')
359 print(f
'with {len(params)} params')
368 c.executemany(query, params)
370 c.execute(query, params)
372 if query.startswith(
'SELECT'):
373 results = c.fetchall()
378 if self.
dbtype ==
'mysql':
379 columns = tuple([d[0]
for d
in c.description])
382 ls.append(dict(zip(columns, row)))
385 ls.append(dict(zip(r.keys(), r)))
390 results = c.lastrowid
398 if isinstance(results, list):
399 Print(f
'DB: Query results with length {len(results)}')
402 Print(f
'DB: Query results returned {results}')
408 """Convenience function to execute many statements at once. Separated into its own function
409 for more readable code.
411 return self.
Execute(query, params, many = 1)
414 def Query(self, query, params = []):
415 """Convenience function to execute many statements at once. Separated into its own function
416 for more readable code.
418 return self.
Execute(query, params, many = 0, returndict = 1)
422 """Enable or disable administrator mode, bypassing security for the current user.
431 """Enable or disable query debugging
440 """Throws an exception if the current user does not belong to one of the groups listed
441 in groupname. It
's a less expensive check than going through object security.
443 if isinstance(groupname, list):
452 raise Exception(errmsg)
454 if groupname
not in self.
groups:
455 raise Exception(errmsg)
459 """Returns true if the current user belongs to the given group
461 return groupname
in self.
groups
465 """Utility function to calculate the current user's access to the given database object.
466 It's only effective if security is enabled on both the database and the object's model.
468 if self.
numsudos or 'admins' in self.
groups or (hasattr(obj,
'dbowner')
and obj.dbowner == self.
userid):
471 typename, access, aclid = self.
objtypes[obj._dbtable]
473 access = self.
ApplyACL(access, aclid)
475 if hasattr(obj,
'dbaccess'):
478 if hasattr(obj,
'dbgroupaccess')
and getattr(obj,
'dbgroup')
in self.groupids:
481 if hasattr(obj,
'dbaclid'):
482 access = self.
ApplyACL(access, obj.dbaclid)
488 """Get the rules for the given ACL. You should not need to call this yourself since it's
489 automatically handled by db.GetAccess()
493 if aclid
not in self.
acls:
494 acl = self.
Execute(
'SELECT rules FROM system_acl WHERE id = ?', aclid)[0]
495 self.
acls[aclid] = json.loads(acl)
if acl
else {}
501 """Get the rules for the given ACL. You should not need to call this yourself since it's
502 automatically handled by db.GetAccess()
510 for k, v
in acl[
'groups'].items():
515 for k, v
in acl[
'users'].items():
523 """Utility function to calculate access control. You should not need to call this yourself
524 since it's automatically handled by db.GetAccess()
526 if newaccess & DB_CAN_CREATE:
527 origaccess = origaccess & (~DB_CANNOT_CREATE) | DB_CAN_CREATE
528 elif newaccess & DB_CANNOT_CREATE:
529 origaccess = origaccess & (~DB_CAN_CREATE) | DB_CANNOT_CREATE
531 if newaccess & DB_CAN_CHANGE:
532 origaccess = origaccess & (~DB_CANNOT_CHANGE) | DB_CAN_CHANGE
533 elif newaccess & DB_CANNOT_CHANGE:
534 origaccess = origaccess & (~DB_CAN_CHANGE) | DB_CANNOT_CHANGE
536 if newaccess & DB_CAN_VIEW:
537 origaccess = origaccess & (~DB_CANNOT_VIEW) | DB_CAN_VIEW
538 elif newaccess & DB_CANNOT_VIEW:
539 origaccess = origaccess & (~DB_CAN_VIEW) | DB_CANNOT_VIEW
541 if newaccess & DB_CAN_DELETE:
542 origaccess = origaccess & (~DB_CANNOT_DELETE) | DB_CAN_DELETE
543 elif newaccess & DB_CANNOT_DELETE:
544 origaccess = origaccess & (~DB_CAN_DELETE) | DB_CANNOT_DELETE
546 if newaccess & DB_CAN_LINK:
547 origaccess = origaccess & (~DB_CANNOT_LINK) | DB_CAN_LINK
548 elif newaccess & DB_CANNOT_LINK:
549 origaccess = origaccess & (~DB_CAN_LINK) | DB_CANNOT_LINK
551 if newaccess & DB_CAN_CHANGE_PERMISSIONS:
552 origaccess = origaccess & (~DB_CANNOT_CHANGE_PERMISSIONS) | DB_CAN_CHANGE_PERMISSIONS
553 elif newaccess & DB_CANNOT_CHANGE_PERMISSIONS:
554 origaccess = origaccess & (~DB_CAN_CHANGE_PERMISSIONS) | DB_CANNOT_CHANGE_PERMISSIONS
556 if newaccess & DB_CAN_VIEW_PROTECTED:
557 origaccess = origaccess & (~DB_CANNOT_VIEW_PROTECTED) | DB_CAN_VIEW_PROTECTED
558 elif newaccess & DB_CANNOT_VIEW_PROTECTED:
559 origaccess = origaccess & (~DB_CAN_VIEW_PROTECTED) | DB_CANNOT_VIEW_PROTECTED
565 """All models must be registered before use, which means creating the
566 table, indexes, and storing a record of the model
for use
in the
567 web database management interface.
569 You normally do
not need to call this function itself since any
570 database function like db.Find(), db.Load(),
and db.Save() will
571 automatically do it
for you.
576 model = model.__class__
579 if V(model,
'_dbinit'):
583 if not V(model,
'_dbtable'):
584 if V(model,
'_apppath'):
585 model._dbtable = model._apppath.replace(
'/',
'_') +
'_' + model.__name__.lower()
587 model._dbtable = model.__name__.lower()
590 if 'id' in model._dbfields:
592 elif 'rid' in model._dbfields:
594 elif 'rbid' in model._dbfields:
599 for k, v
in model._dbfields.items():
600 if v[1].find(
'PRIMARY KEY') != -1:
604 if not hasattr(model,
'_dbflags'):
609 if model._dbtable
in self.
objtypes.keys():
615 for k, v
in model._dbfields.items():
616 fields.append(k +
' ' + self.
MapFieldType(v[0]) +
' ' + v[1])
618 if model._dbflags & DB_SECURE:
619 fields.append(
'dbowner INT NOT NULL DEFAULT 0')
620 fields.append(
'dbgroup INT NOT NULL DEFAULT 0')
621 fields.append(
'dbaccess INT NOT NULL DEFAULT 0')
622 fields.append(
'dbgroupaccess INT NOT NULL DEFAULT 0')
623 fields.append(
'dbaclid INT NOT NULL DEFAULT 0')
625 fields =
','.join(fields)
628 if self.
dbtype ==
'mysql':
629 self.
Execute(f
"CREATE TABLE {model._dbtable}({fields}) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;")
631 self.
Execute(f
"CREATE TABLE {model._dbtable}({fields})")
632 except Exception
as e:
635 if 'exists' not in str(e):
636 raise Exception
from e
639 if V(model,
'_dbindexes'):
642 for r
in model._dbindexes:
646 if not isinstance(indexcolumns, tuple):
647 indexcolumns = [indexcolumns]
649 if self.
dbtype ==
'mysql':
652 for c
in indexcolumns:
653 fielddef = model._dbfields[c][0]
655 if (fielddef.find(
'TEXT') != -1
656 or fielddef.find(
'DICT') != -1
657 or fielddef.find(
'LIST') != -1
658 or fielddef.find(
'TRANSLATION') != -1
659 or fielddef.find(
'FILE') != -1
661 columns.append(c +
'(255)')
665 columns =
', '.join(columns)
667 columns =
', '.join(indexcolumns)
669 self.
Execute(f
"CREATE {indextype} INDEX {model._dbtable}_index{indexnum} ON {model._dbtable}({columns})")
673 if model._dbtable !=
'system_objtype':
678 typename = model._dbtable,
679 access = DB_CANNOT_DELETE
682 | DB_CANNOT_CHANGE_PERMISSIONS
683 | DB_CANNOT_VIEW_PROTECTED,
693 self.
objtypes[model._dbtable] = newtype.ToJSON()
699 """Removes the model from the system registry.
701 Useful if you
're uninstalling an app.
705 self.Execute('DELETE FROM system_objtype WHERE typename = ?', model)
710 """This function is responsible for mapping python types to their
711 database representations. If you are using a database outside of
712 MySQL or SQLite, be sure to add the conversions here.
716 if self.
dbtype ==
'mysql':
718 (
'AUTOINCREMENT',
'AUTO_INCREMENT'),
719 (
'BIGINT PRIMARY KEY',
'BIGINT PRIMARY KEY'),
720 (
'INT PRIMARY KEY',
'INT PRIMARY KEY'),
723 (
'INT16',
'SMALLINT'),
727 (
'DATETIME',
'BIGINT'),
730 (
'SINGLETEXT',
'TEXT'),
731 (
'MULTITEXT',
'TEXT'),
732 (
'PASSWORD',
'TEXT'),
733 (
'IMAGEFILE',
'TEXT'),
734 (
'SOUNDFILE',
'TEXT'),
735 (
'VIDEOFILE',
'TEXT'),
736 (
'SYSTEMFILE',
'TEXT'),
737 (
'NEWLINELIST',
'TEXT'),
740 (
'TRANSLATION',
'TEXT'),
742 elif self.
dbtype ==
'postgresql':
744 (
'AUTOINCREMENT',
'AUTO_INCREMENT'),
745 (
'BIGINT PRIMARY KEY',
'BIGINT PRIMARY KEY'),
746 (
'INT PRIMARY KEY',
'INT PRIMARY KEY'),
749 (
'INT16',
'SMALLINT'),
753 (
'DATETIME',
'BIGINT'),
756 (
'SINGLETEXT',
'TEXT'),
757 (
'MULTITEXT',
'TEXT'),
758 (
'PASSWORD',
'TEXT'),
759 (
'IMAGEFILE',
'TEXT'),
760 (
'SOUNDFILE',
'TEXT'),
761 (
'VIDEOFILE',
'TEXT'),
762 (
'SYSTEMFILE',
'TEXT'),
763 (
'NEWLINELIST',
'TEXT'),
766 (
'TRANSLATION',
'TEXT'),
768 elif self.
dbtype ==
'sqlite':
779 (
'SINGLETEXT',
'TEXT'),
780 (
'MULTITEXT',
'TEXT'),
781 (
'PASSWORD',
'TEXT'),
782 (
'IMAGEFILE',
'TEXT'),
783 (
'SOUNDFILE',
'TEXT'),
784 (
'VIDEOFILE',
'TEXT'),
785 (
'SYSTEMFILE',
'TEXT'),
786 (
'NEWLINELIST',
'TEXT'),
789 (
'TRANSLATION',
'TEXT'),
792 for r
in replacements:
793 fieldtype = fieldtype.replace(r[0], r[1])
799 """Quick utility function to see if a table exists.
801 For model tables, the same functionality could be achieved by checking db._objtypes,
802 but there is always the possibility that another script has created a table between
803 the initialization of the database object
and the current running code, so it
's
804 always best to be sure by directly querying the database itself.
807 self.
Execute(f
"SELECT * FROM {table} LIMIT 1")
809 except Exception
as e:
814 """Utility function to allow the database object itself to work like
817 r = self.Execute("SELECT value FROM system_config WHERE key = ?", key)
826 """Utility function to allow the database object itself to work like
829 self.Query("REPLACE INTO system_config(key, value) VALUES(?, ?)", [key, value])
833 """db.Insert(), db.Update(), and db.Replace() are all convenient
834 wrappers to the underlying db.Save() function for easier
837 return self.
Save(obj,
'insert')
841 """db.Insert(), db.Update(), and db.Replace() are all convenient
842 wrappers to the underlying db.Save() function for easier
845 return self.
Save(obj,
'update')
849 """db.Insert(), db.Update(), and db.Replace() are all convenient
850 wrappers to the underlying db.Save() function for easier
853 return self.
Save(obj,
'replace')
856 def Save(self, obj, method = ''):
857 """This is the function where we convert python types
858 and store them into the database.
860 Any fields named
"rid" or "rbid" will be automatically given a
861 randomized non-existing value. You can check its value on the
862 object after this function returns.
864 Security fields will be automatically added
if security
is enabled
865 for both the database
and the object
's model.
875 dbidvalue = getattr(obj, dbid,
None)
888 enablesecurity = obj.__class__._dbflags & DB_SECURE
892 cancreate = access & DB_CAN_CREATE
893 canchange = access & DB_CAN_CHANGE
896 ((method ==
'insert')
and not cancreate)
897 or ((method ==
'update')
and not canchange)
898 or ((method ==
'replace')
and not canchange
and not cancreate)
900 raise Exception(
"DB.Save: Permission denied")
903 if obj._dbfields[dbid][0].find(
'AUTOINCREMENT') != -1:
908 (method ==
'insert' or method ==
'replace')
910 and (dbid ==
'rid' or dbid ==
'rbid')
914 for i
in range(0, 100):
920 r = self.
Execute(f
"SELECT {dbid} FROM {obj._dbtable} WHERE {dbid} = ?", newid)
926 setattr(obj, dbid, newid)
930 raise Exception(f
"DB.Insert(): Could not find new random ID for table {obj._dbtable}")
935 changedkeys = obj._changed.keys()
938 for k, v
in obj._dbfields.items():
939 if k
not in changedkeys
and k != dbid:
945 (autoincrement
and not dbidvalue)
946 or (method ==
'update')
952 value = getattr(obj, k,
None)
954 if v[0].find(
'PRIMARY KEY') != -1:
956 elif v[0].find(
'INT') != -1:
957 values.append(int(value
if value
else 0))
958 elif v[0].find(
'FLOAT') != -1:
959 values.append(float(value
if value
else 0))
960 elif v[0] ==
'NEWLINELIST':
961 values.append(
'\n'.join(value)
if value
else '')
962 elif v[0] ==
'DICT' or v[0] ==
'TRANSLATION':
963 values.append(json.dumps(value
if value
else {}))
965 values.append(json.dumps(value
if value
else []))
966 elif v[0] ==
'DATETIME':
967 values.append(value.timestamp()
if value
else 0)
971 parameters =
', '.join(
'?' * len(keys))
975 keys.append(
'dbowner')
976 values.append(getattr(obj,
'dbowner', self.
userid))
978 keys.append(
'dbgroup')
979 values.append(getattr(obj,
'dbgroup', 0))
981 keys.append(
'dbaccess')
982 values.append(getattr(obj,
'dbaccess', 0))
984 keys.append(
'dbgroupaccess')
985 values.append(getattr(obj,
'dbgroupaccess', 0))
987 keys.append(
'dbaclid')
988 values.append(getattr(obj,
'dbaclid', 0))
991 if method ==
'insert' or method ==
'replace':
992 needcursor =
not self.
cursor
997 if method ==
'insert':
998 self.
Execute(f
"INSERT INTO {obj._dbtable}({', '.join(keys)}) VALUES({parameters})", values)
1000 self.
Execute(f
"REPLACE INTO {obj._dbtable}({', '.join(keys)}) VALUES({parameters})", values)
1002 if dbid !=
'rid' and dbid !=
'rbid':
1003 if self.
cursor.lastrowid:
1004 setattr(obj, dbid, self.
cursor.lastrowid)
1009 elif method ==
'update':
1011 raise Exception(f
"DB.Update(): Cannot update {obj._dbtable} since no ID was provided")
1015 ls.append(
"UPDATE " + obj._dbtable +
" SET ")
1017 for i
in range(0, len(keys) - 1):
1021 ls.append(keys[i] +
' = ?, ')
1023 ls.append(f
" {keys[len(keys) - 1]} = ? WHERE {dbid} = ?")
1025 values.append(dbidvalue)
1027 self.
Execute(
'\n'.join(ls), values)
1029 raise Exception(f
"Unknown save method {method}")
1034 def Load(self, model, objid, fields = '*'):
1035 """Loads an object given its ID. Like many other database designers,
1036 I consider giving every row its own ID value to be convenient
1037 despite the added space requirements.
1041 enablesecurity = model._dbflags & DB_SECURE
1043 if enablesecurity
and fields !=
'*':
1044 fields +=
', dbowner, dbgroup, dbaccess, dbgroupaccess, dbaclid'
1046 r = self.
Query(f
"SELECT {fields} FROM {model._dbtable} WHERE {model._dbid} = ?", objid)
1049 raise Exception(f
"No object with id {getattr(model, model._dbid, '[blank]')} found in {model._dbtable}")
1054 r[
'_updatechanged'] = 0
1060 if not (access & DB_CAN_VIEW):
1061 raise Exception(
"DB.Load: Permission denied")
1067 """Does the same thing as db.Load(), only you can load a number of
1068 objects at once. This is separated into its own function since
1069 it actually involves different syntax
in returning an array versus
1070 returning a single object.
1074 enablesecurity = model._dbflags & DB_SECURE
1076 if enablesecurity
and fields !=
'*':
1077 fields +=
', dbowner, dbgroup, dbaccess, dbgroupaccess, dbaclid'
1079 questionmarks = [
'?' for x
in ids]
1083 f
'WHERE {model._dbid} IN (' +
', '.join(questionmarks) +
')',
1096 """The Pafera equivalent to SQL SELECT. All it does is to pass the
1097 parameters to DBList()'s constructor.
1099 cond is the filter such
as WHERE
or HAVING
1101 params
is a list of parameters to be used
in the query. SQL
1102 injections are still a thing
in 2016, so
if you
're still
1103 manually constructing queries, be sure that you know what you're
1106 Other available parameters are fields, start, limit, and orderby.
1107 They do pretty much what you think they do
in constructing the
1108 underlying SQL query.
1112 kwargs['cond'] = cond
1113 kwargs[
'params'] = params
1123 """Deletes the given object or objects of the same type matching cond and params.
1125 As with any deletion function, make sure that you
're *really* sure before
1126 using this. If in doubt, enable DB_TRACK_VALUES on this model so that you can
1127 get the object back
if you
've made a mistake.
1131 enablesecurity = model._dbflags & DB_SECURE
1135 for r
in self.
Find(model, cond, params):
1138 if access & DB_CAN_DELETE:
1139 self.
Execute(f
"DELETE FROM {model._dbtable} WHERE {model._dbid} = ?", r[model._dbid])
1140 elif getattr(model, model._dbid,
None):
1143 if access & DB_CAN_DELETE:
1144 self.
Execute(f
"DELETE FROM {model._dbtable} WHERE {model._dbid} = ?", getattr(model, model._dbid))
1146 for r
in self.
Find(model):
1149 if access & DB_CAN_DELETE:
1150 self.
Execute(f
"DELETE FROM {model._dbtable} WHERE {model._dbid} = ?", r[model._dbid])
1153 self.
Execute(f
"DELETE FROM {model._dbtable} " + cond, params)
1154 elif getattr(model, model._dbid,
None):
1155 self.
Execute(f
"DELETE FROM {model._dbtable} WHERE {model._dbid} = ?", getattr(model, model._dbid))
1157 self.
Execute(f
"DELETE FROM {model._dbtable}")
1161 """Utility function to get the name of the database table between
1164 Use this if you need to do any manual operations on link tables.
1171 if obj1._dbtable > obj2._dbtable:
1176 return obj1._dbtable +
'__' + obj2._dbtable
1180 """Creates the link table if necessary.
1186 aidtype = self.
MapFieldType(obj1._dbfields[obj1._dbid][0]).replace(
'PRIMARY KEY',
'').replace(
'AUTOINCREMENT',
'').replace(
'AUTO_INCREMENT',
'')
1188 bidtype = self.
MapFieldType(obj2._dbfields[obj2._dbid][0]).replace(
'PRIMARY KEY',
'').replace(
'AUTOINCREMENT',
'').replace(
'AUTO_INCREMENT',
'')
1190 self.
Execute(f
"""CREATE TABLE {linktable}(
1191 linkaid {aidtype} NOT NULL,
1192 linkbid {bidtype} NOT NULL,
1193 linktype INT NOT NULL,
1194 linknum INT NOT NULL,
1195 linkcomment TEXT NOT NULL,
1196 linkflags INT NOT NULL
1198 self.Execute(f"CREATE UNIQUE INDEX {linktable}_index1 ON {linktable}(linkaid, linkbid)")
1212 """Linking is one of the fundamental concepts in Pafera. It's essentially
1213 a foreign key mechanism that can link two arbitary objects via their
1214 database IDs, thus you can do things like
1223 print(db.Linked(u, group))
1225 Linking
is a bidirectional operation, meaning that both
1226 db.Linked(u, group)
and db.Linked(g, user) will work. For unidirectional
1227 links, it
's suggested to store the linked IDs in a NEWLINELIST field
1228 in an object itself.
1230 Behind the scenes, linking works by create a separate table that connects
1231 IDs between one table
and other. Any attempt to find links are returned
1232 in a DBList() object which performs a SQL JOIN
for one query results.
1234 Linking supports different types of links via the linktype parameter.
1236 The linknum parameter
is used to create results that are returned
in
1237 order of linknum, guaranteeing that the same sequence will be returned
1240 flags can be used to store application-specific meanings outside of
1244 if obj1._dbtable > obj2._dbtable:
1251 enablesecurity = (obj1.__class__._dbflags & DB_SECURE)
or (obj2.__class__._dbflags & DB_SECURE)
1257 if (access1 & DB_CANNOT_LINK)
or (access2 & DB_CANNOT_LINK):
1258 raise Exception(
"DB.Link: Permission denied")
1260 obj1id = getattr(obj1, obj1._dbid)
1261 obj2id = getattr(obj2, obj2._dbid)
1263 if not obj1id
or not obj2id:
1264 raise Exception(
"DB.Link: Missing object IDs")
1266 self.
Execute(f
"""REPLACE INTO {linktable}(
1273 ) VALUES(?, ?, ?, ?, ?, ?)
1293 """A version of db.Link() that will erase all previous links of the
1294 same type and store the given links
as an array. This
is useful
for
1295 representing tree diagrams
from the normally flat SQL architecture
1296 outside of advanced usage
in PostgreSQL
and the sort.
1304 enablesecurity = (obj1.__class__._dbflags & DB_SECURE) or (obj2.__class__._dbflags & DB_SECURE)
1310 if (access1 & DB_CANNOT_LINK)
or (access2 & DB_CANNOT_LINK):
1311 raise Exception(
"DB.Link: Permission denied")
1317 obj1id = getattr(obj1, obj1._dbid)
1318 obj2dbid = obj2._dbid
1321 if not obj1id
or not obj2dbid:
1322 raise Exception(
"DB.LinkArray: Missing object IDs")
1324 for obj2
in obj2list:
1328 getattr(obj2, obj2dbid),
1337 if obj1._dbtable > obj2._dbtable:
1338 self.
Execute(f
"""DELETE FROM {linktable}
1339 WHERE linkbid = ? and linktype = ?
1350 ) VALUES(?, ?, ?, ?, ?, ?)
1355 self.
Execute(f
"""DELETE FROM {linktable}
1356 WHERE linkaid = ? and linktype = ?
1367 ) VALUES(?, ?, ?, ?, ?, ?)
1378 """Unlink two objects, or if obj2 is a model, unlink *every*
1379 object of the given linktype. Use with caution.
1387 model2 = obj2.__class__
1391 enablesecurity = (obj1.__class__._dbflags & DB_SECURE)
or (obj2._dbflags & DB_SECURE)
1397 if (access1 & DB_CANNOT_LINK)
or (access2 & DB_CANNOT_LINK):
1398 raise Exception(
"DB.Link: Permission denied")
1403 self.
Execute(f
"""DELETE FROM {linktable}
1404 WHERE linkaid = ? AND linkbid = ? AND linktype = ?
1407 getattr(obj1, obj1._dbid),
1408 getattr(obj2, obj2._dbid),
1413 self.
Execute(f
"""DELETE FROM {linktable}
1414 WHERE linkaid = ? AND linktype = ?
1417 getattr(obj1, obj1._dbid),
1430 """Convenience function to check if two objects are linked
1431 without actually loading the objects themselves. Commonly used
1432 to check for membership
in a group.
1437 if model1._dbtable > model2._dbtable:
1438 r = self.
Execute(f
"""SELECT linkaid FROM {linktable}
1439 WHERE linkaid = ? AND linkbid = ? AND linktype = ?""",
1440 (model2id, model1id, linktype)
1443 r = self.
Execute(f
"""SELECT linkaid FROM {linktable}
1444 WHERE linkaid = ? AND linkbid = ? AND linktype = ?""",
1445 (model1id, model2id, linktype)
1448 return 1
if r
else 0
1462 """Return the list of objects linked to this object with the given
1463 linktype. Note that this function returns a DBList(), so if you have
1464 many objects linked, it will automatically separate them into chunks
1465 while iterating through them.
1471 if obj1.__class__._dbflags & DB_SECURE:
1474 if not access1 & DB_CAN_LINK:
1475 raise Exception(
'DB.Linked: Permission denied')
1482 obj1id = getattr(obj1, obj1._dbid)
1483 obj2dbid = model2._dbid
1487 if obj1._dbtable > model2._dbtable:
1488 joinclause = f
'JOIN {linktable} ON linkaid = ' + obj2dbid
1491 orderby =
'linknum, linkaid'
1493 newparams = [obj1id, linktype]
1496 cond =
'WHERE linkbid = ? AND linktype = ? AND (' + cond +
')'
1499 cond =
'WHERE linkbid = ? AND linktype = ?'
1501 joinclause = f
'JOIN {linktable} ON linkbid = ' + obj2dbid
1504 orderby =
'linknum, linkbid'
1506 newparams = [obj1id, linktype]
1509 cond =
'WHERE linkaid = ? AND linktype = ? AND (' + cond +
')'
1512 cond =
'WHERE linkaid = ? AND linktype = ?'
1514 cond = joinclause +
' ' + cond
1539 """Convenience function for checking what objects are linked to
1540 model1id without having to load the object itself.
1547 setattr(obj1, obj1._dbid, model1id)
1549 return self.
Linked(obj1, model2, linktype, start, limit, fields, orderby, cond, params)
The main database object.
def LoadMany(self, model, ids, fields=' *')
Does the same thing as db.Load(), only you can load a number of objects at once.
def Debug(self, state)
Enable or disable query debugging.
def GetACL(self, aclid)
Get the rules for the given ACL.
def Update(self, obj)
db.Insert(), db.Update(), and db.Replace() are all convenient wrappers to the underlying db....
def HasGroup(self, groupname)
Returns true if the current user belongs to the given group.
def MapFieldType(self, fieldtype)
This function is responsible for mapping python types to their database representations.
def Close(self)
Close the database connection.
def Unlink(self, obj1, obj2, linktype=0)
Unlink two objects, or if obj2 is a model, unlink every object of the given linktype.
def Link(self, obj1, obj2, linktype=0, linknum=0, comment='', flags=0)
Linking is one of the fundamental concepts in Pafera.
def GetAccess(self, obj)
Utility function to calculate the current user's access to the given database object.
def RequireGroup(self, groupname, errmsg)
Throws an exception if the current user does not belong to one of the groups listed in groupname.
def RegisterModel(self, model)
All models must be registered before use, which means creating the table, indexes,...
def ApplyAccess(self, origaccess, newaccess)
Utility function to calculate access control.
def __getitem__(self, key, value=None)
Utility function to allow the database object itself to work like a big DB2 store.
def CreateLinkTable(self, obj1, obj2)
Creates the link table if necessary.
def Delete(self, model, cond='', params=[])
Deletes the given object or objects of the same type matching cond and params.
def Sudo(self, state)
Enable or disable administrator mode, bypassing security for the current user.
def HasLink(self, model1, model1id, model2, model2id, linktype=0)
Convenience function to check if two objects are linked without actually loading the objects themselv...
def Query(self, query, params=[])
Convenience function to execute many statements at once.
def ExecuteMany(self, query, params=[])
Convenience function to execute many statements at once.
def LinkedToID(self, model1, model1id, model2, linktype=0, start=0, limit=1000, fields=' *', orderby='', cond='', params=[])
Convenience function for checking what objects are linked to model1id without having to load the obje...
def Execute(self, query, params=[], many=0, returndict=0)
Executes a query upon the database.
def __del__(self)
Standard destructor to automatically close the database connection.
def LinkArray(self, obj1, obj2list, linktype=0, comment='', flags=0)
A version of db.Link() that will erase all previous links of the same type and store the given links ...
def Find(self, model, cond='', params=[], **kwargs)
The Pafera equivalent to SQL SELECT.
def Load(self, model, objid, fields=' *')
Loads an object given its ID.
def HasTable(self, table)
Quick utility function to see if a table exists.
def Replace(self, obj)
db.Insert(), db.Update(), and db.Replace() are all convenient wrappers to the underlying db....
def __init__(self, connectionname=None, dbname=None, dbhost='localhost', dbtype='sqlite', dbuser=None, dbpassword=None, dbflags=DB_SECURE|DB_TRACK_CHANGES|DB_TRACK_VALUES, closeondelete=1)
Basic database constructor.
def Begin(self)
Begins a transaction.
def Rollback(self)
Rolls back the current transaction.
def GetLinkTable(self, obj1, obj2)
Utility function to get the name of the database table between two objects.
def Save(self, obj, method='')
This is the function where we convert python types and store them into the database.
def Commit(self)
Commits the current transaction.
def ApplyACL(self, access, aclid)
Get the rules for the given ACL.
def Insert(self, obj)
db.Insert(), db.Update(), and db.Replace() are all convenient wrappers to the underlying db....
def UnregisterModel(self, model)
Removes the model from the system registry.
def Linked(self, obj1, model2, linktype=0, start=0, limit=1000, fields=' *', orderby='', cond='', params=[])
Return the list of objects linked to this object with the given linktype.
def __setitem__(self, key, value)
Utility function to allow the database object itself to work like a big DB2 store.
Bootstrap class for the database.
Bootstrap class for the database.
Bootstrap class for the database.
Bootstrap class for the database.
Bootstrap class for the database.
Iterator class for database access that supports automatic chunking.
Base class for all database models.
def Print(v, *args)
Print a unicode string with arguments.
def V(d, k, default=None)
Utility function to get a value from a dict or object given its name.
def MakeRID()
Returns a random 32-bit int ID.
def MakeRBID()
Returns a random 64-bit int ID.