class TRPropertyBox(QDialog, Ui_TRPropertyBox): """ This displays and manages the Relationship Template Dialog box """ def __init__(self, parent=None, mode=None, objectDict=None, designModel = None): """ objectDict - Relationship Template object dictionary. For creating a new Relationship Template this will be None @param parent reference to the parent widget @type QWidget """ super(TRPropertyBox, self).__init__(parent) self.parent = parent self.schemaModel = self.parent.schemaObject self.settings = QSettings() self.formatChanged = False self.helper = Helper() self.setupUi(self) self.designModel = designModel self.modelData = self.designModel.modelData if objectDict is None: self.objectDict = self.designModel.newRelTemplateDict() else: self.objectDict = objectDict self.mode = mode # get the class that controls the data grid for relationship templates self.RelTemplateCypher = RelTemplateCypher(templateDict=self.objectDict) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # Properties Grid self.gridProps.setModel(self.createPropModel()) comboPropList = [""] comboPropList.extend(sorted(set(self.designModel.instanceList("Property") + self.schemaModel.instanceList("Property")))) dataTypeList = [dataType.value for dataType in DataType] self.gridProps.setItemDelegateForColumn(DATATYPE, CBDelegate(self, dataTypeList, setEditable=False )) self.gridProps.setItemDelegateForColumn(PROPERTY, CBDelegate(self, comboPropList, setEditable=True )) self.gridProps.setItemDelegateForColumn(PROPDEF, NeoEditDelegate(self)) self.gridProps.setColumnWidth(PROPERTY, 350) self.gridProps.setColumnWidth(DATATYPE, 125) self.gridProps.setColumnWidth(PROPREQ, 100) self.gridProps.setColumnWidth(PROPDEF, 150) self.gridProps.setColumnWidth(EXISTS, 100) self.gridProps.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridProps.setSelectionMode(QAbstractItemView.SingleSelection) header = self.gridProps.horizontalHeader() header.setSectionResizeMode(PROPERTY, QHeaderView.Interactive) header.setSectionResizeMode(DATATYPE, QHeaderView.Fixed) header.setSectionResizeMode(PROPREQ, QHeaderView.Fixed) header.setSectionResizeMode(PROPDEF, QHeaderView.Interactive) header.setSectionResizeMode(EXISTS, QHeaderView.Fixed) # constraints grid self.gridConstraints.setModel(self.createConstraintsModel()) conTypeList = ["Property Exists"] self.gridConstraints.setItemDelegateForColumn(CONTYPE, CBDelegate(self, conTypeList, setEditable=False )) self.gridConstraints.setColumnWidth(CONTYPE, 120) self.gridConstraints.setColumnWidth(CONPROP, 350) header = self.gridConstraints.horizontalHeader() header.setSectionResizeMode(CONTYPE, QHeaderView.Fixed) header.setSectionResizeMode(CONPROP, QHeaderView.Interactive) # populate the rel template dropdowns self.loadTemplateDropdowns() # add the data grid widget. self.relGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.RelTemplateCypher) self.relGridLayout = QVBoxLayout(self.dataTabFrame) self.relGridLayout.setObjectName("relGridLayout") self.relGridLayout.addWidget(self.relGrid) #populate ui data from object self.populateUIfromObject() # populate combo boxes used on constraint and index grids self.updateComboBoxes() # sync definition checkboxes with constraints self.syncDefCheckBoxes() if self.mode == "NEW": self.txtRelTemplateName.setFocus() else: # disable definition fields self.txtRelTemplateName.setEnabled(False) self.cboRelName.setEnabled(False) # disable from and to nodes if they have been defined if self.cmbFromTemplate.currentIndex() > 0: self.cmbFromTemplate.setEnabled(False) if self.cmbToTemplate.currentIndex() > 0: self.cmbToTemplate.setEnabled(False) def updateComboBoxes(self): propList = [""] + [ self.gridProps.model().item(row,PROPERTY).data(Qt.EditRole) for row in range(0,self.gridProps.model().rowCount())] self.gridConstraints.setItemDelegateForColumn(CONPROP, CBDelegate(self, propList, setEditable=True )) def loadTemplateDropdowns(self, ): # load source node template dropdown dropdownList = [] dropdownList.append("No Template Selected") nodeList = dropdownList + self.designModel.instanceList("Node Template") self.cmbFromTemplate.addItems(nodeList) self.cmbToTemplate.addItems(nodeList) def createPropModel(self): model = QStandardItemModel(0, 5) model.setHeaderData(PROPERTY, Qt.Horizontal, "Property") model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type") model.setHeaderData(PROPREQ, Qt.Horizontal, "Required") model.setHeaderData(PROPDEF, Qt.Horizontal, "Default Value") model.setHeaderData(EXISTS, Qt.Horizontal, "Exists") # connect model slots model.itemChanged.connect(self.propModelItemChanged) return model def createConstraintsModel(self): # CONSTRAINT GRID - CONTYPE, CONPROP, model = QStandardItemModel(0,2) model.setHeaderData(CONTYPE, Qt.Horizontal, "Constraint Type") model.setHeaderData(CONPROP, Qt.Horizontal, "Property") # connect model slots model.itemChanged.connect(self.constraintModelItemChanged) return model def populateUIfromObject(self, ): # default the combo boxes to nothing selected self.cmbFromTemplate.setCurrentIndex(0) self.cmbToTemplate.setCurrentIndex(0) # get the custom template format if any self.TRFormatDict = self.objectDict.get("TRformat", None) if self.TRFormatDict == None: self.rbTemplateDefaultFormat.setChecked(True) self.btnDefineTemplateFormat.setEnabled(False) else: self.rbTemplateCustomFormat.setChecked(True) self.btnDefineTemplateFormat.setEnabled(True) # get the custom instance format if any self.IRFormatDict = self.objectDict.get("IRformat", None) if self.IRFormatDict == None: self.rbInstanceDefaultFormat.setChecked(True) self.btnDefineInstanceFormat.setEnabled(False) else: self.rbInstanceCustomFormat.setChecked(True) self.btnDefineInstanceFormat.setEnabled(True) # name, relname, desc self.txtRelTemplateName.insert(str(self.objectDict["name"])) self.loadRelationshipNameDropDown() self.editDescription.appendPlainText(self.objectDict["desc"]) # from and to node templates index = self.cmbFromTemplate.findText(self.objectDict["fromTemplate"]) if index >= 0: self.cmbFromTemplate.setCurrentIndex(index) index = self.cmbToTemplate.findText(self.objectDict["toTemplate"]) if index >= 0: self.cmbToTemplate.setCurrentIndex(index) # from and to cardinalities index = self.cmbFromCardinality.findText(self.objectDict["fromCardinality"]) if index >= 0: self.cmbFromCardinality.setCurrentIndex(index) index = self.cmbToCardinality.findText(self.objectDict["toCardinality"]) if index >= 0: self.cmbToCardinality.setCurrentIndex(index) # cardinality description self.editToFromType.setText("Node Type {}".format(self.cmbFromTemplate.currentText())) self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText())) #properties for relProp in self.objectDict["properties"]: dataType = self.designModel.getPropertyDataType(relProp[PROPERTY]) self.addProp(self.gridProps.model(), relProp[PROPERTY], dataType, relProp[PROPREQ], relProp[PROPDEF], relProp[EXISTS]) # load constraints try: #CONTYPE, CONLBL, CONPROP, CONPROPLIST for nodeCon in self.objectDict["constraints"]: self.addConstraint(self.gridConstraints.model(), nodeCon[CONTYPE], nodeCon[CONPROP]) except: pass def loadRelationshipNameDropDown(self, ): # load relationship name dropdown dropdownList = [] dropdownList.append("") # blank option for user to enter a new rel type # get relationship types from the project model templateRelList = [relDict["relname"] for relDict in self.modelData["Relationship Template"] ] # get relationship types from the schemamodel - this needs to be fixed, need common access to schemamodel from project page and instance diagram schemaRelList = [rel["name"] for rel in self.schemaModel.schemaData["Relationship"] ] # schemaRelList = [] # merge, dedup, and sort the two lists and put them in the dropdown dropdownList.extend(sorted(set(templateRelList + schemaRelList))) self.cboRelName.addItems(dropdownList) # if no relationship name has been set then display enabled combo box if str(self.objectDict["relname"]) is None: self.cboRelName.setCurrentIndex(0) elif (str(self.objectDict["relname"]) == "NoRelationshipName" or len(self.objectDict["relname"]) < 1): self.cboRelName.setCurrentIndex(0) else: # if the relationship name has been set then display the rel name index = self.cboRelName.findText(str(self.objectDict["relname"])) if index >= 0: self.cboRelName.setCurrentIndex(index) else: # its not an existing name so just set the text in the combo box self.cboRelName.setEditText(str(self.objectDict["relname"])) # # PROPERTY GRID BUTTONS # @pyqtSlot() def on_btnPropUp_clicked(self): """ User clicks on property up button """ self.helper.moveTableViewRowUp(self.gridProps) @pyqtSlot() def on_btnPropDown_clicked(self): """ User clicks on property down button """ self.helper.moveTableViewRowDown(self.gridProps) @pyqtSlot() def on_btnPropAdd_clicked(self): """ Slot documentation goes here. """ self.gridProps.setSortingEnabled(False) self.addProp(self.gridProps.model(), "","Unknown","N","Null","N") # generic function to adjust grid after adding a row self.helper.adjustGrid(grid=self.gridProps) # # scroll to bottom to insure the new row is visible # self.gridProps.scrollToBottom() @pyqtSlot() def on_btnPropRemove_clicked(self): """ Slot documentation goes here. """ # indexes = self.gridProps.selectionModel().selectedIndexes() # for index in sorted(indexes): # print('Row %d is selected' % index.row()) # self.gridProps.model().removeRows(index.row(),1) indexes = self.gridProps.selectionModel().selectedIndexes() if len(indexes) > 0: for index in sorted(indexes): self.gridProps.model().removeRows(index.row(),1) else: self.helper.displayErrMsg("Remove Property", "You must select a row to remove") def addProp(self,model,prop,dataType, propreq, propdef, exists): item1 = QStandardItem(prop) item1.setEditable(True) item11 = QStandardItem(dataType) item11.setEditable(False) item12 = QStandardItem(propreq) item12.setEditable(True) item12.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item12.setText("Required") item13 = QStandardItem(propdef) # store the datatype in role + 1 item13.setData(dataType, Qt.UserRole+1) item13.setEditable(True) item2 = QStandardItem() item2.setFlags(Qt.ItemIsEnabled) item2.setText("Exists") if exists in [0, 1, 2]: item2.setCheckState(exists) else: item2.setCheckState(Qt.Unchecked) if propreq in [0, 1, 2]: item12.setCheckState(propreq) else: item12.setCheckState(Qt.Unchecked) model.appendRow([item1, item11, item12,item13, item2]) def handleItemClicked(self, item): return def addConstraint(self,model, conType, prop): # CONTYPE, CONPROP item1 = QStandardItem(conType) item1.setEditable(True) item3 = QStandardItem(prop) item3.setEditable(True) model.appendRow([item1, item3]) def syncDefCheckBoxes(self): # clear all checkboxes on the definition tab self.clearCheckBoxes() # loop through the constraints grid model=self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): relCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] # process Property Exists if relCon[CONTYPE] == "Property Exists": self.setPropCheckBox(prop=relCon[CONPROP], chkBox=EXISTS ) self.setPropCheckBox(prop=relCon[CONPROP], chkBox=PROPREQ ) def setPropCheckBox(self, prop=None, chkBox=None): try: model = self.gridProps.model() numrows = model.rowCount() if not prop is None: for row in range(0,numrows): if prop == model.item(row,PROPERTY).data(Qt.EditRole): model.item(row,chkBox).setCheckState(Qt.Checked) except: pass def clearCheckBoxes(self): model = self.gridProps.model() numrows = model.rowCount() for row in range(0,numrows): model.item(row,EXISTS).setCheckState(0) ################################################################################# # DIALOG BUTTONS ################################################################################# @pyqtSlot() def on_okButton_clicked(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_cancelButton_clicked(self): """ User Selects the Cancel button """ QDialog.reject(self) def validate(self): if self.objectDict is None: self.objectDict = {} templateName = self.txtRelTemplateName.text() # template name if self.helper.NoTextValueError(templateName, "Must enter a Relationship Template Name"): self.txtRelTemplateName.setFocus() return False # relationship name # name = self.txtRelName.text() name = self.cboRelName.currentText() if self.helper.NoTextValueError(name, "Must enter a Relationship Name"): # self.txtRelName.setFocus() self.cboRelName.setFocus() return False # dup check if self.mode == 'NEW': if self.helper.DupObjectError(designModel = self.designModel, objName=templateName, topLevel = "Relationship Template", txtMsg = "A Relationship Template named {} already exists".format(name)): self.txtRelTemplateName.setFocus() return False # must have a From Node Template if self.helper.NoComboBoxSelectionError(self.cmbFromTemplate, "Must enter a From Node Template"): self.cmbFromTemplate.setFocus() return False # must have a To Node Template if self.helper.NoComboBoxSelectionError(self.cmbToTemplate, "Must enter a To Node Template"): self.cmbToTemplate.setFocus() return False # property name must exist if self.helper.gridNoNameError(grid=self.gridProps, col=PROPERTY, txtMsg="You must supply a name for each Property"): self.gridProps.setFocus() return False # dup property if self.helper.gridDupEntryError(self.gridProps, col=PROPERTY, txtMsg="has been entered more than once. You can only use a Property once"): self.gridProps.setFocus() return False # # required property must have a default value # model = self.gridProps.model() # numrows = model.rowCount() # for row in range(0,numrows): # nodeProp = [model.item(row,PROPERTY).data(Qt.EditRole), # model.item(row,DATATYPE).data(Qt.EditRole), # model.item(row,PROPREQ).checkState(), # model.item(row,PROPDEF).data(Qt.EditRole), # model.item(row,EXISTS).checkState()] # if nodeProp[PROPREQ] == Qt.Checked and nodeProp[PROPDEF] == "": # self.helper.displayErrMsg("Validate", "Required property {} must have a default value.".format(nodeProp[PROPERTY])) # self.gridProps.setFocus() # return False # constraint type exists if self.helper.gridNoNameError(grid=self.gridConstraints, col=CONTYPE, txtMsg="You must select a constraint type"): self.tabRelTemplate.setCurrentIndex(CONSTRAINT) self.gridConstraints.setFocus() return False model =self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): nodeCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] if nodeCon[CONTYPE] in ["Property Exists", "Property Unique"]: if self.helper.NoTextValueError(nodeCon[CONPROP], "Row {} - You must select a property".format(row+1)): self.tabRelTemplate.setCurrentIndex(CONSTRAINT) self.gridConstraints.setFocus() return False # passed all edits so return True return True def apply(self, ): self.objectDict["TRformat"] = self.TRFormatDict self.objectDict["IRformat"] = self.IRFormatDict self.objectDict["name"] = self.txtRelTemplateName.text() # self.objectDict["relname"] = self.txtRelName.text() self.objectDict["relname"] = self.cboRelName.currentText() # if its a new rel type then add it to the model self.designModel.newRelationship(self.objectDict["relname"]) if self.cmbFromTemplate.currentIndex() > 0: self.objectDict["fromTemplate"] = self.cmbFromTemplate.currentText() else: self.objectDict["fromTemplate"] = '' if self.cmbToTemplate.currentIndex() > 0: self.objectDict["toTemplate"] = self.cmbToTemplate.currentText() else: self.objectDict["toTemplate"] = '' self.objectDict["fromCardinality"] = self.cmbFromCardinality.currentText() self.objectDict["toCardinality"] = self.cmbToCardinality.currentText() desc = self.editDescription.toPlainText() if desc is not None: self.objectDict["desc"] = desc #save the properties self.objectDict["properties"] = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0,numrows): relProp = [model.item(row,PROPERTY).data(Qt.EditRole), model.item(row,DATATYPE).data(Qt.EditRole), model.item(row,PROPREQ).checkState(), model.item(row,PROPDEF).data(Qt.EditRole), model.item(row,EXISTS).checkState()] self.designModel.newProperty(relProp[PROPERTY], relProp[DATATYPE]) # check to see if this is a new Property and create a Property object in the dictionary self.objectDict["properties"].append(relProp) # save the constraints # CONTYPE, CONPROP self.objectDict["constraints"] = [] model =self.gridConstraints.model() numrows = model.rowCount() for row in range(0, numrows): relCon = [model.item(row,CONTYPE).data(Qt.EditRole), model.item(row,CONPROP).data(Qt.EditRole)] self.objectDict["constraints"].append(relCon) @pyqtSlot() def on_rbDefaultFormat_clicked(self): """ Slot documentation goes here. """ self.btnDefineFormat.setEnabled(False) @pyqtSlot() def on_rbCustomFormat_clicked(self): """ Slot documentation goes here. """ self.btnDefineFormat.setEnabled(True) @pyqtSlot() def on_btnDefineFormat_clicked(self): """ Slot documentation goes here. """ d = IRelFormatDlg(self, modelData = None, relFormat = IRelFormat(formatDict=self.TRFormatDict)) if d.exec_(): self.TRFormatDict = IRelFormat(formatDict=d.relFormat.formatDict).formatDict # tell diagrams to redraw self.formatChanged = True def constraintModelItemChanged(self, item): self.syncDefCheckBoxes() @pyqtSlot() def on_btnAddConstraint_clicked(self): """ Slot documentation goes here. """ self.gridConstraints.setSortingEnabled(False) self.addConstraint(self.gridConstraints.model(), "", "") # generic function to adjust grid after adding a row self.helper.adjustGrid(grid=self.gridConstraints) # # scroll to bottom to insure the new row is visible # self.gridConstraints.scrollToBottom() @pyqtSlot() def on_btnRemoveConstraint_clicked(self): """ User clicked the remove row button """ indexes = self.gridConstraints.selectionModel().selectedIndexes() if len(indexes) > 0: for index in sorted(indexes): self.gridConstraints.model().removeRows(index.row(),1) self.syncDefCheckBoxes() else: self.helper.displayErrMsg("Remove Constraint", "You must select a row to remove") # indexes = self.gridConstraints.selectionModel().selectedRows() # for index in sorted(indexes): # self.gridConstraints.model().removeRows(index.row(),1) def propModelItemChanged(self, item): # print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column())) # this fires when checkbox is selected or deselected. columnIndex = item.index().column() propName = self.gridProps.model().item(item.index().row(),PROPERTY).data(Qt.EditRole) dataType = self.designModel.getPropertyDataType(propName) if columnIndex == PROPERTY: # if property has changed then change the datatype propName = self.gridProps.model().item(item.index().row(),PROPERTY).data(Qt.EditRole) # get the defined datatype for the property dataType = self.designModel.getPropertyDataType(propName) self.gridProps.model().item(item.index().row(), DATATYPE).setText(dataType) # store the datatype in role + 1 for the default value self.gridProps.model().item(item.index().row(), PROPDEF).setData(dataType, Qt.UserRole+1) # set default value to a null string self.gridProps.model().item(item.index().row(), PROPDEF).setText("") # if the property doesn't exist yet then allow the datatype to be changed if self.designModel.objectExists(topLevel="Property",objectName=propName ) == False: self.gridProps.model().item(item.index().row(), DATATYPE).setEditable(True) else: self.gridProps.model().item(item.index().row(), DATATYPE).setEditable(False) if columnIndex == DATATYPE: # datatype changed so reset default value and store new datatype dataType = self.gridProps.model().item(item.index().row(),DATATYPE).data(Qt.EditRole) # store the datatype in role + 1 self.gridProps.model().item(item.index().row(), PROPDEF).setData(dataType, Qt.UserRole+1) self.gridProps.model().item(item.index().row(), PROPDEF).setText("") @pyqtSlot(int) def on_tabRelTemplate_currentChanged(self, index): """ If the user has switched to the data tab then update the object dictionary @param index DESCRIPTION @type int """ # user switched to the description tab. must regenerate description if index == DESCRIPTION: self.apply() self.brwsrGeneratedDesc.setText(self.designModel.getRelationshipDescription(self.objectDict)) # user switched to the definition tab. must resync checkboxes with the current values on the constraints tab if index == DEFINITION: self.syncDefCheckBoxes() if index == DATAGRID: if self.validate(): self.apply() self.relGrid.refreshGrid() else: self.tabRelTemplate.setCurrentIndex(0) # user switched to the constraint tab so update the combo boxes to get latest values from def if index == CONSTRAINT: self.updateComboBoxes() @pyqtSlot() def on_btnDefineTemplateFormat_clicked(self): """ Display the Node Template Format Editor """ myTemplateRelFormatDict = self.TRFormatDict # if the template doesn't have a specific instance node format then get the project default if myTemplateRelFormatDict is None: myTemplateRelFormatDict = deepcopy(self.modelData["TRformat"]) d = TRelFormatDlg(self, modelData = None, relFormat = TRelFormat(formatDict=myTemplateRelFormatDict)) if d.exec_(): # self.TRFormatDict = TRelFormat(formatDict=d.relFormat.formatDict).formatDict self.TRFormatDict = d.relFormat.formatDict self.formatChanged = True @pyqtSlot() def on_btnDefineInstanceFormat_clicked(self): """ Display the Instance Node Format Editor """ myInstanceRelFormatDict = self.IRFormatDict # if the template doesn't have a specific instance rel format then get the project default if myInstanceRelFormatDict is None: myInstanceRelFormatDict = deepcopy(self.modelData["IRformat"]) d = IRelFormatDlg(self, modelData = None, relFormat = IRelFormat(formatDict=myInstanceRelFormatDict)) if d.exec_(): # self.IRFormatDict = IRelFormat(formatDict=d.relFormat.formatDict).formatDict self.IRFormatDict = d.relFormat.formatDict self.formatChanged = True @pyqtSlot() def on_rbTemplateDefaultFormat_clicked(self): """ If default radio button selected, then disable the define format button """ self.btnDefineTemplateFormat.setEnabled(False) @pyqtSlot() def on_rbTemplateCustomFormat_clicked(self): """ If custom radio button selected, then enable the define format button """ self.btnDefineTemplateFormat.setEnabled(True) @pyqtSlot() def on_rbInstanceDefaultFormat_clicked(self): """ If default radio button selected, then disable the define format button """ self.btnDefineInstanceFormat.setEnabled(False) @pyqtSlot() def on_rbInstanceCustomFormat_clicked(self): """ If custom radio button selected, then enable the define format button """ self.btnDefineInstanceFormat.setEnabled(True) @pyqtSlot() def on_btnSetDefaultNull_clicked(self): """ User requests to set a property default to Null """ # grid only allows single selection indexes = self.gridProps.selectionModel().selectedIndexes() for index in indexes: valueIndex = self.gridProps.model().index(index.row(), PROPDEF) self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole) @pyqtSlot(int) def on_cmbToTemplate_currentIndexChanged(self, index): """ The to template changed @param index DESCRIPTION @type int """ self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText())) # print(self.editFromToType.text()) @pyqtSlot(int) def on_cmbFromTemplate_currentIndexChanged(self, index): """ The from template changed @param index DESCRIPTION @type int """ self.editToFromType.setText("Node Type {}".format(self.cmbFromTemplate.currentText())) # print(self.editToFromType.text()) @pyqtSlot(int) def on_cmbFromCardinality_currentIndexChanged(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type int """ self.editFromToType.setText("Node Type {}".format(self.cmbToTemplate.currentText()))
class INPropertyBox(QDialog, Ui_INPropertyBox): """ Provide a modal dialog that allows the user to edit an instance node on an instance diagram. """ treeViewUpdate = pyqtSignal() def __init__(self, parent=None, diagramInstance=None, model=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(INPropertyBox, self).__init__(parent) self.startUp = True self.parent = parent self.schemaModel = self.parent.schemaObject self.helper = Helper() self.diagramInstance = diagramInstance # reload the NodeInstance dictionary values in case they've been changed on another diagram self.diagramInstance.reloadDictValues() self.model = model self.modelData = self.model.modelData self.node = None # self.rc = None self.msg = None self.formatChanged = False self.setupUi(self) self.initUI() self.populateUIfromObject() self.loadTemplateDropdown() self.startUp = False def initUI(self, ): # label grid self.gridLabels.setModel(self.createLabelModel()) comboLblList = [""] comboLblList.extend( sorted( set( self.model.instanceList("Label") + self.schemaModel.instanceList("Label")))) # comboLblList = self.model.loadComboBox(topLevel='Label', objectName=None, selectMsg="" ) self.gridLabels.setItemDelegateForColumn( LABEL, CBDelegate(self, comboLblList, setEditable=True)) self.gridLabels.setColumnWidth(LABEL, 200) self.gridLabels.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridLabels.setSelectionMode(QAbstractItemView.SingleSelection) header = self.gridLabels.horizontalHeader() header.setSectionResizeMode(LABEL, QHeaderView.Interactive) # property grid self.gridProps.setSortingEnabled(False) self.gridProps.setModel(self.createPropModel()) self.gridProps.setSortingEnabled(False) comboPropList = [""] comboPropList.extend( sorted( set( self.model.instanceList("Property") + self.schemaModel.instanceList("Property")))) dataTypeList = [dataType.value for dataType in DataType] self.gridProps.setItemDelegate(NeoEditDelegate(self)) self.gridProps.setItemDelegateForColumn( DATATYPE, CBDelegate(self, dataTypeList, setEditable=False)) self.gridProps.setItemDelegateForColumn( PROPERTY, CBDelegate(self, comboPropList, setEditable=True)) self.gridProps.setColumnWidth(PROPERTY, 200) self.gridProps.setColumnWidth(DATATYPE, 125) self.gridProps.setColumnWidth(VALUE, 300) self.gridProps.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridProps.setSelectionMode(QAbstractItemView.SingleSelection) header = self.gridProps.horizontalHeader() header.setSectionResizeMode(PROPERTY, QHeaderView.Interactive) header.setSectionResizeMode(DATATYPE, QHeaderView.Fixed) header.setSectionResizeMode(VALUE, QHeaderView.Stretch) def populateUIfromObject(self, ): try: # print("NZID: {}".format(self.diagramInstance.NZID)) self.editNZID.insert(str(self.diagramInstance.NZID)) except: self.editNZID.insert("No NZID") # print("no NZID") try: # print("NeoID: {}".format(self.diagramInstance.neoID)) self.editNeoID.insert(str(self.diagramInstance.neoID)) except: self.editNeoID.insert("No NeoID") # print("no neoID") #load Label grid for nodeLbl in self.diagramInstance.labelList: self.addLabel(self.gridLabels.model(), nodeLbl[LABEL]) #load Property grid for nodeProp in self.diagramInstance.propList: self.addProp(self.gridProps.model(), nodeProp[PROPERTY], nodeProp[DATATYPE], nodeProp[VALUE]) def loadTemplateDropdown(self, ): # load node template dropdown dropdownList = ["No Template Selected"] dropdownList.extend(sorted(self.model.instanceList("Node Template"))) self.cboTemplate.addItems(dropdownList) if not self.diagramInstance.nodeTemplate is None: index = self.cboTemplate.findText( self.diagramInstance.nodeTemplate) if index >= 0: self.cboTemplate.setCurrentIndex(index) def logMsg(self, msg): # add message to the log if logging: logging.info(msg) def createLabelModel(self): model = QStandardItemModel(0, 1) model.setHeaderData(LABEL, Qt.Horizontal, "Label") return model def createPropModel(self): model = QStandardItemModel(0, 3) model.setHeaderData(PROPERTY, Qt.Horizontal, "Property") model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type") model.setHeaderData(VALUE, Qt.Horizontal, "Value") # connect model slots model.itemChanged.connect(self.propModelItemChanged) return model def mergeTemplateWithInstanceNode(self, templateName): # don't merge template if we're just starting up the UI if self.startUp: return saveIndex, nodeTemplateDict = self.model.getDictByName( topLevel="Node Template", objectName=templateName) if nodeTemplateDict is None: self.logMsg( "Merge Template Error - no node template dictionary for {}". format(templateName)) return # append any labels from the template not already on the instance node to the end of the label grid existingLabelList = [ self.gridLabels.model().item(row, LABEL).data(Qt.EditRole) for row in range(0, self.gridLabels.model().rowCount()) ] templateLabelList = [ nodeLbl[LABEL] for nodeLbl in nodeTemplateDict["labels"] ] newLabelList = list(set(templateLabelList) - set(existingLabelList)) for lbl in newLabelList: self.addLabel(self.gridLabels.model(), lbl) # properties # what's on the form now existingPropList = [ self.gridProps.model().item(row, PROPERTY).data(Qt.EditRole) for row in range(0, self.gridProps.model().rowCount()) ] existingValueList = [ self.gridProps.model().item(row, VALUE).data(Qt.EditRole) for row in range(0, self.gridProps.model().rowCount()) ] # property list from the template newPropList = [ nodeProp[PROPERTY] for nodeProp in nodeTemplateDict["properties"] ] newValueList = [ nodeProp[PROPDEF] for nodeProp in nodeTemplateDict["properties"] ] # this should get default values some day # add new properties to the end of the list mergePropList = (list(set(newPropList) - set(existingPropList))) for prop in mergePropList: val = "" # get default value from template first if prop in newPropList: val = newValueList[newPropList.index(prop)] # override with existing value for same property if it exists if prop in existingPropList: val = existingValueList[existingPropList.index(prop)] # set Null so editor delegates will work if val == "" or val is None: val = "Null" dataType = self.model.getPropertyDataType(prop) self.addProp(self.gridProps.model(), prop, dataType, val) def validate(self, ): # label grid checks if self.helper.gridNoNameError( grid=self.gridLabels, col=LABEL, txtMsg="You must supply a name for each Label"): self.gridLabels.setFocus() return False if self.helper.gridDupEntryError( self.gridLabels, col=LABEL, txtMsg= "has been entered more than once. You can only use a Label once" ): self.gridLabels.setFocus() return False # property grid checks if self.helper.gridNoNameError( grid=self.gridProps, col=PROPERTY, txtMsg="You must supply a name for each Property"): self.gridProps.setFocus() return False if self.helper.gridDupEntryError( self.gridProps, col=PROPERTY, txtMsg= "has been entered more than once. You can only use a Property once" ): self.gridProps.setFocus() return False model = self.gridProps.model() for row in range(0, model.rowCount()): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] # property datatype matches property definition if self.model.propertyDataTypeValid( name=nodeProp[PROPERTY], dataType=nodeProp[DATATYPE]) == False: self.helper.displayErrMsg( "Validate", "You entered datatype {} for property {} which does not match the property definition. Please enter the correct datatype." .format(nodeProp[DATATYPE], nodeProp[PROPERTY])) self.gridProps.setFocus() return False # can't add/update a value for an Unknown datatype property if nodeProp[DATATYPE] == "Unknown" and nodeProp[VALUE] != "Null": self.helper.displayErrMsg( "Validate", "Property {} has Unknown datatype. You can't add/update a value for this property. Set the value to Null" .format(nodeProp[PROPERTY])) self.gridProps.setFocus() return False # template defined required property has a value templateName = self.cboTemplate.currentText() saveIndex, nodeTemplateDict = self.model.getDictByName( topLevel="Node Template", objectName=templateName) if not nodeTemplateDict is None: model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] if nodeProp[VALUE] == "Null": # the value is null so see if it is required for templateProp in nodeTemplateDict["properties"]: if templateProp[PROPERTY] == nodeProp[PROPERTY]: if templateProp[PROPREQ] == Qt.Checked: # this property must have a value self.helper.displayErrMsg( "Validate", "The property {} is required. Please enter a value." .format(nodeProp[PROPERTY])) return False return True def apply(self, ): # update the diagramInstance object with values from the UI. # save the labels self.diagramInstance.labelList = [] model = self.gridLabels.model() numrows = model.rowCount() for row in range(0, numrows): nodeLbl = [model.item(row, LABEL).data(Qt.EditRole)] self.model.newLabel( nodeLbl[LABEL] ) # check to see if this is a new Label and create a Label object in the dictionary self.diagramInstance.labelList.append(nodeLbl) # #save the attributes self.diagramInstance.propList = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] # print("save prop {}".format(nodeProp)) self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE]) self.diagramInstance.propList.append(nodeProp) #save the template selectedTemplate = self.cboTemplate.currentText() self.diagramInstance.nodeTemplate = selectedTemplate # save the node itself in Neo4j rc, msg = self.updateNeo() return rc, msg def updateNeo(self, ): QApplication.setOverrideCursor(Qt.WaitCursor) if self.modelData["SyncMode"] == "On": rc, msg = self.diagramInstance.syncToDB(self.logMsg) if rc is True: returnmsg = "Update Node Succesful" else: returnmsg = "Update Node Failed: {}".format(msg) self.helper.displayErrMsg("Update Node", returnmsg) else: rc = True returnmsg = "Database Sync is off - Node not saved to graph" QApplication.restoreOverrideCursor() return rc, returnmsg @pyqtSlot() def on_btnCreateTemplate_clicked(self): """ Create a new Node Template based on the labels and properties entered into this Instance Node. This is the same logic as in the reverse engineering dialog """ text, ok = QInputDialog.getText(self, 'New Node Template', 'Enter the Node Template Name:') if ok: # make sure they entered something if len(text) < 1: self.helper.displayErrMsg( "Create New Node Template Error", "You must enter a name.".format(text)) #make sure entered name doesn't existing index, nodeDict = self.model.getDictByName("Node Template", text) #create a node template dictionary if not nodeDict is None: self.helper.displayErrMsg( "Create New Node Template Error", "The node template {} already exists".format(text)) return labelList = [] model = self.gridLabels.model() numrows = model.rowCount() for row in range(0, numrows): nodeLbl = [ model.item(row, LABEL).data(Qt.EditRole), Qt.Checked, Qt.Unchecked ] self.model.newLabel( nodeLbl[LABEL] ) # check to see if this is a new Label and create a Label object in the dictionary labelList.append(nodeLbl) propList = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), Qt.Unchecked, "", Qt.Unchecked, Qt.Unchecked, Qt.Unchecked ] self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE]) propList.append(nodeProp) # check for constraints that match label/prop # generate the constraints and indexes conList = [] # look at each constraint in the schemaModel and see if it belongs to the node template self.schemaModel.matchConstraintNodeTemplate( conList, [lbl[0] for lbl in labelList], [prop[0] for prop in propList]) # look at each index in the schemaModel and see if it belongs to the node template idxList = [] self.schemaModel.matchIndexNodeTemplate( idxList, [lbl[0] for lbl in labelList], [prop[0] for prop in propList]) #save it to the model nodeDict = self.model.newNodeTemplate( name=text, labelList=labelList, propList=propList, conList=conList, idxList=idxList, desc="Template generated from Instance Node.") self.model.modelData["Node Template"].append(nodeDict) # refresh the treeview self.model.updateTV() # set combo box self.loadTemplateDropdown() if not (self.cboTemplate.findText(text) is None): self.cboTemplate.setCurrentIndex( self.cboTemplate.findText(text)) @pyqtSlot() def on_btnLabelUp_clicked(self): """ User clicks on label up button """ self.helper.moveTableViewRowUp(self.gridLabels) @pyqtSlot() def on_btnLabelDown_clicked(self): """ User clicks on label down button """ self.helper.moveTableViewRowDown(self.gridLabels) @pyqtSlot() def on_btnLabelAdd_clicked(self): """ Slot documentation goes here. """ self.addLabel(self.gridLabels.model(), "") # common function to adjust grid after appending a row self.helper.adjustGrid(grid=self.gridLabels) # # scroll to bottom to insure the new row is visible # self.gridLabels.scrollToBottom() @pyqtSlot() def on_btnLabelRemove_clicked(self): """ Slot documentation goes here. """ indexes = self.gridLabels.selectionModel().selectedIndexes() for index in sorted(indexes): # print('Row %d is selected' % index.row()) self.gridLabels.model().removeRows(index.row(), 1) @pyqtSlot() def on_btnPropUp_clicked(self): """ User clicks on property up button """ self.helper.moveTableViewRowUp(self.gridProps) @pyqtSlot() def on_btnPropDown_clicked(self): """ User clicks on property down button """ self.helper.moveTableViewRowDown(self.gridProps) @pyqtSlot() def on_btnPropAdd_clicked(self): """ User clicks Add Property """ self.addProp(self.gridProps.model(), "", "String", "Null") # common function to adjust grid after appending a row self.helper.adjustGrid(grid=self.gridProps) # # scroll to bottom to insure the new row is visible # self.gridProps.scrollToBottom() @pyqtSlot() def on_btnMakeNull_clicked(self): """ User requests to set a property value to Null """ # grid only allows single selection indexes = self.gridProps.selectionModel().selectedIndexes() for index in indexes: valueIndex = self.gridProps.model().index(index.row(), VALUE) self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole) @pyqtSlot() def on_btnPropRemove_clicked(self): """ Slot documentation goes here. """ # grid only allows single selection indexes = self.gridProps.selectionModel().selectedIndexes() for index in indexes: self.gridProps.model().removeRows(index.row(), 1) @pyqtSlot() def on_okButton_clicked(self): """ User clicked OK button, validate the data then sync to graph """ if self.validate(): rc, msg = self.apply() if rc == True: QDialog.accept(self) @pyqtSlot() def on_cancelButton_clicked(self): """ Slot documentation goes here. """ QDialog.reject(self) def addLabel(self, model, label): self.gridLabels.setSortingEnabled(False) item1 = QStandardItem(label) item1.setEditable(True) model.appendRow([ item1, ]) # self.gridLabels.resizeColumnsToContents() # self.gridLabels.setSortingEnabled(True) def addProp(self, model, prop, dataType, value): ''' add a row to the property grid ''' self.gridProps.setSortingEnabled(False) item1 = QStandardItem(prop) item1.setEditable(True) item11 = QStandardItem(dataType) item11.setEditable(True) item2 = QStandardItem(value) item2.setData(dataType, Qt.UserRole + 1) item2.setEditable(True) model.appendRow([item1, item11, item2]) @pyqtSlot(int) def on_cboTemplate_currentIndexChanged(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type int """ # print("template changed {}".format(str(index))) # the new node template might have a custom format so set this to true which will force # a diagram redraw when the dialog closes. self.formatChanged = True if index > 0: self.mergeTemplateWithInstanceNode(self.cboTemplate.currentText()) def propModelItemChanged(self, item): # print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column())) templateName = self.cboTemplate.currentText() columnNum = item.index().column() if columnNum == PROPERTY: # if property has changed then change the datatype propName = self.gridProps.model().item(item.index().row(), PROPERTY).data(Qt.EditRole) dataType = self.model.getPropertyDataType(propName) self.gridProps.model().item(item.index().row(), DATATYPE).setText(dataType) # see if this property has a default value defined in the template defaultVal = self.model.getTemplatePropDefVal( templateName=templateName, propName=propName) if not defaultVal is None: self.gridProps.model().item(item.index().row(), VALUE).setText(defaultVal) if columnNum == DATATYPE: # if datatype has changed then change value to "Null" self.gridProps.model().item(item.index().row(), VALUE).setText("Null") dataType = self.gridProps.model().item(item.index().row(), DATATYPE).data(Qt.EditRole) self.gridProps.model().item(item.index().row(), VALUE).setData(dataType, Qt.UserRole + 1) @pyqtSlot(int) def on_tabNodeInspector_currentChanged(self, index): """ User has switched to another tab @param index DESCRIPTION @type int """ # user switched to the description tab. must regenerate description if there is a node template selected if index == DESCRIPTION: if self.cboTemplate.currentIndex() > 0: saveIndex, objectDict = self.model.getDictByName( topLevel="Node Template", objectName=self.cboTemplate.currentText()) if not objectDict is None: self.brwsrGeneratedDesc.setText( self.model.getNodeDescription( self.cboTemplate.currentText())) else: self.helper.displayErrMsg( "Get Description", "Error - could not find node template: {}".format( self.cboTemplate.currentText()))
class IRPropertyBox(QDialog, Ui_IRPropertyBox): """ Class documentation goes here. """ def __init__(self, parent=None, diagramInstance=None, model=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(IRPropertyBox, self).__init__(parent) self.appStartup = True self.mergeTemplate = False self.parent = parent self.schemaModel = self.parent.schemaObject self.helper = Helper() # this is the RelationInstance object - called generically diagramInstance. self.diagramInstance = diagramInstance self.diagramInstance.reloadDictValues() self.model = model self.modelData = self.model.modelData self.rel = None self.setupUi(self) # self.appSetToTemplate = False # self.appSetFromTemplate = False self.initUI() self.populateUIfromObject() self.appStartup = False self.msg = "" def initUI(self, ): # property grid self.gridProps.setSortingEnabled(False) self.gridProps.setModel(self.createPropModel()) self.gridProps.setSortingEnabled(False) comboPropList = [""] comboPropList.extend( sorted( set( self.model.instanceList("Property") + self.schemaModel.instanceList("Property")))) dataTypeList = [dataType.value for dataType in DataType] self.gridProps.setItemDelegate(NeoEditDelegate(self)) self.gridProps.setItemDelegateForColumn( DATATYPE, CBDelegate(self, dataTypeList, setEditable=False)) self.gridProps.setItemDelegateForColumn( PROPERTY, CBDelegate(self, comboPropList, setEditable=True)) self.gridProps.setColumnWidth(PROPERTY, 200) self.gridProps.setColumnWidth(DATATYPE, 125) self.gridProps.setColumnWidth(VALUE, 300) self.gridProps.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridProps.setSelectionMode(QAbstractItemView.SingleSelection) header = self.gridProps.horizontalHeader() header.setSectionResizeMode(PROPERTY, QHeaderView.Interactive) header.setSectionResizeMode(DATATYPE, QHeaderView.Fixed) header.setSectionResizeMode(VALUE, QHeaderView.Stretch) def populateUIfromObject(self, ): # load rel name list, select rel name self.loadRelationshipNameDropDown() # load from nodes, select the from node and load the from node template if any self.loadFromNodeDropdown() # load to nodes, select the to node and load the to node template if any self.loadToNodeDropdown() # load rel template names, select rel template name if any self.loadRelTemplateDropDown() #NZID try: self.EditNZID.insert(str(self.diagramInstance.NZID)) except: print("no NZID") # this shouldn't happen # neoID try: self.editNeoID.insert(str(self.diagramInstance.neoID)) except: self.editNeoID.insert("None") # disable rel name if already defined if self.editNeoID.text() == "None": self.cboRelName.setEnabled(True) else: self.cboRelName.setEnabled(False) # disable dropdowns if start and end nodes defined if self.cboFromNode.currentIndex() > 0: self.cboFromNode.setEnabled(False) if self.cboToNode.currentIndex() > 0: self.cboToNode.setEnabled(False) # load the properties for nodeProp in self.diagramInstance.propList: self.addProp(self.gridProps.model(), nodeProp[PROPERTY], nodeProp[DATATYPE], nodeProp[VALUE]) def loadRelationshipNameDropDown(self, ): # load relationship name (type) dropdown dropdownList = [] dropdownList.append("Enter or Select Relationship Type") # get relationship types from the project model templateRelList = [ relDict["name"] for relDict in self.modelData["Relationship"] ] # templateRelList = [relDict["relname"] for relDict in self.modelData["Relationship Template"] ] # get relationship types from the schemamodel - this needs to be fixed, need common access to schemamodel from project page and instance diagram schemaRelList = [ rel["name"] for rel in self.schemaModel.schemaData["Relationship"] ] # schemaRelList = [] # merge, dedup, and sort the two lists and put them in the dropdown dropdownList.extend(sorted(set(templateRelList + schemaRelList))) self.cboRelName.addItems(dropdownList) # if no relationship name has been set then display enabled combo box if (self.diagramInstance.relName is None or self.diagramInstance.relName == "NoRelationshipName"): self.cboRelName.setCurrentIndex(0) else: # if the relationship name has been set then display the rel name index = self.cboRelName.findText(self.diagramInstance.relName) if index >= 0: self.cboRelName.setCurrentIndex(index) else: # its not a template rel name so just set the text value of the combo box self.cboRelName.setEditText(self.diagramInstance.relName) def loadFromNodeDropdown(self, ): # load from node dropdown dropdownList = [] dropdownList.append("No From Node Selected") nodeList = self.model.instanceDisplayNameList("Instance Node") for node in nodeList: dropdownList.append(node) self.cboFromNode.addItems(dropdownList) if not self.diagramInstance.startNZID is None: item, objectDict = self.model.getDictByName( "Instance Node", self.diagramInstance.startNZID) if not objectDict is None: index = self.cboFromNode.findText( objectDict.get("displayName", "")) if index >= 0: self.cboFromNode.setCurrentIndex(index) self.cboFromNode.setEnabled(False) def loadToNodeDropdown(self, ): # load from node dropdown dropdownList = [] dropdownList.append("No To Node Selected") nodeList = self.model.instanceDisplayNameList("Instance Node") for node in nodeList: dropdownList.append(node) self.cboToNode.addItems(dropdownList) if not self.diagramInstance.endNZID is None: item, objectDict = self.model.getDictByName( "Instance Node", self.diagramInstance.endNZID) if not objectDict is None: index = self.cboToNode.findText( objectDict.get("displayName", "")) if index >= 0: self.cboToNode.setCurrentIndex(index) self.cboToNode.setEnabled(False) def loadRelTemplateDropDown(self, ): # load rel template dropdown self.cboTemplate.clear() startNodeTemplate = self.txtFromTemplate.text() endNodeTemplate = self.txtToTemplate.text() # relName = self.diagramInstance.relName relName = self.cboRelName.currentText() dropdownList = ["No Template Selected"] dropdownList.extend( sorted( self.model.matchAllRelTemplates( startNodeTemplate=startNodeTemplate, endNodeTemplate=endNodeTemplate, relName=relName))) self.cboTemplate.addItems(dropdownList) if not self.diagramInstance.relTemplate is None: index = self.cboTemplate.findText(self.diagramInstance.relTemplate) if index >= 0: self.cboTemplate.setCurrentIndex(index) def createPropModel(self): model = QStandardItemModel(0, 3) model.setHeaderData(PROPERTY, Qt.Horizontal, "Property") model.setHeaderData(DATATYPE, Qt.Horizontal, "Data Type") model.setHeaderData(VALUE, Qt.Horizontal, "Value") # connect model slots model.itemChanged.connect(self.propModelItemChanged) return model def getNodeInstance(self, NZID): ''' Using an NZID, create a NodeInstanceObject and return it if it doesn't exist, return None ''' index, nodeDict = self.model.getDictByName(topLevel="Instance Node", objectName=NZID) nodeInstance = NodeInstance(model=self.model, nodeInstanceDict=nodeDict) return nodeInstance def logMsg(self, msg): # add message to the log if logging: logging.info(msg) def validate(self, ): if self.helper.gridNoNameError( grid=self.gridProps, col=PROPERTY, txtMsg="You must supply a name for each Property"): self.gridProps.setFocus() return False if self.helper.gridDupEntryError( self.gridProps, col=PROPERTY, txtMsg= "has been entered more than once. You can only use a Property once" ): self.gridProps.setFocus() return False if self.helper.NoTextValueError(self.cboRelName.currentText(), "You must enter a relationship name."): self.cboRelName.setFocus() return False if self.cboRelName.currentText( ) == "Enter or Select Relationship Type": self.helper.displayErrMsg("Instance Relationship", "You must enter a relationship name.") self.cboRelName.setFocus() return False if self.cboFromNode.currentIndex() == 0: self.helper.displayErrMsg("Instance Relationship", "You must select a from node.") self.cboFromNode.setFocus() return False if self.cboToNode.currentIndex() == 0: self.helper.displayErrMsg("Instance Relationship", "You must select a to node.") self.cboToNode.setFocus() return False # property datatype matches property definition model = self.gridProps.model() for row in range(0, model.rowCount()): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] if self.model.propertyDataTypeValid( name=nodeProp[PROPERTY], dataType=nodeProp[DATATYPE]) == False: self.helper.displayErrMsg( "Validate", "You entered datatype {} for property {} which does not match the property definition. Please enter the correct datatype." .format(nodeProp[DATATYPE], nodeProp[PROPERTY])) self.gridProps.setFocus() return False # template defined required property has a value templateName = self.cboTemplate.currentText() saveIndex, relTemplateDict = self.model.getDictByName( topLevel="Relationship Template", objectName=templateName) if not relTemplateDict is None: model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] if nodeProp[VALUE] == "Null": # the value is null so see if it is required for templateProp in relTemplateDict["properties"]: if templateProp[PROPERTY] == nodeProp[PROPERTY]: if templateProp[PROPREQ] == Qt.Checked: # this property must have a value self.helper.displayErrMsg( "Validate", "The property {} is required. Please enter a value." .format(nodeProp[PROPERTY])) return False return True def apply(self, ): #update the diagramInstance object with values from the UI. self.diagramInstance.relName = self.cboRelName.currentText() # if its a new rel type then add it to the model self.model.newRelationship(self.diagramInstance.relName) # update property list self.diagramInstance.propList = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), model.item(row, VALUE).data(Qt.EditRole) ] # print(nodeProp) # if its a new property add it to the model self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE]) self.diagramInstance.propList.append(nodeProp) #save the template selectedTemplate = self.cboTemplate.currentText() self.diagramInstance.relTemplate = selectedTemplate # save the relationship itself in Neo4j rc, msg = self.updateNeo() self.msg = msg return rc, msg def updateNeo(self, ): QApplication.setOverrideCursor(Qt.WaitCursor) if self.modelData["SyncMode"] == "On": rc, msg = self.diagramInstance.syncToDB(self.logMsg) if rc is True: returnmsg = "Update Relationship Succesful" else: returnmsg = "Update Relationship Failed: {}".format(msg) self.helper.displayErrMsg("Update Relationship", returnmsg) else: rc = True returnmsg = "Database Sync is off - Relationship not saved to graph" QApplication.restoreOverrideCursor() return rc, returnmsg @pyqtSlot() def on_btnPropAdd_clicked(self): """ Slot documentation goes here. """ self.addProp(self.gridProps.model(), "", "String", "Null") # common function to adjust grid after appending a row self.helper.adjustGrid(grid=self.gridProps) # # scroll to bottom to insure the new row is visible # self.gridProps.scrollToBottom() @pyqtSlot() def on_btnPropRemove_clicked(self): """ Slot documentation goes here. """ indexes = self.gridProps.selectionModel().selectedIndexes() if len(indexes) > 0: for index in sorted(indexes): self.gridProps.model().removeRows(index.row(), 1) else: self.helper.displayErrMsg("Remove Property", "You must select a row to remove") @pyqtSlot() def on_btnPropUp_clicked(self): """ User clicks on property up button """ self.helper.moveTableViewRowUp(self.gridProps) @pyqtSlot() def on_btnPropDown_clicked(self): """ User clicks on property down button """ self.helper.moveTableViewRowDown(self.gridProps) @pyqtSlot() def on_okButton_clicked(self): """ User clicked on OK button, validate data and sync to graph """ if self.validate(): rc, msg = self.apply() if rc == True: QDialog.accept(self) @pyqtSlot() def on_cancelButton_clicked(self): """ Slot documentation goes here. """ QDialog.reject(self) def addProp(self, model, prop, dataType, value): self.gridProps.setSortingEnabled(False) item1 = QStandardItem(prop) item1.setEditable(True) item11 = QStandardItem(dataType) item11.setEditable(True) item2 = QStandardItem(value) item2.setData(dataType, Qt.UserRole + 1) item2.setEditable(True) model.appendRow([item1, item11, item2]) @pyqtSlot() def on_btnCreateTemplate_clicked(self): """ Create a new Relationship Template based on this instance relationship. """ text, ok = QInputDialog.getText( self, 'New Relationship Template', 'Enter the Relationship Template Name:') if ok: # make sure they entered something if len(text) < 1: self.helper.displayErrMsg( "Create New Relationship Template Error", "You must enter a name.".format(text)) #make sure entered name doesn't exist index, relDict = self.model.getDictByName("Relationship Template", text) if not relDict is None: self.helper.displayErrMsg( "Create New Relationship Template Error", "The Relationship template {} already exists".format(text)) return # validate the data first, then add the new relationship template if self.validate(): # if its a new rel type then add it to the model self.model.newRelationship(self.cboRelName.currentText()) # save the properties propList = [] model = self.gridProps.model() numrows = model.rowCount() for row in range(0, numrows): nodeProp = [ model.item(row, PROPERTY).data(Qt.EditRole), model.item(row, DATATYPE).data(Qt.EditRole), Qt.Unchecked, "", Qt.Unchecked ] self.model.newProperty(nodeProp[PROPERTY], nodeProp[DATATYPE]) propList.append(nodeProp) # generate the constraints conList = [] # look at each constraint in the schemaModel and see if it belongs to the rel template self.schemaModel.matchConstraintRelTemplate( conList, [prop[0] for prop in propList], relName=self.cboRelName.currentText()) # get the source/target node template if any # can only save a source target if both instance nodes are based on a node template try: startNodeInstance = self.getNodeInstance( self.diagramInstance.startNZID) fromTemplate = startNodeInstance.nodeTemplate endNodeInstance = self.getNodeInstance( self.diagramInstance.endNZID) toTemplate = endNodeInstance.nodeTemplate if (not fromTemplate == "No Template Selected" and not toTemplate == "No Template Selected"): #save it to the model relDict = self.model.newRelTemplateDict( name=text, relname=self.cboRelName.currentText(), propList=propList, desc= "Template generated from Instance Relationship.", conList=conList, fromTemplate=fromTemplate, toTemplate=toTemplate) self.model.modelData["Relationship Template"].append( relDict) # refresh the treeview self.model.updateTV() # update dropdown and select the newly created template self.loadRelTemplateDropDown() index = self.cboTemplate.findText(text) if index >= 0: self.cboTemplate.setCurrentIndex(index) else: self.helper.displayErrMsg( "Create Template", "Cannot create a relationship template without a from node template and a to node template." ) except Exception as e: self.helper.displayErrMsg( "Create Template", "Error creating relationship template: {}".format( str(e))) def templateChange(self, index): if index > 0: self.mergeTemplateWithInstanceRel(self.cboTemplate.currentText()) def mergeTemplateWithInstanceRel(self, templateName): '''this merges the properties from the relationship template with the properties that already exist on the instance relationship ''' # if app is just starting up don't do this if self.appStartup: return # tell the other functions we're merging the template self.mergeTemplate = True saveIndex, relDict = self.model.getDictByName( topLevel="Relationship Template", objectName=templateName) if relDict is None: self.helper.displayErrMsg( "Merge Relationship Template", "Error - Relationship Template {} doesn't exist".format( templateName)) return # properties # what's on the form now existingPropList = [ self.gridProps.model().item(row, PROPERTY).data(Qt.EditRole) for row in range(0, self.gridProps.model().rowCount()) ] existingValueList = [ self.gridProps.model().item(row, VALUE).data(Qt.EditRole) for row in range(0, self.gridProps.model().rowCount()) ] # property list from the template newPropList = [ nodeProp[PROPERTY] for nodeProp in relDict["properties"] ] newValueList = [ nodeProp[PROPDEF] for nodeProp in relDict["properties"] ] # this should get default values some day # merge them together mergePropList = (list(set(existingPropList + newPropList))) mergePropList.sort() self.gridProps.model().removeRows(0, self.gridProps.model().rowCount()) for prop in mergePropList: val = "" # get default value from template first if prop in newPropList: val = newValueList[newPropList.index(prop)] # override with existing value for same property if it exists if prop in existingPropList: val = existingValueList[existingPropList.index(prop)] # set Null so editor delegates will work if val == "" or val is None: val = "Null" dataType = self.model.getPropertyDataType(prop) self.addProp(self.gridProps.model(), prop, dataType, val) self.diagramInstance.relName = relDict["relname"] index = self.cboRelName.findText(self.diagramInstance.relName) if index >= 0: self.cboRelName.setCurrentIndex(index) # we're done merging self.mergeTemplate = False @pyqtSlot(int) def on_cboTemplate_currentIndexChanged(self, index): """ The User has selected a relationship template to use. @param index DESCRIPTION @type int """ # if they selected a rel template then merge it with whatever is on the gui if index > 0: self.mergeTemplateWithInstanceRel(self.cboTemplate.currentText()) @pyqtSlot(int) def on_cboFromNode_currentIndexChanged(self, index): """ The user has selected a new from instance node for the relationship @param index DESCRIPTION @type int """ if index > 0: # create NodeInstance object displayName = self.cboFromNode.currentText() NZID = self.model.lookupNZIDfromDisplayName( displayName=displayName, topLevel="Instance Node") if not NZID is None: self.diagramInstance.startNode = self.getNodeInstance(NZID) self.diagramInstance.startNZID = NZID # if the instance node is based on a node template then set that fromTemplate = self.diagramInstance.startNode.nodeTemplate self.txtFromTemplate.setText(fromTemplate) else: self.txtFromTemplate.setText("") # force rel template dropdown to refill valid choices if self.mergeTemplate == False: self.loadRelTemplateDropDown() @pyqtSlot(int) def on_cboToNode_currentIndexChanged(self, index): """ The user has selected a new to instance node for the relationship @param index DESCRIPTION @type int """ if index > 0: # create NodeInstance object displayName = self.cboToNode.currentText() NZID = self.model.lookupNZIDfromDisplayName( displayName=displayName, topLevel="Instance Node") if not NZID is None: self.diagramInstance.endNode = self.getNodeInstance(NZID) self.diagramInstance.endNZID = NZID # if the instance node is based on a node template then set that toTemplate = self.diagramInstance.endNode.nodeTemplate self.txtToTemplate.setText(toTemplate) else: self.txtToTemplate.setText("") # force node template dropdown to refill valid choices if self.mergeTemplate == False: self.loadRelTemplateDropDown() @pyqtSlot(int) def on_cboRelName_currentIndexChanged(self, index): """ The relationship type was changed @param index DESCRIPTION @type int """ # user changed the relationship type name so reset the template dropdown to new list of valid templates and pick the "none selected" option which forces the user to reselect a template if self.mergeTemplate == False: self.loadRelTemplateDropDown() self.cboTemplate.setCurrentIndex(0) # if index > 0: ## relName = self.cboRelName.currentText() # # reload rel template dropdown to reflect newly selected rel type ## relTemplate = self.cboTemplate.currentText() # self.loadRelTemplateDropDown() # self.cboTemplate.setCurrentIndex(0) ## relList = ["select a relationship template"] + [relDict["name"] for relDict in self.modelData["Relationship Template"] if relDict["relname"] == relName] ## self.cboTemplate.clear() ## self.cboTemplate.addItems(relList) # # set to no rel template selected. ## self.cboTemplate.setCurrentIndex(0) # else: # # user switched back to no rel name selected so relaod all rel templates # self.loadRelTemplateDropDown() # self.cboTemplate.setCurrentIndex(0) def propModelItemChanged(self, item): # print("item data changed {} at {} {}".format(str(item.checkState()), item.index().row(), item.index().column())) # this checks to see if property name changed and updates the data type accordingly columnIndex = item.index().column() if columnIndex == PROPERTY: # if property has changed then change the datatype propName = self.gridProps.model().item(item.index().row(), PROPERTY).data(Qt.EditRole) dataType = self.model.getPropertyDataType(propName) self.gridProps.model().item(item.index().row(), DATATYPE).setText(dataType) if columnIndex == DATATYPE: # if datatype has changed then change value to "Null" self.gridProps.model().item(item.index().row(), VALUE).setText("Null") dataType = self.gridProps.model().item(item.index().row(), DATATYPE).data(Qt.EditRole) self.gridProps.model().item(item.index().row(), VALUE).setData(dataType, Qt.UserRole + 1) @pyqtSlot() def on_btnSetNull_clicked(self): """ User clicks button to set a property value to Null """ indexes = self.gridProps.selectionModel().selectedIndexes() for index in indexes: valueIndex = self.gridProps.model().index(index.row(), VALUE) self.gridProps.model().setData(valueIndex, "Null", Qt.DisplayRole) @pyqtSlot(int) def on_tabRelInspector_currentChanged(self, index): """ User has switched to another tab @param index DESCRIPTION @type int """ # user switched to the description tab. must regenerate description if there is a node template selected if index == DESCRIPTION: if self.cboTemplate.currentIndex() > 0: saveIndex, objectDict = self.model.getDictByName( topLevel="Relationship Template", objectName=self.cboTemplate.currentText()) if not objectDict is None: self.brwsrGeneratedDesc.setText( self.model.getRelationshipDescription(objectDict)) else: self.helper.displayErrMsg( "Get Description", "Error - could not find node template: {}".format( self.cboTemplate.currentText))