Source code for cute_mongo_forms.container.base

"""
base.py    : Base/example classes for containers (lists, forms, etc.).  Containers use collections in various ways.

* Copyright: 2017-2018 Sampsa Riikonen
* Authors  : Sampsa Riikonen
* Date     : 2017
* Version  : 0.2 

This file is part of the cute_mongo_forms library

License: LGPLv3+ (see the COPYING.LESSER file)
"""

import sys
# from PyQt5 import QtWidgets, QtCore, QtGui # Qt5
from PySide2 import QtWidgets, QtCore, QtGui
from cute_mongo_forms.tools import typeCheck, dictionaryCheck, objectCheck, parameterInitCheck, noCheck, Namespace
from cute_mongo_forms.row import RowWatcher
pre_mod = "container.base : " # a string for aux debugging purposes
# verbose=False # module's verbosity
verbose=True


[docs]class List: """Manages a QListWidget connected to a collection Parameters at instantiation: :param collection: Collection object with records corresponding to Row classes """ parameter_defs={ "collection" : None } def __init__(self,**kwargs): self.pre=self.__class__.__name__+" : " # auxiliary string for debugging output parameterInitCheck(self.parameter_defs,kwargs,self) # check kwargs agains parameter_defs, attach ok'd parameters to this object as attributes self.makeWidget() self.update() self.clearSelection() def makeWidget(self): # Creates the root QtWidget. Adds subwidgets. self.widget=QtWidgets.QListWidget()
[docs] def createItem(self): """Overwrite in child classes to create custom items (say, sortable items, etc.) """ return QtWidgets.QListWidgetItem()
def update(self): # Fills the root and subwidgets with data. self.widget.clear() self.items_by_id={} for entry in self.collection.get(): item =self.createItem() label =self.makeLabel(entry) item.setText(label) item._id =entry["_id"] try: item.classname =entry["classname"] except KeyError: raise(KeyError("Your database contains crap. Do a purge")) self.items_by_id[item._id]=item self.widget.addItem(item) self.widget.sortItems()
[docs] def makeLabel(self,entry): """How to create a label from a database record. Overwrite in child classes. """ return str(entry["_id"])
def clearSelection(self): if (verbose): print(self.pre,"clearSelection") for _id in self.items_by_id: self.items_by_id[_id].setSelected(False) # self.widget.currentItemChanged.emit(None,None) # no way not to choose anything?
[docs] def update_slot(self,_id): """Sending a signal to this slot updates the list. :param _id: Current item is set to this list item. None = request clear selection. """ if (verbose): print(self.pre,"update_slot",_id) self.update() try: item=self.items_by_id[_id] # no such record (probably deleted) # TODO except KeyError: _id=None if (verbose): print(self.pre,"update_slot: _id=",_id) if (_id==None): self.clearSelection() else: self.widget.setCurrentItem(self.items_by_id[_id])
[docs]class SimpleForm: """Embed the widget of a Row into another widget (for including button, extra labels, etc.) """ parameter_defs={ "row_class" : RowWatcher } def __init__(self, **kwargs): self.pre=self.__class__.__name__+" : " # auxiliary string for debugging output parameterInitCheck(self.parameter_defs,kwargs,self) # check kwargs agains parameter_defs, attach ok'd parameters to this object as attributes self.row_instance = self.row_class() self.makeWidget() def makeWidget(self): self.widget = QtWidgets.QWidget() self.lay = QtWidgets.QVBoxLayout(self.widget) self.row_instance.widget.setParent(self.widget) self.lay.addWidget(self.row_instance.widget)
# subclass and continue from here to add buttons, etc
[docs]class FormSet: """Group of forms. Each form corresponds to a different Row class. Only one type of form is visible at a time. Parameters at instantiation: :param collection: Collection object with row classes and the document collection """ parameter_defs={ "collection" : None } class Signals(QtCore.QObject): pass def __init__(self,**kwargs): self.pre=self.__class__.__name__+" : " # auxiliary string for debugging output parameterInitCheck(FormSet.parameter_defs,kwargs,self) # check kwargs agains parameter_defs, attach ok'd parameters to this object as attributes self.row_classes=self.collection.getRowClasses() self.signals=self.Signals() self.makeWidget() self.initVars() self.showCurrent() def initVars(self): self.element =None # NoneType or QListWidgetItem with extra attributes "_id" and "classname" attached self.current_row=None def makeRows(self): # self.default_row_name=self.row_classes[0].__name__ self.row_instance_by_name={} for row_class in self.row_classes: self.row_instance_by_name[row_class.__name__]=row_class() # each row has a widget attribute which is the root of the column qt widgets self.current_row=None def makeWidget(self): self.widget =QtWidgets.QWidget() self.lay =QtWidgets.QVBoxLayout(self.widget) self.makeForm() self.lay.addWidget(self.form) def makeForm(self): self.makeRows() self.form =QtWidgets.QWidget(self.widget) self.form.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.form_lay=QtWidgets.QVBoxLayout(self.form) for key, row in self.row_instance_by_name.items(): if (row.widget): row.widget.setParent(self.form) self.form_lay.addWidget(row.widget) def resetForm(self): for key, row in self.row_instance_by_name.items(): if (row.widget): row.clear() def updateWidget(self): self.form.close() self.makeForm() self.chooseForm_slot(self.element,None) def showCurrent(self): # Hide all widgets, show just the one corresponding to current row if (verbose): print(self.pre,"showCurrent: current_row=",str(self.current_row)) for key in self.row_instance_by_name: self.row_instance_by_name[key].widget.hide() if (type(self.current_row)==type(None)): pass else: self.current_row.widget.show()
[docs] def chooseForm_slot(self,element,element_old): """Calling this slot chooses the form to be shown :param element: an object that has *_id* and *classname* attributes :param element_old: an object that has *_id* and *classname* attributes This slot is typically connected to List classes, widget attribute's, currentItemChanged method (List.widget is QListWidget that has currentItemChanged slot), so the element and element_old parameters are QListWidgetItem instances with extra attributes "_id" and "_classname" attached. Queries the database for element._id """ if (verbose): print(self.pre,"chooseForm_slot :",element) # enable this if you're unsure what's coming here.. if (type(element)==type(None)): self.current_row=None self.element =None else: # print(self.pre,"chooseForm_slot :",element) assert(hasattr(element,"_id")) assert(hasattr(element,"classname")) try: self.current_row=self.row_instance_by_name[element.classname] except KeyError: print(self.pre,"chooseForm_slot : no such classname for this FormSet : ",element.classname) self.current_row=None self.element =None else: self.current_row.get(self.collection,element._id) self.element=element self.showCurrent()
def updateWidget_slot(self): self.updateWidget()
class RowDialog(QtWidgets.QDialog): def __init__(self,typenames,title="",parent=None): super().__init__(parent) self.setWindowTitle(title) self.layout=QtWidgets.QVBoxLayout(self) self.qlis =QtWidgets.QListWidget(self) self.create=QtWidgets.QPushButton("Create",self) self.cancel=QtWidgets.QPushButton("Cancel",self) self.layout.addWidget(self.qlis) self.layout.addWidget(self.create) self.layout.addWidget(self.cancel) for typename in typenames: item=QtWidgets.QListWidgetItem() item.setText(typename) item.typename=typename self.qlis.addItem(item) self.create.clicked.connect(lambda: self.done(1)) self.cancel.clicked.connect(lambda: self.reject()) self.clear() def clear(self): # self.name.clear() pass def exec_(self): self.clear() i=super().exec_() item=self.qlis.currentItem() if (verbose): print("NewRowDialogSimple: item=",item,"code=",i) if (i==0): return None else: return item.typename
[docs]class EditFormSet(FormSet): """Derived from FormSet. Here we have, additionally, buttons for creating new records, and for copying, saving and clearing them. This FormSet makes it possible to write into the collection. """
[docs] class Signals(QtCore.QObject): """Signals emitted by this class: - new_record(object) : emitted when a new record has been added. Carries the record _id. - save_record(object): emitted when a record has been saved. Carries the record _id. """ """ new_record =QtCore.pyqtSignal(object) # emitted when a new record has been added. Sends the object id save_record =QtCore.pyqtSignal(object) # emitted when a record has been saved delete_record=QtCore.pyqtSignal(object) # emitted when a record has been deleted modified =QtCore.pyqtSignal(object) # emitted when one of the above has been triggered """ new_record =QtCore.Signal(object) # emitted when a new record has been added. Sends the object id save_record =QtCore.Signal(object) # emitted when a record has been saved delete_record=QtCore.Signal(object) # emitted when a record has been deleted modified =QtCore.Signal(object) # emitted when one of the above has been triggered
def makeWidget(self): self.widget=QtWidgets.QWidget() self.lay =QtWidgets.QVBoxLayout(self.widget) self.makeForm() # from mother class self.lay.insertWidget(0,self.form) self.row_dialog =RowDialog(self.row_instance_by_name.keys()) self.makeButtons() self.lay.insertWidget(1,self.buttons) def makeButtons(self): self.buttons =QtWidgets.QWidget(self.widget) self.buttons_lay =QtWidgets.QHBoxLayout(self.buttons) self.new_button =QtWidgets.QPushButton ("NEW", self.buttons) self.copy_button =QtWidgets.QPushButton ("COPY", self.buttons) self.save_button =QtWidgets.QPushButton ("SAVE", self.buttons) self.clear_button =QtWidgets.QPushButton ("CLEAR", self.buttons) self.delete_button=QtWidgets.QPushButton ("DELETE", self.buttons) self.new_button. clicked. connect(self.new_slot) self.copy_button. clicked. connect(self.copy_slot) self.save_button. clicked. connect(self.save_slot) self.clear_button. clicked. connect(self.clear_slot) self.delete_button.clicked. connect(self.delete_slot) self.buttons_lay.addWidget(self.new_button) self.buttons_lay.addWidget(self.copy_button) self.buttons_lay.addWidget(self.save_button) self.buttons_lay.addWidget(self.clear_button) self.buttons_lay.addWidget(self.delete_button) self.signals.new_record. connect(self.signals.modified.emit) self.signals.save_record. connect(self.signals.modified.emit) self.signals.delete_record.connect(self.signals.modified.emit) def updateWidget(self): self.form.close() self.makeForm() self.lay.insertWidget(0,self.form) self.chooseForm_slot(self.element,None) # *** internal slots *** def new_slot(self): res=self.row_dialog.exec_() if (type(res)==type(None)): return self.current_row=self.row_instance_by_name[res] self.showCurrent() self.current_row.clear() _id=self.current_row.new(self.collection) if (verbose): print(self.pre,"new_slot: _id=",_id) self.signals.new_record.emit(_id) def copy_slot(self): if (type(self.current_row)==type(None)): if (verbose): print(self.pre,"copy_slot : can't copy None") else: _id=self.current_row.new(self.collection) self.signals.new_record.emit(_id) def save_slot(self): if (type(self.element)==type(None)): if (verbose): print(self.pre,"save_slot : no document chosen yet") return self.current_row.update(self.collection,self.element._id) self.collection.save() if (verbose): print(self.pre,"save_slot: emitting",self.element._id) self.signals.save_record.emit(self.element._id) def clear_slot(self): if (type(self.current_row)==type(None)): if (verbose): print(self.pre,"clear_slot : can't clear None") else: self.current_row.clear() def delete_slot(self): if (type(self.element)==type(None)): print(self.pre,"delete_slot : no document chosen yet") return if (verbose): print(self.pre,"delete_slot: self.element=",self.element) self.current_row.delete(self.collection,self.element._id) self.collection.save() self.signals.delete_record.emit(self.element._id) self.initVars() self.showCurrent()
[docs]class PermissionFormSet(EditFormSet): """A special FormSet derived from EditFormSet. Links two tables together with foreign keys. :param collection: Database collection. Database should have two foreign key columns :param key1_name: Name of the column having the first foreign key :param key1_name: Name of the column having the second foreign key """ parameter_defs={ "collection" : None, "key1_name" : str, # collection should have two foreign key columns having these labels "key2_name" : str } def __init__(self,**kwargs): self.pre=self.__class__.__name__+" : " # auxiliary string for debugging output parameterInitCheck(PermissionFormSet.parameter_defs,kwargs,self) # check kwargs agains parameter_defs, attach ok'd parameters to this object as attributes self.row_classes=self.collection.getRowClasses() self.signals=self.Signals() self.makeWidget() self.initVars() def makeButtons(self): self.buttons =QtWidgets.QWidget(self.widget) self.buttons_lay =QtWidgets.QHBoxLayout(self.buttons) self.new_button =QtWidgets.QPushButton ("NEW", self.buttons) self.save_button =QtWidgets.QPushButton ("SAVE", self.buttons) self.clear_button =QtWidgets.QPushButton ("CLEAR", self.buttons) self.delete_button=QtWidgets.QPushButton ("DELETE", self.buttons) self.new_button. clicked. connect(self.new_slot) self.save_button. clicked. connect(self.save_slot) self.clear_button. clicked. connect(self.clear_slot) self.delete_button.clicked. connect(self.delete_slot) self.buttons_lay.addWidget(self.new_button) self.buttons_lay.addWidget(self.save_button) self.buttons_lay.addWidget(self.clear_button) self.buttons_lay.addWidget(self.delete_button) self.signals.new_record. connect(self.signals.modified.emit) self.signals.save_record. connect(self.signals.modified.emit) self.signals.delete_record.connect(self.signals.modified.emit) def initVars(self): self.element =None # something with "_id" and "classname" attributes self.current_row=None self.col1_key =None self.col2_key =None def setForm(self): if (verbose): print(self.pre,"setForm : ",self.col1_key,self.col2_key) if (self.col1_key!=None and self.col2_key!=None): row_query=self.collection.get(query={self.key1_name:self.col1_key,self.key2_name:self.col2_key}) # let's see if the collection has two foreign keys corresponding to requested values .. try: dic=next(row_query) except StopIteration: dic=None else: dic=None if (verbose): print(self.pre,"setForm : dic=",dic) # must go from dictionary to namespace .. remember that copy_slot, new_slot, etc. assume an object with "_id" and "classname" attributes if (type(dic)==type(None)): self.element=None else: self.element =Namespace() self.element._id =dic["_id"] self.element.classname =dic["classname"] if (type(self.element)==type(None)): self.current_row=None else: self.current_row=self.row_instance_by_name[self.row_classes[0].__name__] # assume just one row class .. self.current_row.set_(dic) # no need to query the database again (using Row.get). Just set the values self.showCurrent() # *** internal slots *** def new_slot(self): self.current_row=self.row_instance_by_name[self.row_classes[0].__name__] self.showCurrent() self.current_row.clear() if (self.col1_key!=None and self.col2_key!=None): self.current_row[self.key1_name].setValue(self.col1_key) self.current_row[self.key2_name].setValue(self.col2_key) else: print(self.pre,"Must choose two columns") return _id=self.current_row.new(self.collection) if (verbose): print(self.pre,": new_slot : ",self.collection) self.setForm() self.signals.new_record.emit(_id) def save_slot(self): if (type(self.element)==type(None)): print(self.pre,"save_slot : no document chosen yet") return self.current_row.update(self.collection,self.element._id) self.collection.save() def clear_slot(self): if (type(self.current_row)==type(None)): print(self.pre,"clear_slot : can't clear None") else: self.current_row.clear() def delete_slot(self): if (type(self.element)==type(None)): print(self.pre,"delete_slot : no document chosen yet") return if (verbose): print(self.pre,"delete_slot: self.element=",self.element) self.current_row.delete(self.collection,self.element._id) self.collection.save() self.signals.delete_record.emit(self.element._id) self.initVars() self.showCurrent() # *** API slots ***
[docs] def chooseRecord1_slot(self,element,element_old): """Calling this slot chooses the current record from the first collection :param element: an object that has *_id* and *classname* attributes :param element_old: an object that has *_id* and *classname* attributes """ if (type(element)==type(None)): self.col1_key=None else: # print(self.pre,"chooseForm_slot :",element) assert(hasattr(element,"_id")) assert(hasattr(element,"classname")) # is this id in the permission table? self.col1_key=element._id self.setForm()
[docs] def chooseRecord2_slot(self,element,element_old): """Calling this slot chooses the current record from the second collection :param element: an object that has *_id* and *classname* attributes :param element_old: an object that has *_id* and *classname* attributes """ if (type(element)==type(None)): self.col2_key=None else: # print(self.pre,"chooseForm_slot :",element) assert(hasattr(element,"_id")) assert(hasattr(element,"classname")) # is this id in the permission table? self.col2_key=element._id self.setForm()
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()