"""
base.py : The Row class
* Copyright: 2017-2018 Sampsa Riikonen
* Authors : Sampsa Riikonen
* Date : 2017
* Version : 0.2.3
This file is part of the cute_mongo_forms library
License: LGPLv3+ (see the COPYING.LESSER file)
"""
# from PyQt5 import QtWidgets, QtCore, QtGui # Qt5
from PySide2 import QtWidgets, QtCore, QtGui
import sys
import copy
import collections
from cute_mongo_forms.tools import typeCheck, dictionaryCheck, objectCheck, parameterInitCheck, noCheck
from cute_mongo_forms.column.base import Column, LineEditColumn
pre_mod = "cute_mongo_forms.row.base : " # a string for aux debuggin purposes
verbose = False # module's verbosity
class ColumnSpec:
def __init__(self, column_class, **kwargs):
self.column_class = column_class
self.kwargs = kwargs
def instantiate(self):
return self.column_class(**self.kwargs)
class RowWatcher(type):
def __init__(cls, name, bases, clsdict):
if len(cls.mro()) > 2:
# print("was subclassed by " + name +" "+cls.__name__)
cls.genKeys()
# print(cls.keys)
super(RowWatcher, cls).__init__(name, bases, clsdict)
[docs]class Row(metaclass=RowWatcher):
"""
Defines a common columns structure for documents in a [document database] collection. The column structure (i.e. "Row") is defined in the child class header like this:
::
columns=[
ColumnSpec(LineEditColumn, key_name="firstname", label_name="First Name"),
...
]
This class knows how to create a Qt form, corresponding to the column structure
"""
name = None # if not defined, use classes __name__ attribute
columns = []
parameter_defs = {}
keys = [] # filled by first call to getKeys
# re-generated each time you call "class Classname(Row) ..". Why this?
# We need the "keyset" at class level, not at object level
keyset = None
[docs] @classmethod
def genKeys(cls):
"""This is called when you declare the class, i.e. at "Class MyNewRow(Row) ...". It updates the class variables "keys" and "keyset"
"""
cls.keys = []
for col in cls.columns: # ColumnSpec instances
if ("key_name" in col.kwargs):
# remember.. ColumnSpec has kwargs thats going to be passed to
# each column when they're instantiated. "key_name" has the
# name of the column key.
cls.keys.append(col.kwargs["key_name"])
cls.keyset = set(cls.keys)
@classmethod
def genChecker(cls):
pass
""" # TODO associated "orm" class for programmatic (i.e. non-widget) manipulation of the underlying database: we want to check that what we write to database is correct
class RowCheck: #
def __init__(self):
for col in cls.columns:
pass
# or ..
for key, column in column_by_name.items()
self.column_check_by_name[key] =column.ColumnCheck() # ColumnCheck instance. Houses a copy of the value.
def __getattr__(self,key):
# classname and _id must be handled separately
# _id is saved as an attribute of RowCheck
# classname should be a constant
column_check=self.column_check_by_name[key]
return column_check.get()
def __setattr__(self,key):
column_check=self.column_check_by_name[key]
column_check.set(value)
cls.RowCheck=RowCheck
obj=Row.jsonToObj(doc) # instantiates and returns a new RowCheck object
#
value=obj.key1 => returns value (from RowCheck.column_check_by_name[key1].value == ColumnCheck.value)
obj.key1=value => perform the check => if ok, value goes into RowCheck.column_check_by_name[key1] == ColumnCheck.value
class ColumnCheck: # internal class of each Column class
def __init__(self):
self.value=default_value
def get(self):
return self.value # always correct, of course..
def set(self,value):
# perform type and limits check here
self.value=value
"""
def __init__(self, **kwargs):
# auxiliary string for debugging output
self.pre = self.__class__.__name__ + " : "
# check kwargs agains parameter_defs, attach ok'd parameters to this
# object as attributes
parameterInitCheck(self.parameter_defs, kwargs, self)
self.initColumns()
self.makeWidget()
self.customInit()
def customInit(self):
pass
def initColumns(self):
# find class variables ..
self.column_by_name = collections.OrderedDict()
# self.columns_=[] # list of tuples: name, label, instance
self.columns_ = [] # now just a list of column instances
for column_def in self.columns:
col_instance = column_def.instantiate() # instantiate the column
self.column_by_name[col_instance.key_name] = col_instance
self.columns_.append(col_instance)
[docs] def getName(self):
"""Return a name of this class that can be displayed (instead of just the classname)
"""
if self.__class__.name:
return self.__class__.name
return self.__class__.__name__
def __getattr__(self, key):
try:
column = self.column_by_name[key]
except KeyError:
raise(AttributeError("No such column " + str(key)))
return
else:
return column
def __getitem__(self, key):
try:
column = self.column_by_name[key]
except KeyError:
raise(AttributeError("No such column " + str(key)))
return
else:
return column
def __str__(self):
st = ""
for key, column in self.column_by_name.items():
st += key + ":" + str(column.getValue()) + " "
return st
def __collect__(self):
"""Collect dictionary from QWidgets
"""
dic = {} # this will be written to db
for key, column in self.column_by_name.items():
dic[key] = column.getValue()
# dic["classname"]=self.__class__.__name__ # not here, but in
# collection
return dic
[docs] def new(self, collection):
"""New entry to collection
"""
dic = self.__collect__()
return collection.new(self.__class__, dic)
[docs] def update(self, collection, _id):
"""Save from QtWidgets to collection
"""
dic = self.__collect__()
dic["_id"] = _id
# print(self.pre,"update",dic)
collection.update(self.__class__, dic)
[docs] def delete(self, collection, _id):
"""Delete a record
"""
collection.delete(_id)
def clear(self):
for key, column in self.column_by_name.items():
# print(self.pre,"clear",key)
column.reset()
[docs] def get(self, collection, _id):
"""Load one entry from db to QtWidgets
"""
it = collection.get({"_id": _id}) # there should be only one ..
try:
res = next(it)
except StopIteration:
print(self.pre, "get : Could not get _id=", _id)
return
# print(self.pre,"get",res)
for key, column in self.column_by_name.items():
try:
val = res[key]
except KeyError:
print(
self.pre,
"get : WARNING : record does not have column",
key,
"full record=",
res)
column.reset()
else:
column.setValue(val)
[docs] def set_(self, dic):
"""Sets the widget values
"""
for key, column in self.column_by_name.items():
try:
val = dic[key]
except KeyError:
print(
self.pre,
"get : WARNING : record does not have column",
key,
"full record=",
res)
column.reset()
else:
column.setValue(val)
[docs] def set_column_value(self, col_key, value):
"""Sets the value of one column in the widget
"""
self.column_by_name[col_key].setValue(value)
[docs] def get_column_value(self, col_key):
"""Gets a value from one column of the widget
"""
return self.column_by_name[col_key].getValue()
def placeWidgetPair(self, i, pair):
if (pair[0]):
self.lay.addWidget(pair[0], i, 0)
if (pair[1]):
self.lay.addWidget(pair[1], i, 1)
def connectNotifications(self):
for i, column in enumerate(self.columns_):
if (column.widget is not None):
sig = column.getNotifySignal()
if (sig):
sig.connect(self.update_notify_slot)
def blockSignals(self):
for i, column in enumerate(self.columns_):
if (column.widget is not None):
column.widget.blockSignals(True);
def unBlockSignals(self):
for i, column in enumerate(self.columns_):
if (column.widget is not None):
column.widget.blockSignals(False);
def update_notify_slot(self):
# print(self.pre, "update_notify_slot")
pass
def purge(self, dic):
if (verbose):
print(self.pre, "purge", dic)
# uses self.keyset
needs_update = False
tmpdic = copy.deepcopy(dic)
tmpdic.pop("_id")
tmpdic.pop("classname") # remove metadata
dicset = set(tmpdic) # fields in the record to be checked
# the document record has extra fields:
for df in dicset.difference(
self.keyset): # dicset-keyset = extra keys in dicset
if (verbose):
print(self.pre, "purge : removing key", df, "from", dic)
dic.pop(df) # remove fields from the record
needs_update = True
# the document record is missing fields:
for df in self.keyset.difference(
dicset): # keyset-dicset = keyset has more keys
# gets the default value
dic[df] = self.column_by_name[df].getValue()
if (verbose):
print(
self.pre,
"purge : appending key/value",
df,
dic[df],
"full record now=",
dic)
needs_update = True
# by now the document should be consistent with the schema in self.keys
for key in self.keys: # run through all key value pairs
col = self.column_by_name[key]
# purge dangling foreign key references. Return valid reference(s)
# in val
update, val = col.purge(dic[key])
if (update):
if (verbose):
print(
self.pre,
"purge : updated value in key",
key,
"to",
val)
dic[key] = val
needs_update = True
return needs_update, dic
def test1():
st = """Empty test
"""
pre = pre_mod + "test1 :"
print(pre, st)
def test2():
st = """Empty test
"""
pre = pre_mod + "test2 :"
print(pre, st)
def main():
pre = pre_mod + "main :"
print(pre, "main: arguments: ", sys.argv)
if (len(sys.argv) < 2):
print(pre, "main: needs test number")
else:
st = "test" + str(sys.argv[1]) + "()"
exec(st)
if (__name__ == "__main__"):
main()