10from pprint
import pprint
21 """Base class for all database models. Derived classes should add
23 _dbfields List of all model fields
24 _dbindexes List of indexes to add to the database
25 _dblinks List of linked objects for the database model manager
26 _dbdisplay List of default fields to display
for the database
28 _dbflags Enable security, tracking changes,
and tracking changed
42 """Initialize all fields at creation like a good programmer should
44 for k, v
in self._dbfields.items():
45 if v[0].find(
'PRIMARY KEY') != -1:
46 setattr(self, k,
None)
47 elif v[0].find(
'INT') != -1:
49 elif v[0].find(
'FLOAT') != -1:
51 elif v[0] ==
'DATETIME':
52 setattr(self, k, datetime.datetime.now())
60 """We take the easy route where the hash for an object is just its
63 return getattr(self, self._dbid)
67 """Two objects are equivalent if their models are the same and their
70 return (self.
__class__ == o.__class__)
and (self.
id == o.id)
and (self.
id and o.id)
74 """Comparing means simply subracting the database ids. Of course,
75 this only works if you use integer IDs.
77 return getattr(self, self._dbid) - getattr(o, self._dbid)
81 """Simply calls the toJSON() function.
87 """Simply outputs the toJSON() function with the model name.
93 """While operator = only checks class and ids, this checks every field
94 to see if two objects
or an object
and a dict have the same values.
95 Handy
for implementing copy-on-change systems
97 if isinstance(o, dict):
98 for k, v
in self._dbfields.items():
99 if getattr(self, k,
None) != o.get(k,
None):
102 for k, v
in self._dbfields.items():
103 if getattr(self, k,
None) != getattr(o, k,
None):
109 def _HashPassword(self, password, salt):
110 """Default password function. Override if you need more security.
112 return base64.b64encode(hashlib.pbkdf2_hmac(
'sha256', password.encode(
'utf-8'), salt, 100000)).decode(
'ascii')
116 """We use this method instead of direct attribute access in order to
117 keep track of what values have been changed. Only updated values
118 are saved to the database, increasing efficiency and output.
120 The special field _updatechanged allows initialization of values
121 without triggering changed fields. It
's used by the library itself
122 to turn database rows into initialized objects. Set it to 0 if you
123 want to disable tracking, otherwise the default behavior will be
126 _updatechanged = kwargs.get('_updatechanged')
128 if _updatechanged ==
None:
131 for k, v
in self._dbfields.items():
144 if v[0].find(
'INT') != -1:
145 setattr(self, k, int(value
if value
else 0))
146 elif v[0].find(
'FLOAT') != -1:
147 setattr(self, k, float(value
if value
else 0))
148 elif v[0].find(
'NEWLINELIST') != -1:
149 if value
and not isinstance(value, list):
150 value = [x.strip()
for x
in filter(
None, value.split(
'\n'))]
152 setattr(self, k, value
if value
else [])
153 elif (v[0] ==
'DICT' or v[0] ==
'TRANSLATION')
and isinstance(value, str):
155 setattr(self, k, json.loads(value))
156 except Exception
as e:
158 elif v[0] ==
'LIST' and isinstance(value, str):
160 setattr(self, k, json.loads(value))
161 except Exception
as e:
163 elif v[0] ==
'DATETIME':
164 if isinstance(value, datetime.datetime):
165 setattr(self, k, value)
166 elif isinstance(value, int)
or isinstance(value, float):
167 setattr(self, k, datetime.datetime.fromtimestamp(value))
169 setattr(self, k, dateutil.parser.parse(value))
171 setattr(self, k, value)
173 if self._dbflags & pafera.db.DB_SECURE:
180 if getattr(self, r,
None)
is not v
and updatechanged:
183 setattr(self, r, fields[r])
187 """This is a convenience method to update fields without going
188 through the Set() method. Useful for working on mutable
189 collections like lists
and dicts.
191 fieldnames should be a list of fields separated by a comma
and
192 space
as in 'id, name, business'
194 fieldnames = fieldnames.split(', ')
201 """Change all datetime fields to the new time offset. Useful for
202 switching between local and GMT times.
204 for k, v
in self._dbfields.items():
205 if v[0] ==
'DATETIME':
206 d = getattr(self, k,
None)
209 self.
d = datetime.datetime.fromtimestamp(d.timestamp() + timeoffset)
213 """This special function hashes the password before saving the field.
214 You should explicitly call this to avoid situations where you're
215 hashing an existing hash instead of the raw password itself.
217 salt = os.urandom(32)
220 setattr(self, field, base64.b64encode(salt).decode('ascii') +
'' + key)
225 """This special function checks to see if the password matches the
226 hash stored in the field.
228 oldpassword = getattr(self, field)
230 salt, hashed, empty = oldpassword.split('=')
233 salt = base64.b64decode(salt.encode(
'ascii'))
243 """Converts this object into a format suitable for inclusion in JSON.
248 fields = fields.split(
', ')
250 for k, v
in self._dbfields.items():
251 if fields
and k
not in fields:
255 rid = getattr(self, k)
258 if 'PASSWORD' in v[0]:
260 elif 'DATETIME' in v[0]:
261 values[k] = getattr(self, k).isoformat()
263 values[k] = getattr(self, k)
269 """Special security functions that are useful only if you have enabled
270 security in _dbflags
for the model.
276 """Special security functions that are useful only if you have enabled
277 security in _dbflags
for the model.
283 """Special security functions that are useful only if you have enabled
284 security in _dbflags
for the model.
290 """Since we store ACLs as a database lookup, be sure to use these getters
291 and setters
if you plan on using ACLs.
305 """ACLs in Pafera are defined as a set of rules similar to cascading
306 style sheets. The default access for the model comes first, then
307 is overridden by group access, then overridden by user access.
309 Note that
for group access,
if *one* group has access, then the
310 values
for the other groups are ignored.
312 if not acl
or (
not ArrayV(acl,
'users')
and not ArrayV(acl,
'groups')):
322 acljson = json.dumps(acl, sort_keys = 1)
324 r = db.Execute(
'SELECT id FROM system_acl WHERE rules = ?', acljson)
329 self.
dbaclid = self.Execute(
'INSERT INTO system_acl(rules) VALUES(?)', acljson)
333 """Iterator class for database access that supports automatic chunking.
334 Using this class, you can examine billions of rows of data without
335 worrying about memory usage.
"""
339 """Same parameters as db.Find(), mainly because that function only
340 sets up the parameters and lets this
class do the heavy lifting.
344 params = kwargs.get('params')
346 if not isinstance(params, list):
371 """convenience functions to allow use in iterator loops.
378 """convenience functions to allow use in iterator loops.
392 return self[self.
pos]
396 """Conversion function to check if the dataset is empty.
405 """Returns the parameters for this iterator. Handy for debugging.
410 return f
"""DBList({self.model})
418 """Returns the length of the underlying dataset.
422 r = self.
db.Execute(f
"SELECT COUNT(*) FROM {self.model._dbtable} {self.cond}", self.
params)[0]
424 r = self.
db.Execute(f
"SELECT COUNT(*) FROM {self.model._dbtable} {self.cond}")[0]
426 r = self.
db.Execute(f
"SELECT COUNT(*) FROM {self.model._dbtable}")[0]
435 """Resets the condition and parameters for the query used. It saves a
436 little bit of time as opposed to creating a new object.
447 """Resets the order for the query used. It saves a
448 little bit of time as opposed to creating a new object.
458 """Total number of rows for the current query.
467 """Returns the item at pos, retrieving it from the database if
485 """Deletes the item from the database. Note that this will alter
486 the length of the dataset.
505 def _RefreshCache(self, pos):
506 """Fetch new data from the database. You should not need to call this
507 directly as __getitem__() automatically does it
for you.
509 query = f"SELECT {self.fields} FROM {self.model._dbtable} "
512 query = query + self.
cond
515 query = query +
' ORDER BY ' + self.
orderby
517 if ' LIMIT ' not in query:
519 query = query + f
" LIMIT {pos}, 1"
521 query = query + f
" LIMIT {pos}, {self.limit}"
525 for r
in self.
db.Query(query, self.
params):
527 o = self.
model.__class__()
531 r[
'_updatechanged'] = 0
536 access = self.
db.GetAccess(o)
538 if not (access & pafera.db.DB_CAN_VIEW):
547 """Enables random ordering for the returned elements. Note that this
548 will cause severe database activity if you use it on a dataset that
549 won
't fit in memory, so is a convenience for small datasets in
550 local memory more than anything else.
Iterator class for database access that supports automatic chunking.
def Count(self)
Returns the length of the underlying dataset.
def __repr__(self)
Returns the parameters for this iterator.
def __init__(self, db, model, **kwargs)
Same parameters as db.Find(), mainly because that function only sets up the parameters and lets this ...
def __getitem__(self, pos)
Returns the item at pos, retrieving it from the database if necessary.
def __len__(self)
Total number of rows for the current query.
def __bool__(self)
Conversion function to check if the dataset is empty.
def __next__(self)
convenience functions to allow use in iterator loops.
def OrderBy(self, order)
Resets the order for the query used.
def SetRandom(self, active)
Enables random ordering for the returned elements.
def Filter(self, cond, params)
Resets the condition and parameters for the query used.
def __delitem__(self, pos)
Deletes the item from the database.
def _RefreshCache(self, pos)
def __iter__(self)
convenience functions to allow use in iterator loops.
Base class for all database models.
def HasSameValues(self, o)
While operator = only checks class and ids, this checks every field to see if two objects or an objec...
def GetACL(self, db)
Since we store ACLs as a database lookup, be sure to use these getters and setters if you plan on usi...
def SetAccess(self, access)
Special security functions that are useful only if you have enabled security in _dbflags for the mode...
def __repr__(self)
Simply outputs the toJSON() function with the model name.
def SetPassword(self, field, password)
This special function hashes the password before saving the field.
def SetGroup(self, groupid)
Special security functions that are useful only if you have enabled security in _dbflags for the mode...
def UpdateFields(self, fieldnames)
This is a convenience method to update fields without going through the Set() method.
def _HashPassword(self, password, salt)
def __init__(self)
Initialize all fields at creation like a good programmer should.
def Set(self, **kwargs)
We use this method instead of direct attribute access in order to keep track of what values have been...
def SetOwner(self, ownerid)
Special security functions that are useful only if you have enabled security in _dbflags for the mode...
def __eq__(self, o)
Two objects are equivalent if their models are the same and their IDs are the same.
def OffsetTime(self, timeoffset)
Change all datetime fields to the new time offset.
def SetACL(self, db, acl)
ACLs in Pafera are defined as a set of rules similar to cascading style sheets.
def __hash__(self)
We take the easy route where the hash for an object is just its database ID.
def CheckPassword(self, field, password)
This special function checks to see if the password matches the hash stored in the field.
def ToJSON(self, fields='')
Converts this object into a format suitable for inclusion in JSON.
def __cmp__(self, o)
Comparing means simply subracting the database ids.
def __str__(self)
Simply calls the toJSON() function.
def StrV(d, k, default='')
Utility function to get a string from a dict or object given its name.
def ArrayV(d, k, default=[])
Utility function to get an array from a dict or object given its name.
def IntV(d, k, min=None, max=None, default=0)
Utility function to get an int from a dict or object given its name.
def ToShortCode(val, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a 32-bit value into a six character alphanumeric code.