def my_excepthook(type, value, tback): ''' catch any exception not otherwise handled by the application and show it to the user without crashing the program. ''' msg = "### {}--{}--{} ###".format(type, value, tback.tb_frame) clipboard = QApplication.clipboard() clipboard.setText(msg) # display error message to the user helper = Helper() helper.displayErrMsg( "Unexpected Error", """ An unexpected error has occured. Please paste this error message into an email to [email protected] (it's already in the clipboard) {} """.format(msg)) # log the error if logging: logging.info("An unexpected error has occured. \n {}".format(msg))
class PageItem(): def __init__(self, neoConName=None, actionButton=None, pageWidget=None, pageWidgetIndex=None): self.settings = QSettings() self.helper = Helper() # the name of the neocon for this page self.neoConName = neoConName # see if we need to prompt for the password self.promptPW = None self.checkPW() # the qaction on the menubar self.actionButton = actionButton self.pageWidget = pageWidget self.pageWidgetIndex = pageWidgetIndex return def checkPW(self, ): '''If the connection is prompt for password, then prompt until the user enters something. ''' # get the neocon dictionary neoDict = self.settings.value("NeoCon/connection/{}".format( self.neoConName)) if not neoDict is None: if neoDict["prompt"] == "True": # prompt for a password if needed and save what the user enters pw = '' while len(pw) < 1: pw = self.helper.passwordPrompt(conName=self.neoConName, conURL=neoDict["URL"]) if not pw is None: if len(pw) > 0: # save the encrypted password in the page item so it's ready to be used by any function self.promptPW = self.helper.putText(pw) else: self.helper.displayErrMsg( "Prompt Password", "You must enter a password.")
class ConstraintRelPropExistsDlg(QDialog, Ui_ConstraintRelPropExistsDlg): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(ConstraintRelPropExistsDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaModel self.helper = Helper() self.initUi() def initUi(self, ): aList = sorted(self.schemaModel.instanceList("Property")) self.cbProperty.addItem("") for indexName in aList: self.cbProperty.addItem(indexName) aList = sorted(self.schemaModel.instanceList("Relationship")) self.cbRelationships.addItem("") for indexName in aList: self.cbRelationships.addItem(indexName) return def validate(self, ): # must enter or select a relationship if self.helper.NoTextValueError( self.cbRelationships.currentText(), "You must enter or select a Relationship"): return False # must add at least one property to the list if self.helper.NoTextValueError(self.cbProperty.currentText(), "You must enter or select a Property"): return False return True def apply(self, ): """ Generate and run the create constraint statement """ prop = self.cbProperty.currentText() relationship = self.cbRelationships.currentText() self.cypherCommand = None ''' CREATE CONSTRAINT ON ()-[r:RelID]-() ASSERT exists(r.propname) ''' self.cypherCommand = "CREATE CONSTRAINT ON ()-[p:{}]-() ASSERT exists(p.{})".format( relationship, prop) if self.cypherCommand: QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createConstraint(self.cypherCommand) QApplication.restoreOverrideCursor() self.helper.displayErrMsg("Create Constraint", msg) else: self.helper.displayErrMsg( "Create Constraint", "Error Generating Constraint Statement.") @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self) @pyqtSlot() def on_pbAddToList_clicked(self): """ Get the property name in the combo box and add it to the list """ self.lstProperty.addItem(self.cbProperty.currentText()) @pyqtSlot() def on_pbRemoveList_clicked(self): """ Remove the selected property from the list """ self.lstProperty.takeItem(self.lstProperty.currentRow()) @pyqtSlot() def on_rbUnique_clicked(self): """ Slot documentation goes here. """ self.cbLabel.setEnabled(True) self.cbProperty.setEnabled(True) self.cbRelationships.setEnabled(False) self.pbAddToList.setEnabled(True) self.pbRemoveList.setEnabled(True) @pyqtSlot() def on_rbNodePropExists_clicked(self): """ Slot documentation goes here. """ self.cbLabel.setEnabled(True) self.cbProperty.setEnabled(True) self.cbRelationships.setEnabled(False) self.pbAddToList.setEnabled(True) self.pbRemoveList.setEnabled(True) @pyqtSlot() def on_rbNodeKey_clicked(self): """ Slot documentation goes here. """ self.cbLabel.setEnabled(True) self.cbProperty.setEnabled(True) self.cbRelationships.setEnabled(False) self.pbAddToList.setEnabled(True) self.pbRemoveList.setEnabled(True) @pyqtSlot() def on_rbRelPropExists_clicked(self): """ Slot documentation goes here. """ self.cbLabel.setEnabled(False) self.cbProperty.setEnabled(True) self.cbRelationships.setEnabled(True) self.pbAddToList.setEnabled(True) self.pbRemoveList.setEnabled(True)
class ConstraintNodePropUniqueDlg(QDialog, Ui_ConstraintNodePropUniqueDlg): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(ConstraintNodePropUniqueDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaModel self.helper = Helper() self.initUi() def initUi(self, ): aList = sorted(self.schemaModel.instanceList("Label")) self.cbLabel.addItem("") for indexName in aList: self.cbLabel.addItem(indexName) aList = sorted(self.schemaModel.instanceList("Property")) self.cbProperty.addItem("") for indexName in aList: self.cbProperty.addItem(indexName) def validate(self, ): # must enter or select a label if self.helper.NoTextValueError(self.cbLabel.currentText(), "You must enter or select a Label"): return False # must add at least one property to the list if self.helper.NoTextValueError(self.cbProperty.currentText(), "You must enter or select a Property"): return False return True def apply(self, ): """ Generate and run the create constraint statement """ prop = self.cbProperty.currentText() label = self.cbLabel.currentText() self.cypherCommand = None ''' CREATE CONSTRAINT ON (p:Person) ASSERT p.name IS UNIQUE ''' self.cypherCommand = "CREATE CONSTRAINT ON (p:{}) ASSERT p.{} IS UNIQUE".format( label, prop) if self.cypherCommand: QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createConstraint(self.cypherCommand) QApplication.restoreOverrideCursor() self.helper.displayErrMsg("Create Constraint", msg) else: self.helper.displayErrMsg( "Create Constraint", "Error Generating Constraint Statement.") @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self)
class DropObjectDlg(QDialog, Ui_DropObjectDlg): """ Class documentation goes here. """ def __init__(self, parent=None, objectType=None, objectName=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(DropObjectDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.objectType = objectType self.objectName = objectName self.schemaModel = self.parent.schemaModel self.helper = Helper() self.initUi() def initUi(self, ): self.lblTitle.setText("Drop {}".format(self.objectType)) self.editObject.setText(self.objectName) return def logMsg(self, msg): ''' If logging is active, then log the message ''' if logging: logging.info(msg) def validate(self, ): return True def apply(self, ): """ Generate the drop statement """ if self.objectType == "User": self.cypherCommand = "CALL dbms.security.deleteUser('{}')".format( self.objectName) elif self.objectType == "Role": self.cypherCommand = "CALL dbms.security.deleteRole('{}')".format( self.objectName) else: if (not "ASSERT (" in self.objectName and self.objectType == "Node Key"): self.objectName = self.objectName.replace( "ASSERT ", "ASSERT (") self.objectName = self.objectName.replace(" IS", ") IS") self.cypherCommand = "Drop {}".format(self.objectName) else: self.cypherCommand = "Drop {}".format(self.objectName) if self.cypherCommand: QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.dropObject(self.cypherCommand) QApplication.restoreOverrideCursor() self.helper.displayErrMsg("Drop Schema Object", msg) self.logMsg("Drop Schema Object - {}".format(msg)) else: self.helper.displayErrMsg( "Drop Schema Object", "Error Generating Drop Schema Object Statement.") self.logMsg("Error Generating Drop Schema Object Statement.") @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self)
class DiagramScene(QGraphicsScene): def __init__(self, parent=None): super(DiagramScene, self).__init__(parent) self.parent = parent self.model = self.parent.model self.helper = Helper() def addIRelToScene(self, NZID=None, startNode=None, endNode=None, mode=None): ''' called by drawDiagram to initially draw an existing diagram ''' try: # get required data, if any of this errors out then skip adding this to the diagram # startNZID = startNode.data(NODEID) # endNZID = endNode.data(NODEID) relationInstanceDict = None index, relationInstanceDict = self.model.getDictByName( topLevel="Instance Relationship", objectName=NZID) startNZID = relationInstanceDict["startNZID"] endNZID = relationInstanceDict["endNZID"] # create a new relation instance object relInstance = RelationInstance( parent=self, model=self.model, relationInstanceDict=relationInstanceDict, startNode=self.parent.itemDict[startNZID].itemInstance, endNode=self.parent.itemDict[endNZID].itemInstance) # create a new relation item object relItem = RelationItem(self, relationInstance=relInstance) # add the relation item object to the diagram item dictionary self.parent.itemDict[relInstance.NZID] = relItem # this counts how many relationships exist between two nodes self.parent.addRelationship(relInstance) except: print("error adding instance rel") def addInode(self, point, nodeInstance=None): ''' Add a Node Instance to the diagram ''' try: nodeItem = NodeItem(self, point.x(), point.y(), nodeInstance=nodeInstance) # add it to the diagramEditor's list of node/rels self.parent.itemDict[nodeInstance.NZID] = nodeItem except: print("error adding Instance Node") return nodeInstance.NZID def addTNode(self, point, nodeTemplateDict=None, NZID=None): ''' Add a Node template to the diagram at the given point. Used by the drawdiagram function ''' try: # create a NodeTemplateItem nodeTemplateItem = NodeTemplateItem( self, point.x(), point.y(), nodeTemplateDict=nodeTemplateDict, NZID=NZID) # add it to the diagramEditor's list of node/rels self.parent.itemDict[nodeTemplateItem.NZID] = nodeTemplateItem except: print("error adding template Node") def addTRelToScene(self, relTemplateName=None, NZID=None, startNZID=None, endNZID=None, mode=None): # if adding a relationship template while initially loading the diagram or while dropping arelationship template, you will get the name of the relationship template try: index, relationTemplateDict = self.model.getDictByName( topLevel="Relationship Template", objectName=relTemplateName) # create a new RelationTemplateItem object relationTemplateItem = RelationTemplateItem( scene=self, NZID=NZID, relationTemplateDict=relationTemplateDict, startNodeItem=self.parent.itemDict[startNZID], endNodeItem=self.parent.itemDict[endNZID]) # add the relation item object to the diagram item dictionary self.parent.itemDict[ relationTemplateItem.NZID] = relationTemplateItem # tell the relationship to set it's loation and draw itself # for some reason, rel template doesn't draw itself when the object is created. You have to tell it to draw relationTemplateItem.drawIt2() except: print("error adding template Relationship") ########################################################### # diagram render methods ########################################################### def renderDiagram(self, diagramType=None, diagramName=None): try: self.diagramName = diagramName saveIndex, diagramDict = self.model.getDictByName( topLevel=diagramType, objectName=diagramName) self.diagramDict = diagramDict # this is all the diagram data and contains the saved diagram items self.itemDict = { } # dictionary of graphic items rendered on the scene self.drawDiagram() self.setSceneRect(self.itemsBoundingRect()) self.clearSelection() except Exception as e: self.helper.displayErrMsg( "Render Diagram", "Error rendering diagram {}. Error: {}".format( self.diagramName, str(e))) def saveImage(self, fileName=None): try: self.image = QImage(self.sceneRect().size().toSize(), QImage.Format_ARGB32_Premultiplied) self.image.fill(Qt.white) self.painter = QPainter(self.image) self.render(self.painter) self.painter.end() self.image.save(fileName) except Exception as e: self.helper.displayErrMsg( "Save Image", "Error saving diagram image file: {}. Error: {}".format( fileName, str(e))) def drawDiagram(self, ): '''get saved diagram object data and create diagram objects on the scene''' # print("loaded diagramDict {}".format(self.diagramDict)) #generate all the node instances for diagramItem in self.diagramDict["items"]: # print(str(diagramItem)) if diagramItem["diagramType"] == "Instance Node": # get the node instance object saveIndex, nodeDict = self.model.getDictByName( topLevel="Instance Node", objectName=diagramItem["NZID"]) nodeInstance = NodeInstance(model=self.model, nodeInstanceDict=nodeDict) self.addInode(QPointF(diagramItem["x"], diagramItem["y"]), nodeInstance=nodeInstance) elif diagramItem["diagramType"] == "Instance Relationship": self.addIRelToScene(NZID=diagramItem["NZID"]) elif diagramItem["diagramType"] == "Node Template": # get the node template saveIndex, nodeTemplateDict = self.model.getDictByName( topLevel="Node Template", objectName=diagramItem["name"]) self.addTNode(QPointF(diagramItem["x"], diagramItem["y"]), nodeTemplateDict=nodeTemplateDict, NZID=diagramItem["NZID"]) elif diagramItem["diagramType"] == "Relationship Template": self.addTRelToScene(relTemplateName=diagramItem["name"], NZID=diagramItem["NZID"], startNZID=diagramItem["startNZID"], endNZID=diagramItem["endNZID"])
class FrmPropList(QFrame, Ui_Frame): """ This frame provides a UI for selecting a set of properties from all properties and setting their order """ def __init__(self, parent=None, curVal=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(FrmPropList, self).__init__(parent) self.setupUi(self) self.helper = Helper() self.curVal = curVal def addProps(self, propList): # load the list of all properties in the template for prop in propList: if len(prop) > 0: self.gridAllProps.addItem(prop) # load the list of selected properties if not self.curVal is None: selectedPropList = self.curVal.split(", ") for selectedProp in selectedPropList: if selectedProp in [ str(self.gridAllProps.item(i).text()) for i in range(self.gridAllProps.count()) ]: self.gridSelectedProps.addItem(selectedProp) self.setPropListDisplay() def genPropList(self, ): ''' generate a comma seperated list of properties from the selected properties list view ''' propComma = "" propList = [ str(self.gridSelectedProps.item(i).text()) for i in range(self.gridSelectedProps.count()) ] propComma = ", ".join(x for x in propList) return propComma def setPropListDisplay(self, ): ''' set the label with the generated property list ''' self.lblPropList.setText(self.genPropList()) @pyqtSlot() def on_btnAdd_clicked(self): """ User clicks >> button to add a prop to the prop list """ index = self.gridAllProps.currentRow() if (index is not None and index > -1): selProperty = self.gridAllProps.item(index).text() if selProperty in [ str(self.gridSelectedProps.item(i).text()) for i in range(self.gridSelectedProps.count()) ]: self.helper.displayErrMsg( "Add Property", "Property {} can only be selected once".format( selProperty)) else: self.gridSelectedProps.addItem( self.gridAllProps.item(index).text()) self.setPropListDisplay() else: self.helper.displayErrMsg("Add Property", "You must select a property to add.") @pyqtSlot() def on_btnRemove_clicked(self): """ user clicks the << button to remove a prop from the proplist """ self.gridSelectedProps.takeItem(self.gridSelectedProps.currentRow()) self.setPropListDisplay() @pyqtSlot() def on_btnMoveUp_clicked(self): """ User clicks the Move Up button, move the selected property up on in the list """ self.helper.moveListItemUp(self.gridSelectedProps) self.setPropListDisplay() @pyqtSlot() def on_btnMoveDown_clicked(self): """ User clicks the Move Down button, move the selected property up on in the list """ self.helper.moveListItemDown(self.gridSelectedProps) self.setPropListDisplay()
class NodeTemplateCypher(): def __init__(self, parent=None, templateDict=None): self.parent = parent self.templateDict = templateDict self.type = "Node" self.helper = Helper() # this determines if the DataGridWidget should call genMatch on a grid refresh or should it suppy it's own cypher def isGeneric(self, ): return False # set which button driven functionality will be enabled def refreshOK(self, ): return True def exportOK(self, ): return True def newOK(self, ): return True def deleteOK(self, ): return True def rowSelect(self, ): return False def setNullOK(self, ): return True def genDeleteDetach(self, row=None, dataGrid=None): model = dataGrid.model() # get the nodeID self.nodeID = "no node" for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "nodeID": self.nodeID = model.item(row, header).data(Qt.EditRole) cypher = 'match (n) \n where id(n) = {} \n detach delete n'.format( self.nodeID) return cypher def genUpdateLabel(self, updateIndex=None, dataGrid=None): model = dataGrid.model() # get the nodeID for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "nodeID": self.nodeID = model.item(updateIndex.row(), header).data(Qt.EditRole) # get if the checkbox is checked or not if model.item(updateIndex.row(), updateIndex.column()).checkState() == Qt.Checked: self.operation = "Set" else: self.operation = "Remove" # get the label name self.updateLabel = model.headerData(updateIndex.column(), Qt.Horizontal, Qt.DisplayRole) p1 = self.nodeID p2 = self.operation p3 = self.updateLabel cypher = 'match (n) \n where id(n) = {} \n {} n:{} '.format( p1, p2, p3) return cypher def genUpdateProp(self, updateIndex=None, dataGrid=None): cypher = "" rc = True model = dataGrid.model() self.nodeID = None # get the nodeID for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "nodeID": self.nodeID = model.item(updateIndex.row(), header).data(Qt.EditRole) if self.nodeID is None: return False, "Node ID not found." try: # get the new data value which is a string self.updateData = model.item( updateIndex.row(), updateIndex.column()).data(Qt.EditRole) # get the property name self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal, Qt.DisplayRole) # get the correct neo4j type neoType = model.item(updateIndex.row(), updateIndex.column()).data(Qt.UserRole + 1) # if the datatype comes back unknown then generate cypher comment if neoType == 'Unknown': cypher = "Can't update unknown datatype, correct datatype in node template." rc = False else: # generate the correct syntax that you set the property equal to self.setEqualTo = self.helper.genPropEqualTo( dataValue=self.updateData, neoType=neoType) p1 = self.nodeID p2 = self.updateProp p3 = self.setEqualTo cypher = "match (n) \n where id(n) = {} \n set n.{} = {} ".format( p1, p2, p3) rc = True except BaseException as e: # something went wrong cypher = "Error generating cypher: {}".format(repr(e)) rc = False finally: return rc, cypher def genRemoveProp(self, updateIndex=None, dataGrid=None): model = dataGrid.model() cypher = None self.nodeID = None # get the nodeID for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "nodeID": self.nodeID = model.item(updateIndex.row(), header).data(Qt.EditRole) if self.nodeID is None: return cypher # get the property name self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal, Qt.DisplayRole) # MAKE SURE IT ISN'T A REQUIRED PROPERTY for prop in self.templateDict["properties"]: if prop[PROPERTY] == self.updateProp: if prop[PROPREQ] != Qt.Checked: p1 = self.nodeID p2 = self.updateProp cypher = "match (n) \n where id(n) = {} \n remove n.{} ".format( p1, p2) else: self.helper.displayErrMsg( "Set Null", "Property {} is required by the Node Template. Cannot remove this property." .format(self.updateProp)) return cypher def genNewNode(self): nodeName = "n" p1 = self.genWhereLabelList(nodeName) p11 = self.genSetPropList(nodeName) p2 = " id(" + nodeName + ") as nodeID " p3 = self.genReturnLblList(nodeName) p4 = self.genReturnPropList(nodeName) if p3 != "": p2 = p2 + " , " if p4 != "" and p3 != "": p3 = p3 + " , " if p4 != "" and p3 == "": p2 = p2 + " , " cypher = 'create ({}) \n {} \n return {} \n {} \n {} '.format( p1, p11, p2, p3, p4) return cypher def genMatchReturnNodeOnly(self): nodeName = "n" p1 = self.genWhereLabelList(nodeName) if len(p1) > 0: p1 = " where " + p1 p2 = " id(" + nodeName + ") as nodeID " p3 = " n as Node" cypher = 'match (n) \n {} \n return {}, \n {} '.format(p1, p2, p3) #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP editParmDict = [] editParmDict.append([NODEID, False]) # editParmDict.append( [PROP, False] ) # editParmDict.append( [NODE, False] ) for label in self.templateDict["labels"]: if label[REQUIRED] == 2: editParmDict.append([REQLBL, False]) else: editParmDict.append([OPTLBL, True]) for prop in self.templateDict["properties"]: editParmDict.append([PROP, True]) return cypher, editParmDict def genMatch(self): nodeName = "n" p1 = self.genWhereLabelList(nodeName) if len(p1) > 0: p1 = " where " + p1 # p2 = " id(" + nodeName + ") as nodeID, \n n.NZID, \n n as Node " p2 = " id(" + nodeName + ") as nodeID " p3 = self.genReturnLblList(nodeName) p4 = self.genReturnPropList(nodeName) if p3 != "": p2 = p2 + " , " if p4 != "" and p3 != "": p3 = p3 + " , " if p4 != "" and p3 == "": p2 = p2 + " , " cypher = 'match (n) \n {} \n return {} \n {} \n {} '.format( p1, p2, p3, p4) #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP editParmDict = [] editParmDict.append([NODEID, False]) # editParmDict.append( [PROP, False] ) # editParmDict.append( [NODE, False] ) for label in self.templateDict["labels"]: if label[REQUIRED] == 2: editParmDict.append([REQLBL, False]) else: editParmDict.append([OPTLBL, True]) for prop in self.templateDict["properties"]: editParmDict.append([PROP, True]) return cypher, editParmDict def genWhereLabelList(self, nodeName): 'only check for labels that are marked required' lblList = "" if len(self.templateDict["labels"]) > 0: lblList = nodeName + ":" + ":".join( x[LABEL] for x in self.templateDict["labels"] if x[REQUIRED] == 2) return lblList def genReturnPropList(self, nodeName): 'return all properties in the template' propList = "" propList = ",".join(nodeName + "." + x[PROPERTY] + " as " + x[PROPERTY] for x in self.templateDict["properties"]) return propList def genReturnLblList(self, nodeName): 'return all labels in the template' lblList = "" lblList = ",".join(nodeName + ":" + x[LABEL] + " as " + x[LABEL] for x in self.templateDict["labels"]) return lblList def genSetPropList(self, nodeName): 'return a set statement for each property that has a default value (i.e. required)' setPropList = [] if not self.templateDict is None: for prop in self.templateDict["properties"]: # if the property has a default value then generate the set statement. if prop[PROPDEF] != "": # generate the correct syntax that you set the property equal to setEqualTo = self.helper.genPropEqualTo( dataValue=prop[PROPDEF], neoType=prop[DATATYPE]) setPropList.append("set {}.{} = {}".format( nodeName, prop[PROPERTY], setEqualTo)) setProps = " \n ".join(setProp for setProp in setPropList) return setProps
class EditRoleDlg(QDialog, Ui_EditRoleDlg): """ Class documentation goes here. """ def __init__(self, parent=None, mode=None, roleName=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(EditRoleDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.roleName = roleName self.mode = mode self.helper = Helper() self.initUI() def initUI(self, ): self.txtRoleName.setText(self.roleName) self.txtRoleName.setReadOnly(True) # populate the user combo box self.cbUserName.addItems(self.parent.schemaModel.instanceList("User")) # populate role users in the list box roleUsers = self.parent.schemaModel.getRoleUsers( roleName=self.roleName) for roleUser in roleUsers: self.lstRoleUsers.addItem(roleUser) @pyqtSlot() def on_dlgBtnBox_accepted(self): """ User clicks Close """ QDialog.accept(self) @pyqtSlot() def on_dlgBtnBox_rejected(self): """ User clicks something that generates the reject """ QDialog.reject(self) @pyqtSlot() def on_btnAddUser_clicked(self): """ Give the user selected from the combobox the role """ addUser = self.cbUserName.currentText() currentUsers = [ str(self.lstRoleUsers.item(i).text()) for i in range(self.lstRoleUsers.count()) ] if addUser in currentUsers: # error, the user already has the selected role self.helper.displayErrMsg("Edit Role", "The role already has this user.") else: # give the role the user QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.addUserRole(userName=addUser, role=self.roleName) if rc: self.lstRoleUsers.addItem(addUser) else: self.helper.displayErrMsg("Add User to Role Error", msg) QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnRemoveUsers_clicked(self): """ Remove the selected user from the list of users in the role """ if self.lstRoleUsers.currentItem() == None: self.helper.displayErrMsg("Remove User From Role", "You must select a user to remove.") return removeUser = self.lstRoleUsers.currentItem().text() QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.removeUserRole(userName=removeUser, role=self.roleName) if rc: self.lstRoleUsers.takeItem(self.lstRoleUsers.currentRow()) else: self.helper.displayErrMsg("Remove User from Role Error", msg) QApplication.restoreOverrideCursor()
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))
class SyncToDBDlg(QDialog, Ui_Dialog): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(SyncToDBDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.objectName = self.parent.diagramName self.designModel = self.parent.model self.syncNeoCon = self.designModel.modelNeoCon self.itemDict = self.parent.itemDict self.helper = Helper() self.initPage() #populate ui data from object self.populateUIfromObject() def initPage(self, ): # header area self.txtDiagramName.setText(self.objectName) self.txtNeoCon.setText("{} - {}".format( self.syncNeoCon.name, self.syncNeoCon.neoDict["URL"])) # action grid # ITEMTYPE, DIAGRAMITEM, GRAPHDBITEM, MATCH, ACTION self.gridSyncActions.setModel(self.createSyncActionModel()) self.gridSyncActions.setColumnWidth(ITEMKEY, 50) self.gridSyncActions.setColumnWidth(ITEMTYPE, 125) self.gridSyncActions.setColumnWidth(DIAGRAMITEM, 400) self.gridSyncActions.setColumnWidth(ACTION, 120) self.gridSyncActions.setColumnWidth(GRAPHDBITEM, 400) self.gridSyncActions.setColumnWidth(MATCH, 80) self.gridSyncActions.setItemDelegateForColumn(ACTION, CBDelegate(self, [SYNCTO,SYNCNONE,SYNCFROM] )) header = self.gridSyncActions.horizontalHeader() header.setSectionResizeMode(ITEMKEY, QHeaderView.Fixed) header.setSectionResizeMode(ITEMTYPE, QHeaderView.Fixed) header.setSectionResizeMode(DIAGRAMITEM, QHeaderView.Interactive) header.setSectionResizeMode(GRAPHDBITEM, QHeaderView.Interactive) header.setSectionResizeMode(MATCH, QHeaderView.Fixed) header.setSectionResizeMode(ACTION, QHeaderView.Fixed) header.setHighlightSections(False) def createSyncActionModel(self): # ITEMTYPE, DIAGRAMITEM, ACTION, GRAPHDBITEM, MATCH model = QStandardItemModel(0, 6) model.setHeaderData(ITEMKEY, Qt.Horizontal, "Key") model.setHeaderData(ITEMTYPE, Qt.Horizontal, "Type") model.setHeaderData(DIAGRAMITEM, Qt.Horizontal, "Diagram Item") model.setHeaderData(ACTION, Qt.Horizontal, "Action") model.setHeaderData(GRAPHDBITEM, Qt.Horizontal, "Neo4j Item") model.setHeaderData(MATCH, Qt.Horizontal, "Match") return model def populateUIfromObject(self, ): if self.itemDict is not None: # scan the nodes for key, value in self.itemDict.items(): objectItemDict = value.getObjectDict() if objectItemDict["diagramType"] == "Instance Node": rc, msg = value.itemInstance.checkNode() self.addActionRow(rc, msg, value) # scan the relationships for key, value in self.itemDict.items(): objectItemDict = value.getObjectDict() if objectItemDict["diagramType"] == "Instance Relationship": rc, msg = value.relationInstance.checkRelationship() self.addActionRow(rc, msg, value) def addActionRow(self, rc, msg, diagramItem): # ITEMTYPE, DIAGRAMITEM, ACTION, GRAPHDBITEM, MATCH objectItemDict = diagramItem.getObjectDict() item0 = QStandardItem(diagramItem.NZID()) item0.setEditable(False) item1 = QStandardItem(objectItemDict["diagramType"]) item1.setEditable(False) if objectItemDict["diagramType"] == "Instance Node": objectDict = diagramItem.itemInstance.getObjectDict() # item2 = QStandardItem("({} {})".format(diagramItem.itemInstance.labelList, diagramItem.itemInstance.propList)) item2 = QStandardItem(objectDict["displayName"]) item2.setEditable(False) item3 = QStandardItem(self.syncNeoCon.displayNode(diagramItem.itemInstance.node)) item3.setEditable(False) elif objectItemDict["diagramType"] == "Instance Relationship": objectDict = diagramItem.relationInstance.getObjectDict() # item2 = QStandardItem("({})".format(diagramItem.relationInstance.propList)) item2 = QStandardItem(objectDict["displayName"]) item2.setEditable(False) item3 = QStandardItem(self.syncNeoCon.displayRelationship(diagramItem.relationInstance.relationship)) item3.setEditable(False) else: item2 = QStandardItem("({})".format("No Object")) item2.setEditable(False) item3 = QStandardItem("No Object") item3.setEditable(False) if rc == FOUNDMATCH: item4 = QStandardItem("Match") if rc == FOUNDNOMATCH: item4 = QStandardItem("Different") if rc == NOTFOUND: item4 = QStandardItem("Not in Neo4j") item4.setEditable(False) if rc == FOUNDMATCH: item5 = QStandardItem(SYNCNONE) if rc == FOUNDNOMATCH: item5 = QStandardItem(SYNCTO) if rc == NOTFOUND: item5 = QStandardItem(SYNCTO) item5.setEditable(True) self.gridSyncActions.model().appendRow([item0, item1,item2, item5, item3, item4]) def validate(self, ): return True def apply(self, ): ''' The dialog passes all edits so process all the sync actions. ''' model = self.gridSyncActions.model() numrows = model.rowCount() for row in range(0,numrows): syncAction = model.item(row,ACTION).data(Qt.EditRole) rc = None msg = "" if syncAction == SYNCTO: key = model.item(row,ITEMKEY ).data(Qt.EditRole) item = self.itemDict[key] if item.diagramType == "Instance Relationship": rc, msg = item.relationInstance.syncToDB() if item.diagramType == "Instance Node": rc, msg = item.itemInstance.syncToDB() if syncAction == SYNCFROM: key = model.item(row,ITEMKEY ).data(Qt.EditRole) item = self.itemDict[key] if item.diagramType == "Instance Relationship": rc, msg = item.relationInstance.syncFromDB() if item.diagramType == "Instance Node": rc, msg = item.itemInstance.syncFromDB() # display error message if any if not rc is None: if rc == False: self.helper.displayErrMsg("Sync Diagram Error", msg) @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self)
class dlgReverseEngineer(QDialog, Ui_dlgReverseEngineer): """ This dialog box provides a facility to scan a neo4j database and generate node and relationship templates """ def __init__(self, parent=None, schemaModel=None, model=None, settings=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(dlgReverseEngineer, self).__init__(parent) self.parent = parent self.schemaModel = schemaModel self.echo = True self.settings = settings self.model = model self.helper = Helper() self.neoTypeFunc = NeoTypeFunc() self.myNeoCon = self.model.modelNeoCon self.setupUi(self) self.initPage() def initPage(self, ): # header area self.editNeoURL.setText("{} - {}".format(self.myNeoCon.name, self.myNeoCon.neoDict["URL"])) self.cbScanNodes.setCheckState(Qt.Checked) self.cbScanRels.setCheckState(Qt.Checked) # count of nodes and rels self.countStuff() # update percents self.testScan() # results area self.clearResults() # buttons self.stopScan = False self.btnStop.setEnabled(False) self.btnStart.setEnabled(True) def countStuff(self, ): # get max ID's in use try: cypher = '''call dbms.queryJmx("org.neo4j:instance=kernel#0,name=Primitive count") yield attributes with keys(attributes) as k , attributes unwind k as row return "ID Allocations" as type,row,attributes[row]["value"] ''' #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1: self.maxRelId = 0 self.maxNodeId = 0 for row in self.myNeoCon.resultSet: if row["row"] == 'NumberOfNodeIdsInUse': self.maxNodeId = row['attributes[row]["value"]'] if row["row"] == 'NumberOfRelationshipIdsInUse': self.maxRelId = row['attributes[row]["value"]'] msg = "Max Node ID: {} Max Relationship ID: {}".format( self.maxNodeId, self.maxRelId) # self.editNumNodes.setText(str(self.myNeoCon.resultSet[0]["count(*)"])) else: msg = "Get Max ID Error {}".format(msg1) except BaseException as e: msg = "{} - Get Max ID failed.".format(repr(e)) finally: QApplication.restoreOverrideCursor() self.displayScanMsg(msg) # get number of nodes try: cypher = "MATCH (n) RETURN count(*)" #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1: msg = "Counted {} Nodes.".format(str(self.myNeoCon.resultSet)) self.editNumNodes.setText( str(self.myNeoCon.resultSet[0]["count(*)"])) else: msg = "Count Nodes Error {}".format(msg1) except BaseException as e: msg = "{} - Node Count failed.".format(repr(e)) finally: QApplication.restoreOverrideCursor() self.displayScanMsg(msg) # get number of rels try: cypher = "MATCH ()-[r]->() RETURN count(*)" #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1: msg = "Counted {} Relationships.".format( str(self.myNeoCon.resultSet)) self.editNumRels.setText( str(self.myNeoCon.resultSet[0]["count(*)"])) else: msg = "Count Relationships Error {}".format(msg1) except BaseException as e: msg = "{} - Relationships Count failed.".format(repr(e)) finally: QApplication.restoreOverrideCursor() self.displayScanMsg(msg) def clearResults(self, ): # message text area self.editProgress.clear() # NODE results grid # GENERATE,, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN self.gridTemplates.setModel(self.createResultsModel()) self.gridTemplates.setColumnWidth(GENERATE, 50) self.gridTemplates.setColumnWidth(TEMPLATENAME, 150) self.gridTemplates.setColumnWidth(LABELPATTERN, 300) self.gridTemplates.setColumnWidth(PROPERTYPATTERN, 300) self.gridTemplates.setColumnWidth(NODECOUNT, 100) header = self.gridTemplates.horizontalHeader() header.setSectionResizeMode(GENERATE, QHeaderView.Fixed) header.setSectionResizeMode(TEMPLATENAME, QHeaderView.Interactive) header.setSectionResizeMode(LABELPATTERN, QHeaderView.Interactive) header.setSectionResizeMode(PROPERTYPATTERN, QHeaderView.Interactive) header.setSectionResizeMode(NODECOUNT, QHeaderView.Fixed) # RELATIONSHIP results grid # GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, RELPROPERTYPATTERN = range(6) self.gridTemplates_Rel.setModel(self.createRelResultsModel()) self.gridTemplates_Rel.setColumnWidth(GENERATE, 50) self.gridTemplates_Rel.setColumnWidth(RELTEMPLATENAME, 150) self.gridTemplates_Rel.setColumnWidth(RELATIONSHIPNAME, 125) self.gridTemplates_Rel.setColumnWidth(FROMTEMPLATE, 150) self.gridTemplates_Rel.setColumnWidth(TOTEMPLATE, 150) self.gridTemplates_Rel.setColumnWidth(RELPROPERTYPATTERN, 300) self.gridTemplates_Rel.setColumnWidth(RELCOUNT, 100) self.gridTemplates_Rel.setColumnWidth(RELKEY, 100) header = self.gridTemplates_Rel.horizontalHeader() header.setSectionResizeMode(GENERATE, QHeaderView.Fixed) header.setSectionResizeMode(RELTEMPLATENAME, QHeaderView.Interactive) header.setSectionResizeMode(RELATIONSHIPNAME, QHeaderView.Interactive) header.setSectionResizeMode(FROMTEMPLATE, QHeaderView.Interactive) header.setSectionResizeMode(TOTEMPLATE, QHeaderView.Interactive) header.setSectionResizeMode(RELPROPERTYPATTERN, QHeaderView.Interactive) header.setSectionResizeMode(RELCOUNT, QHeaderView.Fixed) header.setSectionResizeMode(RELKEY, QHeaderView.Fixed) def createRelResultsModel(self): # GENERATE, TEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, PROPERTYPATTERN = range(6) model = QStandardItemModel(0, 8) model.setHeaderData(GENERATE, Qt.Horizontal, "") model.setHeaderData(RELTEMPLATENAME, Qt.Horizontal, "Template Name") model.setHeaderData(RELATIONSHIPNAME, Qt.Horizontal, "Relationship Name") model.setHeaderData(FROMTEMPLATE, Qt.Horizontal, "From Node Template") model.setHeaderData(TOTEMPLATE, Qt.Horizontal, "To Node Template") model.setHeaderData(RELPROPERTYPATTERN, Qt.Horizontal, "Property Pattern") model.setHeaderData(RELCOUNT, Qt.Horizontal, "# Scanned") model.setHeaderData(RELKEY, Qt.Horizontal, "Unique Key") model.dataChanged.connect(self.templateRelGridChanged) return model def createResultsModel(self): # GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN model = QStandardItemModel(0, 5) model.setHeaderData(GENERATE, Qt.Horizontal, "") model.setHeaderData(TEMPLATENAME, Qt.Horizontal, "Template Name") model.setHeaderData(LABELPATTERN, Qt.Horizontal, "Label Pattern") model.setHeaderData(PROPERTYPATTERN, Qt.Horizontal, "Property Pattern") model.setHeaderData(NODECOUNT, Qt.Horizontal, "# Scanned") model.dataChanged.connect(self.templateGridChanged) return model def addResultRow(self, model, c1, c2, c3, c4, c5): # GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN, COUNT item1 = QStandardItem(c1) item1.setEditable(True) item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item2 = QStandardItem(c2) item3 = QStandardItem(c3) item3.setEditable(False) item4 = QStandardItem(c4) item4.setEditable(False) item5 = QStandardItem(c5) item5.setEditable(False) if c1 in [0, 1, 2]: item1.setCheckState(c1) else: item1.setCheckState(Qt.Unchecked) model.appendRow([item1, item2, item3, item4, item5]) def addRelResultRow(self, model, c1, c2, c3, c4, c5, c6, c7, c8): # GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, PROPERTYPATTERN, COUNT item1 = QStandardItem(c1) item1.setEditable(True) item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item2 = QStandardItem(c2) # save the original rel templatename in case the user renames it on the grid. item2.setData(c2, Qt.UserRole) item3 = QStandardItem(c3) item3.setEditable(False) item4 = QStandardItem(c4) item4.setEditable(False) item5 = QStandardItem(c5) item5.setEditable(False) item6 = QStandardItem(c6) item6.setEditable(False) item7 = QStandardItem(c7) item7.setEditable(False) item8 = QStandardItem(c8) item8.setEditable(False) if c1 in [0, 1, 2]: item1.setCheckState(c1) else: item1.setCheckState(Qt.Unchecked) # print(c1, c2, c3, c4, c5, c6, c7, c8) model.appendRow( [item1, item2, item3, item4, item5, item6, item7, item8]) def newScan(self, ): self.clearResults() self.patternList = [] # keeps track of the unique label combinations # self.patternListCount = [] # keeps track of the number of nodes matching the unique label combinations in patternList self.relPatternListCount = [] self.nodeDict = {} # dictionary to hold discovered node patterns self.relDict = { } # dictionary to hold discovered relationship patterns def displayScanMsg(self, text): if self.echo: # display on UI self.editProgress.appendPlainText("{}: {}".format( str(datetime.now()), text)) QApplication.processEvents() # add real logging here # logging.info(text) def logMessage(self, msg): if self.echo: # display on UI self.browserOutput.append(msg) QApplication.processEvents() # logging.info(msg) def templateGridChanged(self, index1, index2): ''' The user can over write the automatically generted Node template name. This needs to be recorded in the nodeDict template dictionary so it can be properly generated. ''' if index1.column() == TEMPLATENAME: # print("row - {}, col - {}, data - {}".format(index1.row(), index1.column(), index1.data(role = Qt.DisplayRole))) # print("before:{}".format(self.nodeDict["{0:0>5}".format(index1.row())])) beforeTemplateName = self.nodeDict["{0:0>5}".format( index1.row())]["templateLblName"] afterTemplateName = index1.data(role=Qt.DisplayRole) #update the node pattern dictionary with the new name try: self.nodeDict["{0:0>5}".format( index1.row())]["templateLblName"] = afterTemplateName # update the rel dict for key, value in self.relDict.items(): if value["toTemplate"] == beforeTemplateName: value["toTemplate"] = afterTemplateName if value["fromTemplate"] == beforeTemplateName: value["fromTemplate"] = afterTemplateName # update the rel template grid on the UI model = self.gridTemplates_Rel.model() numrows = model.rowCount() for row in range(0, numrows): curRelTemplateName = model.item(row, RELTEMPLATENAME).data( Qt.EditRole) if model.item(row, FROMTEMPLATE).data( Qt.EditRole) == beforeTemplateName: model.setData(model.index(row, FROMTEMPLATE), afterTemplateName) model.setData( model.index(row, RELTEMPLATENAME), curRelTemplateName.replace(beforeTemplateName, afterTemplateName)) if model.item(row, TOTEMPLATE).data( Qt.EditRole) == beforeTemplateName: model.setData(model.index(row, TOTEMPLATE), afterTemplateName) model.setData( model.index(row, RELTEMPLATENAME), curRelTemplateName.replace(beforeTemplateName, afterTemplateName)) except: pass # print("error") finally: pass # print("AFTER:{}".format(self.nodeDict["{0:0>5}".format(index1.row())])) def templateRelGridChanged(self, index1, index2): ''' The user can over write the automatically generated Relationship template name. ''' return @pyqtSlot() def on_btnStart_clicked(self): """ Reverse Engineer The current Graph. Create Node and Relationship templates """ # switch to the progress tab self.tabWidget.setCurrentIndex(0) # for now, must scan nodes if self.cbScanNodes.isChecked() == False: self.cbScanNodes.setCheckState(Qt.Checked) #setup for a new scan self.newScan() self.stopScan = False self.btnStop.setEnabled(True) self.btnStart.setEnabled(False) # do the scan self.scanAll() # reset the buttons self.btnStop.setEnabled(False) self.btnStart.setEnabled(True) # switch to the results tab self.tabWidget.setCurrentIndex(1) def getDataTypes(self, nodeID=None, relID=None, propDataTypeDict=None, propList=None): if not nodeID is None: returnPropList = self.genReturnPropList("n", propList) if len(returnPropList) > 0: cypher = "match (n) where id(n) = {} return {}".format( str(nodeID), returnPropList) else: return if not relID is None: returnPropList = self.genReturnPropList("r", propList) if len(returnPropList) > 0: cypher = "match ()-[r]->() where id(r) = {} return {}".format( str(relID), returnPropList) else: return #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1: record = self.myNeoCon.resultSet[0] # get the datatype for each property # value has the correct python object for each cell in the result set for index, value in record.items(): if not value is None: dataType = self.neoTypeFunc.getNeo4jDataType(value) propDataTypeDict[index] = dataType def genReturnPropList(self, nodeName, propList): 'return all properties in the template' genPropList = "" genPropList = ",".join(nodeName + "." + x + " as " + x for x in propList) return genPropList def scanAll(self, ): msg = "Scan finished" if self.cbScanNodes.isChecked() == True: # scan the nodes self.displayScanMsg("Start Scanning Nodes.") limitAmt = self.spinProcessSize.value() skipIncrement = self.spinSkipAmt.value() skipAmt = 0 totAmt = 0 self.moreData = True try: while (self.moreData and self.stopScan == False): rc = False cypher = "match (n) return id(n) as nodeID, labels(n), keys(n) skip {} limit {}".format( str(skipAmt), str(limitAmt)) #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1 == True: x = self.processModelChunk() if x > 0: totAmt = totAmt + x self.displayScanMsg( "Skip to: {} Processed: {} Total Processed {} Nodes." .format(str(skipAmt), str(limitAmt), totAmt)) skipAmt = skipAmt + limitAmt + skipIncrement else: self.moreData = False rc = True msg = "Scan Nodes complete" else: msg = "Scan Nodes Error {}".format(msg1) self.moreData = False except BaseException as e: msg = "{} - Node Scan failed.".format(repr(e)) finally: self.displayScanMsg(msg) # add scanned node templates to the grid if self.stopScan == False: self.genNodeResult() if self.cbScanRels.isChecked() == True: # scan the relationships self.displayScanMsg("Start Scanning Relationships.") limitAmt = self.spinProcessSize.value() skipIncrement = self.spinSkipAmt.value() skipAmt = 0 totAmt = 0 self.moreData = True try: while (self.moreData and self.stopScan == False): rc = False cypher = "match (f)-[r]->(t) return id(r), keys(r), type(r), labels(f), labels(t) skip {} limit {}".format( str(skipAmt), str(limitAmt)) #run the query rc1, msg1 = self.myNeoCon.runCypherAuto(cypher) if rc1: x = self.processRelModelChunk() if x > 0: totAmt = totAmt + x self.displayScanMsg( "Skip to: {} Processed: {} Total Processed {} Relationships." .format(str(skipAmt), str(x), totAmt)) skipAmt = skipAmt + limitAmt + skipIncrement else: self.moreData = False rc = True msg = "Scan Relationships complete" else: msg = "Scan Relationships Error {}".format(msg1) self.moreData = False except BaseException as e: msg = "{} - Relationships Scan failed.".format(repr(e)) finally: self.displayScanMsg(msg) if self.stopScan == False: self.genRelResult() return rc, msg def processModelChunk(self, ): ctr = 0 for record in self.myNeoCon.resultSet: ctr = ctr + 1 labels = record["labels(n)"] props = record["keys(n)"] nodeID = record["nodeID"] try: x = self.patternList.index(labels) patternName = "{0:0>5}".format(x) except ValueError: nextx = len(self.patternList) self.patternList.insert(nextx, labels) patternName = "{0:0>5}".format(nextx) self.nodeDict[patternName] = {} self.nodeDict[patternName]["propList"] = [] self.nodeDict[patternName]["labelList"] = labels self.nodeDict[patternName]["templateName"] = "Node{}".format( patternName) self.nodeDict[patternName]["templateLblName"] = "{}".format( "_".join(labels)) self.nodeDict[patternName]["propDataType"] = {} self.nodeDict[patternName]["count"] = 0 finally: count = self.nodeDict[patternName]["count"] + 1 self.nodeDict[patternName]["count"] = count # get datatypes for newly discovered properties newProps = list( set(props) - set(self.nodeDict[patternName].get("propList", []))) if len(newProps) > 0: self.getDataTypes( nodeID=nodeID, propDataTypeDict=self.nodeDict[patternName] ["propDataType"], propList=newProps) # merge in any newly discovered properties self.nodeDict[patternName]["propList"] = list( set(self.nodeDict[patternName].get("propList", []) + props)) return ctr def processRelModelChunk(self, ): ctr = 0 for record in self.myNeoCon.resultSet: ctr = ctr + 1 relProps = record["keys(r)"] relType = record["type(r)"] fromLbls = record["labels(f)"] toLbls = record["labels(t)"] relID = record["id(r)"] # get from and to node templates fromTemplate = "Unknown" toTemplate = "Unknown" for nodepattern, nodedata in self.nodeDict.items(): if nodedata["labelList"] == fromLbls: fromTemplate = nodedata["templateLblName"] if nodedata["labelList"] == toLbls: toTemplate = nodedata["templateLblName"] try: relKey = "{}:{}:{}".format(relType, fromTemplate, toTemplate) # check to see if the relKey entry in the dictionary already exists, if it doesn't you get a KeyError check = self.relDict[relKey] except KeyError: # count how many times the relType has been used in a rel template countReltypeUsed = self.countRelType(relType) # the relkey doesn't exist so add it to the dictionary self.relDict[relKey] = {} self.relDict[relKey]["propList"] = [] self.relDict[relKey]["propDataType"] = {} if countReltypeUsed > 0: self.relDict[relKey]["templateName"] = "{0}{1:0>3}".format( relType, countReltypeUsed) else: self.relDict[relKey]["templateName"] = relType self.relDict[relKey]["relName"] = relType self.relDict[relKey]["fromTemplate"] = fromTemplate self.relDict[relKey]["toTemplate"] = toTemplate self.relDict[relKey]["count"] = 0 finally: count = self.relDict[relKey]["count"] + 1 self.relDict[relKey]["count"] = count # get datatypes for newly discovered properties newProps = list( set(relProps) - set(self.relDict[relKey].get("propList", []))) if len(newProps) > 0: self.getDataTypes( relID=relID, propDataTypeDict=self.relDict[relKey]["propDataType"], propList=newProps) # merge in the properties self.relDict[relKey]["propList"] = list( set(self.relDict[relKey].get("propList", []) + relProps)) return ctr def countRelType(self, relType): cnt = 0 for key in self.relDict.keys(): if self.relDict[key]["relName"] == relType: cnt = cnt + 1 return cnt def testScan(self): '''simulate the scan to get total nodes and percent nodes ''' # simulate Node Count self.stopScan = False totNodes = int(self.editNumNodes.text()) nodesRemaining = totNodes totRels = int(self.editNumRels.text()) relsRemaining = totRels limitAmt = self.spinProcessSize.value() skipIncrement = self.spinSkipAmt.value() skipAmt = 0 totAmt = 0 self.moreData = True try: while (self.moreData and self.stopScan == False): if nodesRemaining > limitAmt: x = limitAmt nodesRemaining = nodesRemaining - limitAmt - skipIncrement else: x = nodesRemaining nodesRemaining = 0 if x > 0: totAmt = totAmt + x # print("Skip to: {} Processed: {} Total Processed {} Nodes.".format(str(skipAmt), str(limitAmt),totAmt)) skipAmt = skipAmt + limitAmt + skipIncrement else: self.moreData = False else: self.moreData = False except BaseException as e: self.helper.displayErrMsg( "Calcuate Percents", "{} - Error Calculating Percents.".format(repr(e))) finally: # update UI self.txtNodeScanAmt.setText(str(totAmt)) displayPercent = "{0:.0%}".format(totAmt / totNodes) self.txtNodePercent.setText(displayPercent) # simulate Rel Count self.stopScan = False totRels = int(self.editNumRels.text()) relsRemaining = totRels limitAmt = self.spinProcessSize.value() skipIncrement = self.spinSkipAmt.value() skipAmt = 0 totAmt = 0 self.moreData = True try: while (self.moreData and self.stopScan == False): if relsRemaining > limitAmt: x = limitAmt relsRemaining = relsRemaining - limitAmt - skipIncrement else: x = relsRemaining relsRemaining = 0 if x > 0: totAmt = totAmt + x # print("Skip to: {} Processed: {} Total Processed {} Nodes.".format(str(skipAmt), str(limitAmt),totAmt)) skipAmt = skipAmt + limitAmt + skipIncrement else: self.moreData = False else: self.moreData = False except BaseException as e: self.helper.displayErrMsg( "Calcuate Percents", "{} - Error Calculating Percents.".format(repr(e))) finally: # update UI self.txtRelScanAmt.setText(str(totAmt)) if totRels == 0 or totRels is None: displayPercent = "{0:.0%}".format(0) else: displayPercent = "{0:.0%}".format(totAmt / totRels) self.txtRelPercent.setText(displayPercent) def genNodeResult(self, ): ''' Scan the reverse engineered node dictionary and create Node Template rows in the grid GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN ''' for key in sorted(self.nodeDict.keys()): value = self.nodeDict[key] self.addResultRow(self.gridTemplates.model(), Qt.Checked, str(value["templateLblName"]), str(value["labelList"]), str(value["propList"]), str(value["count"])) # print("{}-{} {}".format(key, value["labelList"], value["propList"])) def genNodeTemplates(self, ): ''' Scan the reverse engineered node dictionary and create Node Templates GENERATE, TEMPLATENAME, LABELPATTERN, PROPERTYPATTERN ''' # generate node templates model = self.gridTemplates.model() numrows = model.rowCount() for row in range(0, numrows): genFlag = model.item(row, GENERATE).checkState() # is the pattern checked? if genFlag == Qt.Unchecked: continue if self.genNodeTemplate(model, row) == False: break # uncheck the template model.item(row, GENERATE).setCheckState(Qt.Unchecked) # refresh the treeview self.model.updateTV() def genRelTemplates(self, ): # generate rel templates model = self.gridTemplates_Rel.model() numrows = model.rowCount() for row in range(0, numrows): genFlag = model.item(row, GENERATE).checkState() # is the pattern checked? if genFlag == Qt.Unchecked: continue if self.genRelTemplate(model, row) == False: break # uncheck the template model.item(row, GENERATE).setCheckState(Qt.Unchecked) # refresh the treeview self.model.updateTV() def genNodeTemplate(self, model, row): templateName = model.item(row, TEMPLATENAME).data(Qt.EditRole) labelPattern = model.item(row, LABELPATTERN).data(Qt.EditRole) # find entry in nodeDict that matches the label pattern in the grid # you can't use the template name because the user may have overwritten it labelPatternList = None for key, value in self.nodeDict.items(): if str(value["labelList"]) == labelPattern: labelPatternList = value["labelList"] propertyPatternList = value["propList"] propertyDataTypeDict = value.get("propDataType", {}) # this should never happen if labelPatternList == None: self.helper.displayErrMsg( "Create New Node Template Error", "The label pattern {} not found".format(labelPattern)) self.gridTemplates.setFocus() return False # make sure there is a template name if self.helper.NoTextValueError( templateName, "You must supply a name for the Node Template"): self.gridTemplates.setFocus() return False #make sure template name doesn't exist index, nodeDict = self.model.getDictByName("Node Template", templateName) if not nodeDict is None: self.helper.displayErrMsg( "Create New Node Template Error", "The node template {} already exists".format(templateName)) self.gridTemplates.setFocus() return False # print("{} {} {}".format(templateName, str(labelPatternList),str(propertyPatternList) )) labelList = [] for label in labelPatternList: nodeLbl = [label, Qt.Checked, Qt.Unchecked] self.model.newLabel(label) labelList.append(nodeLbl) propList = [] for property in propertyPatternList: dataType = propertyDataTypeDict.get(property, "Unknown") nodeProp = [ property, dataType, Qt.Unchecked, "", Qt.Unchecked, Qt.Unchecked, Qt.Unchecked ] self.model.newProperty(property, dataType=dataType) propList.append(nodeProp) # generate the constraints and indexes conList = [] # CONTYPE, CONLBL, CONPROP, CONPROPLIST # 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 nodeTemplateDict = self.model.newNodeTemplate( name=templateName, labelList=labelList, propList=propList, conList=conList, idxList=idxList, desc="Template generated from Reverse Engineering.") self.model.modelData["Node Template"].append(nodeTemplateDict) return True def genRelTemplate(self, model, row): ''' Create a relationship template based on the row in the results grid GENERATE, RELTEMPLATENAME, RELATIONSHIPNAME, FROMTEMPLATE, TOTEMPLATE, RELPROPERTYPATTERN = range(6) ''' # templateName and relationship name templateName = model.item(row, RELTEMPLATENAME).data(Qt.EditRole) relName = model.item(row, RELATIONSHIPNAME).data(Qt.EditRole) fromTemplate = model.item(row, FROMTEMPLATE).data(Qt.EditRole) toTemplate = model.item(row, TOTEMPLATE).data(Qt.EditRole) uniqueKey = model.item(row, RELKEY).data(Qt.EditRole) # # get property pattern from relDict since it is an actual list, not a string like what is stored in the grid propertyPatternList = self.relDict[uniqueKey]["propList"] propertyDataTypeDict = self.relDict[uniqueKey].get("propDataType", {}) # make sure there is a relationship name if self.helper.NoTextValueError( relName, "You must supply a name for the Relationship"): self.gridTemplates_Rel.setFocus() return False # make sure there is a template name if self.helper.NoTextValueError( templateName, "You must supply a name for the Relationship Template"): self.gridTemplates_Rel.setFocus() return False #make sure template name doesn't exist index, nodeDict = self.model.getDictByName("Relationship Template", templateName) if not nodeDict is None: self.helper.displayErrMsg( "Create New Relationship Template Error", "The Relationship template {} already exists".format( templateName)) self.gridTemplates.setFocus() return False # print("{} {}".format(templateName,str(propertyPatternList) )) # if its a new rel type then add it to the model self.model.newRelationship(relName) # properties propList = [] for property in propertyPatternList: dataType = propertyDataTypeDict.get(property, "Unknown") relProp = [property, dataType, Qt.Unchecked, "", Qt.Unchecked] self.model.newProperty(property, dataType=dataType) propList.append(relProp) # 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=relName) relDict = self.model.newRelTemplateDict( name=templateName, relname=relName, propList=propList, fromTemplate=fromTemplate, toTemplate=toTemplate, conList=conList, desc="Template generated from Reverse Engineering.") self.model.modelData["Relationship Template"].append(relDict) return True def genRelResult(self, ): ''' Scan the reverse engineered relationship dictionary and create Relationship Template rows in the grid ''' for key in sorted(self.relDict.keys()): value = self.relDict[key] # self.addRelResultRow(self.gridTemplates_Rel.model(), Qt.Checked, "{}".format(key), str(value["relName"]), str(value["fromTemplate"]), str(value["toTemplate"]), str(value["propList"]), str(value["count"]) ) self.addRelResultRow(self.gridTemplates_Rel.model(), Qt.Checked, str(value["templateName"]), str(value["relName"]), str(value["fromTemplate"]), str(value["toTemplate"]), str(value["propList"]), str(value["count"]), key) @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Slot documentation goes here. @param button DESCRIPTION @type QAbstractButton """ QDialog.accept(self) # @pyqtSlot() # def on_rbAllNodes_clicked(self): # """ # User selects to scan all nodes so set relationships to scan all # """ # if self.rbAllNodes.isChecked(): # self.rbAllRels.setChecked(True) # else: # self.rbAllRels.setChecked(False) # @pyqtSlot() # def on_rbPercentNodes_clicked(self): # """ # User selects to scan a percentage of nodes so set relationships to scan percentage # """ # if self.rbPercentNodes.isChecked(): # self.rbPercentRels.setChecked(True) # else: # self.rbPercentRels.setChecked(False) # # @pyqtSlot() # def on_rbAllRels_clicked(self): # """ # User selects to scan all Rels, so set nodes to scan all # """ # if self.rbAllRels.isChecked(): # self.rbAllNodes.setChecked(True) # else: # self.rbAllNodes.setChecked(False) # # @pyqtSlot() # def on_rbPercentRels_clicked(self): # """ # User selects to scan a percentage of relationships # Set nodes to scan percentage # """ # if self.rbPercentRels.isChecked(): # self.rbPercentNodes.setChecked(True) # else: # self.rbPercentNodes.setChecked(False) @pyqtSlot() def on_btnSaveTemplates_clicked(self): """ User requests to Generate the node templates """ self.genNodeTemplates() return @pyqtSlot(bool) def on_cbScanRels_clicked(self, checked): """ The scan relationships checkbox has been clicked Check to make sure the scan Nodes relationship has also been clicked. You can't scan relationships without having scanned node patterns. @param checked DESCRIPTION @type bool """ if checked: self.cbScanNodes.setCheckState(Qt.Checked) @pyqtSlot() def on_pbUncheck_clicked(self): """ Uncheck all the node templates """ model = self.gridTemplates.model() numrows = model.rowCount() for row in range(0, numrows): model.item(row, GENERATE).setCheckState(Qt.Unchecked) @pyqtSlot() def on_pbCheck_clicked(self): """ check all the node template checkboxes """ model = self.gridTemplates.model() numrows = model.rowCount() for row in range(0, numrows): model.item(row, GENERATE).setCheckState(Qt.Checked) @pyqtSlot() def on_pbUncheck_Rel_clicked(self): """ Uncheck all the relationship templates """ model = self.gridTemplates_Rel.model() numrows = model.rowCount() for row in range(0, numrows): model.item(row, GENERATE).setCheckState(Qt.Unchecked) @pyqtSlot() def on_pbCheck_Rel_clicked(self): """ check all the node template checkboxes """ model = self.gridTemplates_Rel.model() numrows = model.rowCount() for row in range(0, numrows): model.item(row, GENERATE).setCheckState(Qt.Checked) @pyqtSlot() def on_btnSaveTemplates_Rel_clicked(self): """ User requests to Generate the relationship templates """ self.genRelTemplates() return @pyqtSlot(bool) def on_cbScanNodes_clicked(self, checked): """ The scan nodes checkbox has been clicked If they unchecked it display a message and turn it back on. You always have to scan Nodes """ if not checked: self.helper.displayErrMsg("Reverse Engineer", "You always have to scan the Nodes") self.cbScanNodes.setCheckState(Qt.Checked) @pyqtSlot() def on_btnStop_clicked(self): """ User clicks the stop button to stop reverse engineering """ self.stopScan = True self.displayScanMsg("User Canceled Scan") @pyqtSlot(int) def on_spinProcessSize_valueChanged(self, p0): """ User changed the process size spin box @param p0 DESCRIPTION @type int """ # update the percents self.testScan() @pyqtSlot(int) def on_spinSkipAmt_valueChanged(self, p0): """ User changed the skip amount spin box @param p0 DESCRIPTION @type int """ #update the percents self.testScan()
class CypherPageWidget(QWidget, Ui_CypherPageWidget): """ Implements a tab on the main UI that provides the schema editor, cypher editor, and file explorer. """ treeViewUpdate = pyqtSignal() def __init__(self, parent=None, pageItem=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CypherPageWidget, self).__init__(parent) self.pageType = "CYPHER" self.settings = QSettings() self.parent = parent self.pageItem = pageItem self.setupUi(self) self.initUI() self.helper = Helper() self.treeViewUpdate.connect(self.populateTree) ######################################################################## # Schema editor setup ######################################################################## self.schemaNeoDriver = NeoDriver(name=self.pageItem.neoConName, promptPW=self.pageItem.promptPW) self.schemaModel = SchemaModel(self, neoDriver=self.schemaNeoDriver) self.refreshSchemaModel() self.schemaModel.setUpdateTreeViewMethod( method=self.on_btnRefresh_clicked) self.tvSchema.setContextMenuPolicy(Qt.CustomContextMenu) self.tvSchema.customContextMenuRequested.connect(self.openMenu) self.clearTree() self.populateTree() # display a default cypher tab self.on_btnNew_clicked() # display an error message if the schema connection doesn't work rc, msg = self.schemaModel.testSchemaConnection() if rc == False: self.helper.displayErrMsg("Connect Schema", "The Connection Failed: {}".format(msg)) def logMsg(self, msg): ''' If logging is active, then log the message ''' if logging: logging.info(msg) ############################################################################## # UI setups not generated ############################################################################## def initUI(self): try: self.defaultProjPath = self.settings.value("Default/ProjPath") if self.defaultProjPath is None: self.defaultProjPath = '//' except: self.defaultProjPath = '//' self.currentProjPath = self.defaultProjPath self.setUpFileExplorer(self.defaultProjPath) ######################################################################################### # schema tree view methods ######################################################################################### def clearTree(self, ): self.editNeo4j.clear() self.tvSchema.clear() self.tvSchema.setColumnCount(1) self.tvSchema.setHeaderLabels(["Schema Items"]) self.tvSchema.setItemsExpandable(True) def populateTree(self, addObject=None): # put neocon url in text box above the tree view self.editNeo4j.setText("{} - {}".format( self.schemaNeoDriver.name, self.schemaNeoDriver.neoDict["URL"])) #selected = None self.tvSchema.clear() self.tvSchema.setColumnCount(1) self.tvSchema.setHeaderLabels(["Schema Items"]) self.tvSchema.setItemsExpandable(True) nodeTypes = {} parent = self.tvSchema.invisibleRootItem() # add tree items for item in self.schemaModel.schemaData["TopLevel"]: topItem = self.addParent(parent, 0, item, "data") nodeTypes[item] = topItem for object in sorted(self.schemaModel.schemaData[item], key=itemgetter('name')): # childItem = self.addChild(topItem, 0, object["name"], "data") self.addChild(topItem, 0, object["name"], "data") self.tvSchema.resizeColumnToContents(0) def addParent(self, parent, column, title, data): item = QTreeWidgetItem(parent, [title]) item.setData(column, Qt.UserRole, data) item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) item.setExpanded(True) return item def addChild(self, parent, column, title, data): item = QTreeWidgetItem(parent, [ title, ]) item.setData(column, Qt.UserRole, data) return item ##################################################################################### # Context menus for schema tree view ##################################################################################### def openMenu(self, position): selected = self.tvSchema.currentItem() if not (selected is None): parent = self.tvSchema.currentItem().parent() # top level object menus if (parent is None): menu = QMenu() if (selected.data(0, 0) == "User"): self.addMenu(menu=menu, text="New...", method=self.newUser) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Role"): self.addMenu(menu=menu, text="New...", method=self.newRole) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Index"): self.addMenu(menu=menu, text="New...", method=self.newIndex) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Relationship Property Exists"): self.addMenu(menu=menu, text="New...", method=self.newConstraintRelPropExists) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Node Key"): self.addMenu(menu=menu, text="New...", method=self.newConstraintNodeKey) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Node Property Unique"): self.addMenu(menu=menu, text="New...", method=self.newConstraintNodePropUnique) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) if (selected.data(0, 0) == "Node Property Exists"): self.addMenu(menu=menu, text="New...", method=self.newConstraintNodePropExists) self.addMenu(menu=menu, text="Generate Create(s)...", method=self.genCreates) self.addMenu(menu=menu, text="Generate Drop(s)...", method=self.genDrops) # pop up the menu menu.exec_(self.tvSchema.mapToGlobal(position)) # object instance pop up menus if not (parent is None): menu = QMenu() if parent.data(0, 0) == "Label": self.addMenu(menu=menu, text="Generate Match...", method=self.matchSchemaObject) if parent.data(0, 0) == "Property": self.addMenu(menu=menu, text="Generate Match...", method=self.matchSchemaObject) if parent.data(0, 0) == "Relationship": self.addMenu(menu=menu, text="Generate Match...", method=self.matchSchemaObject) if parent.data(0, 0) == "User": self.addMenu(menu=menu, text="Edit...", method=self.editUser) self.addMenu(menu=menu, text="Drop...", method=self.dropSchemaObject) if parent.data(0, 0) == "Role": self.addMenu(menu=menu, text="Edit...", method=self.editRole) self.addMenu(menu=menu, text="Drop...", method=self.dropSchemaObject) if parent.data(0, 0) == "Index": self.addMenu(menu=menu, text="Drop...", method=self.dropSchemaObject) self.addMenu(menu=menu, text="Generate Match...", method=self.matchSchemaObject) if parent.data(0, 0) in [ "Node Key", "Node Property Unique", "Node Property Exists", "Relationship Property Exists" ]: self.addMenu(menu=menu, text="Drop...", method=self.dropSchemaObject) self.addMenu(menu=menu, text="Generate Match...", method=self.matchSchemaObject) # pop up the menu menu.exec_(self.tvSchema.mapToGlobal(position)) def addMenu(self, menu=None, text=None, method=None): menuAction = menu.addAction(text) menuAction.triggered.connect(method) ############################################################################## # schema menu options ############################################################################## def resetPassword(self, ): """ User requests the change user password from main menu """ d = ChangeUserPW(parent=self) if d.exec_(): pass def newUser(self, ): ''' prompt for the new user name and bring up the user editor dialog box ''' text, ok = QInputDialog.getText(self, 'Create New User', 'Enter the User Name:') if ok: # make sure they entered a valid user name # see if the user name already exists if text in [ object["name"] for object in self.schemaModel.schemaData["User"] ]: self.helper.displayErrMsg( "Create New User", "The user {} already exists. Cannot create new user.". format(text)) else: # create the user with requires password change on first login. QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createUser(text) if rc: self.schemaModel.updateTV() QApplication.restoreOverrideCursor() #show the edit user dialog box d = EditUserDlg(self, userName=text, mode="NEW") if d.exec_(): self.on_btnRefresh_clicked() else: self.helper.displayErrMsg("Create User Error", msg) QApplication.restoreOverrideCursor() def editUser(self, ): ''' edit an existing user ''' d = EditUserDlg(self, userName=self.tvSchema.currentItem().data(0, 0), mode="EDIT") if d.exec_(): self.on_btnRefresh_clicked() def newRole(self, ): ''' prompt for the new user name and bring up the user editor dialog box ''' text, ok = QInputDialog.getText(self, 'Create New Role', 'Enter the Role Name:') if ok: # make sure they entered a valid Role Name # see if the user name already exists if text in [ object["name"] for object in self.schemaModel.schemaData["Role"] ]: self.helper.displayErrMsg( "Create New Role", "The role {} already exists. Cannot create new role.". format(text)) else: # create the role if needed QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createRole(text) if rc: self.schemaModel.updateTV() QApplication.restoreOverrideCursor() d = EditRoleDlg(self, roleName=text, mode="NEW") if d.exec_(): self.on_btnRefresh_clicked() else: self.helper.displayErrMsg("Create Role Error", msg) QApplication.restoreOverrideCursor() def editRole(self, ): ''' edit an existing role ''' d = EditRoleDlg(self, roleName=self.tvSchema.currentItem().data(0, 0), mode="EDIT") if d.exec_(): self.on_btnRefresh_clicked() return def newIndex(self, ): d = CreateIndexDlg(self) if d.exec_(): self.on_btnRefresh_clicked() def newConstraintNodeKey(self, ): d = ConstraintNodeKeyDlg(self) if d.exec_(): self.on_btnRefresh_clicked() def newConstraintNodePropExists(self, ): d = ConstraintNodePropExistsDlg(self) if d.exec_(): self.on_btnRefresh_clicked() def newConstraintNodePropUnique(self, ): d = ConstraintNodePropUniqueDlg(self) if d.exec_(): self.on_btnRefresh_clicked() def newConstraintRelPropExists(self, ): d = ConstraintRelPropExistsDlg(self) if d.exec_(): self.on_btnRefresh_clicked() def dropSchemaObject(self, type): objectName = self.tvSchema.currentItem().data(0, 0) objectType = self.tvSchema.currentItem().parent().data(0, 0) d = DropObjectDlg(self, objectType=objectType, objectName=objectName) if d.exec_(): self.on_btnRefresh_clicked() return def matchSchemaObject(self, ): objectName = self.tvSchema.currentItem().data(0, 0) objectType = self.tvSchema.currentItem().parent().data(0, 0) cypher = self.schemaModel.genMatchFromConstraint(objectName=objectName, objectType=objectType) # add the comment editCypher = "// Generated by NodeEra from schema connection: {} \n// Match {} schema object: {} \n{}".format( self.pageItem.neoConName, objectType, objectName, cypher) self.addCypherEditorTab(fileName=None, fileText=editCypher, mode=MODENEW) def genCreates(self, ): ''' display the generate schema dialog with the object selected, and the generate create only option selected ''' objectName = self.tvSchema.currentItem().data(0, 0) d = GenerateSchemaDlg(self.parent, objectType=objectName, genOption="CreateOnly") if d.exec_(): pass def genDrops(self, ): ''' display the generate schema dialog with the object selected, and the generate drop only option selected ''' objectName = self.tvSchema.currentItem().data(0, 0) d = GenerateSchemaDlg(self.parent, objectType=objectName, genOption="DropOnly") if d.exec_(): pass ############################################################################## # signal slots ############################################################################## @pyqtSlot(int) def on_tabCypher_tabCloseRequested(self, index): """ Process request to close a schema tab @param index DESCRIPTION @type int """ # print("cypher tab index {} request close".format(str(index))) if self.tabCypher.widget(index).tabType == "CYPHER": self.tabCypher.widget(index).close() self.tabCypher.removeTab(index) ############################################################################## # main buttons ############################################################################## @pyqtSlot() def on_btnUndo_clicked(self): """ Slot documentation goes here. """ if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_UNDO) @pyqtSlot() def on_btnRedo_clicked(self): """ Slot documentation goes here. """ if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_REDO) @pyqtSlot() def on_btnComment_clicked(self): """ comment the selected lines. if no lines are selected, then comment the line where the cursor is. adds a single line comment "//" to the very beginning of each selected line. """ if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": startPosition = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONSTART) endPosition = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONEND) startLine = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, startPosition) endLine = self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_LINEFROMPOSITION, endPosition) if startPosition != endPosition: # if there is a selection, then comment every line in the selection for line in range(startLine, endLine + 1): self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_GOTOLINE, line) self.tabCypher.currentWidget().editor.insert("//") else: # there is no selection, so comment the line where the cursor is self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_GOTOLINE, startLine) self.tabCypher.currentWidget().editor.insert("//") @pyqtSlot() def on_btnUnComment_clicked(self): """ uncomment the selected lines, if they have a comment. only supports removing a single line comment "//" at the very beginning of the line. """ if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": startPosition = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONSTART) endPosition = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETSELECTIONEND) startLine = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_LINEFROMPOSITION, startPosition) endLine = self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_LINEFROMPOSITION, endPosition) if startPosition != endPosition: # if there is a selection, then comment every line in the selection for line in range(startLine, endLine + 1): self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_GOTOLINE, line) currentPos = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS) self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_SETSELECTIONSTART, currentPos) self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_SETSELECTIONEND, currentPos + 2) # see if its a // if self.tabCypher.currentWidget().editor.selectedText( ) == "//": self.tabCypher.currentWidget( ).editor.removeSelectedText() else: # this will remove the selection self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GOTOLINE, startLine) else: # there is no selection, so comment the line where the cursor is self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_GOTOLINE, startLine) currentPos = self.tabCypher.currentWidget( ).editor.SendScintilla(QsciScintilla.SCI_GETCURRENTPOS) self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_SETSELECTIONSTART, currentPos) self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_SETSELECTIONEND, currentPos + 2) # see if its a // if self.tabCypher.currentWidget().editor.selectedText( ) == "//": self.tabCypher.currentWidget( ).editor.removeSelectedText() else: # this will remove the selection self.tabCypher.currentWidget().editor.SendScintilla( QsciScintilla.SCI_GOTOLINE, startLine) @pyqtSlot() def on_btnRefresh_clicked(self): """ This refreshes the schema treeview """ self.refreshSchemaModel() @pyqtSlot() def refreshSchemaModel(self): QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.refreshModel() if rc == False: self.helper.displayErrMsg("Refresh Schema", msg) self.populateTree() QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnOpen_clicked(self): """ Slot documentation goes here. """ dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFile) dlg.setAcceptMode(QFileDialog.AcceptOpen) dlg.setNameFilters([ "Cypher Query (*.cyp *.cypher)", "Cypher Query (*.cyp)", "Cypher Query (*.cypher)", "all files (*.*)" ]) dlg.setDirectory(self.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() if fileNames: fileName = fileNames[0] self.addCypherEditorTab(fileName=fileName, mode=MODEEDIT) @pyqtSlot() def on_btnNew_clicked(self): """ Add a new CypherEditnGrid tab """ # update unnamed file counter global unNamedFileCounter unNamedFileCounter = unNamedFileCounter + 1 # Add a new tab with a CypherEditnGrid widget newCypherEditnGrid = CypherEditGridWidget(parent=self, mode=MODENEW) index = self.tabCypher.addTab( newCypherEditnGrid, "{}".format("Unsaved-0{}".format(unNamedFileCounter))) newCypherEditnGrid.tabIndex = index self.tabCypher.setCurrentIndex(index) @pyqtSlot() def on_btnSave_clicked(self): """ Slot documentation goes here. bool QsciScintilla::isModified ( ) const Returns true if the text has been modified. """ #get current tab # print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName)) if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": self.tabCypher.currentWidget().save() @pyqtSlot() def on_btnSaveAs_clicked(self): """ Slot documentation goes here. """ # print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName)) if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": self.tabCypher.currentWidget().saveAs() @pyqtSlot() def on_btnClose_clicked(self): """ Slot documentation goes here. """ # print ("current tab is {}-{}".format(self.tabCypher.currentWidget().tabType, self.tabCypher.currentWidget().tabName)) if self.tabCypher.count() > 0: if self.tabCypher.currentWidget().tabType == "CYPHER": self.on_tabCypher_tabCloseRequested( self.tabCypher.currentIndex()) @pyqtSlot() def on_btnZoomIn_clicked(self): """ iterate thru all the tab widgets and tell them to zoom in """ for index in range(self.tabCypher.count()): self.tabCypher.widget(index).zoomIn() @pyqtSlot() def on_btnZoomOut_clicked(self): """ iterate thru all the tab widgets and tell them to zoom out """ for index in range(self.tabCypher.count()): self.tabCypher.widget(index).zoomOut() ############################################################################## # support functions ############################################################################## def addCypherEditorTab(self, fileName=None, fileText=None, mode=None): if fileName is None: # update unnamed file counter global unNamedFileCounter unNamedFileCounter = unNamedFileCounter + 1 fileName = "{}".format("Unsaved-0{}".format(unNamedFileCounter)) tab = CypherEditGridWidget(parent=self, fileName=fileName, fileText=fileText, mode=mode) head, tail = ntpath.split(QFileInfo(fileName).fileName()) self.tabCypher.addTab(tab, "{}".format(tail)) self.tabCypher.setCurrentWidget(tab) def save(self, ): '''the parent calls this method to tell the cypherPageWidget to save all it's open cyphereditngrid tabs ''' for tabIndex in range(0, self.tabCypher.count()): tab = self.tabCypher.widget(tabIndex) # switch focus to the tab self.tabCypher.setCurrentIndex(tabIndex) # tell the tab to close tab.close() ##################################################################################################### ## file system explorer related methods ##################################################################################################### def setUpFileExplorer(self, folder): tvFileModel = QFileSystemModel() tvFileModel.setRootPath(folder) self.tvFileSystem.setModel(tvFileModel) self.tvFileSystem.setRootIndex(tvFileModel.index(folder)) self.tvFileSystem.hideColumn(1) self.tvFileSystem.hideColumn(2) self.tvFileSystem.hideColumn(3) self.tvFileSystem.setContextMenuPolicy(Qt.CustomContextMenu) self.tvFileSystem.customContextMenuRequested.connect( self.fileSystemMenu) # self.tvFileSystem.doubleClicked.connect(self.tvFileSystem_doubleClicked) self.setPathDisplay(folder) def setPathDisplay(self, pathName): self.editPath.clear() self.editPath.setText(pathName) self.editPath.setToolTip(pathName) @pyqtSlot() def on_btnPickPath_clicked(self): """ The user selects a new folder as the top level of the file system explorer """ folder = str( QFileDialog.getExistingDirectory(self, "Select Directory", self.currentProjPath)) if folder: self.setUpFileExplorer(folder) self.currentProjPath = folder ########################################################################################## ## file system tree view methods ########################################################################################## def fileSystemMenu(self, position): #get a qmodelindex index = self.tvFileSystem.selectedIndexes()[0] if not (index is None): # print("{}-{}".format(index.row(), index.column())) # get a QFileInfo fileInfo = index.model().fileInfo(index) if fileInfo.isFile(): if fileInfo.suffix().upper() == "MDL": # print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName())) menu = QMenu() editCypherAction = menu.addAction("Open Model") editCypherAction.triggered.connect( self.openFileSystemModel) menu.exec_(self.tvFileSystem.mapToGlobal(position)) return if fileInfo.suffix().upper() == "CYP": # print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName())) menu = QMenu() editCypherAction = menu.addAction("Edit Cypher") editCypherAction.triggered.connect( self.openFileSystemCypher) menu.exec_(self.tvFileSystem.mapToGlobal(position)) return def openFileSystemCypher(self, ): index = self.tvFileSystem.selectedIndexes()[0] if not (index is None): # get a QFileInfo fileInfo = index.model().fileInfo(index) fileName = fileInfo.absoluteFilePath() self.logMsg("Open Cypher File: {}".format(fileName)) self.addCypherEditorTab(fileName=fileName, mode=MODEEDIT) def openFileSystemModel(self, ): index = self.tvFileSystem.selectedIndexes()[0] if not (index is None): # get a QFileInfo fileInfo = index.model().fileInfo(index) fileName = fileInfo.absoluteFilePath() self.logMsg("Open Project File: {}".format(fileName)) self.parent.loadProject(fileName=fileName) @pyqtSlot(QModelIndex) def on_tvFileSystem_doubleClicked(self, index): """ User doubleclicks on the file system tree view - if it is a .cyp file then load it into a cypher tab - if is is a .mdl file then load it into a project tab @param index DESCRIPTION @type QModelIndex """ index = self.tvFileSystem.selectedIndexes()[0] # print("{}-{}".format(index.row(), index.column())) fileInfo = index.model().fileInfo(index) if fileInfo.isFile(): if fileInfo.suffix().upper() == "MDL": # print ("{}-{}-{}".format(fileInfo.path(), fileInfo.suffix(), fileInfo.fileName())) self.openFileSystemModel() if fileInfo.suffix().upper() == "CYP": self.openFileSystemCypher() @pyqtSlot(QModelIndex) def on_tvSchema_doubleClicked(self, index): """ User doubleclicks on the schema tree view - if it is a user then show user editor - if is is a role then show role editor @param index DESCRIPTION @type QModelIndex """ index = self.tvSchema.selectedIndexes()[0] # print("{}-{}".format(index.row(), index.column())) selected = self.tvSchema.currentItem() if not (selected is None): parent = self.tvSchema.currentItem().parent() if not (parent is None): # print(parent.data(0, 0)) # print(self.tvSchema.currentItem().data(0,0)) if parent.data(0, 0) == "User": self.editUser() if parent.data(0, 0) == "Role": self.editRole()
class CypherEditGridWidget(QWidget, Ui_cypherEditGridWidget): """ Class documentation goes here. """ def __init__(self, parent=None, fileName=None, fileText=None, mode=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CypherEditGridWidget, self).__init__(parent) self.setupUi(self) self.parent = parent self.settings = QSettings() self.initUI() self.initScintilla() self.helper = Helper() self.mode = mode self.tabType = "CYPHER" self.tabName = fileName self.tabIndex = None # this is the index into the tabWidget of the tab this widget is on self.fileName = fileName self.fileText = fileText self.resultSet = None # create a neocon object for this file tab self.neoDriver = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # add the data grid widget. self.dataGridGeneric = DataGridGeneric() self.dataGrid = DataGridWidget(self, neoCon=self.neoDriver, genCypher=self.dataGridGeneric) self.nodeGridLayout = QVBoxLayout(self.frmDataGrid) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.setContentsMargins(1, 1, 1, 1) self.nodeGridLayout.setSpacing(1) self.nodeGridLayout.addWidget(self.dataGrid) if self.mode == MODENEW: if not self.fileText is None: self.loadText() if self.mode == MODEEDIT: self.loadFile() # position the splitter self.show( ) # you have to do this to force all the widgets sizes to update half = int((self.frmEditnGrid.height() / 2)) self.splitter.setSizes([half, half]) def logMsg(self, msg): if logging: logging.info(msg) def initUI(self, ): #initialize state of commit buttons and dropdown self.btnCommit.setEnabled(False) self.btnRollBack.setEnabled(False) self.cmbAutoCommit.setCurrentIndex(0) def initScintilla(self): # add and initialize the control to self.frmEditor self.editor = QsciScintilla() self.editor.setLexer(None) self.editor.setUtf8(True) # Set encoding to UTF-8 self.editor.setWrapMode(QsciScintilla.WrapNone) self.editor.setEolVisibility(False) self.editor.setIndentationsUseTabs(False) self.editor.setTabWidth(4) self.editor.setIndentationGuides(True) self.editor.setAutoIndent(True) self.editor.setMarginType(0, QsciScintilla.NumberMargin) self.editor.setMarginWidth(0, "00000") self.editor.setMarginsForegroundColor(QColor("#ffffffff")) self.editor.setMarginsBackgroundColor(QColor("#00000000")) self.verticalLayout_2.addWidget(self.editor) # setup the lexer self.lexer = CypherLexer(self.editor) self.editor.setLexer(self.lexer) self.setScintillaFontSize() self.editor.SendScintilla(self.editor.SCI_GETCURRENTPOS, 0) self.editor.setCaretForegroundColor(QColor("#ff0000ff")) self.editor.setCaretLineVisible(True) self.editor.setCaretLineBackgroundColor(QColor("#1f0000ff")) self.editor.setCaretWidth(2) self.editor.setBraceMatching(QsciScintilla.SloppyBraceMatch) def setScintillaFontSize(self, ): # set font size to value saved in settings try: fontSize = int(self.settings.value("Lexer/FontSize", "10")) except: fontSize = 10 finally: for style in range(5): self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, fontSize) self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, fontSize) self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, fontSize) ##################################################################################### # methods related to the cypher file ##################################################################################### def loadText(self, ): QApplication.setOverrideCursor(Qt.WaitCursor) self.editor.append(self.fileText) self.editor.setModified(True) QApplication.restoreOverrideCursor() def loadFile(self, ): file = QFile(self.fileName) if not file.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning( self, "NodeMaker", "Cannot read file %s:\n%s." % (self.fileName, file.errorString())) return False instr = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) self.editor.append(instr.readAll()) self.editor.setModified(False) QApplication.restoreOverrideCursor() def save(self, ): if self.mode == MODENEW: self.saveAs() else: self.saveIt() def saveAs(self, ): # first test to see if the file has changed # get filename to save as # dlg = QFileDialog() dlg.setAcceptMode(QFileDialog.AcceptSave) dlg.setDefaultSuffix("cyp") dlg.setNameFilters([ "Cypher Query (*.cyp *.cypher)", "Cypher Query (*.cyp)", "Cypher Query (*.cypher)", "all files (*.*)" ]) dlg.setDirectory(self.parent.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() if fileNames: self.fileName = fileNames[0] # save the file self.saveIt() def saveIt(self, ): file = QFile(self.fileName) if not file.open(QFile.WriteOnly | QFile.Text): QMessageBox.warning( self, "NodeEra", "Cannot write file %s:\n%s." % (self.fileName, file.errorString())) return outstr = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) outstr << self.editor.text() head, tail = ntpath.split(QFileInfo(self.fileName).fileName()) self.parent.tabCypher.setTabText(self.parent.tabCypher.currentIndex(), tail) self.mode = MODEEDIT QApplication.restoreOverrideCursor() def close(self, ): # print("editngrid close {}".format(self.fileName)) # see if there is an open transaction and cancel the close self.checkOpenTxn() # see if text has changed and save the file if the user wants to if self.editor.isModified(): # see if the user wants to save it if self.fileName is None: # these are unsaved cypher files so they have no filename yet displayName = self.parent.tabCypher.tabText(self.tabIndex) else: displayName = self.fileName if self.helper.saveChangedObject("Cypher File", displayName): self.save() return True ############################################################## # Button methods ############################################################## @pyqtSlot() def on_btnRun_clicked(self): """ Run the query at the cursor. """ self.runFileCursor() def runFileCursor(self): self.logMsg("User requests run Cypher in Cursor") # parse the text editor and get the index to the cypher command the cursor is pointing at currentCypherIndex = self.getSelectedCypher() # check if cursor in a query if currentCypherIndex is None: self.helper.displayErrMsg( "Run Query", "You must position cursor within the Cypher query.") QApplication.restoreOverrideCursor() return # get the cypher statement to be executed startOffset = self.cypherList[currentCypherIndex][0] self.dataGrid.cypher = self.cypherList[currentCypherIndex][1] # prompt the user for parameter values if any userCanceled = False if len(self.cypherParms[currentCypherIndex][1]) > 0: # print("Parms:{}".format(self.cypherParms[currentCypherIndex][1])) # display dialog to gather parms d = CypherParmEntryDlg( parent=self, parms=self.cypherParms[currentCypherIndex][1]) if d.exec_(): self.dataGrid.parmData = d.parmDict else: userCanceled = True self.dataGrid.parmData = None # print("Parm Dictionary:{}".format(self.dataGrid.parmData)) else: self.dataGrid.parmData = None # see if the user canceled the query rather than enter parameters if userCanceled: self.helper.displayErrMsg("Run Query", "User Canceled Query.") QApplication.restoreOverrideCursor() return # make sure the cypher is not just spaces, or nothing but a semicolon if (self.dataGrid.cypher.isspace() or len(self.dataGrid.cypher) == 0 or self.dataGrid.cypher.strip() == ";"): self.helper.displayErrMsg( "Run Query", "You must position cursor within the Cypher query.") else: QApplication.setOverrideCursor(Qt.WaitCursor) self.dataGrid.refreshGrid() QApplication.restoreOverrideCursor() # see if there was a syntax error and position cursor try: offset = self.dataGrid.neoCon.cypherLogDict["offset"] if offset > -1: self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS, offset + startOffset) except: pass finally: ### hack needed on mac os to force scintilla to show cursor and highlighted line self.helper.displayErrMsg("Run Query With Cursor", "Query Complete") def getSelectedCypher(self): ''' Build a list of cypher commands from the text in the editor ''' # get position of cursor which is a zero based offset, it seems to return zero if editor hasn't been clicked on yet try: position = self.editor.SendScintilla( QsciScintilla.SCI_GETCURRENTPOS, 0) except: position = 0 # initialize cypherList self.cypherList = [] self.cypherParms = [] parmList = [] currentCypherIndex = None # get the full text from the editor text = self.editor.text() # make sure there is something in the text if len(text) < 1: return currentCypherIndex # Walk through all the characters in text, and store start offset and end offset of each command startOffset = 0 endOffset = 0 foundCypher = False # tracks if we have found at least one character that is potentially a non-comment cypher command inComment = False # tracks if we're looking at comment characters inParm = False # tracks if we're looking at parameter characters inCypher = False # tracks if we've found a non comment character while scanning newParm = "" for chrCtr in range(0, len(text)): # print("before: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher)) # see if we're in a comment ( this doesn't work for multiline comments) if chrCtr + 1 < len(text) and text[chrCtr] == '/': inParm = False if text[chrCtr + 1] == "/": inComment = True inCypher = False # see if end of line elif ord(text[chrCtr]) in [13, 10]: inParm = False if chrCtr + 1 < len(text): if not text[chrCtr + 1] in [13, 10]: # end of line ends the comment inComment = False elif text[chrCtr] == "$": if not inComment: foundCypher = True inParm = True elif text[chrCtr] == " ": if not inComment: foundCypher = True inParm = False elif (text[chrCtr] == ";" and inComment == False): foundCypher = True inParm = False endOffset = chrCtr # save each command in the list self.cypherList.append( [startOffset, text[startOffset:endOffset + 1]]) self.cypherParms.append([startOffset, parmList]) parmList = [] # HAPPY PATH - see if this is the command where the cursor is located if (position >= startOffset and position <= endOffset): currentCypherIndex = len(self.cypherList) - 1 # set the next starting offset startOffset = chrCtr + 1 endOffset = startOffset elif inComment == False: foundCypher = True inCypher = True if inParm: newParm = newParm + text[chrCtr] else: if len(newParm) > 0: # print("Parameter: {}".format(newParm)) parmList.append(newParm) newParm = "" # print("after: chrCtr:{} char: {} ord:{} inComment:{} inParm:{} inCypher:{}".format(chrCtr, text[chrCtr], ord(text[chrCtr]), inComment, inParm, inCypher)) # at this point all characters have been processed, must deal with edge cases, no final semicolon etc if len( self.cypherList ) == 0: # we never found a semi colon so the entire file is one cypher statement # return the entire text file self.cypherList.append([0, text]) self.cypherParms.append([0, parmList]) parmList = [] currentCypherIndex = 0 else: # we found some characters after the last semi colon. lastCypher = "" try: lastCypher = text[startOffset:len(text)] if lastCypher.isspace( ) == True: # if there is only whitespace after the last semicolon then return the last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 elif len( lastCypher ) < 1: # there are no characters after the last semicolon, but cursor is positioned past it then return the last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 elif inCypher == False and foundCypher == False: # none of the characters are outside a comment so return last found cypher if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 else: self.cypherList.append( [startOffset, lastCypher] ) # since some characters are present, add them as the last cypher command self.cypherParms.append([startOffset, parmList]) parmList = [] if currentCypherIndex is None: currentCypherIndex = len(self.cypherList) - 1 except: if currentCypherIndex is None: currentCypherIndex = len( self.cypherList ) - 1 # return the last cypher command found if any error # print("cypher list: {}".format(self.cypherList)) return currentCypherIndex @pyqtSlot() def on_btnRunScript_clicked(self): """ this button will run all the cypher commands in the file one at a time. """ self.runFileAsScript() def runFileAsScript(self, ): ''' run each cypher command in the file one at a time. ''' QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests run Cypher file as a script") # parse the text editor into cypher commands, we don't care which one the cursor is in self.getSelectedCypher() if len(self.cypherList) < 1: self.helper.displayErrMsg( "Run File As Script", "The file has no cypher commands in it.") for cypherCmd in self.cypherList: # this is the starting offset of the cypher command in the entire file cypherOffset = cypherCmd[0] self.dataGrid.cypher = cypherCmd[1] # set editor selection to the cypher command self.editor.SendScintilla(QsciScintilla.SCI_SETSEL, cypherOffset, cypherOffset + len(self.dataGrid.cypher)) # skip any cypher is not just spaces, or nothing but a semicolon if (self.dataGrid.cypher.isspace() or len(self.dataGrid.cypher) == 0 or self.dataGrid.cypher.strip() == ";"): #self.helper.displayErrMsg("Run Query", "You must position cursor within the Cypher query.") pass else: self.dataGrid.refreshGrid() QApplication.processEvents() # see if there was a syntax error and position cursor try: offset = self.dataGrid.neoCon.cypherLogDict["offset"] if offset > -1: self.editor.SendScintilla(QsciScintilla.SCI_GOTOPOS, offset + cypherOffset) except: pass # set editor selection to the end of the file self.editor.SendScintilla(QsciScintilla.SCI_SETSEL, len(self.editor.text()), len(self.editor.text())) QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnCommit_clicked(self): """ User clicks on the Commit button. Commit the TXN. """ self.doCommit() def doCommit(self): self.logMsg("User request Commit the transaction") if not self.neoDriver is None: rc, msg = self.neoDriver.commitTxn() self.logMsg("Commit Complete - {}".format(msg)) @pyqtSlot() def on_btnRollBack_clicked(self): """ User clicks on the Rollback button. Rollback the TXN. """ self.doRollBack() def doRollBack(self): self.logMsg("User request Rollback the transaction") if not self.neoDriver is None: rc, msg = self.neoDriver.rollbackTxn() self.logMsg("Rollback Complete - {}".format(msg)) def zoomIn(self, ): """ increase Font Size """ # self.editor.SendScintilla(QsciScintilla.SCI_ZOOMIN) # currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0) # self.settings.setValue("Lexer/FontSize", currentFontSize) # get style 0 font size - all styles use same size currentFontSize = self.editor.SendScintilla( QsciScintilla.SCI_STYLEGETSIZE, 0) currentFontSize = currentFontSize + 2 if currentFontSize < 24: self.settings.setValue("Lexer/FontSize", currentFontSize) for style in range(255): self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize) def zoomOut(self, ): """ decrease font size """ # self.editor.SendScintilla(QsciScintilla.SCI_ZOOMOUT) # currentFontSize = self.editor.SendScintilla(QsciScintilla.SCI_STYLEGETSIZE, 0) # self.settings.setValue("Lexer/FontSize", currentFontSize) # get style 0 font size - all styles use same size currentFontSize = self.editor.SendScintilla( QsciScintilla.SCI_STYLEGETSIZE, 0) currentFontSize = currentFontSize - 2 if currentFontSize > 4: self.settings.setValue("Lexer/FontSize", currentFontSize) for style in range(255): # was 5 self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, style, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 34, currentFontSize) # self.editor.SendScintilla(QsciScintilla.SCI_STYLESETSIZE, 35, currentFontSize) def checkOpenTxn(self): ''' check if current txn is still open. if autocommit is false then ask the user to commit or rollback if autocommit is true then do the commit ''' if not (self.neoDriver is None): if not (self.neoDriver.tx is None): if self.neoDriver.tx.closed() is False: if self.neoDriver.autoCommit is True: self.doCommit() else: # prompt user to commit or rollback the current transaction msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setText( "You have an uncommitted transaction. Do you want to commit? (click Yes to commit, No to rollback" ) msgBox.setWindowTitle("Commit or Rollback") msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) result = msgBox.exec_() if result == QMessageBox.Yes: self.doCommit() else: self.doRollBack() @pyqtSlot(int) def on_cmbAutoCommit_currentIndexChanged(self, index): """ User has changed the auto commit dropdown. @param index DESCRIPTION @type int """ self.logMsg("User request transaction mode changed to {}".format( self.cmbAutoCommit.currentText())) if self.cmbAutoCommit.currentText() == "Auto Commit On": self.checkOpenTxn() if not (self.neoDriver is None): self.neoDriver.setAutoCommit(True) self.btnCommit.setEnabled(False) self.btnRollBack.setEnabled(False) if self.cmbAutoCommit.currentText() == "Auto Commit Off": self.checkOpenTxn() if not (self.neoDriver is None): self.neoDriver.setAutoCommit(False) self.btnCommit.setEnabled(True) self.btnRollBack.setEnabled(True)
class DataGridWidget(QWidget, Ui_DataGridWidget): """ Class documentation goes here. """ def __init__(self, parent=None, neoCon=None, genCypher=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(DataGridWidget, self).__init__(parent) self.setupUi(self) self.helper = Helper() self.neoTypeFunc = NeoTypeFunc() self.resultSet = None self.cypher = None self.parmData = None self.editParmDict = None self.genCypher = genCypher self.templateDict = self.genCypher.templateDict # the parent widget must supply a neoCon # this is the NeoDriver instance, but we still use the older neocon variable at this level in the code self.neoCon = neoCon self.parent = parent try: self.designModel = parent.designModel except: self.designModel = None self.neoCon.setAutoCommit(True) self.initUI() # data grid scrolling self.topRow = 1 # data grid cell selection variables self.saveIndex = None self.prevIndex = None self.saveData = None def initUI(self, ): # get list of allowed functions on the data grid widget self.btnRefresh.setEnabled(self.genCypher.refreshOK()) self.btnExport.setEnabled(self.genCypher.exportOK()) self.btnNew.setEnabled(self.genCypher.newOK()) self.btnDelete.setEnabled(self.genCypher.deleteOK()) self.btnSetNull.setEnabled(self.genCypher.setNullOK()) self.btnRefresh.setVisible(self.genCypher.refreshOK()) self.btnExport.setVisible(self.genCypher.exportOK()) self.btnNew.setVisible(self.genCypher.newOK()) self.btnDelete.setVisible(self.genCypher.deleteOK()) self.btnSetNull.setVisible(self.genCypher.setNullOK()) # log grid self.gridLog.setModel(self.createLogModel()) self.gridLog.setSortingEnabled(False) self.gridLog.setWordWrap(True) self.gridLog.setColumnWidth(0, 150) self.gridLog.setColumnWidth(PLAN, 100) self.gridLog.setColumnWidth(ERROR, 100) self.gridLog.setColumnWidth(CYPHER, 500) for x in range(UPDATES, IDXDEL + 1): self.gridLog.setColumnWidth(x, 150) self.gridLog.verticalHeader().setDefaultAlignment(Qt.AlignTop) # data grid self.gridCypherData.setAlternatingRowColors(True) # turn on row selection mode if self.genCypher.rowSelect(): self.gridCypherData.setSelectionBehavior( QAbstractItemView.SelectRows) def logMsg(self, msg): ''' This method writes the message to the trace tab and the application log ''' # print("datagridwidget log method: {}".format(msg)) # add message to the log if logging: logging.info(msg) # add message to the trace tab ts = ('%s' % datetime.datetime.now()) outmsg = "{} - {}".format(ts, msg) self.textTrace.append(outmsg) def logWatch(self, ): return # httpWatch = self.httpCapturer.getvalue() # boltWatch = self.boltCapturer.getvalue() # # # update trace tab # if len(httpWatch) > 0: # self.logMsg("HTTP Watch: {}".format(httpWatch)) # if len(boltWatch) > 0: # self.logMsg("BOLT Watch: {}".format(boltWatch)) # # close and reopen the stream to clear it out # self.closeWatch() # self.initWatch() ##################################################################################### # methods related to the log tab ##################################################################################### def createLogModel(self, ): # TS, DURATION, CYPHER, ERROR, PLAN, UPDATES, LBLADD, LBLREMOVE, PROPSET, NODEADD, NODEDEL, RELADD, RELDEL, CONADD, CONDEL, IDXADD, IDXDEL = range(15) model = QStandardItemModel(0, 17) model.setHeaderData(TS, Qt.Horizontal, "Start Time") model.setHeaderData(DURATION, Qt.Horizontal, "Duration") model.setHeaderData(CYPHER, Qt.Horizontal, "Cypher") model.setHeaderData(ERROR, Qt.Horizontal, "Error") model.setHeaderData(PLAN, Qt.Horizontal, "Plan") model.setHeaderData(UPDATES, Qt.Horizontal, "Updates") model.setHeaderData(LBLADD, Qt.Horizontal, "Labels Added") model.setHeaderData(LBLDEL, Qt.Horizontal, "Labels Removed") model.setHeaderData(PROPSET, Qt.Horizontal, "Properties Updated") model.setHeaderData(NODEADD, Qt.Horizontal, "Nodes Created") model.setHeaderData(NODEDEL, Qt.Horizontal, "Nodes Deleted") model.setHeaderData(RELADD, Qt.Horizontal, "Relationships Created") model.setHeaderData(RELDEL, Qt.Horizontal, "Relationships Deleted") model.setHeaderData(CONADD, Qt.Horizontal, "Constraints Added") model.setHeaderData(CONDEL, Qt.Horizontal, "Constraints Deleted") model.setHeaderData(IDXADD, Qt.Horizontal, "Indexes Added") model.setHeaderData(IDXDEL, Qt.Horizontal, "Indexes Deleted") model.rowsInserted.connect(self.autoScroll) return model def refreshSchemaTreeView(self): ''' tell the schema editor to refresh its tree view if any schema objects changed ''' if not self.neoCon.stats is None: try: # see if any schema objects changed if (self.neoCon.stats.constraints_added > 0 or self.neoCon.stats.constraints_removed > 0 or self.neoCon.stats.indexes_added > 0 or self.neoCon.stats.indexes_removed > 0 or self.neoCon.stats.labels_added > 0 or self.neoCon.stats.labels_removed > 0): # print("refresh schema model") self.parent.parent.refreshSchemaModel() except: pass return def addGridRow(self, ): # TS, DURATION, CYPHER, ERROR, PLAN, UPDATES, LBLADD, LBLREMOVE, PROPSET, NODEADD, NODEDEL, RELADD, RELDEL, CONADD, CONDEL, IDXADD, IDXDEL = range(15) ''' add a row to the log grid ''' cypherLogDict = self.neoCon.cypherLogDict model = self.gridLog.model() stats = self.neoCon.stats self.refreshSchemaTreeView() try: deltaTime = ( '%s' % (cypherLogDict['endTime'] - cypherLogDict['startTime'])) except: deltaTime = "no delta time" item1 = QStandardItem('%s' % cypherLogDict.get('startTime', 'no start time')) item1.setEditable(False) item1.setData(Qt.AlignTop, Qt.TextAlignmentRole) item2 = QStandardItem(deltaTime) item2.setEditable(False) item2.setData(Qt.AlignTop, Qt.TextAlignmentRole) item3 = QStandardItem(cypherLogDict.get('cypher', 'no cypher')) item3.setEditable(False) item3.setData(Qt.AlignTop, Qt.TextAlignmentRole) # make the plan column wider if there is any output if (str(cypherLogDict.get('plan', "")) == "{}" or str(cypherLogDict.get('plan', "")) == "") or cypherLogDict.get('plan', None) is None: planOutput = "" else: planOutput = str(cypherLogDict.get('plan', "No Plan Output")) # increase the width of the column. self.gridLog.setColumnWidth(PLAN, 300) itemX = QStandardItem(planOutput) itemX.setEditable(False) itemX.setData(Qt.AlignTop, Qt.TextAlignmentRole) # make the error message column wider if there is any output if (str(cypherLogDict.get('error', "")) == "{}" or str(cypherLogDict.get('error', "")) == ""): errorOutput = "" else: errorOutput = cypherLogDict.get('error', "") self.gridLog.setColumnWidth(ERROR, 300) itemE = QStandardItem(errorOutput) itemE.setEditable(False) itemE.setData(Qt.AlignTop, Qt.TextAlignmentRole) if not stats is None: try: updates = stats.contains_updates except: updates = 'unknown' item4 = QStandardItem(str(updates)) item4.setEditable(False) item4.setData(Qt.AlignTop, Qt.TextAlignmentRole) item5 = QStandardItem(str(stats.labels_added)) item5.setEditable(False) item5.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item6 = QStandardItem(str(stats.labels_removed)) item6.setEditable(False) item6.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item7 = QStandardItem(str(stats.properties_set)) item7.setEditable(False) item7.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item8 = QStandardItem(str(stats.nodes_created)) item8.setEditable(False) item8.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item9 = QStandardItem(str(stats.nodes_deleted)) item9.setEditable(False) item9.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item10 = QStandardItem(str(stats.relationships_created)) item10.setEditable(False) item10.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item11 = QStandardItem(str(stats.relationships_deleted)) item11.setEditable(False) item11.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item12 = QStandardItem(str(stats.constraints_added)) item12.setEditable(False) item12.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item13 = QStandardItem(str(stats.constraints_removed)) item13.setEditable(False) item13.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item14 = QStandardItem(str(stats.indexes_added)) item14.setEditable(False) item14.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) item15 = QStandardItem(str(stats.indexes_removed)) item15.setEditable(False) item15.setData(Qt.AlignTop | Qt.AlignRight, Qt.TextAlignmentRole) else: item4 = QStandardItem('') item4.setEditable(False) item4.setData(Qt.AlignTop, Qt.TextAlignmentRole) item5 = QStandardItem('') item5.setEditable(False) item5.setData(Qt.AlignTop, Qt.TextAlignmentRole) item6 = QStandardItem('') item6.setEditable(False) item6.setData(Qt.AlignTop, Qt.TextAlignmentRole) item7 = QStandardItem('') item7.setEditable(False) item7.setData(Qt.AlignTop, Qt.TextAlignmentRole) item8 = QStandardItem('') item8.setEditable(False) item8.setData(Qt.AlignTop, Qt.TextAlignmentRole) item9 = QStandardItem('') item9.setEditable(False) item9.setData(Qt.AlignTop, Qt.TextAlignmentRole) item10 = QStandardItem('') item10.setEditable(False) item10.setData(Qt.AlignTop, Qt.TextAlignmentRole) item11 = QStandardItem('') item11.setEditable(False) item11.setData(Qt.AlignTop, Qt.TextAlignmentRole) item12 = QStandardItem('') item12.setEditable(False) item12.setData(Qt.AlignTop, Qt.TextAlignmentRole) item13 = QStandardItem('') item13.setEditable(False) item13.setData(Qt.AlignTop, Qt.TextAlignmentRole) item14 = QStandardItem('') item14.setEditable(False) item14.setData(Qt.AlignTop, Qt.TextAlignmentRole) item15 = QStandardItem('') item15.setEditable(False) item15.setData(Qt.AlignTop, Qt.TextAlignmentRole) model.appendRow([ item1, item2, item3, itemX, itemE, item4, item5, item6, item7, item8, item9, item10, item11, item12, item13, item14, item15, ]) self.gridLog.resizeRowsToContents() QTimer.singleShot(0, self.gridLog.scrollToBottom) def autoScroll(self): # position on last row in gird lastRow = self.gridLog.model().rowCount() index = self.gridLog.model().index(lastRow, 0, parent=QModelIndex()) self.gridLog.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridLog.selectRow(index.row()) def clearGridLog(self, ): self.gridLog.model().removeRows(0, self.gridLog.model().rowCount()) @pyqtSlot() def on_btnExportLog_clicked(self): """ Export the contents of the log tab to .csv file """ # get filename to save as dlg = QFileDialog() dlg.setAcceptMode(QFileDialog.AcceptSave) dlg.setDefaultSuffix("csv") dlg.setNameFilters(["Log File (*.csv)", "all files (*.*)"]) dlg.setDirectory(self.parent.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() try: if fileNames: self.fileName = fileNames[0] # save the file with open(self.fileName, 'w', newline='') as csvfile: csvWriter = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) for row in range(self.gridLog.model().rowCount()): rowItems = [] for col in range( self.gridLog.model().columnCount()): value = self.gridLog.model().index( row, col, QModelIndex()).data(Qt.DisplayRole) rowItems.append(value) csvWriter.writerow(c for c in rowItems) except BaseException as e: msg = "{} - {} failed.".format("Write CSV", repr(e)) self.helper.displayErrMsg("Export CSV Error", msg) @pyqtSlot() def on_btnClearLog_clicked(self): """ Clear the log grid """ self.clearGridLog() ##################################################################################### # methods related to the data tab ##################################################################################### def forceSelectionChange(self, ): ''' this method changes the current selection to force any unprocessed updates to take place. ''' self.gridCypherData.clearSelection() def displayDataRetrievedMsg(self, ): if self.neoCon.endReached == True: msg = "All Data Retrieved." else: msg = "More Data Exists." self.displayGridMessage("Retrieved {} records. {}".format( self.neoCon.chunkEnd, msg)) # self.txtPosition.setText("Retrieved {} records. {}".format(self.neoCon.chunkEnd, msg )) def displayGridMessage(self, message): if not message is None: self.txtPosition.setText(message) else: self.txtPosition.setText("") def clearModel(self): # if we already have a model get rid of it if not (self.resultSet is None): del self.resultSet self.gridCypherData.setModel(None) def newResultModel(self): # headers = self.neoCon.cursor.keys() headers = self.neoCon.result.keys() if not (headers is None): self.resultModel = QStandardItemModel(0, len(headers)) for index, header in enumerate(headers): self.resultModel.setHeaderData(index, Qt.Horizontal, header) # assign the model to the grid self.gridCypherData.setModel(self.resultModel) # set the editor delegate self.gridCypherData.setItemDelegate(NeoEditDelegate(self)) # connect model slots self.resultModel.itemChanged.connect(self.resultModelItemChanged) # connect grid slots self.gridCypherData.selectionModel().selectionChanged.connect( self.dataGridSelectionChanged) self.gridCypherData.resizeColumnsToContents() def runFileCursor(self, ): ''' 1. start dialog box to Run a cypher query and return a cursor to the result set 2. Retrieve the first chunk of data ''' # QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests retrieve {}".format(self.genCypher.type)) # clear the grid self.clearModel() try: rc = False self.logMsg(self.cypher) # the dialog box actually spins up a thread that runs the query d = GetCursorDlg(neoCon=self.neoCon, cypher=self.cypher, mode="cursor", parmData=self.parmData) if d.exec_(): rc1 = d.rc msg1 = d.msg if rc1: self.logMsg("run cypher {}".format(msg1)) if not self.neoCon.stats is None: self.logMsg("stats {}".format(self.neoCon.stats)) self.newResultModel() #get the first chunk x, msg2 = self.neoCon.forwardCursor() self.logMsg("rows fetched: {} - {}".format(str(x), msg2)) self.loadModelChunk() self.tabWidget.setCurrentIndex(DATA) self.displayDataRetrievedMsg() # force a repaint of the grid. this is needed as a workaround for MAC OS self.gridCypherData.repaint() # end workaround rc = True msg = "Run cypher complete" else: msg = "run cypher Error {}".format(msg1) except BaseException as e: msg = "{} - Query failed.".format(repr(e)) finally: # update trace tab self.logWatch() # add the row to the log grid self.addGridRow() # set tab focus depending on results QApplication.restoreOverrideCursor() if rc == False: # display error message then switch to trace tab self.helper.displayErrMsg("Run Query With Cursor", msg) self.tabWidget.setCurrentIndex(TRACE) else: # switch to data tab self.tabWidget.setCurrentIndex(DATA) self.logMsg(msg) # - append the table model with the current cursor chunk def loadModelChunk(self, ): self.gridCypherData.setSortingEnabled(False) for record in self.neoCon.cursorChunk: itemList = [] # generate the qstandarditem for each column in the result set based on the editParmDict dictionary # value has the correct python object for each cell in the result set # need to store both the original value and the string representation for index, value in enumerate(record.values()): item = QStandardItem() #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP, RELNAME if not self.editParmDict is None: columnType = self.editParmDict[index][0] editable = self.editParmDict[index][1] else: columnType = UNKNOWN editable = False # get the data type of the retrieved data if not null if not value is None: dataType = self.neoTypeFunc.getNeo4jDataType(value) else: # get the property name from the header propName = self.gridCypherData.model().headerData( index, Qt.Horizontal, Qt.DisplayRole) if not self.editParmDict is None: dataTypeLookup = "" dataType = None # get the datatype defined for the property name in the node or rel template if "properties" in self.templateDict: dataTypeLookup = [ prop[1] for prop in self.templateDict["properties"] if prop[0] == propName ] if len(dataTypeLookup) > 0: dataType = dataTypeLookup[0] else: # see if you can infer the datatype from the column type if columnType == NODE: dataType = "Node" if columnType == RELATIONSHIP: dataType = "Relationship" if dataType is None: dataType = DataType.UNKNOWN.value else: dataType = DataType.UNKNOWN.value # UNKNOWN DATA TYPES FROM DYNAMIC CYPHER if columnType == UNKNOWN: if dataType == "Node": nodeText = ("({} {})".format(str(value.labels), str(dict(value)))) item.setText(nodeText) else: if value is None: item.setText("Null") else: displayText = self.neoTypeFunc.convertTypeToString( value) item.setText(displayText) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # these are never editable so just display them elif columnType in (NODEID, RELID, RELNAME): item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setText(str(value)) elif columnType == NODE: # if str(editable) != "True": # if not editable then set the flags, otherwise the default flags are ok if not editable: item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if value is None: item.setText("Null") else: nodeText = ("({} {})".format(str(value.labels), str(dict(value)))) item.setText(nodeText) elif columnType == RELATIONSHIP: if str(editable) == "True": item.setText(str(value)) else: item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if value is None: item.setText("Null") else: item.setText(str(value)) elif columnType == PROP: if not editable: item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if value is None: item.setText("Null") else: displayText = self.neoTypeFunc.convertTypeToString( value) item.setText(displayText) elif columnType in (OPTLBL, REQLBL): item.setFlags(Qt.NoItemFlags) self.gridCypherData.setColumnWidth(index, 100) self.gridCypherData.horizontalHeader( ).setSectionResizeMode(index, QHeaderView.Fixed) # set the state of the checkbox based on the data returned by the query. if value is None: item.setCheckState(Qt.Unchecked) elif str(value) == 'True': item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) if str(editable) == "True": item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) else: item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # store the actual data in the user defined role item.setData(value, Qt.UserRole) # store the datatype in role + 1 item.setData(dataType, Qt.UserRole + 1) # print("Column: Value retrieved: {} python type: {}".format(str(value), type(value))) # add the item just created to the item list itemList.append(item) # add the entire item list to the model. this adds a row of data to the grid self.gridCypherData.model().appendRow(itemList) # set self.gridCypherData.resizeColumnsToContents() def runCypher(self, requestType, cypher): ''' Run a Cypher query and return the entire result set ''' QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests {}".format(requestType)) try: rc = False self.logMsg(cypher) #run the query rc1, msg1 = self.neoCon.runCypherAuto(cypher) if rc1: self.logMsg("{} Node {}".format(requestType, msg1)) self.logMsg("stats {}".format(self.neoCon.stats)) rc = True msg = "{} Node complete".format(requestType) else: msg = "{} Node Error {}".format(requestType, msg1) except BaseException as e: msg = "{} - {} failed.".format(requestType, repr(e)) finally: # update trace tab self.logWatch() # add the row to the log grid self.addGridRow() # set tab focus depending on results QApplication.restoreOverrideCursor() if rc == False: self.helper.displayErrMsg("Process Node", msg) self.tabWidget.setCurrentIndex(TRACE) else: # show positive message at bottom of grid self.displayGridMessage(msg) self.tabWidget.setCurrentIndex(DATA) self.logMsg(msg) @pyqtSlot() def on_btnExport_clicked(self): """ Export the query result set to a csv file """ self.logMsg("User requests export current query") # get filename to save as dlg = DlgExportCSV(parent=self) if dlg.exec_(): try: self.logMsg("export {}".format(dlg.fileName)) # # run the query, fetch all rows into the resultset if (not self.cypher is None and len(self.cypher) > 0): self.runCypher("Export Current Query", self.cypher) # write the csv file with open(dlg.fileName, 'w', newline='') as csvfile: csvwriter = csv.writer( csvfile, delimiter=dlg.delimiterChar, quotechar=dlg.quoteChar, quoting=dlg.useQuote, doublequote=dlg.doubleQuote, escapechar=dlg.escapeChar, lineterminator=dlg.lineTerminator) if dlg.writeHeader: # csvwriter.writerow(self.neoCon.cursor.keys()) csvwriter.writerow(self.neoCon.result.keys()) for record in self.neoCon.resultSet: csvwriter.writerow(record.values()) self.helper.displayErrMsg("Export Query", "Data Export Complete!") else: self.logMsg("No current query to export") self.helper.displayErrMsg("Export Query", "No current query to export") except BaseException as e: self.logMsg("Error Exporting Data - {}".format(repr(e))) self.helper.displayErrMsg( "Export Query", "Error Exporting Data - {}".format(repr(e))) @pyqtSlot() def on_btnBegin_clicked(self): """ position back to the first row in the result set """ self.logMsg("User requests scroll to top") index = self.gridCypherData.model().index(0, 0, parent=QModelIndex()) self.gridCypherData.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridCypherData.selectRow(0) self.displayDataRetrievedMsg() self.gridCypherData.repaint() @pyqtSlot() def on_btnBack_clicked(self): """ scroll the table back one "chunk" """ # set top row to the row of the currently selected item if self.gridCypherData.currentIndex().row() < 0: self.topRow = 0 else: self.topRow = self.gridCypherData.currentIndex().row() # backup by chunksize self.topRow = self.topRow - self.neoCon.chunkSize if self.topRow < 0: self.topRow = 0 index = self.gridCypherData.model().index(self.topRow, 0, parent=QModelIndex()) self.gridCypherData.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridCypherData.selectRow(self.topRow) self.displayDataRetrievedMsg() self.gridCypherData.repaint() @pyqtSlot() def on_btnForward_clicked(self): """ if positioned in the last chunk: move the cursor through the result set one "chunk". scroll the table to the first row of the most recently retrieved chunk. if positioned prior to the last chunk: scroll the table forward to the beginning of the next chunk. no data is retrieved. """ QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests scroll forward") # set top row to the currently selected row if self.gridCypherData.currentIndex().row() < 0: self.topRow = 0 else: self.topRow = self.gridCypherData.currentIndex().row() # print("starting top row:{}".format(self.topRow)) # if we are positioned inside the most recently retrieved chunk then get another chunk if self.topRow + 1 >= self.neoCon.chunkStart: # print("begin chunkstart:{} chunkend:{} topRow:{}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow)) ctr, msg = self.neoCon.forwardCursor() # update trace tab self.logWatch() self.logMsg("Retrieved {} records. Message:{}".format(ctr, msg)) self.loadModelChunk() self.topRow = self.topRow + self.neoCon.chunkSize + 1 # see if past end of tableview if self.topRow > self.gridCypherData.model().rowCount(): self.topRow = self.gridCypherData.model().rowCount() index = self.gridCypherData.model().index(self.topRow - 1, 0, parent=QModelIndex()) self.gridCypherData.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridCypherData.selectRow(index.row()) # print("read data. chunkstart:{} chunkend:{} topRow:{}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow)) else: # we are positioned in a previously retrieved chunk not the current chunk so just scroll forward chunkSize self.topRow = self.topRow + self.neoCon.chunkSize # print("chunkstart:{} chunkend:{} newtop: {}".format(self.neoCon.chunkStart, self.neoCon.chunkEnd, self.topRow)) if self.topRow > self.neoCon.chunkEnd - 1: self.topRow = self.neoCon.chunkEnd index = self.gridCypherData.model().index(self.topRow - 1, 0, parent=QModelIndex()) self.gridCypherData.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridCypherData.selectRow(index.row()) QApplication.restoreOverrideCursor() self.displayDataRetrievedMsg() self.gridCypherData.repaint() @pyqtSlot() def on_btnEnd_clicked(self): """ User clicks the move to end button. Do Fetch cursor until all rows retrieved and loaded to the grid """ QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests scroll to end") while self.neoCon.forwardCursor()[0] > 0: self.logMsg("Advanced to record {}".format(self.neoCon.chunkEnd)) self.loadModelChunk() index = self.gridCypherData.model().index(self.neoCon.chunkEnd - 1, 0, parent=QModelIndex()) self.gridCypherData.scrollTo(index, hint=QAbstractItemView.PositionAtTop) self.gridCypherData.selectRow(index.row()) QApplication.restoreOverrideCursor() self.displayDataRetrievedMsg() self.gridCypherData.repaint() def createBlankNode(self, ): self.neoID = None self.cypher = self.genCypher.genNewNode() self.runCypher("New Node", self.cypher) if self.neoCon.resultSet: self.neoID = self.neoCon.resultSet[0]["nodeID"] return self.neoID @pyqtSlot() def on_btnRefresh_clicked(self): """ Generate the match query and run it. """ self.refreshGrid() def refreshGrid(self, **kwargs): ''' 1. if special use data grid then generate the cypher match statement 2. run the cypher statement and populate the grid the calling application is responsible for locking the cursor ''' if not self.genCypher.isGeneric(): # see if a nodetemplatedict is being passed in as a kwarg aTemplateDict = kwargs.get("nodeTemplateDict", None) if not aTemplateDict is None: self.templateDict = aTemplateDict self.cypher, self.editParmDict = self.genCypher.genMatch(**kwargs) else: self.editParmDict = None if not self.cypher is None: self.runFileCursor() else: self.clearModel() @pyqtSlot() def on_btnNew_clicked(self): """ Create a new Node/Rel based on the template """ if self.genCypher.type == "Node": # display the new node dialog box dlg = EditNodeDlg(parent=self) dlg.exec() # refresh the grid self.refreshGrid() # position to the new node if not dlg.neoID is None: findIt = self.gridCypherData.model().findItems( str(dlg.neoID), flags=Qt.MatchExactly, column=0) if len(findIt) > 0: findItem = findIt[0] self.gridCypherData.scrollTo(findItem.index()) self.gridCypherData.selectRow(findItem.row()) if self.genCypher.type == "Relationship": # display the new rel dialog box dlg = EditRelDlg(parent=self) dlg.exec() # refresh the grid self.refreshGrid() # position to the new rel if not dlg.neoID is None: findIt = self.gridCypherData.model().findItems( str(dlg.neoID), flags=Qt.MatchExactly, column=2) if len(findIt) > 0: findItem = findIt[0] self.gridCypherData.scrollTo(findItem.index()) self.gridCypherData.selectRow(findItem.row()) @pyqtSlot() def on_btnDelete_clicked(self): """ Delete the node or relationship identified by the rows selected in the grid. """ indexes = self.gridCypherData.selectionModel().selectedIndexes() if len(indexes) > 0: startIndex = indexes[0] endIndex = indexes[len(indexes) - 1] if self.helper.delRowsPrompt(startIndex.row() + 1, endIndex.row() + 1): # print("delete rows {}-{}".format(startIndex.row()+1, endIndex.row()+1)) for row in range(startIndex.row(), endIndex.row() + 1): # delete the node in neo4j self.deleteDetachCypher = self.genCypher.genDeleteDetach( row=row, dataGrid=self.gridCypherData) # do the update self.runCypher("Delete/Detach Node", self.deleteDetachCypher) # refresh the grid to make deleted rows go away self.on_btnRefresh_clicked() else: pass # print("delete canceled") else: self.helper.displayErrMsg( "Delete Row", "You must select a row or rows to delete.") ##################################################################################### # methods related to the trace tab ##################################################################################### @pyqtSlot() def on_btnExportTrace_clicked(self): """ Export the contents of the trace tab to .txt file """ # get filename to save as dlg = QFileDialog() dlg.setAcceptMode(QFileDialog.AcceptSave) dlg.setDefaultSuffix("txt") dlg.setNameFilters(["Trace File (*.txt)", "all files (*.*)"]) dlg.setDirectory(self.parent.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() if fileNames: self.fileName = fileNames[0] # save the file file = QFile(self.fileName) if not file.open(QFile.WriteOnly | QFile.Text): self.helper.displayErrMsg( "Export Trace Error", "Cannot write file {} {}".format( self.fileName, file.errorString())) return outstr = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) outstr << self.textTrace.toPlainText() QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnClearTrace_clicked(self): """ clear the contents of the trace tab """ self.textTrace.clear() ########################################################################### # KEYBOARD AND MOUSE EVENTS - keep these functions as examples and for debugging when needed ########################################################################### # @pyqtSlot(QModelIndex) # def on_gridCypherData_pressed(self, index): # """ # Slot documentation goes here. # # @param index DESCRIPTION # @type QModelIndex # """ # print("Cell pressed {} {}".format(index.row(), index.column())) # @pyqtSlot(QModelIndex) # def on_gridCypherData_clicked(self, index): # """ # Slot documentation goes here. # # @param index DESCRIPTION # @type QModelIndex # """ # print("Cell clicked {} {} - data {}".format(index.row(), index.column(), index.data(role=Qt.DisplayRole))) # @pyqtSlot(QModelIndex) # def on_gridCypherData_entered(self, index): # """ # Slot documentation goes here. # # @param index DESCRIPTION # @type QModelIndex # """ # print("Cell entered {} {}".format(index.row(), index.column())) # @pyqtSlot(QModelIndex) # def on_gridCypherData_activated(self, index): # """ # Slot documentation goes here. # # @param index DESCRIPTION # @type QModelIndex # """ # print("Cell activated {} {} - data {}".format(index.row(), index.column(), index.data(role=Qt.DisplayRole))) def dataGridSelectionChanged(self, selected, deselected): # no longer used. # print("dataGridSelectionChanged") return def resultModelItemChanged(self, item): # print("item data changed {} at row:{} col:{}".format(str(item.checkState()), item.index().row(), item.index().column())) # a cell changed so update the node label or property columnIndex = item.index().column() if not self.editParmDict is None: columnType = self.editParmDict[columnIndex][0] else: columnType = UNKNOWN # is this a label column? if columnType in (OPTLBL, REQLBL): # force selection of this cell self.gridCypherData.setCurrentIndex(item.index()) # update the node in neo4j self.updateCypher = self.genCypher.genUpdateLabel( updateIndex=item.index(), dataGrid=self.gridCypherData) # do the update self.runCypher("Update Label", self.updateCypher) if columnType == PROP: # generate a cypher match/set statement aka update rc, self.updateCypher = self.genCypher.genUpdateProp( updateIndex=item.index(), dataGrid=self.gridCypherData) # print("data changed from {} to {} cypher {}".format(self.saveData, item.index().data(role = Qt.DisplayRole), self.updateCypher)) # do the update if rc == True: self.runCypher("Update Property", self.updateCypher) else: self.helper.displayErrMsg("Update Property", self.updateCypher) @pyqtSlot() def on_btnSetNull_clicked(self): """ User clicks the set property value null button """ indexes = self.gridCypherData.selectionModel().selectedIndexes() if len(indexes) > 0: selectedIndex = indexes[0] columnNum = indexes[0].column() if not self.editParmDict is None: columnType = self.editParmDict[columnNum][0] else: columnType = UNKNOWN if columnType == PROP: # print("set item null {} at row:{} col:{}".format(selectedIndex.data(role = Qt.DisplayRole), selectedIndex.row(), selectedIndex.column())) # generate a cypher match/set statement aka update self.removeCypher = self.genCypher.genRemoveProp( updateIndex=selectedIndex, dataGrid=self.gridCypherData) # do the update if self.removeCypher != None: self.runCypher("Remove Property", self.removeCypher) self.refreshGrid() else: self.helper.displayErrMsg("Set Value Null", "You must select a property value.") else: self.helper.displayErrMsg("Set Value Null", "You must select a property value.")
class CreateIndexDlg(QDialog, Ui_CreateIndexDlg): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CreateIndexDlg, self).__init__(parent) self.helper = Helper() self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaModel self.initUi() def initUi(self, ): aList = self.schemaModel.instanceList("Label") self.cbLabel.addItem("") aSortedList = sorted(aList) for indexName in aSortedList: self.cbLabel.addItem(indexName) aList = sorted(self.schemaModel.instanceList("Property")) self.cbProperty.addItem("") for indexName in aList: self.cbProperty.addItem(indexName) return def validate(self, ): # must enter or select a label if self.helper.NoTextValueError(self.cbLabel.currentText(), "You must enter or select a Label"): return False # must add at least one property to the list if self.helper.NoTextValueError(self.lstProperty, "You must have at least one property in the list."): return False return True def apply(self, ): """ Generate the create index statement """ propList = [str(self.lstProperty.item(i).text()) for i in range(self.lstProperty.count())] propComma = ",".join(x for x in propList) self.cypherCommand = "CREATE INDEX ON :{}({})".format(self.cbLabel.currentText(), str(propComma)) if self.cypherCommand: QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createIndex(self.cypherCommand) QApplication.restoreOverrideCursor() self.helper.displayErrMsg("Create Index", msg) else: self.helper.displayErrMsg("Create Index", "Error Generating Index Statement.") @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self) @pyqtSlot() def on_pbAddToList_clicked(self): """ Get the property name in the combo box and add it to the list """ self.lstProperty.addItem(self.cbProperty.currentText()) @pyqtSlot() def on_pbRemoveList_clicked(self): """ Remove the selected property from the list """ self.lstProperty.takeItem(self.lstProperty.currentRow()) @pyqtSlot() def on_btnMoveUp_clicked(self): """ User clicks the Move Up button, move the selected property up on in the list """ self.helper.moveListItemUp(self.lstProperty) @pyqtSlot() def on_btnMoveDown_clicked(self): """ User clicks the Move Down button, move the selected property up on in the list """ self.helper.moveListItemDown(self.lstProperty)
class ConstraintNodeKeyDlg(QDialog, Ui_ConstraintNodeKeyDlg): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(ConstraintNodeKeyDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaModel self.helper = Helper() self.initUi() def initUi(self, ): aList = sorted(self.schemaModel.instanceList("Label")) self.cbLabel.addItem("") for indexName in aList: self.cbLabel.addItem(indexName) aList = sorted(self.schemaModel.instanceList("Property")) self.cbProperty.addItem("") for indexName in aList: self.cbProperty.addItem(indexName) def validate(self, ): # must enter or select a label if self.helper.NoTextValueError(self.cbLabel.currentText(), "You must enter or select a Label"): return False # must add at least one property to the list if self.helper.NoTextValueError( self.lstProperty, "You must have at least one property in the list."): return False return True def apply(self, ): """ Generate and run the create constraint statement """ propList = [ "p." + str(self.lstProperty.item(i).text()) for i in range(self.lstProperty.count()) ] propComma = ",".join(x for x in propList) label = self.cbLabel.currentText() self.cypherCommand = None ''' CREATE CONSTRAINT ON (p:Person) ASSERT (p.firstname, p.surname) IS NODE KEY ''' self.cypherCommand = "CREATE CONSTRAINT ON (p:{}) ASSERT ({}) IS NODE KEY".format( label, str(propComma)) if self.cypherCommand: QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.schemaModel.createConstraint(self.cypherCommand) QApplication.restoreOverrideCursor() self.helper.displayErrMsg("Create Node KeyConstraint", msg) else: self.helper.displayErrMsg( "Create Node Key Constraint", "Error Generating Node Key Constraint Statement.") @pyqtSlot() def on_buttonBox_accepted(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self) @pyqtSlot() def on_pbAddToList_clicked(self): """ Get the property name in the combo box and add it to the list """ if len( self.lstProperty.findItems(self.cbProperty.currentText(), Qt.MatchFixedString)) == 0: self.lstProperty.addItem(self.cbProperty.currentText()) else: self.helper.displayErrMsg( "Create Node Key Constraint", "You can't use the same property twice.") @pyqtSlot() def on_pbRemoveList_clicked(self): """ Remove the selected property from the list """ self.lstProperty.takeItem(self.lstProperty.currentRow()) @pyqtSlot() def on_btnMoveUp_clicked(self): """ User clicks the Move Up button, move the selected property up on in the list """ self.helper.moveListItemUp(self.lstProperty) @pyqtSlot() def on_btnMoveDown_clicked(self): """ User clicks the Move Down button, move the selected property down on in the list """ self.helper.moveListItemDown(self.lstProperty)
class EditRelDlg(QDialog, Ui_EditRelDlg): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(EditRelDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.designModel = self.parent.designModel self.templateDict = self.parent.templateDict self.neoCon = self.parent.neoCon self.helper = Helper() self.rel = None self.neoID = None # add edit relationship widet self.editRel = EditRelWidget(self, templateDict=self.templateDict) self.editRelLayout = QVBoxLayout(self.frameBody) self.editRelLayout.setObjectName("editRelLayout") self.editRelLayout.addWidget(self.editRel) # finish UI setup self.txtTitle.setText("Add New Relationship") self.lastAddNeoID = None def logMsg(self, msg): # add message to the log if logging: logging.info(msg) @pyqtSlot() def on_btnAddNew_clicked(self): """ User clicks the add new button so add a new relationship """ # validate the data entry if self.validate(): QApplication.setOverrideCursor(Qt.WaitCursor) try: # create a new relationship selectedFromNode = self.editRel.cmbFromNode.currentText() endIndex = selectedFromNode.find("]") fromNeoID = selectedFromNode[1:endIndex] selectedToNode = self.editRel.cmbToNode.currentText() endIndex = selectedToNode.find("]") toNeoID = selectedToNode[1:endIndex] createCypher = self.helper.genCreateRelCypher( relInstanceDict=self.getObjectDict(), fromNeoID=fromNeoID, toNeoID=toNeoID) rc, msg = self.neoCon.runCypherExplicit(createCypher) QApplication.restoreOverrideCursor() if not rc == True: self.helper.displayErrMsg("Create New Relationship", msg) else: firstRec = self.neoCon.resultSet[0] if not firstRec is None: self.neoID = firstRec["id(r)"] self.rel = firstRec["r"] self.helper.displayErrMsg("Create New Relationship", "New Relationship Created.") except BaseException as e: self.helper.displayErrMsg( "Create New Relationship", "Error creating Relationship - {}".format(repr(e))) finally: QApplication.restoreOverrideCursor() def getObjectDict(self, ): ''' This function returns a dictionary with all the data entered on the UI ''' objectDict = {} objectDict["neoID"] = self.neoID objectDict["relName"] = self.templateDict["relname"] #save the attributes propList = [] model = self.editRel.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, VALUE).data(Qt.EditRole) ] # only include properties that aren't null propList.append(relProp) objectDict["properties"] = propList return objectDict def validate(self, ): # make sure all properties have valid datatypes # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # make sure all required properties have a value if not self.templateDict is None: model = self.editRel.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, VALUE).data(Qt.EditRole) ] if relProp[VALUE] == "Null": # the value is null so see if it is required for templateProp in self.templateDict["properties"]: if templateProp[PROPERTY] == relProp[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(relProp[PROPERTY])) return False return True @pyqtSlot() def on_btnClose_clicked(self): """ User Clicks the close button so close the dialog """ QDialog.accept(self)
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 TPPropertyBox(QDialog, Ui_TPPropertyBox): """ Class documentation goes here. """ def __init__(self, parent=None, mode=None, objectDict=None, designModel=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(TPPropertyBox, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaObject self.settings = QSettings() self.helper = Helper() self.designModel = designModel self.modelData = self.designModel.modelData if objectDict is None: self.objectDict = self.designModel.newPathTemplateDict() else: self.objectDict = objectDict self.mode = mode # get the class that controls the data grid for relationship templates self.CypherGenPath = CypherGenPath(parent=self, templateDict=self.objectDict) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.pageItem.neoConName, promptPW=self.parent.pageItem.promptPW) # path treeview setup self.tvPath.setContextMenuPolicy(Qt.CustomContextMenu) self.tvPath.customContextMenuRequested.connect(self.openMenu) self.tvPath.setDragDropMode(QAbstractItemView.DragOnly) # add the data grid widget. self.nodeGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.CypherGenPath) self.nodeGridLayout = QVBoxLayout(self.dataTabFrame) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.addWidget(self.nodeGrid) self.populatingMetaBox = False #populate ui data from object self.populateUIfromObject() if self.mode == "NEW": self.txtPathTemplateName.setFocus() else: # disable definition fields self.txtPathTemplateName.setEnabled(False) def populateUIfromObject(self): self.txtPathTemplateName.setText(self.objectDict["name"]) self.editDescription.appendPlainText(self.objectDict["description"]) self.populateTree() def validate(self): if self.objectDict is None: self.objectDict = {} templateName = self.txtPathTemplateName.text() # template name if self.helper.NoTextValueError(templateName, "Must enter a Path Template Name"): self.txtPathTemplateName.setFocus() return False # find duplicate cypher variables varList = [] # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) returnCount = 0 while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() thisQueryPathNode = queryPathNodeItem.data(0, Qt.UserRole) if thisQueryPathNode.type in ["Node", "Relationship"]: returnCount = returnCount + thisQueryPathNode.numReturnProps( ) if returnCount < 1: self.helper.displayErrMsg( thisQueryPathNode.templateName, "Must return at least one property.") return False if self.helper.NoTextValueError( thisQueryPathNode.templateName, "Must supply a template name."): return False if self.helper.NoTextValueError( thisQueryPathNode.cypherVar, "Must supply a cypher variable for {}.".format( thisQueryPathNode.templateName)): return False if thisQueryPathNode.cypherVar in varList: self.helper.displayErrMsg( "Validate Path", "Error - duplicate cypher variable {} defined in template: {} " .format(thisQueryPathNode.cypherVar, thisQueryPathNode.templateName)) return False if thisQueryPathNode.templateName == "No Template Selected" and thisQueryPathNode.blankTemplate == False: self.helper.displayErrMsg( "Validate Path", "Error - must select a template or set blank template to True " ) return False varList.append(thisQueryPathNode.cypherVar) tvPathIterator.__iadd__(1) else: break # passed all edits so return True return True def apply(self, ): '''save the object dictionary''' self.objectDict["name"] = self.txtPathTemplateName.text() self.objectDict["description"] = self.editDescription.toPlainText() nodeList = [] # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: # print("save node {}".format(tvPathIterator.value().text(0))) queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() nodeList.append(queryPathNodeDict) tvPathIterator.__iadd__(1) else: break self.objectDict["queryPath"] = nodeList self.designModel.setModelDirty() #----------------------------------------------------------------------------------------------------------------------- # metabox grid methods #----------------------------------------------------------------------------------------------------------------------- def createMetaBoxModel(self): # ATTRNAME, ATTRVALUE gridNodeMeta model = QStandardItemModel(0, 2) model.setHeaderData(ATTRNAME, Qt.Horizontal, "Attribute") model.setHeaderData(ATTRVALUE, Qt.Horizontal, "Value") return model def clearMetaBox(self): self.lblMetaHeader.setText("Definition") # metabox grid setup # ATTRNAME, ATTRVALUE gridMetaBox self.gridMetaBox.setModel(None) self.metaBoxModel = self.createMetaBoxModel() self.gridMetaBox.setModel(self.metaBoxModel) self.gridMetaBox.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridMetaBox.setSelectionMode(QAbstractItemView.SingleSelection) self.gridMetaBox.setColumnWidth(ATTRNAME, 150) self.gridMetaBox.setColumnWidth(ATTRVALUE, 300) # header header = self.gridMetaBox.horizontalHeader() header.setSectionResizeMode(ATTRNAME, QHeaderView.Fixed) header.setSectionResizeMode(ATTRVALUE, QHeaderView.Stretch) # set editor delegate self.gridMetaBox.setItemDelegateForColumn(ATTRVALUE, MetaBoxDelegate(self)) # self.gridMetaBox.itemDelegateForColumn(ATTRVALUE).closeEditor.connect(self.metaBoxEditorClosed) # connect model slots self.metaBoxModel.itemChanged.connect(self.metaBoxModelItemChanged) # connect grid slots self.gridMetaBox.selectionModel().selectionChanged.connect( self.metaBoxGridSelectionChanged) def addMetaRow(self, attribute=None, value=None, editable=None): ''' add a row to the metabox grid ''' # print("add metarow {}-{}-{}".format(attribute, value, editable)) self.gridMetaBox.setSortingEnabled(False) # attribute if attribute is None: attribute = "" item1 = QStandardItem(attribute) item1.setEditable(False) # value if value is None: value = "" item2 = QStandardItem(str(value)) # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it item2.setData(attribute, Qt.UserRole) item2.setEditable(editable) self.gridMetaBox.model().appendRow([item1, item2]) def populateMetaBox(self, queryPathNode=None): self.populatingMetaBox = True if not queryPathNode is None: self.clearMetaBox() self.lblMetaHeader.setText("{} Definition".format( queryPathNode.type)) for attr in queryPathNode.attributes(): self.addMetaRow(attribute=attr[0], value=attr[1], editable=attr[2]) self.populatingMetaBox = False def metaBoxModelItemChanged(self, item): if self.populatingMetaBox == False: print("item data changed {} at row:{} col:{}".format( str(item.checkState()), item.index().row(), item.index().column())) # a cell changed so see if other changes are needed # update the queryPathNode object selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) name = self.gridMetaBox.model().item( item.index().row(), ATTRNAME).data(Qt.EditRole) value = self.gridMetaBox.model().item( item.index().row(), ATTRVALUE).data(Qt.EditRole) queryPathNode.updateAttr(name=name, value=value) # if name == "Blank Template" and value == "True": # # set template name to first entry in list # queryPathNode.updateAttr(name="Node Template", value="No Template Selected") # reload the metabox since changing one property can update others self.populateMetaBox(queryPathNode=queryPathNode) # repopulate return property grid as template may have self.populatePropBox(queryPathNode=queryPathNode) # update the tree view description queryPathNode.updateTreeView() # refresh cypher view self.refreshCypher() # # update displayName # self.rePopulateMetaBox(queryPathNode=queryPathNode) # force selection of this cell self.gridMetaBox.setCurrentIndex(item.index()) def metaBoxGridSelectionChanged(self): # not used # print("metabox grid selection changed") return def getCurrentCypherVar(self, ): # get the queryPathNode object selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) return queryPathNode.cypherVar return None #----------------------------------------------------------------------------------------------------------------------- # property box grid methods #----------------------------------------------------------------------------------------------------------------------- def createPropBoxModel(self): # PROPRETURN, PROPPARM, PROPNAME, COLNAME model = QStandardItemModel(0, 4) model.setHeaderData(PROPRETURN, Qt.Horizontal, "Return") model.setHeaderData(PROPPARM, Qt.Horizontal, "Parameter") model.setHeaderData(PROPNAME, Qt.Horizontal, "Property/Function") model.setHeaderData(COLNAME, Qt.Horizontal, "Column Name") return model def clearPropBox(self): # property box grid setup # PROPRETURN, PROPPARM, PROPNAME, COLNAME self.gridPropBox.setModel(None) self.propBoxModel = self.createPropBoxModel() self.gridPropBox.setModel(self.propBoxModel) self.gridPropBox.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridPropBox.setSelectionMode(QAbstractItemView.SingleSelection) self.gridPropBox.setColumnWidth(PROPRETURN, 100) self.gridPropBox.setColumnWidth(PROPPARM, 100) self.gridPropBox.setColumnWidth(PROPNAME, 400) self.gridPropBox.setColumnWidth(COLNAME, 400) # header header = self.gridPropBox.horizontalHeader() header.setSectionResizeMode(PROPRETURN, QHeaderView.Fixed) header.setSectionResizeMode(PROPPARM, QHeaderView.Fixed) header.setSectionResizeMode(PROPNAME, QHeaderView.Stretch) header.setSectionResizeMode(COLNAME, QHeaderView.Stretch) # set editor delegate # self.gridPropBox.setItemDelegateForColumn(PROPNAME, MetaBoxDelegate(self)) # connect model slots self.propBoxModel.itemChanged.connect(self.propBoxModelItemChanged) # connect grid slots self.gridPropBox.selectionModel().selectionChanged.connect( self.propBoxGridSelectionChanged) # def addPropRow(self, propName=None, colName=None, propParm=None, propReturn=None ): def addPropRow(self, returnProp=None): ''' add a row to the property box grid ''' # PROPRETURN, PROPPARM, PROPNAME, COLNAME self.gridPropBox.setSortingEnabled(False) # checkbox to add property to the return clause propReturn = returnProp[PROPRETURN] if propReturn is None or propReturn == "": propReturn = Qt.Unchecked item1 = QStandardItem() item1.setEditable(True) item1.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) # # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it # item1.setData(propReturn, Qt.UserRole) if propReturn in [0, 1, 2]: item1.setCheckState(propReturn) else: item1.setCheckState(Qt.Unchecked) item1.setText("") # checkbox to indicate property is a parameter propParm = returnProp[PROPPARM] if propParm is None or propParm == "": propParm = Qt.Unchecked item2 = QStandardItem() item2.setEditable(True) item2.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) # # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it # item2.setData(propParm, Qt.UserRole) if propParm in [0, 1, 2]: item2.setCheckState(propParm) else: item2.setCheckState(Qt.Unchecked) item2.setText("") # property name if returnProp[PROPNAME] is None: returnProp[PROPNAME] = "" item3 = QStandardItem(returnProp[PROPNAME]) item3.setEditable(False) # column name if returnProp[COLNAME] is None or returnProp[COLNAME] == "": colName = "{}_{}".format(str(self.getCurrentCypherVar()), returnProp[PROPNAME]) else: colName = returnProp[COLNAME] item4 = QStandardItem(colName) item4.setEditable(True) # add it to the grid self.gridPropBox.model().appendRow([item1, item2, item3, item4]) def populatePropBox(self, queryPathNode=None): # PROPRETURN, PARAMETER, PROPNAME, COLNAME if not queryPathNode is None: self.clearPropBox() for returnProp in queryPathNode.returnProps: self.addPropRow(returnProp) def propBoxModelItemChanged(self, item): # a cell changed so see if other changes are needed # update the queryPathNode object # print("item data changed {} {} at row:{} col:{}".format(str(item.checkState()), propName, item.index().row(), item.index().column())) selected = self.tvPath.currentItem() if not (selected is None): queryPathNode = selected.data(0, Qt.UserRole) propName = self.gridPropBox.model().item( item.index().row(), PROPNAME).data(Qt.EditRole) colName = self.gridPropBox.model().item(item.index().row(), COLNAME).data(Qt.EditRole) propReturn = self.gridPropBox.model().item( item.index().row(), PROPRETURN).checkState() propParm = self.gridPropBox.model().item(item.index().row(), PROPPARM).checkState() queryPathNode.updateProp([propReturn, propParm, propName, colName]) self.refreshCypher() # force selection of this cell self.gridPropBox.setCurrentIndex(item.index()) def propBoxGridSelectionChanged(self): # not used # print("propbox grid selection changed") return #----------------------------------------------------------------------------------------------------------------------- # tree view methods #----------------------------------------------------------------------------------------------------------------------- def clearTree(self): self.tvPath.clear() self.tvPath.setColumnCount(1) self.tvPath.setHeaderLabels(["Query Path"]) self.tvPath.setItemsExpandable(True) def getMaxTreeOrder(self, ): '''scan the tree and find the max order attribute. This is used to increment and create the next highest order number''' max = 0 # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() order = queryPathNodeDict.get("order", 0) # save the value for order if it's greater if order > max: max = order tvPathIterator.__iadd__(1) else: break return max def findParentWidget(self, findOrder=None): '''scan the tree and find the treeviewwidget with the matching parentOrder''' # if the find id is None then this is the root so return the tree view itself if findOrder is None: return self.tvPath # find the parent tree view widget parentWidget = None # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator( self.tvPath, flags=QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: queryPathNodeItem = tvPathIterator.value() queryPathNodeDict = queryPathNodeItem.data(0, Qt.UserRole).dict() order = queryPathNodeDict.get("order", 0) # save the value for order if it's greater if order == findOrder: parentWidget = queryPathNodeItem break tvPathIterator.__iadd__(1) else: break return parentWidget def populateTree(self, ): self.clearTree() # print("path dict {}".format(self.objectDict)) # add tree items if len(self.objectDict["queryPath"]) > 0: for tvPathDict in self.objectDict["queryPath"]: # create the tree view path item object pathItem = QueryPathNode(designModel=self.designModel, nodeDict=tvPathDict) # print("add path item {}".format(pathItem.displayName)) # figure out who the parent is parent = self.findParentWidget(findOrder=pathItem.parentOrder) # add the treewidgetitem to the tree pathItem.treeItem = self.addTreeNode(parent=parent, pathItem=pathItem) else: # add a new root node pathItem = QueryPathNode(designModel=self.designModel, root=True, parentOrder=None, order=0, type="Path Template") pathItem.treeItem = self.addTreeNode(parent=self.tvPath, pathItem=pathItem) self.tvPath.resizeColumnToContents(0) self.tvPath.setCurrentItem(self.tvPath.topLevelItem(0)) # update cypher self.refreshCypher() def addTreeNode(self, parent=None, pathItem=None): # print("add tree node {}".format(pathItem.displayName)) item = QTreeWidgetItem(parent, [pathItem.displayName]) item.setData(0, Qt.UserRole, pathItem) item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) item.setExpanded(True) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) return item ########################################################################################### # query path tree view methods ########################################################################################### def openMenu(self, position): selected = self.tvPath.currentItem() if not (selected is None): tvPathNode = selected.data(0, Qt.UserRole) if (tvPathNode.type == "Path Template"): menu = QMenu() addNodeAction = menu.addAction("Add Node") addNodeAction.triggered.connect(self.addNodeTreeNode) addNodeAction = menu.addAction("Add Relationship") addNodeAction.triggered.connect(self.addRelTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return if (tvPathNode.type == "Node"): menu = QMenu() # addNodeAction = menu.addAction("Add Relationship") # addNodeAction.triggered.connect(self.addRelTreeNode) subOutMenu = QMenu("Out Bound Relationships", parent=menu) # generate outboundrel menu items outBoundList = self.parent.model.getOutboundRelTemplates( nodeTemplateName=tvPathNode.templateName) for relTemplate in outBoundList: aSubAction = subOutMenu.addAction(relTemplate["name"]) aSubAction.setData(relTemplate) aSubAction.triggered.connect(self.addOutboundRel) # print("outboundlist {}".format(relTemplate)) if len(outBoundList) == 0: aSubAction = subOutMenu.addAction( "No Outbound Relationship Templates") menu.addMenu(subOutMenu) subInMenu = QMenu("In Bound Relationships", parent=menu) # generate outboundrel menu items inBoundList = self.parent.model.getInboundRelTemplates( nodeTemplateName=tvPathNode.templateName) for relTemplate in inBoundList: aSubAction = subInMenu.addAction(relTemplate["name"]) aSubAction.setData(relTemplate) aSubAction.triggered.connect(self.addInboundRel) # print("inboundlist {}".format(relTemplate)) if len(inBoundList) == 0: aSubAction = subInMenu.addAction( "No Inbound Relationship Templates") menu.addMenu(subInMenu) removeNodeAction = menu.addAction("Remove this Node") removeNodeAction.triggered.connect(self.removeTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return if (tvPathNode.type == "Relationship"): menu = QMenu() addNodeAction = menu.addAction("Add Node") addNodeAction.triggered.connect(self.addNodeTreeNode) addNodeAction = menu.addAction("Remove This Relationship") addNodeAction.triggered.connect(self.removeTreeNode) menu.exec_(self.tvPath.mapToGlobal(position)) return def addOutboundRel(self): # get the action that called this method aSubAction = QObject.sender(self) relTemplateDict = aSubAction.data() if not relTemplateDict is None: # add the rel template to the path self.addRelTreeNode(templateName=relTemplateDict["name"]) # add the node template to the path self.addNodeTreeNode(templateName=relTemplateDict["toTemplate"]) def addInboundRel(self): # get the action that called this method aSubAction = QObject.sender(self) relTemplateDict = aSubAction.data() if not relTemplateDict is None: # add the rel template to the path self.addRelTreeNode(templateName=relTemplateDict["name"]) # add the node template to the path self.addNodeTreeNode(templateName=relTemplateDict["fromTemplate"]) def addRelTreeNode(self, templateName=None): '''add a relationship tree-node into the query path tree''' parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 # print("add rel node {}-{}".format(order, parentOrder)) queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Relationship", templateName=templateName) # set a ypher variable name for the relationship queryPathNode.cypherVar = "r{}".format( str(queryPathNode.order).lower()) # queryPathNode.reltype = self.designModel.getRelType(templateName) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() def addNodeTreeNode(self, templateName=None): '''add a node tree-node into the query path tree''' # don't know why this happens... if type(templateName) is bool: templateName = None parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 # print("add node node {}-{}".format(order, parentOrder)) queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Node", templateName=templateName) # set a cypher variable name for the Node queryPathNode.cypherVar = "{}".format( queryPathNode.templateName[0].lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() def removeTreeNode(self): '''remove a node from the tree and all descedants''' # print("remove item") currentItem = self.tvPath.currentItem() parentItem = currentItem.parent() parentItem.removeChild(currentItem) self.tvPath.takeTopLevelItem( self.tvPath.indexOfTopLevelItem(currentItem)) # update cypher self.refreshCypher() @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) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_tvPath_currentItemChanged(self, current, previous): """ Not Used @param current DESCRIPTION @type QTreeWidgetItem @param previous DESCRIPTION @type QTreeWidgetItem """ print("on tvpath current item changed") return @pyqtSlot(QModelIndex) def on_tvPath_clicked(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type QModelIndex """ print("on_tvPath_clicked") @pyqtSlot(QModelIndex) def on_tvPath_activated(self, index): """ Slot documentation goes here. @param index DESCRIPTION @type QModelIndex """ print("on_tvPath_activated") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemClicked(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemClicked") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemActivated(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemActivated") @pyqtSlot(QTreeWidgetItem, int) def on_tvPath_itemEntered(self, item, column): """ Slot documentation goes here. @param item DESCRIPTION @type QTreeWidgetItem @param column DESCRIPTION @type int """ print("on_tvPath_itemEntered") @pyqtSlot() def on_tvPath_itemSelectionChanged(self): """ User clicks on an item in the tree view """ print("on_tvPath_itemSelectionChanged") selected = self.tvPath.currentItem() if not (selected is None): # parent = self.tvPath.currentItem().parent() queryPathNode = selected.data(0, Qt.UserRole) self.populateMetaBox(queryPathNode=queryPathNode) self.populatePropBox(queryPathNode=queryPathNode) @pyqtSlot(int) def on_tabPathTemplate_currentChanged(self, index): """ The user has switched to a different tab DEFINITION, DESCRIPTION, DATAGRID @param index DESCRIPTION @type int """ # user switched to the description tab. regenerate the description # if index == DESCRIPTION: # if self.validate(): # self.apply() # # get generated description # self.brwsrGenDescription.setText(self.designModel.getNodeDescription(self.objectDict["name"])) # user switched to the definition tab. # if index == DEFINITION: # self.syncDefCheckBoxes() # user switched to the data grid then update object dictionary so query will generate with latest values if index == DATAGRID: if self.validate(): self.apply() self.nodeGrid.refreshGrid() else: self.tabPathTemplate.setCurrentIndex(0) def refreshCypher(self): if self.validate(): self.apply() self.cypher, self.editParmDict = self.nodeGrid.genCypher.genMatch() # print("cypher:{}".format(self.cypher)) self.txtCypher.clear() self.txtCypher.appendPlainText(self.cypher) @pyqtSlot() def on_btnAddNode_clicked(self): """ user clicks on add node button """ '''add a node tree-node into the query path tree''' parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Node", templateName="Anonymous Node") # set a cypher variable name for the Node queryPathNode.cypherVar = "n{}".format( str(queryPathNode.order).lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() @pyqtSlot() def on_btnAddRel_clicked(self): """ user clicks on add relationship button """ parentItem = self.tvPath.currentItem() parentDict = parentItem.data(0, Qt.UserRole).dict() parentOrder = parentDict.get("order", 0) order = self.getMaxTreeOrder() + 1 queryPathNode = QueryPathNode(designModel=self.designModel, parentOrder=parentOrder, order=order, type="Relationship", templateName="Anonymous Relationship") # set a ypher variable name for the relationship queryPathNode.cypherVar = "r{}".format( str(queryPathNode.order).lower()) queryPathNode.treeItem = self.addTreeNode(parent=parentItem, pathItem=queryPathNode) self.tvPath.setCurrentItem(queryPathNode.treeItem) # update cypher self.refreshCypher() @pyqtSlot() def on_btnRemove_clicked(self): """ User clicks on remove button """ return @pyqtSlot() def on_btnAdd_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet raise NotImplementedError @pyqtSlot() def on_btnRemove_2_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet raise NotImplementedError
class NodeeraMain(QMainWindow, Ui_NodeeraMain): displayWelcomeMsg = pyqtSignal(str) closeWelcomeMsg = pyqtSignal() """ Create Ui_NodeeraMain and display it. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(NodeeraMain, self).__init__(parent) self.setupUi(self) self.parent = parent # object data self.pageDict = {} self.curPage = None self.helper = Helper() # get startup settings self.initSettings() # launch the welcome wagon self.welcomeDlg = HelloUserDlg(self) self.welcomeDlg.show() QApplication.processEvents() self.displayWelcomeMsg.emit("Starting NodeEra...") # setup stuff not covered by generated code icon = QIcon() icon.addPixmap(QPixmap("icons/RUN and DATA TAB/Disconnected.png"), QIcon.Normal, QIcon.Off) self.actionOpen_Connection.setIcon(icon) # main window self.setTitle("No Connection") self.resize(self.winSize) self.move(self.position) # display welcome message self.displayWelcomeMsg.emit("Welcome To NodeEra") # display announcement message from website try: url = 'https://{}'.format(appPage) response = requests.get(url) if response.status_code == 200: start = response.text.find('class="display"') + 16 end = response.text.find('</p>', start) self.displayWelcomeMsg.emit(response.text[start:end]) self.displayWelcomeMsg.emit( "For more information see www.singerlinks.com/nodeera") else: self.logMsg( "Unable to access announcement page:{} http response:{}". format(appPage, response.status_code)) self.displayWelcomeMsg.emit( "Unable to access announcement page:{} http response:{}". format(appPage, response.status_code)) except Exception as e: self.logMsg("Error accessing announcement page - {}".format( repr(e))) self.displayWelcomeMsg.emit( "Error accessing announcement page - {}".format(repr(e))) # auto open last used connection try: lastNeoConName = self.settings.value("NeoCon/LastUsed") if not lastNeoConName is None: neoDict = self.settings.value( "NeoCon/connection/{}".format(lastNeoConName)) self.openConnection(neoConName=lastNeoConName, neoConDict=neoDict) self.displayWelcomeMsg.emit(" ") self.displayWelcomeMsg.emit( "Opened most recent connection: {}".format(lastNeoConName)) self.setTitle(lastNeoConName) except: self.logMsg( "Unable to open last connection: {}".format(lastNeoConName)) self.displayWelcomeMsg.emit( "Unable to open last connection: {}".format(lastNeoConName)) # auto close the welcome dialog box # time.sleep(5) # self.closeWelcomeMsg.emit() def logMsg(self, msg): if logging: logging.info(msg) def initSettings(self, ): ''' get the system settings needed to start NodeEra. If a system setting doesn't exist then create it with default value - this happens on initial startup ''' self.settings = QSettings() try: self.expirationDate = self.helper.getText( self.settings.value("License/expirationDate")) if self.expirationDate is None: self.expirationDate = 'No expiration date set' self.settings.setValue( "License/expirationDate", self.helper.putText(self.expirationDate)) except: self.expirationDate = 'No expiration date set' self.settings.setValue("License/expirationDate", self.helper.putText(self.expirationDate)) try: self.currentVersion = self.settings.value("License/currentVersion") if self.currentVersion is None: self.currentVersion = currentVersion self.settings.setValue("License/currentVersion", self.currentVersion) elif self.currentVersion != currentVersion: self.currentVersion = currentVersion self.settings.setValue("License/currentVersion", self.currentVersion) except: self.currentVersion = currentVersion self.settings.setValue("License/currentVersion", self.currentVersion) try: self.winSize = self.settings.value("MainWindow/Size") if self.winSize is None: self.winSize = QSize(800, 500) except: self.winSize = QSize(800, 500) try: self.position = self.settings.value("MainWindow/Position") if self.position is None: self.position = QPoint(0, 0) except: self.position = QPoint(0, 0) try: self.recentList = self.settings.value("Default/RecentList") if self.recentList is None: self.recentList = [] self.settings.setValue("Default/RecentList", self.recentList) except: self.recentList = [] self.settings.setValue("Default/RecentList", self.recentList) try: self.defaultLoggingPath = self.settings.value( "Default/LoggingPath") if self.defaultLoggingPath is None: self.logDir = os.getcwd() self.logDir = os.path.realpath(os.path.abspath(self.logDir)) self.settings.setValue("Default/LoggingPath", self.logDir) except: self.logDir = os.getcwd() self.logDir = os.path.realpath(os.path.abspath(self.logDir)) self.settings.setValue("Default/LoggingPath", self.logDir) try: self.defaultProjPath = self.settings.value("Default/ProjPath") if self.defaultProjPath is None: self.defaultProjPath = os.getcwd() self.defaultProjPath = os.path.realpath( os.path.abspath(self.defaultProjPath)) self.settings.setValue("Default/ProjectPath", self.defaultProjPath) except: self.defaultProjPath = os.getcwd() self.defaultProjPath = os.path.realpath( os.path.abspath(self.defaultProjPath)) self.settings.setValue("Default/ProjectPath", self.defaultProjPath) # default custom formats for diagram objects # Display Format - Instance Node try: test = self.settings.value("Default/Format/InstanceNode") if test is None: self.settings.setValue("Default/Format/InstanceNode", INodeFormat().formatDict) except: self.settings.setValue("Default/Format/InstanceNode", INodeFormat().formatDict) # Display Format - Instance Relationship try: test = self.settings.value("Default/Format/InstanceRelation") if test is None: self.settings.setValue("Default/Format/InstanceRelation", IRelFormat().formatDict) except: self.settings.setValue("Default/Format/InstanceRelation", IRelFormat().formatDict) # Display Format - Template Node try: test = self.settings.value("Default/Format/TemplateNode") if test is None: self.settings.setValue("Default/Format/TemplateNode", TNodeFormat().formatDict) except: self.settings.setValue("Default/Format/TemplateNode", TNodeFormat().formatDict) # Display Format - Template Relationship try: test = self.settings.value("Default/Format/TemplateRelation") if test is None: self.settings.setValue("Default/Format/TemplateRelation", TRelFormat().formatDict) except: self.settings.setValue("Default/Format/TemplateRelation", TRelFormat().formatDict) # page setup try: test = self.settings.value("Default/PageSetup") if test is None: self.settings.setValue("Default/PageSetup", PageSetup().objectDict) except: self.settings.setValue("Default/PageSetup", PageSetup().objectDict) # default project neocon try: defaultNeoConName = self.settings.value("NeoCon/Default") if defaultNeoConName is None: self.settings.setValue("NeoCon/Default", "LOCAL") except: self.settings.setValue("NeoCon/Default", "LOCAL") # LOCAL neocon definition try: self.localNeoCon = self.settings.value("NeoCon/connection/LOCAL") if self.localNeoCon is None: self.settings.setValue("NeoCon/connection/LOCAL", NeoDriver().localNeoConDict()) except: self.settings.setValue("NeoCon/connection/LOCAL", NeoDriver().localNeoConDict()) # default lexer font size try: defaultLexerFontSize = self.settings.value("Lexer/FontSize") if defaultLexerFontSize is None: self.settings.setValue("Lexer/FontSize", "10") except: self.settings.setValue("Lexer/FontSize", "10") # validate all neocons have the prompt dictionary key which was added in 1.04 self.settings.beginGroup("NeoCon/connection") neoKeys = self.settings.childKeys() for key in neoKeys: neoDict = self.settings.value(key) promptVal = neoDict.get("prompt", None) if promptVal is None: neoDict["prompt"] = "False" self.settings.setValue(key, neoDict) self.settings.endGroup() def setTitle(self, filename): self.setWindowTitle("{} - {}".format(productName, filename)) def setMenuAccess(self, ): ''' determine what connection tab is currently selected and enable/disable menu items as needed ''' # turn on all Settings menu items for action in self.menuSettings.actions(): if type(action) == QAction and not action.isSeparator(): action.setEnabled(True) try: # get tab widget (schema or project) that is currently active - if there isn't one this will cause an exception currentTab = self.stackedPageItems.currentWidget( ).tabPage.currentWidget() # enable all the schema actions for action in self.menuNeo.actions(): if type(action) == QAction and not action.isSeparator(): action.setEnabled(True) for action in self.menuProject.actions(): # enable project actions if type(action) == QAction and not action.isSeparator(): if currentTab.pageType == "PROJECT": action.setEnabled(True) else: if action.text() in [ "New", "Open...", "Recent Projects" ]: action.setEnabled(True) else: action.setEnabled(False) except: # no connection tabs are open # disable all project menu actions for action in self.menuProject.actions(): if type(action) == QAction and not action.isSeparator(): action.setEnabled(False) for action in self.menuNeo.actions(): if type(action) == QAction and not action.isSeparator(): if action.text() in [ "Close Connection", "Generate Schema..." ]: action.setEnabled(False) else: action.setEnabled(True) ######################################################################## # NEO CONNECTION Dropdown Menu Actions ######################################################################## @pyqtSlot() def on_actionOpen_Connection_triggered(self): """ This slot provides functionality for the open connection button """ d = dlgNeoCons(parent=self) if d.exec_(): if d.selectedNeoConName: # make sure it isn't already opened if d.selectedNeoConName not in self.pageDict: self.openConnection(neoConName=d.selectedNeoConName, neoConDict=d.selectedNeoConDict) else: self.helper.displayErrMsg( "NodeEra - Open Connection", "Connection {} is already open.".format( d.selectedNeoConName)) # switch to the page they tried to open self.pageDict[d.selectedNeoConName].actionButton.trigger() def openConnection(self, neoConName=None, neoConDict=None): ''' User selects a connection to open so create the schema page and display it ''' # set the last used neocon in system settings self.settings.setValue("NeoCon/LastUsed", neoConName) # create a new toolbar button and add it to the toolbar newConAction = QAction(self) newConAction.setObjectName("newConnection") newConAction.setText("Neo4j - {}".format(neoConName)) newConAction.setData(neoConName) newConAction.setToolTip(neoConDict["URL"]) newConAction.setCheckable(True) newConAction.setChecked(True) newConAction.triggered.connect(self.connectionClicked) self.tbConnection.addAction(newConAction) # add a tabPage widget to the stacked widget newPageWidget = PageWidget(parent=self) newPageWidget.pageType = "Schema" widgetIndex = self.stackedPageItems.addWidget(newPageWidget) # save new pageItem newPageItem = PageItem(neoConName=neoConName, actionButton=newConAction, pageWidget=newPageWidget, pageWidgetIndex=widgetIndex) self.pageDict[neoConName] = newPageItem # add the schema tab cypherTab = CypherPageWidget(parent=self, pageItem=newPageItem) newPageWidget.tabPage.addTab(cypherTab, "Schema - {}".format(neoConName)) # click the new action to force selection logic newConAction.trigger() self.logMsg("Open Connection: {}".format(neoConName)) @pyqtSlot() def connectionClicked(self): ''' User clicks on a connection in the menu bar so switch to that stacked widget ''' self.logMsg("connection clicked {}".format(self.sender().text())) # uncheck all the page action buttons for pageName in self.pageDict: self.pageDict[pageName].actionButton.setChecked(False) # check the one just clicked self.sender().setChecked(True) # save the current page name self.curPage = self.sender().data() # switch the stacked page widget to the one just clicked self.stackedPageItems.setCurrentIndex( self.pageDict[self.curPage].pageWidgetIndex) # update the main window title self.setTitle(self.curPage) # adjust the menu items self.setMenuAccess() @pyqtSlot() def on_actionClose_Connection_triggered(self): """ Close the active connection and remove the page from the UI """ if self.curPage: if self.curPage in self.pageDict: # must find the schema tab and tell it to close, it will tell all other tabs to close. schema tab is always the first one (index=0) self.pageDict[self.curPage].pageWidget.closeSchemaTab() self.removeConnection() def removeConnection(self, ): '''if the tab page widget is responding to the close request it only needs this logic to remove the connection ''' curPage = self.pageDict.get(self.curPage, None) if not curPage is None: # remove the pageWidget from the stacked widget self.stackedPageItems.removeWidget( self.pageDict[self.curPage].pageWidget) del self.pageDict[self.curPage].pageWidget # remove the action from menu's and toolbars self.tbConnection.removeAction( self.pageDict[self.curPage].actionButton) # take the page out of the dictionary del self.pageDict[self.curPage] # if any pages left select the first one if len(self.pageDict) > 0: for pageName in self.pageDict: self.pageDict[pageName].actionButton.trigger() break else: # there are no open connections and the home page is closed self.setTitle("No Connection") @pyqtSlot() def on_actionNeo4j_Connection_Manager_triggered(self): """ Display the Connection Manager """ d = dlgNeoCons(parent=self) if d.exec_(): pass @pyqtSlot() def on_actionExit_triggered(self): """ User selected the File / Exit menu item. Tell all the open connections to close """ self.closeOpenStuff() # close the app self.close() def closeEvent(self, event): # close open connections self.closeOpenStuff() #save the window state self.settings.setValue("MainWindow/Size", self.size()) self.settings.setValue("MainWindow/Position", self.pos()) event.accept() def closeOpenStuff(self): # get a list of all the keys in the pageDict dictionary keys = list(self.pageDict.keys()) # iterate thru the list of dictionary keys. this is required as the dictionary will be changing size, i.e. you can't simply iterate thru the dictionary for key in keys: pageItem = self.pageDict[key] actionButton = pageItem.actionButton actionButton.trigger() # must find the schema tab and tell it to close, it will tell all other tabs to close. schema tab is always the first one (index=0) pageItem.pageWidget.closeSchemaTab() self.removeConnection() ##################################################################### # SETTINGS DROPDOWNS ##################################################################### @pyqtSlot() def on_actionSystem_Preferences_triggered(self): """ User selects the System Preferences menu item. Display the system preferences dialog box. """ self.editSystemPreferences() def editSystemPreferences(self, ): """ User selects the System Preferences menu item. Display the system preferences dialog box. """ if not (self.settings is None): d = SystemPreferenceBox(self, settings=self.settings) if d.exec_(): self.settings.sync() ##################################################################### # PROJECT METHODS ##################################################################### @pyqtSlot() def on_actionNewProject_triggered(self): """ Open new project """ self.loadProject(fileName=None) @pyqtSlot() def on_actionOpenProject_triggered(self): """ Open an existing project file """ dlg = QFileDialog() dlg.setFileMode(QFileDialog.ExistingFile) dlg.setAcceptMode(QFileDialog.AcceptOpen) dlg.setNameFilters(["NodeEra Project (*.mdl)", "all files (*.*)"]) dlg.setDirectory(self.settings.value("Default/ProjPath")) if dlg.exec_(): fileNames = dlg.selectedFiles() if fileNames: fileName = fileNames[0] self.loadProject(fileName=fileName) @pyqtSlot() def on_actionSaveProject_triggered(self): """ Save the open project """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": newName = curPageWidget.tabPage.currentWidget().saveProject() # if this was an unsaved project then it was really a save as so the tab name has to be updated if newName is not None: curPageWidget.tabPage.setTabText( curPageWidget.tabPage.currentIndex(), "Project: {} - {}".format( newName, self.pageDict[self.curPage].neoConName)) @pyqtSlot() def on_actionSaveProjectAs_triggered(self): """ Save Project As """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": newName = curPageWidget.tabPage.currentWidget().saveProjectAs() if newName is not None: curPageWidget.tabPage.setTabText( curPageWidget.tabPage.currentIndex(), "Project: {} - {}".format( newName, self.pageDict[self.curPage].neoConName)) @pyqtSlot() def on_actionReverse_Engineer_triggered(self): """ User selects the Reverse Engineer menu item. Display the Reverse Engineer dialog box. """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": curPageWidget.tabPage.currentWidget().reverseEngineerGraph() else: self.helper.displayErrMsg( "Reverse Engineer", "{} not a project".format( curPageWidget.tabPage.currentWidget().pageType)) @pyqtSlot() def on_actionProjectProperties_triggered(self): """ User selects the project properties menu item. Display the project properties dialog box. """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": curPageWidget.tabPage.currentWidget().editProjectProperties() else: self.helper.displayErrMsg( "Project Properties", "{} not a project".format( curPageWidget.tabPage.currentWidget().pageType)) def getSchemaObject(self, ): curPageWidget = self.stackedPageItems.currentWidget() for ctr in range(0, curPageWidget.tabPage.count()): tab = curPageWidget.tabPage.widget(ctr) if tab.pageType == "CYPHER": return tab.schemaModel return None def getSchemaTab(self, ): curPageWidget = self.stackedPageItems.currentWidget() for ctr in range(0, curPageWidget.tabPage.count()): tab = curPageWidget.tabPage.widget(ctr) if tab.pageType == "CYPHER": return tab return None def loadProject(self, fileName=None): ''' Load the project file with name fileName. If fileName is None, create a new empty project. ''' # create a temporary file name if its a new project if fileName is None: # update unnamed file counter global unNamedFileCounter unNamedFileCounter = unNamedFileCounter + 1 shortName = "{}".format( "New Project-0{}".format(unNamedFileCounter)) else: head, shortName = ntpath.split(QFileInfo(fileName).fileName()) # make sure the project isn't already loaded curPageWidget = self.stackedPageItems.currentWidget() for ctr in range(0, curPageWidget.tabPage.count()): tab = curPageWidget.tabPage.widget(ctr) if tab.pageType == "PROJECT": if (not fileName is None) and (tab.fileName == fileName): self.helper.displayErrMsg( "Open Project", "The project file: {} is already open. It can only be open once." .format(fileName)) return # create the project widget projectTab = ProjectPageWidget(parent=self, settings=self.settings, pageItem=self.pageDict[self.curPage], fileName=fileName) curPageWidget = self.stackedPageItems.currentWidget() # add the project widget as a tab on the current page widget x = curPageWidget.tabPage.addTab( projectTab, "Project: {} - {}".format(shortName, self.pageDict[self.curPage].neoConName)) curPageWidget.tabPage.setCurrentIndex(x) @pyqtSlot() def on_actionOnline_Help_triggered(self): """ User selects the Online Help menu item. Dislay Online Help menu. """ d = OnlineHelpDLG(self) if d.exec_(): pass @pyqtSlot() def on_actionAbout_triggered(self): """ User selects Help / About menu item. Display the about dialog box """ d = HelpAboutDLG(self) if d.exec_(): pass @pyqtSlot() def on_actionGenerate_Schema_triggered(self): """ User requests the generate schema dialog box from main menu """ d = GenerateSchemaDlg(self) if d.exec_(): pass @pyqtSlot() def on_actionForward_Engineer_triggered(self): """ User requests to perform forward engineering from the open project from main menu """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": curPageWidget.tabPage.currentWidget().forwardEngineerGraph() else: self.logMsg( "User requested to forward engineer but current tab is not a Project" ) @pyqtSlot() def on_actionGenerate_Reports_triggered(self): """ User selects the Generate Project Reports menu item. Display the Generate Reports dialog box. """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "PROJECT": curPageWidget.tabPage.currentWidget().generateProjectReports() @pyqtSlot() def on_actionReset_User_Password_triggered(self): """ User requests the change user password from main menu """ curPageWidget = self.stackedPageItems.currentWidget() if not curPageWidget is None: if curPageWidget.tabPage.currentWidget().pageType == "CYPHER": curPageWidget.tabPage.currentWidget().resetPassword() @pyqtSlot(QAction) def on_menuRecent_Projects_triggered(self, action): """ User clicked on a recent file menu item @param action DESCRIPTION @type QAction """ self.loadProject(fileName=action.data()) @pyqtSlot() def on_menuRecent_Projects_aboutToShow(self): """ user hovering on recent projects menu item """ recentList = self.settings.value("Default/RecentList") if len(recentList) == 0: return else: # remove any existing actions self.menuRecent_Projects.clear() for projectFile in recentList: # create actions for the recent files aSubAction = self.menuRecent_Projects.addAction(projectFile) aSubAction.setData(projectFile)
class CopyNodeToDiagramDlg(QDialog, Ui_CopyNodeToDiagramDlg): """ Class documentation goes here. """ def __init__(self, parent=None, rightClickPos=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(CopyNodeToDiagramDlg, self).__init__(parent) self.setupUi(self) self.parent = parent self.settings = QSettings() self.rightClickPos = rightClickPos self.designModel = self.parent.model self.syncNeoCon = self.designModel.modelNeoCon self.itemDict = self.parent.itemDict self.helper = Helper() self.neoTypeFunc = NeoTypeFunc() self.nodeGrid = None # header area self.txtDiagramName.setText(self.parent.diagramName) self.txtNeoCon.setText("{} - {}".format( self.syncNeoCon.name, self.syncNeoCon.neoDict["URL"])) # load node template dropdown and disable it dropdownList = [] dropdownList.append("No Template Selected") dropdownList.extend( sorted(self.designModel.instanceList("Node Template"))) self.cboNodeTemplates.addItems(dropdownList) self.rbFilterTemplate.setChecked(True) # get neocon object for this project page self.neoCon = NeoDriver(name=self.parent.parent.pageItem.neoConName, promptPW=self.parent.parent.pageItem.promptPW) # add the data grid widget. self.addNodeCypher = AddNodeCypher() self.nodeGrid = DataGridWidget(self, neoCon=self.neoCon, genCypher=self.addNodeCypher) self.nodeGridLayout = QVBoxLayout(self.frmDataGrid) self.nodeGridLayout.setObjectName("nodeGridLayout") self.nodeGridLayout.addWidget(self.nodeGrid) def refreshDatabaseNodeGrid(self, ): # first make sure the node grid has been created. if self.nodeGrid is None: return nodeTemplate = self.cboNodeTemplates.currentText() index, nodeTemplateDict = self.designModel.getDictByName( topLevel="Node Template", objectName=nodeTemplate) if self.rbFilterTemplate.isChecked() == True: useTemplate = True else: useTemplate = False self.nodeGrid.refreshGrid(useTemplate=useTemplate, nodeTemplate=nodeTemplate, nodeTemplateDict=nodeTemplateDict) def getNodeRels(self, nodeID, nodeIDList): ''' Run a query that retrieves all relationships between one node and all other nodes on a diagram ''' try: msg = None p1 = str(nodeID) p2 = str(nodeIDList) cypher = '''match (f)-[r]->(t) where ((id(f) = {} and id(t) in {}) or (id(t) = {} and id(f) in {})) return id(f), f.NZID, f, id(r), type(r), r.NZID, r, id(t), t.NZID, t '''.format(p1, p2, p1, p2) # print(cypher) #run the query rc1, msg1 = self.syncNeoCon.runCypherAuto(cypher) except BaseException as e: msg = "{} - Get Relationships failed.".format(repr(e)) finally: QApplication.restoreOverrideCursor() if not msg is None: self.helper.displayErrMsg("Error Retrieving Relationships.", msg) ##################################################################################### # dialog buttons ##################################################################################### @pyqtSlot(QAbstractButton) def on_buttonBox_clicked(self, button): """ Slot documentation goes here. @param button DESCRIPTION @type QAbstractButton """ @pyqtSlot() def on_buttonBox_accepted(self): """ """ QDialog.accept(self) @pyqtSlot() def on_buttonBox_rejected(self): """ Slot documentation goes here. """ QDialog.reject(self) @pyqtSlot() def on_rbAllNodes_clicked(self): """ User selects all Nodes option, so configure UI and refresh the grid """ self.cboNodeTemplates.setEnabled(False) self.refreshDatabaseNodeGrid() @pyqtSlot() def on_rbFilterTemplate_clicked(self): """ User selects filter on template option """ self.cboNodeTemplates.setEnabled(True) self.refreshDatabaseNodeGrid() @pyqtSlot(int) def on_cboNodeTemplates_currentIndexChanged(self, index): """ User changes the node template selection so refresh the grid @param index DESCRIPTION @type int """ self.refreshDatabaseNodeGrid() @pyqtSlot() def on_btnAdd_clicked(self): """ User clicks the add button so add the selected nodes to the diagram """ applyX = self.rightClickPos.x() applyY = self.rightClickPos.y() QApplication.setOverrideCursor(Qt.WaitCursor) if self.rbAllNodes.isChecked() == True: ID, LBLS, PROPS, NODE = range(4) else: ID, NODE = range(2) # get the selected node template if self.rbFilterTemplate.isChecked() == True: nodeTemplate = self.cboNodeTemplates.currentText() else: nodeTemplate = None # make sure there is data in the grid if self.nodeGrid.gridCypherData.selectionModel() is None: self.helper.displayErrMsg("Copy Node", "Select a Node Template or All Nodes.") QApplication.restoreOverrideCursor() return # check if any rows selected if len(self.nodeGrid.gridCypherData.selectionModel().selectedRows( column=0)) > 0: # looking at nodes in the database for itemIndex in self.nodeGrid.gridCypherData.selectionModel( ).selectedRows(column=0): # get the Neo4j node id from the result set instanceNodeID = self.nodeGrid.gridCypherData.model().item( itemIndex.row(), ID).data(Qt.EditRole) # see if it's already an instance node in the model instanceNZID = self.designModel.lookupNZID( neoID=instanceNodeID, topLevel="Instance Node") # get the node object n = self.nodeGrid.gridCypherData.model().item( itemIndex.row(), NODE).data(Qt.UserRole) # get the list of labels from the node object lbls = [[lbl] for lbl in n.labels] # get the dictionary of properties from the node object and convert it to a list props = [] for key, val in dict(n).items(): props.append([ key, self.neoTypeFunc.getNeo4jDataType(value=val), self.neoTypeFunc.convertTypeToString(dataValue=val) ]) # create a new node instance dictionary newNodeInstanceDict = self.designModel.newNodeInstance( nodeID=instanceNodeID, templateName=nodeTemplate, labelList=lbls, propList=props, NZID=instanceNZID) NZID = self.parent.scene.dropINode( point=QPointF(applyX, applyY), nodeInstanceDict=newNodeInstanceDict, NZID=instanceNZID) if not NZID is None: applyX = applyX + 200 QApplication.restoreOverrideCursor() self.helper.displayErrMsg( "Copy Node", "Selected Node(s) were added to diagram.") # update project model self.parent.model.setModelDirty() self.parent.model.updateTV() else: QApplication.restoreOverrideCursor() self.helper.displayErrMsg( "Copy Node", "You must select a node to add to the diagram.") QApplication.restoreOverrideCursor()
class NeoEditDelegate(QStyledItemDelegate): def __init__(self, owner): super(NeoEditDelegate, self).__init__(owner) self.booleanItems = ["True", "False"] self.hintSize = None self.helper = Helper() def createEditor(self, parent, option, index): # create the appropriate widget based on the datatype dataType = index.data(Qt.UserRole + 1) self.hintSize = QSize(option.rect.width(), option.rect.height()) if dataType == DataType.INT.value: self.editor = QLineEdit(parent) self.editor.setValidator(QIntValidator()) elif dataType == DataType.FLOAT.value: self.editor = QLineEdit(parent) self.editor.setValidator(QDoubleValidator()) elif dataType == DataType.STRING.value: self.editor = QLineEdit(parent) elif dataType == DataType.BOOLEAN.value: self.editor = QComboBox(parent) self.editor.addItems(self.booleanItems) elif dataType == DataType.POINTWGS84.value: self.editor = FrmGPoint(parent) self.editor.setAutoFillBackground(True) self.hintSize = QSize(300, 40) elif dataType == DataType.POINTCARTESIAN.value: self.editor = FrmPoint(parent) self.editor.setAutoFillBackground(True) self.hintSize = QSize(300, 40) elif dataType == DataType.TIME.value: # self.editor = FrmTime(parent=parent, tz=True) self.editor = QLineEdit(parent) elif dataType == DataType.LOCALTIME.value: # self.editor = FrmTime(parent=parent, tz=False) self.editor = QLineEdit(parent) elif dataType == DataType.DATE.value: self.editor = QDateTimeEdit(parent) self.editor.setCalendarPopup(True) self.editor.setDisplayFormat("yyyy/MM/dd") elif dataType == DataType.DATETIME.value: # self.editor = QDateTimeEdit(parent) # self.editor.setCalendarPopup(False) # self.editor.setDisplayFormat("yyyy-MM-dd hh:mm:ss:zzz") self.editor = QLineEdit(parent) elif dataType == DataType.LOCALDATETIME.value: self.editor = QLineEdit(parent) elif dataType == DataType.DURATION.value: self.editor = QLineEdit(parent) else: self.editor = QLineEdit(parent) return self.editor def setEditorData(self, editor, index): ''' this takes the string stored in the grid cell and populates the editor widget with that value. ''' value = index.data(Qt.DisplayRole) dataType = index.data(Qt.UserRole + 1) if isinstance(editor, QLineEdit): if ((dataType == DataType.INT.value or dataType == DataType.FLOAT.value) and value == "Null"): editor.setText("") else: editor.setText(value) # boolean datatype uses a listbox elif isinstance(editor, QComboBox): try: num = self.booleanItems.index(value) editor.setCurrentIndex(num) except: editor.setCurrentIndex(0) elif isinstance(editor, FrmPoint): self.editor.setText(value) elif isinstance(editor, FrmGPoint): self.editor.setText(value) elif isinstance(editor, QDateTimeEdit): try: aDate = None if dataType == DataType.DATE.value: if value == "Null": aDate = QDate.currentDate() editor.setDate(aDate) else: aDate = QDate.fromString(value, format=Qt.ISODate) editor.setDate(aDate) except: pass def setModelData(self, editor, model, index): ''' this takes the current value of the editor widget, converts it back to a string and stores it back in the item model ''' dataType = index.data(Qt.UserRole + 1) # string, integer, and float use a qlineedit if isinstance(editor, QLineEdit): if dataType == DataType.DATETIME.value: value = editor.text() try: aNeoDateTime = DateTime.from_iso_format(value) saveStr = aNeoDateTime.iso_format() model.setData(index, saveStr, Qt.DisplayRole) except: self.helper.displayErrMsg( "Date Time", "Entered text [{}] is not a valid ISO date time.". format(value)) model.setData(index, value, Qt.DisplayRole) elif dataType == DataType.LOCALDATETIME.value: value = editor.text() try: aNeoDateTime = DateTime.from_iso_format(value) saveStr = aNeoDateTime.iso_format() model.setData(index, saveStr, Qt.DisplayRole) except: self.helper.displayErrMsg( "Date Time", "Entered text [{}] is not a valid ISO date time.". format(value)) model.setData(index, value, Qt.DisplayRole) elif dataType in [DataType.LOCALTIME.value, DataType.TIME.value]: value = editor.text() try: aNeoTime = Time.from_iso_format(value) saveStr = aNeoTime.iso_format() tz = "" if aNeoTime.tzinfo is not None: offset = aNeoTime.tzinfo.utcoffset(self) tz = "%+03d:%02d" % divmod( offset.total_seconds() // 60, 60) returnStr = "{}{}".format(saveStr, tz) model.setData(index, returnStr, Qt.DisplayRole) except: self.helper.displayErrMsg( "Time", "Entered text [{}] is not a valid ISO time.".format( value)) model.setData(index, value, Qt.DisplayRole) else: value = editor.text() model.setData(index, value, Qt.DisplayRole) # boolean datatype uses a listbox elif isinstance(editor, QComboBox): value = editor.currentText() model.setData(index, value, Qt.DisplayRole) elif isinstance(editor, FrmPoint): value = editor.getText() model.setData(index, value, Qt.DisplayRole) elif isinstance(editor, FrmGPoint): value = editor.getText() model.setData(index, value, Qt.DisplayRole) # elif isinstance(editor, FrmTime ): # value = editor.getText() # model.setData(index, value, Qt.DisplayRole) # elif isinstance(editor, FrmDateTime ): # value = editor.getText() # model.setData(index, value, Qt.DisplayRole) elif isinstance(editor, QDateTimeEdit): if dataType == DataType.DATE.value: value = editor.date().toString(Qt.ISODate) model.setData(index, value, Qt.DisplayRole) # if dataType == DataType.DATETIME.value: # value = editor.dateTime().toString("yyyy-MM-dd hh:mm:ss:zzz") ## print("editor datetime: {} editor string: {}".format(editor.dateTime(), value)) # model.setData(index, value, Qt.DisplayRole) def updateEditorGeometry(self, editor, option, index): dataType = index.data(Qt.UserRole + 1) if isinstance(editor, FrmPoint): editor.setGeometry( QRect(option.rect.left(), option.rect.top(), editor.frameGeometry().width(), editor.frameGeometry().height())) self.editor.parent().parent().setRowHeight( index.row(), editor.frameGeometry().height()) self.editor.parent().parent().setColumnWidth( index.column(), editor.frameGeometry().width()) elif isinstance(editor, FrmGPoint): editor.setGeometry( QRect(option.rect.left(), option.rect.top(), editor.frameGeometry().width(), editor.frameGeometry().height())) self.editor.parent().parent().setRowHeight( index.row(), editor.frameGeometry().height()) self.editor.parent().parent().setColumnWidth( index.column(), editor.frameGeometry().width()) elif isinstance(editor, QDateTimeEdit): if dataType == DataType.DATETIME.value: # for some reason, the qdatetimeedit doesn't know it's width so we hard code it editor.setGeometry( QRect(option.rect.left(), option.rect.top(), 180, editor.frameGeometry().height())) self.editor.parent().parent().setRowHeight( index.row(), editor.frameGeometry().height()) self.editor.parent().parent().setColumnWidth( index.column(), editor.frameGeometry().width()) else: editor.setGeometry( QRect(option.rect.left(), option.rect.top(), 100, editor.frameGeometry().height())) self.editor.parent().parent().setRowHeight( index.row(), editor.frameGeometry().height()) self.editor.parent().parent().setColumnWidth( index.column(), editor.frameGeometry().width()) else: editor.setGeometry(option.rect)
class EditRelWidget(QWidget, Ui_EditRelWidget): """ This widget provides a generic UI to enter a relationship """ def __init__(self, parent=None, templateDict=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(EditRelWidget, self).__init__(parent) self.setupUi(self) self.helper = Helper() self.parent = parent self.neoCon = self.parent.neoCon self.designModel = self.parent.designModel self.templateDict = templateDict # rel type self.lblRelationship.setText("Relationship Type: {}".format( self.templateDict["relname"])) # from Node self.lblFromNode.setText("From Node - Node Template: {}".format( self.templateDict["fromTemplate"])) index, nodeTemplateDict = self.designModel.getDictByName( topLevel="Node Template", objectName=self.templateDict["fromTemplate"]) fromNodeTemplateCypher = NodeTemplateCypher( templateDict=nodeTemplateDict) getNodesCypher, x = fromNodeTemplateCypher.genMatchReturnNodeOnly() # print(getNodesCypher) rc, msg = self.runCypher( requestType="Get From Nodes based on Node Template: {}".format( self.templateDict["fromTemplate"]), cypher=getNodesCypher) if rc == True: self.loadNodeDropDown(dropDown=self.cmbFromNode) else: self.helper.displayErrMsg( "Load From Nodes", "Error loading From Nodes - {}".format(msg)) # to Node self.lblToNode.setText("To Node - Node Template: {}".format( self.templateDict["toTemplate"])) index, nodeTemplateDict = self.designModel.getDictByName( topLevel="Node Template", objectName=self.templateDict["toTemplate"]) fromNodeTemplateCypher = NodeTemplateCypher( templateDict=nodeTemplateDict) getNodesCypher, x = fromNodeTemplateCypher.genMatchReturnNodeOnly() # print(getNodesCypher) rc, msg = self.runCypher( requestType="Get From Nodes based on Node Template: {}".format( self.templateDict["toTemplate"]), cypher=getNodesCypher) if rc == True: self.loadNodeDropDown(dropDown=self.cmbToNode) else: self.helper.displayErrMsg( "Load To Nodes", "Error loading From Nodes - {}".format(msg)) # property grid self.gridProps.setSortingEnabled(False) self.gridProps.setModel(self.createPropModel()) self.gridProps.setItemDelegate(NeoEditDelegate(self)) 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) self.populateUIfromObject() def logMsg(self, msg): # add message to the log if logging: logging.info(msg) 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") return model def populateUIfromObject(self, ): if self.templateDict is not None: #load props PROPERTY, EXISTS, PROPREQ,PROPDEF,UNIQUE, PROPNODEKEY for nodeProp in self.templateDict["properties"]: self.addProp(self.gridProps.model(), nodeProp[PROPERTY], nodeProp[DATATYPE], nodeProp[PROPREQ], nodeProp[PROPDEF]) def loadNodeDropDown(self, dropDown=None): # print(self.neoCon.resultSet) dropdownList = [] nodeList = [ "[{}]-".format(str(result["nodeID"])) + str(result["Node"]) for result in self.neoCon.resultSet ] dropdownList.extend(nodeList) dropDown.addItems(dropdownList) def addProp(self, model, prop, dataType, required, default): ''' add a row to the property grid ''' self.gridProps.setSortingEnabled(False) if required == Qt.Checked: item1 = QStandardItem("{}".format(prop)) else: item1 = QStandardItem("{}".format(prop)) item1.setEditable(False) item2 = QStandardItem(dataType) item2.setEditable(False) if default is None or default == "": item3 = QStandardItem("Null") else: item3 = QStandardItem(default) item3.setData(dataType, Qt.UserRole + 1) item3.setEditable(True) model.appendRow([item1, item2, item3]) def runCypher(self, requestType, cypher): ''' Run a Cypher query and return the entire result set ''' QApplication.setOverrideCursor(Qt.WaitCursor) self.logMsg("User requests {}".format(requestType)) try: rc = False self.logMsg(cypher) #run the query rc1, msg1 = self.neoCon.runCypherAuto(cypher) if rc1: self.logMsg("{} Relationship {}".format(requestType, msg1)) rc = True msg = "{} Relationship complete".format(requestType) else: rc = False msg = "{} Relationship Error {}".format(requestType, msg1) except BaseException as e: msg = "{} - {} failed.".format(requestType, repr(e)) finally: QApplication.restoreOverrideCursor() if rc == False: self.helper.displayErrMsg("Process Query", msg) self.logMsg(msg) return rc, msg @pyqtSlot() def on_btnSetNull_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_btnAddNew_clicked(self): """ User clicks button to add new relationship """ # the add logic is in the parent dialog self.parent.on_btnAddNew_clicked()
class FormPropertyBox(QDialog, Ui_FormPropertyBox): """ Form Definition Editor """ def __init__(self, parent=None, mode=None, objectDict = None, designModel = None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(FormPropertyBox, self).__init__(parent) self.setupUi(self) self.parent = parent self.schemaModel = self.parent.schemaObject self.settings = QSettings() self.helper = Helper() self.designModel = designModel self.modelData = self.designModel.modelData if objectDict is None: self.objectDict = self.designModel.newFormDict() else: self.objectDict = objectDict self.mode = mode # path treeview setup self.tvOutline.setContextMenuPolicy(Qt.CustomContextMenu) self.tvOutline.customContextMenuRequested.connect(self.openMenu) self.tvOutline.setDragDropMode(QAbstractItemView.DragOnly) # add preview widget self.formPreview = FormMain(self.frmPreview ) self.formPreviewLayout = QVBoxLayout(self.frmPreview) self.formPreviewLayout.setObjectName("MainFormLayout") self.formPreviewLayout.addWidget(self.formPreview) self.populatingMetaBox = False # position the splitter self.show() # you have to do this to force all the widgets sizes to update third = int((self.width() / 3)) self.vSplitter.setSizes([third*2, third]) half = int((self.height() / 2)) self.hSplitter.setSizes([half, half]) #populate ui data from object self.populateUIfromObject() def populateUIfromObject(self): self.populateTree() self.genForm() def genForm(self): '''generate the user defined form ''' if self.validate(): self.apply() self.formPreview.generateForm(formDict=self.objectDict) #----------------------------------------------------------------------------------------------------------------------- # tree view methods #----------------------------------------------------------------------------------------------------------------------- def clearTree(self): self.tvOutline.clear() self.tvOutline.setColumnCount(1) self.tvOutline.setHeaderLabels(["Form Outline"]) self.tvOutline.setItemsExpandable(True) def getMaxTreeOrder(self, ): '''scan the tree and find the max itemNum attribute. This is used to increment and create the next highest item number''' max = 0 # iterate through the treeview tvPathIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All) while tvPathIterator: if not tvPathIterator.value() is None: formTVItem = tvPathIterator.value() formItem = formTVItem.data(0, Qt.UserRole) # save the value for order if it's greater if formItem.itemDict["idNum"] > max: max = formItem.itemDict["idNum"] tvPathIterator.__iadd__(1) else: break return max def populateTree(self, ): self.clearTree() # print("path dict {}".format(self.objectDict)) # add tree items if len(self.objectDict["formOutline"]) > 0: for formItemDict in self.objectDict["formOutline"]: # create the tree view form item object if formItemDict["type"] == "Form": formItem = FormDef(itemDict=formItemDict) if formItemDict["type"] == "Row": formItem = FormRowDef(itemDict=formItemDict) if formItemDict["type"] == "Widget": if formItemDict["widgetType"] == "Label": formItem = LabelWidgetDef(itemDict=formItemDict) if formItemDict["widgetType"] == "Button": formItem = ButtonWidgetDef(itemDict=formItemDict) # get the parent tree view widget parent = self.findParentWidget(findID=formItemDict["parentID"]) # add the treewidgetitem to the tree formItem.treeItem = self.addTreeNode(parent=parent, formItem=formItem) else: # add a new root node formItem = FormDef(root=True, idNum=0, parentID=None, type="Form", itemName=self.objectDict["name"]) formItem.treeItem = self.addTreeNode(parent=self.tvOutline, formItem=formItem) self.tvOutline.resizeColumnToContents(0) self.tvOutline.setCurrentItem(self.tvOutline.topLevelItem(0)) def addTreeNode(self, parent=None, formItem=None): # print("add tree node {}".format(pathItem.displayName)) item = QTreeWidgetItem(parent, [formItem.displayName]) item.setData(0, Qt.UserRole, formItem) item.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator) item.setExpanded (True) item.setFlags( Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled ) return item def findParentWidget(self, findID=None): '''scan the tree and find the treeviewwidget with the matching parentOrder''' # if the find id is None then this is the root so return the tree view itself if findID is None: return self.tvOutline # find the parent tree view widget parentWidget = None # iterate through the treeview tvOutlineIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All) while tvOutlineIterator: if not tvOutlineIterator.value() is None: # get the treeview widget tvWidget = tvOutlineIterator.value() # get the form item object from the widget formItem = tvWidget.data(0, Qt.UserRole) # get the idNum of the form item object idNum = formItem.itemDict["idNum"] # check to see if this is the idNum we're looking for if idNum == findID: parentWidget = tvWidget break tvOutlineIterator.__iadd__(1) else: break return parentWidget def openMenu(self,position): selected = self.tvOutline.currentItem() if not (selected is None): tvFormItem = selected.data(0, Qt.UserRole) if (tvFormItem.itemDict["type"] == "Form"): menu = QMenu() addItemAction = menu.addAction("Add Row") addItemAction.triggered.connect(self.addRow) menu.exec_(self.tvOutline.mapToGlobal(position)) return if (tvFormItem.itemDict["type"] == "Row"): menu = QMenu() addItemAction = menu.addAction("Add Button Widget") addItemAction.triggered.connect(self.addButtonWidget) addItemAction = menu.addAction("Add Label Widget") addItemAction.triggered.connect(self.addLabelWidget) addItemAction = menu.addAction("Remove This Row") addItemAction.triggered.connect(self.removeRow) menu.exec_(self.tvOutline.mapToGlobal(position)) return if (tvFormItem.itemDict["type"] == "Widget"): menu = QMenu() addItemAction = menu.addAction("Remove This Widget") addItemAction.triggered.connect(self.removeWidget) menu.exec_(self.tvOutline.mapToGlobal(position)) return def addRow(self): parentTVItem = self.tvOutline.currentItem() parentFormItem = parentTVItem.data(0, Qt.UserRole) parentID = parentFormItem.itemDict["idNum"] nextID = self.getMaxTreeOrder() + 1 newFormItem = FormRowDef(parentID=parentID, idNum=nextID, type="Row") newFormItem.treeItem = self.addTreeNode(parent=parentTVItem, formItem=newFormItem) self.tvOutline.setCurrentItem(newFormItem.treeItem) # update form view self.genForm() def removeRow(self): self.removeTVItem() def addLabelWidget(self): self.addWidget(type="Label") def addButtonWidget(self): self.addWidget(type="Button") def addWidget(self, type=None): parentTVItem = self.tvOutline.currentItem() parentFormItem = parentTVItem.data(0, Qt.UserRole) parentID = parentFormItem.itemDict["idNum"] nextID = self.getMaxTreeOrder() + 1 if type == "Label": newFormItem = LabelWidgetDef(parentID=parentID, idNum=nextID, type="Widget") if type == "Button": newFormItem = ButtonWidgetDef(parentID=parentID, idNum=nextID, type="Widget") if not newFormItem is None: newFormItem.treeItem = self.addTreeNode(parent=parentTVItem, formItem=newFormItem) self.tvOutline.setCurrentItem(newFormItem.treeItem) # update form view self.genForm() def removeWidget(self): self.removeTVItem() def removeTVItem(self): '''remove a form item from the tree and all descedants''' currentItem = self.tvOutline.currentItem() formDefItem = currentItem.data(0, Qt.UserRole) if formDefItem.itemDict["type"] == "Form": self.helper.displayErrMsg("Form Editor", "Cannot remove the form definition") return parentItem = currentItem.parent() parentItem.removeChild(currentItem) self.tvOutline.takeTopLevelItem(self.tvOutline.indexOfTopLevelItem(currentItem)) # update form view self.genForm() def validate(self): return True def apply(self): '''save the object dictionary''' formItemList = [] # iterate through the treeview tvOutlineIterator = QTreeWidgetItemIterator(self.tvOutline, flags = QTreeWidgetItemIterator.All) while tvOutlineIterator: if not tvOutlineIterator.value() is None: # get the treeview widget tvWidget = tvOutlineIterator.value() # get the form item object from the widget formDefItem = tvWidget.data(0, Qt.UserRole) if formDefItem.itemDict["type"] == "Form": self.objectDict["name"] = formDefItem.itemDict["itemName"] self.objectDict["description"] = formDefItem.itemDict["description"] formItemList.append(formDefItem.dict()) tvOutlineIterator.__iadd__(1) else: break self.objectDict["formOutline"] = formItemList self.designModel.setModelDirty() @pyqtSlot() def on_btnAdd_clicked(self): """ User clicks the add button """ selected = self.tvOutline.currentItem() if not (selected is None): tvFormItem = selected.data(0, Qt.UserRole) if (tvFormItem.type == "Form"): self.addRow() return if (tvFormItem.type == "Row"): self.addWidget() return if (tvFormItem.type == "Widget"): self.helper.displayErrMsg("Form Design", "Cannot add anything below a widget") return @pyqtSlot() def on_btnRemove_clicked(self): """ User clicks remove button """ self.removeTVItem() @pyqtSlot() def on_btnUp_clicked(self): """ User clicks the up button """ print("move tree item up") @pyqtSlot() def on_btnDown_clicked(self): """ User clicks the down button """ print("move tree item down") @pyqtSlot() def on_btnClose_clicked(self): """ Slot documentation goes here. """ if self.validate(): self.apply() QDialog.accept(self) @pyqtSlot() def on_tvOutline_itemSelectionChanged(self): """ User clicks on an item in the tree view """ print("on_tvOutline_itemSelectionChanged") selected = self.tvOutline.currentItem() if not (selected is None): # parent = self.tvPath.currentItem().parent() formDefItem = selected.data(0, Qt.UserRole) self.populateMetaBox(formDefItem=formDefItem) #----------------------------------------------------------------------------------------------------------------------- # metabox grid methods #----------------------------------------------------------------------------------------------------------------------- def createMetaBoxModel(self): # ATTRNAME, ATTRVALUE gridNodeMeta model = QStandardItemModel(0, 2) model.setHeaderData(ATTRNAME, Qt.Horizontal, "Attribute") model.setHeaderData(ATTRVALUE, Qt.Horizontal, "Value") return model def clearMetaBox(self): # self.lblMetaHeader.setText("Definition") # metabox grid setup # ATTRNAME, ATTRVALUE gridMetaBox self.gridMetaBox.setModel(None) self.metaBoxModel = self.createMetaBoxModel() self.gridMetaBox.setModel(self.metaBoxModel) self.gridMetaBox.setSelectionBehavior(QAbstractItemView.SelectItems) self.gridMetaBox.setSelectionMode(QAbstractItemView.SingleSelection) self.gridMetaBox.setColumnWidth(ATTRNAME, 150) self.gridMetaBox.setColumnWidth(ATTRVALUE, 300) # hide vertical header vheader = self.gridMetaBox.verticalHeader() vheader.hide() # header header = self.gridMetaBox.horizontalHeader() header.setSectionResizeMode(ATTRNAME, QHeaderView.Fixed) header.setSectionResizeMode(ATTRVALUE, QHeaderView.Stretch) # set editor delegate # self.gridMetaBox.setItemDelegateForColumn(ATTRVALUE, MetaBoxDelegate(self)) # self.gridMetaBox.itemDelegateForColumn(ATTRVALUE).closeEditor.connect(self.metaBoxEditorClosed) # connect model slots self.metaBoxModel.itemChanged.connect(self.metaBoxModelItemChanged) # connect grid slots self.gridMetaBox.selectionModel().selectionChanged.connect(self.metaBoxGridSelectionChanged) def addMetaRow(self, attribute=None, dictKey=None, value=None, editable=None ): ''' add a row to the metabox grid ''' # print("add metarow {}-{}-{}".format(attribute, value, editable)) self.gridMetaBox.setSortingEnabled(False) # attribute if attribute is None: attribute = "" item1 = QStandardItem(attribute) # save the dictKey in the attribute name so we can update the itemDict later item1.setData(dictKey, Qt.UserRole) item1.setEditable(False) # value if value is None: value = "" item2 = QStandardItem(str(value)) # save the attribute name in the value item so the custom editor MetaBoxDelegate can find it item2.setData(attribute, Qt.UserRole) item2.setEditable(editable) self.gridMetaBox.model().appendRow([item1,item2 ]) def populateMetaBox(self, formDefItem=None): self.populatingMetaBox = True if not formDefItem is None: self.clearMetaBox() for attr in formDefItem.attributes(): self.addMetaRow(attribute=attr[0], dictKey = attr[1], value=formDefItem.itemDict[attr[1]], editable=attr[2] ) self.populatingMetaBox = False def metaBoxModelItemChanged(self, item): if self.populatingMetaBox == False: print("item data changed {} at row:{} col:{}".format(str(item.checkState()), item.index().row(), item.index().column())) # a cell changed so see if other changes are needed selected = self.tvOutline.currentItem() if not (selected is None): formDefItem = selected.data(0, Qt.UserRole) # name= self.gridMetaBox.model().item(item.index().row(),ATTRNAME).data(Qt.EditRole) # get the dictionary key for this attribute name= self.gridMetaBox.model().item(item.index().row(),ATTRNAME).data(Qt.UserRole) # get the value entered by the user value= self.gridMetaBox.model().item(item.index().row(),ATTRVALUE).data(Qt.EditRole) formDefItem.updateAttr(name=name, value=value) # update the tree view description formDefItem.updateTreeView() # force selection of this cell self.gridMetaBox.setCurrentIndex(item.index()) # update form view self.genForm() def metaBoxGridSelectionChanged(self): # not used print("metabox grid selection changed") return
class ChangeUserPW(QDialog, Ui_ChangeUserPW): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(ChangeUserPW, self).__init__(parent) self.setupUi(self) self.helper = Helper() self.settings = QSettings() self.parent = parent self.editPW.setEchoMode(QLineEdit.Password) self.editNewPW.setEchoMode(QLineEdit.Password) self.editRepeatPW.setEchoMode(QLineEdit.Password) def logMessage(self, msg): if logging: logging.info(msg) @pyqtSlot() def on_btnCurrentShow_clicked(self): """ User clicks the show button for the current password """ if self.btnCurrentShow.text() == "Show": self.btnCurrentShow.setText("Hide") self.editPW.setEchoMode(QLineEdit.Normal) else: self.btnCurrentShow.setText("Show") self.editPW.setEchoMode(QLineEdit.Password) @pyqtSlot() def on_btnNewShow_clicked(self): """ User clicks the show button for the new passwords """ if self.btnNewShow.text() == "Show": self.btnNewShow.setText("Hide") self.editNewPW.setEchoMode(QLineEdit.Normal) self.editRepeatPW.setEchoMode(QLineEdit.Normal) else: self.btnNewShow.setText("Show") self.editNewPW.setEchoMode(QLineEdit.Password) self.editRepeatPW.setEchoMode(QLineEdit.Password) @pyqtSlot() def on_btnReset_clicked(self): """ User clicks the reset password button so do it """ if self.validate(): self.resetPassword() def validate(self, ): if self.helper.NoTextValueError(self.editUserID.text(), "You must enter a user ID to login with."): self.editUserID.setFocus() return False if self.helper.NoTextValueError(self.editPW.text(), "You must enter a password to login with."): self.editPW.setFocus() return False if self.helper.NoTextValueError(self.editNewPW.text(), "You must enter a new password."): self.editNewPW.setFocus() return False if self.helper.NoTextValueError(self.editRepeatPW.text(), "You must repeat the new password."): self.editRepeatPW.setFocus() return False if self.editNewPW.text() != self.editRepeatPW.text(): self.helper.displayErrMsg("Reset Password", "The new password does not match the repeat password.") self.editNewPW.setFocus() return False return True def resetPassword(self): '''login to Neo4j and reset the password ''' # get the currently selected schema tab's neocon name newConName = self.parent.pageItem.neoConName # get the neocon dictionary from settings newConDict = self.settings.value("NeoCon/connection/{}".format(newConName)) newConDict["userid"]=self.editUserID.text() savePW = self.helper.putText(self.editPW.text()) newConDict["password"]=savePW # create a new neoCon using the userid/password the person entered on this form self.newNeoCon = NeoDriver(name=newConName, neoDict = newConDict) QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.changePassword(userName=self.editUserID.text(), pw=self.editNewPW.text(), forceChange=False) if rc: self.helper.displayErrMsg("Change Password", msg) else: self.helper.displayErrMsg("Change Password Error", msg) QApplication.restoreOverrideCursor() def changePassword(self, userName=None, pw=None, forceChange=None): try: # cypherCmd = "CALL dbms.security.changeUserPassword('{}','{}',{})".format(userName, pw, str(forceChange)) cypherCmd = "CALL dbms.changePassword('{}')".format(pw) self.logMessage("Attempting: {}".format(cypherCmd)) #run the query rc1, msg1 = self.newNeoCon.runCypherAuto(cypherCmd) if rc1: msg = "Password Changed." else: msg = "Change Password Error {}".format(msg1) except BaseException as e: msg = "{} - Change Password Error.".format(repr(e)) finally: self.logMessage(msg) return rc1, msg @pyqtSlot() def on_btnClose_clicked(self): """ User clicks the Close button so exit the dialog """ QDialog.accept(self)
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 RelationInstance(): ''' This represents one NodeEra managed relationship that can be on one or more diagrams. This class manages reading and writing the relationship to Neo4j The RelationItem class creates the arc graphics item and the text graphics item and draws them on the scene. startNode and endNode must be a node instance ''' def __init__(self, parent, relationInstanceDict=None, startNode=None, endNode=None, model=None): self.helper = Helper() self.neoTypeFunc = NeoTypeFunc() self.diagramType = "Instance Relationship" self.logMsg = None self.relationInstanceDict = relationInstanceDict self.parent = parent self.model = model self.neoCon = self.model.modelNeoCon self.relationship = None # set local variables to what is in the dictionary if self.relationInstanceDict: self.relTemplate = self.relationInstanceDict.get("relTemplate", None) self.relName = self.relationInstanceDict.get("relName", "NoRelationshipName") self.propList = self.relationInstanceDict.get("properties", []) self.startNZID = self.relationInstanceDict.get("startNZID", None) self.endNZID = self.relationInstanceDict.get("endNZID", None) self.startNode = None self.endNode = None if self.relationInstanceDict.get("NZID", None) == None: self.NZID = str(uuid.uuid4()) else: self.NZID = self.relationInstanceDict["NZID"] self.neoID=self.relationInstanceDict.get("neoID", None) # if no dictionary passed then set defaults else: self.relName = "NoRelationshipName" self.relTemplate = None self.propList = [] self.NZID = str(uuid.uuid4()) self.neoID=None self.startNZID = None self.endNZID = None self.startNode = None self.endNode = None # get start and end node item objects if not startNode is None: self.startNode = startNode self.startNZID = self.startNode.NZID if not endNode is None: self.endNode = endNode self.endNZID = self.endNode.NZID self.getFormat() if not self.relationInstanceDict: self.relationInstanceDict = self.getObjectDict() def reloadDictValues(self, ): ''' This function will reset the local variables to the instance rel dictionary. This is needed when open diagram relationship instances need to be updated. ''' index, relDict = self.model.getDictByName(topLevel="Instance Relationship",objectName=self.NZID) if relDict: # set local variables to what is in the dictionary self.relInstanceDict = relDict self.nodeTemplate = self.relInstanceDict.get("relTemplate", None) self.propList = self.relInstanceDict.get("properties", []) self.NZID = self.relInstanceDict["NZID"] self.neoID=self.relInstanceDict.get("neoID", None) def getFormat(self, ): ''' determine if the rel instance has a template format or should use the project default format ''' # get the default self.relFormat = IRelFormat(formatDict=self.model.modelData["IRformat"]) # get a custom template format if there is one if not self.relTemplate is None: index, relTemplateDict = self.model.getDictByName(topLevel="Relationship Template",objectName=self.relTemplate) if not relTemplateDict is None: self.instanceRelFormatDict = relTemplateDict.get("IRformat", None) if not self.instanceRelFormatDict is None: self.relFormat = IRelFormat(formatDict=self.instanceRelFormatDict) def getObjectDict(self, ): objectDict = {} objectDict["NZID"] = self.NZID objectDict["name"] = self.NZID objectDict["neoID"] = self.neoID objectDict["diagramType"] = self.diagramType objectDict["relName"] = self.relName objectDict["properties"] = self.propList objectDict["startNZID"] = self.startNZID objectDict["endNZID"] = self.endNZID objectDict["relTemplate"] = self.relTemplate self.genRelSignature(objectDict) return objectDict def genRelSignature(self, relDict): '''generate the display name for a relationship instance ''' if self.neoID is None: relID = self.NZID else: relID = self.neoID if (self.startNode is None or self.startNode.neoID is None): fromID = "0" else: fromID = self.startNode.neoID if (self.endNode is None or self.startNode.neoID is None): toID = "0" else: toID = self.endNode.neoID propList = self.helper.genPropValueList("n", relDict) signature = "({})-[{}:{} {}{}{}]->({})".format(str(fromID), str(relID), self.relName, "{", propList, "}", str(toID)) relDict["displayName"] = signature def setLogMethod(self, logMethod=None): if logMethod is None: if self.logMsg is None: self.logMsg = self.noLog else: self.logMsg = logMethod def noLog(self, msg ): return def deleteRel(self, logMethod=None): '''This method deletes the relationship from the database.''' # set the logging method self.setLogMethod(logMethod) # if it doesn't have a neoID it's never been created in the db so nothing to delete if self.neoID is None: self.logMsg("The Relationship does not exist in the database. Cannot delete.") return True, "No Relationship to delete." # delete the Relationship based on the neoID cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + " delete r" rc, msg = self.neoCon.runCypherAuto(cypher) self.logMsg(msg) return rc, msg def checkRelationship(self, logMethod=None): '''This method checks to see if a matching relationship exists in the database. ''' # set the logging method self.setLogMethod(logMethod) # if it doesn't have a neoID it's never been created in the db so create a blank one if self.neoID is None: rc = NOTFOUND msg = "Diagram Relationship Doesn't Exist in the Graph" return rc, msg # see if the node still exists in the db based on the node id cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + " return r" rc, msg = self.neoCon.runCypherExplicit(cypher) if rc is False: self.logMsg(msg) msg = "Error Querying Graph." return NOTFOUND, msg else: if len(self.neoCon.resultSet) > 0: firstRec = self.neoCon.resultSet[0] if not firstRec is None: self.relationship = firstRec["r"] # the rel was found so set it if self.compareRel(): return FOUNDMATCH, "Diagram Relationship Matches Graph" else: return FOUNDNOMATCH, "Diagram Relationship Doesn't Match Graph" else: return NOTFOUND, "Diagram Relationship Doesn't Exist in Graph" else: return NOTFOUND, "Diagram Relationship Doesn't Exist in Graph" return def compareRel(self): '''Compare the Relationship object retrieved from the database with the current state of the relationship item''' rc = True # need to check if the start and end nodes are the same pass # compare graph properties to diagram properties for propName, propVal in dict(self.relationship).items(): try: dataType = self.neoTypeFunc.getNeo4jDataType(value=propVal) dataVal = self.neoTypeFunc.convertTypeToString(propVal) # special case as an integer is also a valid float if dataType == "Integer": if ([propName, "Integer", dataVal] in self.propList or [propName, "Float", dataVal] in self.propList): continue else: rc = False break elif [propName, dataType, dataVal] in self.propList: continue else: rc = False break except: rc = False break # compare diagram properties to graph properties for prop in self.propList: try: # is the property name one of the keys in the node if prop[0] in [propName for propName, propVal in dict(self.relationship).items() ]: pVal = [propVal for propName, propVal in dict(self.relationship).items() if propName == prop[0]][0] # does the datatype and data value match - special case for Float because the value could be an int or a float if (prop[1] == "Float" and self.neoTypeFunc.getNeo4jDataType(value=pVal) in ["Float", "Integer"] and prop[2] == self.neoTypeFunc.convertTypeToString(pVal) ): continue elif (prop[1] == self.neoTypeFunc.getNeo4jDataType(value=pVal) and prop[2] == self.neoTypeFunc.convertTypeToString(pVal)): continue else: rc = False break else: # is the prop missing in the neo4j relationship because it's null on the diagram? if prop[2] == "Null": continue else: rc = False break except: rc = False break return rc def getRelationship(self, logMethod=None): ''' This method retrieves the relationship from Neo4j and sets the relationship object. At this point the relationship object looks like it is in Neo4j ''' # set the logging method self.setLogMethod(logMethod) # if there's no neoID then there isn't a relationship in neo4j if self.neoID is None: return None, "Relationship [{}] doesn't exist in Neo4j".format(self.neoID) # try to retrieve the relationship based on the neoid cypher = "match ()-[r]->() where id(r) = " + str(self.neoID) + " return r" rc, msg = self.neoCon.runCypherAuto(cypher) if rc is False: return rc, "Retrieve Relationship [{}] error - {}".format(self.neoID, msg) else: # relationship found, save it firstRec = None if len(self.neoCon.resultSet) > 0: firstRec = self.neoCon.resultSet[0] if not firstRec is None: self.relationship = firstRec["r"] # the rel was found so set it return True, "Relationship [{}] Found".format(self.neoID) # relationship not found, it might have been deleted thru the backend else: return None, "Relationship [{}] Not Found".format(self.neoID) def createNewRelationship(self, logMethod=None): ''' create a new relationship in neo4j based on the current state of the relation instance object ''' # set the logging method self.setLogMethod(logMethod) # force creation of the start and end nodes in Neo4j if they don't exist rc, msg = self.startNode.getNode(logMethod) if rc == False: return rc, "Create Relationship error on from node - {}".format(msg) rc, msg = self.endNode.getNode(logMethod) if rc == False: return rc, "Create Relationship error on to node - {}".format(msg) # generate cypher stmt to create the relationship cypher = self.helper.genCreateRelCypher(relInstanceDict = self.getObjectDict(), rel = self.relationship, fromNeoID = self.startNode.neoID, toNeoID=self.endNode.neoID) # run the create rc, msg = self.neoCon.runCypherAuto(cypher) if rc is True: firstRec = None if len(self.neoCon.resultSet) > 0: firstRec = self.neoCon.resultSet[0] if not firstRec is None: self.relationship = firstRec["r"] # the rel was found so set it self.neoID = firstRec["id(r)"] self.relationInstanceDict["neoID"] = self.neoID return rc, "New Relationship Created - {}".format(msg) else: self.logMsg(msg) return rc, "Create New Relationship [{}] error - {}".format(self.NZID, msg) return False, "Unexpected Error" def updateRelProps(self, ): ''' set the properties of the relationship object to what has been entered on the dialog ''' # properties relProps = dict(self.relationship) #set all existing property values in the relationship object to None for key in relProps: self.relationship[key]=None # set all property values from the grid for property in self.propList: self.relationship[property[0]]=property[1] def updateRelationship(self, logMethod=None): ''' relationship already exists, so all you can do is change the properties if any ''' # set the logging method self.setLogMethod(logMethod) # generate cypher stmt to create the relationship cypher = self.helper.genUpdateRelCypher(relInstanceDict = self.getObjectDict(), rel = self.relationship, relID = self.neoID) # run the create rc, msg = self.neoCon.runCypherAuto(cypher) if rc is True: return rc, "Existing Relationship Updated - {}".format(msg) else: self.logMsg(msg) return rc, "Update Existing Relationship [{}] error - {}".format(self.NZID, msg) def syncToDB(self, logMethod=None): # get the relationship from the db, rc, msg = self.getRelationship(logMethod) if rc is True: # relationship found in neo4j and relationship object set. generate cypher to update relationship to look like the ui rc, msg = self.updateRelationship(logMethod) if rc is True: return rc, "Sync Relationship OK - {}".format(msg) else: return rc, "Sync Relationship Error - {}".format(msg) elif rc is False: # error processing the match statement - big fail return rc, "Sync Relationship Error - {}".format(msg) elif rc is None: # relationship object was not found in neo4j so create it rc, msg = self.createNewRelationship(logMethod) if rc is True: return rc, "Sync Relationship OK - {}".format(msg) else: return rc, "Sync Relationship Error - {}".format(msg) else: return rc, "Unknown Sync Relationship Error - {}".format(msg) def syncFromDB(self, logMethod=None): # set the logging method self.setLogMethod(logMethod) # should never happen, but if this instance rel has no neoID it can't be matched to a neo4j node so no way to sync it from neo4j if self.neoID is None: msg = "Error - this instance relationship has never been saved to Neo4j" self.helper.displayErrMsg("Sync From DB", msg) return False, msg rc, msg = self.getRelationship() if rc is True: # get the dictionary of properties from the relationship object and convert it to a list props = [] for key, val in dict(self.relationship).items(): props.append( [key, self.neoTypeFunc.getNeo4jDataType(value=val),self.neoTypeFunc.convertTypeToString(dataValue=val) ]) # set instance props to look like what's in the neo4j relationship object just retrieved self.propList = props # update the object dictionary self.relationInstanceDict = self.getObjectDict() # save it to the design model self.saveToModel() return True, "Instance Relationship Synced" else: return False, "Error syncing Instance Relationship" def saveToModel(self, ): # save the current state of the relation instance to the project model saveIndex = self.model.instanceTopLevelIndex("Instance Relationship", self.NZID) if not saveIndex is None: self.model.modelData["Instance Relationship"][saveIndex]=self.relationInstanceDict
class RelTemplateCypher(): def __init__(self, parent=None, templateDict=None): self.parent = parent self.templateDict = templateDict self.type = "Relationship" self.helper = Helper() # this determines if the DataGridWidget should call genMatch on a grid refresh or should it suppy it's own cypher def isGeneric(self, ): return False # set which button driven functionality will be enabled def refreshOK(self, ): return True def exportOK(self, ): return True def newOK(self, ): return True def deleteOK(self, ): return True def rowSelect(self, ): return False def setNullOK(self, ): return True def genDeleteDetach(self, row=None, dataGrid=None): model = dataGrid.model() # get the relID self.relID = None for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "rel_id": self.relID = model.item(row, header).data(Qt.EditRole) if not self.relID is None: p1 = self.relID cypher = " ".join( ["match (f)-[r]->(t) \n", "where id(r) = {} \n", "delete r"]).format(p1) return cypher def genUpdateProp(self, updateIndex=None, dataGrid=None): cypher = "" rc = True model = dataGrid.model() # get the RELATIONSHIP ID self.relID = None for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "rel_id": self.relID = model.item(updateIndex.row(), header).data(Qt.EditRole) if self.relID is None: return False, "Relationship ID not found." try: self.updateData = model.item( updateIndex.row(), updateIndex.column()).data(Qt.EditRole) self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal, Qt.DisplayRole) # get the correct neo4j type neoType = model.item(updateIndex.row(), updateIndex.column()).data(Qt.UserRole + 1) # if the datatype comes back unknown then generate cypher comment if neoType == 'Unknown': cypher = "Can't update unknown datatype, correct datatype in relationship template." rc = False else: # generate the correct syntax that you set the property equal to self.setEqualTo = self.helper.genPropEqualTo( dataValue=self.updateData, neoType=neoType) p1 = self.relID p2 = self.updateProp p3 = self.setEqualTo cypher = " ".join([ "match (f)-[r]->(t) \n", "where id(r) = {} \n", "set r.{} = {}" ]).format(p1, p2, p3) rc = True except BaseException as e: # something went wrong cypher = "Error generating cypher: {}".format(repr(e)) rc = False finally: return rc, cypher def genRemoveProp(self, updateIndex=None, dataGrid=None): model = dataGrid.model() cypher = None self.relID = None # get the relID for header in range(model.columnCount()): if model.headerData(header, Qt.Horizontal, Qt.DisplayRole) == "rel_id": self.relID = model.item(updateIndex.row(), header).data(Qt.EditRole) if self.relID is None: return cypher # get the property name self.updateProp = model.headerData(updateIndex.column(), Qt.Horizontal, Qt.DisplayRole) # MAKE SURE IT ISN'T A REQUIRED PROPERTY for prop in self.templateDict["properties"]: if prop[PROPERTY] == self.updateProp: if prop[PROPREQ] != Qt.Checked: p1 = self.relID p2 = self.updateProp cypher = "match (f)-[r]->(t) \n where id(r) = {} \n remove r.{} ".format( p1, p2) else: self.helper.displayErrMsg( "Set Null", "Property {} is required by the Relationship Template. Cannot remove this property." .format(self.updateProp)) return cypher def genMatch(self): ''' Generate the match cypher statement that the data grid will use to refresh the grid. Generate the editParmDict dictionary that tells the data grid how to handle each column in the result set ''' # fromNodeVar = "f" # toNodeVar = "t" relVar = "r" p1 = self.templateDict["relname"] p3 = self.genReturnPropList(relVar) if len(p3) > 1: p2 = "," else: p2 = "" cypher = " ".join([ "match (f)-[r]->(t) \n", "where type(r) = '{}' \n", "return id(f) as From_NodeID, \n", "id(t) as To_NodeID, \n", "id(r) as rel_id, \n", "type(r) as rel_name {} \n", "{} \n", ]).format(p1, p2, p3) #PROP, REQLBL, OPTLBL, NODEID, RELID, NODE, RELATIONSHIP, RELNAME editParmDict = [] editParmDict.append([NODEID, False]) editParmDict.append([NODEID, False]) editParmDict.append([RELID, False]) editParmDict.append([RELNAME, False]) for prop in self.templateDict["properties"]: editParmDict.append([PROP, True]) return cypher, editParmDict def genReturnPropList(self, nodeName): 'return all properties in the template' propList = "" propList = ",".join(nodeName + "." + x[PROPERTY] + " as " + x[PROPERTY] + " \n" for x in self.templateDict["properties"]) return propList def genSetPropList(self, nodeName): 'return a set statement for each property that has a default value (i.e. required)' setPropList = [] if not self.templateDict is None: for prop in self.templateDict["properties"]: # if the property has a default value then generate the set statement. if prop[PROPDEF] != "": # generate the correct syntax that you set the property equal to setEqualTo = self.helper.genPropEqualTo( dataValue=prop[PROPDEF], neoType=prop[DATATYPE]) setPropList.append("set {}.{} = {}".format( nodeName, prop[PROPERTY], setEqualTo)) setProps = " \n ".join(setProp for setProp in setPropList) return setProps
class EditUserDlg(QDialog, Ui_EditUserDlg): """ Class documentation goes here. """ def __init__(self, parent=None, mode=None, userName=None): """ Constructor @param parent reference to the parent widget @type QWidget """ super(EditUserDlg, self).__init__(parent) self.parent = parent self.setupUi(self) self.userName = userName self.mode = mode self.helper = Helper() self.initUI() def initUI(self, ): self.txtUserName.setText(self.userName) self.txtUserName.setReadOnly(True) # get the user status and change password flag self.resetFlags() # populate the role combo box self.cbRoleName.addItems(self.parent.schemaModel.instanceList("Role")) # populate users roles in the list box userRoles = self.parent.schemaModel.getUserRoles( userName=self.userName) for userRole in userRoles: self.lstUserRoles.addItem(userRole) def resetFlags(self, ): # get the user status and change password flag self.userFlags = self.parent.schemaModel.getUserFlags( userName=self.userName) if "is_suspended" in self.userFlags: self.pbActivate.setChecked(False) self.pbSuspend.setChecked(True) else: self.pbSuspend.setChecked(False) self.pbActivate.setChecked(True) if "password_change_required" in self.userFlags: self.pbChangeOnLogin.setChecked(True) else: self.pbChangeOnLogin.setChecked(False) # the password change required flag can't be changed, the radio button just indicates how it is currently set self.pbChangeOnLogin.setEnabled(False) @pyqtSlot() def on_btnAddRole_clicked(self): """ Give the user a role from the combobox of roles """ addRole = self.cbRoleName.currentText() currentRoles = [ str(self.lstUserRoles.item(i).text()) for i in range(self.lstUserRoles.count()) ] if addRole in currentRoles: # error, the user already has the selected role self.helper.displayErrMsg("Edit User", "The user already has this role.") else: # give the user the role QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.addUserRole( userName=self.userName, role=addRole) if rc: self.lstUserRoles.addItem(addRole) else: self.helper.displayErrMsg("Add Role to User Error", msg) QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnRemoveRoles_clicked(self): """ Remove the selected role from the user """ removeRole = self.lstUserRoles.currentItem().text() QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.removeUserRole( userName=self.userName, role=removeRole) if rc: self.lstUserRoles.takeItem(self.lstUserRoles.currentRow()) else: self.helper.displayErrMsg("Remove Role from User Error", msg) QApplication.restoreOverrideCursor() @pyqtSlot() def on_dlgBtnBox_accepted(self): """ User clicks Close """ QDialog.accept(self) @pyqtSlot() def on_dlgBtnBox_rejected(self): """ User clicks something that generates the reject """ QDialog.reject(self) @pyqtSlot() def on_pbActivate_clicked(self): """ Activate a suspended user """ if not "is_suspended" in self.userFlags: # nothing to do return QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.activateUser(userName=self.userName) if rc: self.resetFlags() else: self.helper.displayErrMsg("Activate User Error", msg) self.resetFlags() QApplication.restoreOverrideCursor() @pyqtSlot() def on_pbSuspend_clicked(self): """ Suspend an active user """ if "is_suspended" in self.userFlags: # nothing to do return QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.suspendUser(userName=self.userName) if rc: self.resetFlags() else: self.helper.displayErrMsg("Suspend User Error", msg) self.resetFlags() QApplication.restoreOverrideCursor() @pyqtSlot() def on_btnChangePassword_clicked(self): """ User requests to change the password. """ if not self.helper.NoTextValueError(self.txtNewPassword.text(), "You must supply a new password"): QApplication.setOverrideCursor(Qt.WaitCursor) rc, msg = self.parent.schemaModel.changePassword( userName=self.userName, pw=self.txtNewPassword.text(), forceChange=self.pbChangeOnLogin.isChecked()) if rc: self.helper.displayErrMsg("Change Password", "The password has been changed") else: self.helper.displayErrMsg("Change Password Error", msg) self.resetFlags() QApplication.restoreOverrideCursor()