Source code for cute_mongo_forms.row.base

"""
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()
[docs] def placeWidget(self, i, key_name): """Place column widget to line i """ column = self[key_name] labelname = column.label_name label = QtWidgets.QLabel(labelname, self.widget) column.widget.setParent(self.widget) # add to the layout self.lay.addWidget(label, i, 0) self.lay.addWidget(column.widget, i, 1)
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);
[docs] def makeWidget(self): """Creates a Qt form, using the column structure """ self.widget = QtWidgets.QWidget() self.lay = QtWidgets.QGridLayout(self.widget) """ for key in self.column_by_name: print(self.pre,"makeWidget: >>",key) """ for i, column in enumerate(self.columns_): if (column.widget is not None): #""" labelname = column.label_name label = QtWidgets.QLabel(labelname, self.widget) column.widget.setParent(self.widget) # add to the layout self.lay.addWidget(label, i, 0) self.lay.addWidget(column.widget, i, 1) #""" # self.placeWidget(i, column.key_name) # print(self.pre,"makeWidget :",labelname,label,column.widget) self.connectNotifications()
[docs] def updateWidget(self): """Updates the widgets. This is necessary if columns relate to documents that have been changed. """ for key, column in self.column_by_name.items(): column.updateWidget()
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()