def add_spatial_unit_layer(self): """ Adds spatial unit layers on the map canvas when View Social Tenure is launched. :return: None :rtype: NoneType """ sp_unit_manager = SpatialUnitManagerDockWidget(self._plugin.iface, self._plugin) spatial_unit_lyr = sp_unit_manager.entity_layer_names( self.spatial_unit) for lyr in spatial_unit_lyr: sp_unit_manager.add_layer_by_name(lyr)
def draw_spatial_unit(self, spatial_unit, model): """ Draw geometry of the given model in the respective local and web views. :param model: Source model whose geometry will be drawn. :type model: object :param clear_existing: Clears any existing features prior to adding the new features. :type clear_existing: bool """ if model is None: msg = QApplication.translate( "SpatialPreview", "Data model is empty, the spatial " "unit cannot be rendered.") QMessageBox.critical( self, QApplication.translate("SpatialPreview", "Spatial Unit Preview"), msg) return table_name = spatial_unit.name if not pg_table_exists(table_name): msg = QApplication.translate( "SpatialPreview", "The spatial unit data source could " "not be retrieved, the feature cannot " "be rendered.") QMessageBox.critical( self, QApplication.translate("SpatialPreview", "Spatial Unit Preview"), msg) return sp_unit_manager = SpatialUnitManagerDockWidget(self.iface()) spatial_cols = sp_unit_manager.geom_columns(spatial_unit) geom, geom_col = None, "" sc_obj = None for sc in spatial_cols: db_geom = getattr(model, sc.name) #Use the first non-empty geometry # value in the collection if not db_geom is None: sc_obj = sc geom_col = sc.name geom = db_geom QApplication.processEvents() lyr = sp_unit_manager.geom_col_layer_name(table_name, sc_obj) sp_unit_manager.add_layer_by_name(lyr) if geom is not None: self.highlight_spatial_unit(spatial_unit, geom, self.local_map.canvas) self._web_spatial_loader.add_overlay(model, geom_col)
def draw_spatial_unit(self, spatial_unit, model): """ Draw geometry of the given model in the respective local and web views. :param model: Source model whose geometry will be drawn. :type model: object :param clear_existing: Clears any existing features prior to adding the new features. :type clear_existing: bool """ if model is None: msg = QApplication.translate("SpatialPreview", "Data model is empty, the spatial " "unit cannot be rendered.") QMessageBox.critical(self, QApplication.translate( "SpatialPreview", "Spatial Unit Preview"), msg) return table_name = spatial_unit.name if not pg_table_exists(table_name): msg = QApplication.translate("SpatialPreview", "The spatial unit data source could " "not be retrieved, the feature cannot " "be rendered.") QMessageBox.critical( self, QApplication.translate( "SpatialPreview", "Spatial Unit Preview"), msg ) return sp_unit_manager = SpatialUnitManagerDockWidget(self.iface()) spatial_cols = sp_unit_manager.geom_columns(spatial_unit) geom, geom_col = None, "" sc_obj = None for sc in spatial_cols: db_geom = getattr(model, sc.name) #Use the first non-empty geometry # value in the collection if not db_geom is None: sc_obj = sc geom_col = sc.name geom = db_geom QApplication.processEvents() lyr = sp_unit_manager.geom_col_layer_name( table_name, sc_obj ) sp_unit_manager.add_layer_by_name(lyr) if geom is not None: self.highlight_spatial_unit( spatial_unit, geom, self.local_map.canvas ) self._web_spatial_loader.add_overlay( model, geom_col )
def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self.btnSearch.setIcon(GuiUtils.get_icon('search.png')) self.btnClearSearch.setIcon(GuiUtils.get_icon('reset.png')) self._plugin = plugin self.search_done = False # self.tbPropertyPreview.set_iface(self._plugin.iface) QTimer.singleShot( 100, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) self.curr_profile = current_profile() self.spatial_units = self.curr_profile.social_tenure.spatial_units # Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin) self.geom_cols = [] for spatial_unit in self.spatial_units: each_geom_col = self.sp_unit_manager.geom_columns(spatial_unit) self.geom_cols.extend(each_geom_col) # Configure notification bar self._notif_search_config = NotificationBar(self.vl_notification) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None # Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved) self._source_doc_manager.setEditPermissions(False) self.initGui() self.add_spatial_unit_layer() self.details_tree_view = DetailsTreeView(iface, self._plugin, self) # else: # self.details_tree_view = self._plugin.details_tree_view self.details_tree_view.activate_feature_details(True) self.details_tree_view.add_tree_view() self.details_tree_view.model.clear() count = pg_table_count(self.curr_profile.social_tenure.name) self.setWindowTitle( self.tr('{}{}'.format(self.windowTitle(), '- ' + str(count) + ' rows'))) self.active_spu_id = -1 self.toolBox.setStyleSheet(''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''') self.details_tree_view.view.setStyleSheet(''' QTreeView:!active { selection-background-color: #72a6d9; } ''')
class ViewSTRWidget(WIDGET, BASE): """ Search and browse the social tenure relationship of all participating entities. """ def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self.btnSearch.setIcon(GuiUtils.get_icon('search.png')) self.btnClearSearch.setIcon(GuiUtils.get_icon('reset.png')) self._plugin = plugin self.search_done = False # self.tbPropertyPreview.set_iface(self._plugin.iface) QTimer.singleShot( 100, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) self.curr_profile = current_profile() self.spatial_units = self.curr_profile.social_tenure.spatial_units # Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin) self.geom_cols = [] for spatial_unit in self.spatial_units: each_geom_col = self.sp_unit_manager.geom_columns(spatial_unit) self.geom_cols.extend(each_geom_col) # Configure notification bar self._notif_search_config = NotificationBar(self.vl_notification) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None # Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved) self._source_doc_manager.setEditPermissions(False) self.initGui() self.add_spatial_unit_layer() self.details_tree_view = DetailsTreeView(iface, self._plugin, self) # else: # self.details_tree_view = self._plugin.details_tree_view self.details_tree_view.activate_feature_details(True) self.details_tree_view.add_tree_view() self.details_tree_view.model.clear() count = pg_table_count(self.curr_profile.social_tenure.name) self.setWindowTitle( self.tr('{}{}'.format(self.windowTitle(), '- ' + str(count) + ' rows'))) self.active_spu_id = -1 self.toolBox.setStyleSheet(''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''') self.details_tree_view.view.setStyleSheet(''' QTreeView:!active { selection-background-color: #72a6d9; } ''') def add_tool_buttons(self): """ Add toolbar buttons of add, edit and delete buttons. :return: None :rtype: NoneType """ tool_buttons = QToolBar() tool_buttons.setObjectName('form_toolbar') tool_buttons.setIconSize(QSize(16, 16)) self.addSTR = QAction(GuiUtils.get_icon('add.png'), QApplication.translate('ViewSTRWidget', 'Add'), self) self.editSTR = QAction(GuiUtils.get_icon('edit.png'), QApplication.translate('ViewSTRWidget', 'Edit'), self) self.deleteSTR = QAction( GuiUtils.get_icon('remove.png'), QApplication.translate('ViewSTRWidget', 'Remove'), self) tool_buttons.addAction(self.addSTR) tool_buttons.addAction(self.editSTR) tool_buttons.addAction(self.deleteSTR) self.toolbarVBox.addWidget(tool_buttons) def initGui(self): """ Initialize widget """ self.tb_actions.setVisible(False) self._load_entity_configurations() self.add_tool_buttons() # Connect signals self.tbSTREntity.currentChanged.connect(self.entityTabIndexChanged) self.btnSearch.clicked.connect(self.searchEntityRelations) self.btnClearSearch.clicked.connect(self.clearSearch) # self.tvSTRResults.expanded.connect(self.onTreeViewItemExpanded) # Set the results treeview to accept requests for context menus # self.tvSTRResults.setContextMenuPolicy(Qt.CustomContextMenu) # self.tvSTRResults.customContextMenuRequested.connect( # self.onResultsContextMenuRequested # ) if not self._can_create: self.addSTR.hide() if not self._can_edit: self.editSTR.hide() else: self.editSTR.setDisabled(True) if not self._can_delete: self.deleteSTR.hide() else: self.deleteSTR.setDisabled(True) self.addSTR.triggered.connect(self.load_new_str_editor) self.deleteSTR.triggered.connect(self.delete_str) self.editSTR.triggered.connect(self.load_edit_str_editor) # Load async for the current widget self.entityTabIndexChanged(0) def init_progress_dialog(self): """ Initializes the progress dialog. """ self.progress = QProgressBar(self) self.progress.resize(self.width(), 10) self.progress.setTextVisible(False) def add_spatial_unit_layer(self): """ Add the spatial unit layer into the map canvas for later use. """ # Used for startup of view STR, just add the first geom layer. if len(self.geom_cols) > 0: for spatial_unit in self.spatial_units: layer_name_item = self.sp_unit_manager.geom_col_layer_name( spatial_unit.name, self.geom_cols[0]) self.sp_unit_manager.add_layer_by_name(layer_name_item) def _check_permissions(self): """ Enable/disable actions based on the permissions defined in the content group. """ if self._can_edit: self.tb_actions.addAction(self._new_str_action) else: self.tb_actions.removeAction(self._new_str_action) if len(self.tb_actions.actions()) == 0: self.tb_actions.setVisible(False) else: self.tb_actions.setVisible(True) def _load_entity_configurations(self): """ Specify the entity configurations. """ try: self.parties = self.curr_profile.social_tenure.parties tb_str_entities = self.parties + self.spatial_units for i, t in enumerate(tb_str_entities): QApplication.processEvents() entity_cfg = self._entity_config_from_profile( str(t.name), t.short_name) if not entity_cfg is None: entity_widget = self.add_entity_config(entity_cfg) # entity_widget.setNodeFormatter( # EntityNodeFormatter( # entity_cfg, self.tvSTRResults, self # ) # ) except DummyException as pe: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(str(pe)) def _entity_config_from_profile(self, table_name, short_name): """ Creates an EntityConfig object from the table name. :param table_name: Name of the database table. :type table_name: str :return: Entity configuration object. :rtype: EntityConfig """ table_display_name = format_name(short_name) entity = self.curr_profile.entity_by_name(table_name) model = entity_model(entity) if model is not None: # Entity configuration entity_cfg = EntityConfiguration() entity_cfg.Title = table_display_name entity_cfg.STRModel = model entity_cfg.data_source_name = table_name for col, factory in self._get_widget_factory(entity): entity_cfg.LookupFormatters[col.name] = factory # Load filter and display columns # using only those which are of # numeric/varchar type searchable_columns = entity_searchable_columns(entity) display_columns = entity_display_columns(entity) for c in searchable_columns: if c != 'id': entity_cfg.filterColumns[c] = format_name(c) for c in display_columns: if c != 'id': entity_cfg.displayColumns[c] = format_name(c) return entity_cfg else: return None def _get_widget_factory(self, entity): """ Get widget factory for specific column type :param entity: Current column entity object :type entity: Entity :return c: Column object corresponding to the widget factory :rtype c: BaseColumn :return col_factory: Widget factory corresponding to the column type :rtype col_factory: ColumnWidgetRegistry """ for c in entity.columns.values(): col_factory = ColumnWidgetRegistry.factory(c.TYPE_INFO) if col_factory is not None: yield c, col_factory(c) def add_entity_config(self, config): """ Set an entity configuration option and add it to the 'Search Entity' tab. """ entityWidg = STRViewEntityWidget(config) entityWidg.asyncStarted.connect(self._progressStart) entityWidg.asyncFinished.connect(self._progressFinish) tabIndex = self.tbSTREntity.addTab(entityWidg, config.Title) return entityWidg def entityTabIndexChanged(self, index): """ Raised when the tab index of the entity search tab widget changes. """ # Get the current widget in the tab container entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.loadAsync() def searchEntityRelations(self): """ Slot that searches for matching items for the specified entity and corresponding STR entities. """ entityWidget = self.tbSTREntity.currentWidget() entity_name = entityWidget.config.data_source_name self._reset_controls() if isinstance(entityWidget, EntitySearchItem): valid, msg = entityWidget.validate() if not valid: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(msg) return results, searchWord = entityWidget.executeSearch() # Show error message if len(results) == 0: noResultsMsg = QApplication.translate( 'ViewSTR', 'No results found for "{}"'.format(searchWord)) self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(noResultsMsg) return party_names = [ e.name for e in self.curr_profile.social_tenure.parties ] entity = self.curr_profile.entity_by_name(entity_name) result_ids = [r.id for r in results] if entity_name in party_names: self.active_spu_id = self.details_tree_view.search_party( entity, result_ids) else: self.details_tree_view.search_spatial_unit(entity, result_ids) # self.tbPropertyPreview._iface.activeLayer().selectByExpression("id={}".format(self.active_spu_id)) # self.details_tree_view._selected_features = self.tbPropertyPreview._iface.activeLayer().selectedFeatures() # self._load_root_node(entity_name, formattedNode) def clearSearch(self): """ Clear search input parameters (for current widget) and results. """ entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.reset() self._reset_controls() def _reset_controls(self): # Clear tree view self._resetTreeView() # Clear document listings self._deleteSourceDocTabs() # Remove spatial unit memory layer self.tbPropertyPreview.remove_layer() def on_select_results(self): """ Slot which is raised when the selection is changed in the tree view selection model. """ if len(self.details_tree_view.view.selectedIndexes()) < 1: self.disable_buttons() return self.search_done = True index = self.details_tree_view.view.selectedIndexes()[0] item = self.details_tree_view.model.itemFromIndex(index) QApplication.processEvents() # STR node - edit social tenure relationship if item.text() == self.details_tree_view.str_text: entity = self.curr_profile.social_tenure str_model = self.details_tree_view.str_models[item.data()] self.details_tree_view.selected_model = str_model self.details_tree_view.selected_item = SelectedItem(item) documents = self.details_tree_view._supporting_doc_models( entity.name, str_model) self._load_source_documents(documents) # if there is supporting document, # expand supporting document tab if len(documents) > 0: self.toolBox.setCurrentIndex(1) self.disable_buttons(False) # party node - edit party elif item.data() in self.details_tree_view.spatial_unit_items.keys(): self.toolBox.setCurrentIndex(0) entity = self.details_tree_view.spatial_unit_items[item.data()] model = self.details_tree_view.feature_model(entity, item.data()) self.draw_spatial_unit(entity.name, model) self.disable_buttons() canvas = iface.mapCanvas() if canvas: canvas.zoomToFullExtent() else: self.disable_buttons() def disable_buttons(self, status=True): if self._can_edit: self.deleteSTR.setDisabled(status) if self._can_delete: self.editSTR.setDisabled(status) def str_party_column_obj(self, record): """ Gets the current party column name in STR table by finding party column with value other than None. :param record: The STR record or result. :type record: Dictionary :return: The party column name with value. :rtype: String """ for party in self.parties: party_name = party.short_name.lower() party_id = '{}_id'.format(party_name) if party_id not in record.__dict__: return None if record.__dict__[party_id] != None: party_id_obj = getattr(self.str_model, party_id) return party_id_obj def load_edit_str_editor(self): self.details_tree_view.edit_selected_node(self.details_tree_view) self.btnSearch.click() self.disable_buttons() def load_new_str_editor(self): try: # Check type of node and perform corresponding action add_str = STREditor() add_str.exec_() except DummyException as ex: QMessageBox.critical( self._plugin.iface.mainWindow(), QApplication.translate("STDMPlugin", "Loading Error"), str(ex)) def delete_str(self): self.details_tree_view.delete_selected_item() self.btnSearch.click() self.disable_buttons() def onSourceDocumentRemoved(self, container_id, doc_uuid, removed_doc): """ Slot raised when a source document is removed from the container. If there are no documents in the specified container then remove the tab. """ curr_container = self.tbSupportingDocs.currentWidget() curr_doc_widget = curr_container.findChildren(DocumentWidget) for doc in curr_doc_widget: if doc.fileUUID == doc_uuid: doc.deleteLater() self.removed_docs = removed_doc def draw_spatial_unit(self, entity_name, model): """ Render the geometry of the given spatial unit in the spatial view. :param row_id: Sqlalchemy object representing a feature. """ entity = self.curr_profile.entity_by_name(entity_name) self.tbPropertyPreview.draw_spatial_unit(entity, model) def showEvent(self, event): """ (Re)load map layers in the viewer and main canvas. :param event: Window event :type event: QShowEvent """ self.setEnabled(True) if QTimer is not None: QTimer.singleShot(200, self.init_mirror_map) return QMainWindow.showEvent(self, event) def init_mirror_map(self): self._notify_no_base_layers() # Add spatial unit layer if it doesn't exist self.tbPropertyPreview.refresh_canvas_layers() self.tbPropertyPreview.load_web_map() def _notify_no_base_layers(self): """ Checks if there are any base layers that will be used when visualizing the spatial units. If there are no base layers then insert warning message. """ self._notif_search_config.clear() num_layers = len(QgsProject.instance().mapLayers()) if num_layers == 0: msg = QApplication.translate( "ViewSTR", "No basemap layers are loaded in the " "current project. Basemap layers " "enhance the visualization of spatial units.") self._notif_search_config.insertWarningNotification(msg) def _deleteSourceDocTabs(self): """ Removes all source document tabs and deletes their references. """ tabCount = self.tbSupportingDocs.count() while tabCount != 0: srcDocWidget = self.tbSupportingDocs.widget(tabCount - 1) self.tbSupportingDocs.removeTab(tabCount - 1) del srcDocWidget tabCount -= 1 self._strID = None self._source_doc_manager.reset() def _resetTreeView(self): """ Clears the results tree view. """ # Reset tree view strModel = self.details_tree_view.view.model() resultsSelModel = self.details_tree_view.view.selectionModel() if strModel: strModel.clear() if resultsSelModel: if self.search_done: resultsSelModel.selectionChanged.disconnect( self.on_select_results) resultsSelModel.selectionChanged.connect(self.on_select_results) def _load_source_documents(self, source_docs): """ Load source documents into document listing widget. """ # Configure progress dialog progress_msg = QApplication.translate( "ViewSTR", "Loading supporting documents...") progress_dialog = QProgressDialog(self) if len(source_docs) > 0: progress_dialog.setWindowTitle(progress_msg) progress_dialog.setRange(0, len(source_docs)) progress_dialog.setWindowModality(Qt.WindowModal) progress_dialog.setFixedWidth(380) progress_dialog.show() progress_dialog.setValue(0) self._notif_search_config.clear() self.tbSupportingDocs.clear() self._source_doc_manager.reset() if len(source_docs) < 1: empty_msg = QApplication.translate( 'ViewSTR', 'No supporting document is uploaded ' 'for this social tenure relationship.') self._notif_search_config.clear() self._notif_search_config.insertWarningNotification(empty_msg) for i, (doc_type_id, doc_obj) in enumerate(source_docs.items()): # add tabs, and container and widget for each tab tab_title = self._source_doc_manager.doc_type_mapping[doc_type_id] tab_widget = QWidget() tab_widget.setObjectName(tab_title) cont_layout = QVBoxLayout(tab_widget) cont_layout.setObjectName('widget_layout_' + tab_title) scrollArea = QScrollArea(tab_widget) scrollArea.setFrameShape(QFrame.NoFrame) scrollArea_contents = QWidget() scrollArea_contents.setObjectName('tab_scroll_area_' + tab_title) tab_layout = QVBoxLayout(scrollArea_contents) tab_layout.setObjectName('layout_' + tab_title) scrollArea.setWidgetResizable(True) scrollArea.setWidget(scrollArea_contents) cont_layout.addWidget(scrollArea) self._source_doc_manager.registerContainer(tab_layout, doc_type_id) for doc in doc_obj: try: # add doc widgets self._source_doc_manager.insertDocFromModel( doc, doc_type_id) except DummyException as ex: LOGGER.debug(str(ex)) self.tbSupportingDocs.addTab(tab_widget, tab_title) progress_dialog.setValue(i + 1) # def _on_node_reference_changed(self, rootHash): # """ # Method for resetting document listing and map preview # if another root node and its children # are selected then the documents are reset as # well as the map preview control. # """ # if rootHash != self._curr_rootnode_hash: # self._deleteSourceDocTabs() # self._curr_rootnode_hash = rootHash def _progressStart(self): """ Load progress dialog window. For items whose durations is unknown, 'isindefinite' = True by default. If 'isindefinite' is False, then 'rangeitems' has to be specified. """ pass def _progressFinish(self): """ Hide progress dialog window. """ pass def _edit_permissions(self): """ Returns True/False whether the current logged in user has permissions to create new social tenure relationships. If true, then the system assumes that they can also edit STR records. """ canEdit = False userName = globals.APP_DBCONN.User.UserName authorizer = Authorizer(userName) newSTRCode = "9576A88D-C434-40A6-A318-F830216CA15A" # Get the name of the content from the code cnt = Content() createSTRCnt = cnt.queryObject().filter( Content.code == newSTRCode).first() if createSTRCnt: name = createSTRCnt.name canEdit = authorizer.CheckAccess(name) return canEdit
def __init__(self, entity, parent=None, state=MANAGE, load_records=True): EntityBrowser.__init__(self, entity, parent, state, load_records) self.record_id = 0 self.highlight = None self.load_records = load_records self.selection_layer = None #Add action toolbar if the state contains Manage flag if (state & MANAGE) != 0: add = QApplication.translate("EntityBrowserWithEditor", "Add") edit = QApplication.translate("EntityBrowserWithEditor", "Edit") remove = QApplication.translate("EntityBrowserWithEditor", "Remove") self._newEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/add.png"), add, self) self.connect(self._newEntityAction, SIGNAL("triggered()"), self.onNewEntity) self._editEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/edit.png"), edit, self) self._editEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "edit_tool")) self.connect(self._editEntityAction, SIGNAL("triggered()"), self.onEditEntity) self._removeEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/remove.png"), remove, self) self._removeEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "remove_tool")) self.connect(self._removeEntityAction, SIGNAL("triggered()"), self.onRemoveEntity) #Manage position of the actions based on whether the entity # supports documents. if self.can_view_supporting_documents: manage_acts = [ self._newEntityAction, self._editEntityAction, self._removeEntityAction ] self.tbActions.insertActions(self._view_docs_act, manage_acts) #Add action separator self._act_sep = QAction(self) self._act_sep.setSeparator(True) self.tbActions.insertAction(self._view_docs_act, self._act_sep) else: self.tbActions.addAction(self._newEntityAction) self.tbActions.addAction(self._editEntityAction) self.tbActions.addAction(self._removeEntityAction) if isinstance(parent, EntityEditorDialog): if self.can_view_supporting_documents: self._view_docs_act.setVisible(False) self.parent_entity = parent._entity else: self.parent_entity = None # hide the add button and add layer preview for spatial entity if entity.has_geometry_column() and self.parent_entity is None: self.sp_unit_manager = SpatialUnitManagerDockWidget(iface) self.geom_cols = self.sp_unit_manager.geom_columns( self._entity) self.add_spatial_unit_layer() self.tbEntity.clicked.connect(self.on_select_attribute) self.tbEntity.entered.connect(self.on_select_attribute) self.shift_spatial_entity_browser() # Hide the add button from spatial tables # self._newEntityAction.setVisible(False) self._editor_dlg = EntityEditorDialog
class EntityBrowserWithEditor(EntityBrowser): """ Entity browser with added functionality for carrying out CRUD operations directly. """ def __init__(self, entity, parent=None, state=MANAGE, load_records=True): EntityBrowser.__init__(self, entity, parent, state, load_records) self.record_id = 0 self.highlight = None self.load_records = load_records self.selection_layer = None #Add action toolbar if the state contains Manage flag if (state & MANAGE) != 0: add = QApplication.translate("EntityBrowserWithEditor", "Add") edit = QApplication.translate("EntityBrowserWithEditor", "Edit") remove = QApplication.translate("EntityBrowserWithEditor", "Remove") self._newEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/add.png"), add, self) self.connect(self._newEntityAction, SIGNAL("triggered()"), self.onNewEntity) self._editEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/edit.png"), edit, self) self._editEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "edit_tool")) self.connect(self._editEntityAction, SIGNAL("triggered()"), self.onEditEntity) self._removeEntityAction = QAction( QIcon(":/plugins/stdm/images/icons/remove.png"), remove, self) self._removeEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "remove_tool")) self.connect(self._removeEntityAction, SIGNAL("triggered()"), self.onRemoveEntity) #Manage position of the actions based on whether the entity # supports documents. if self.can_view_supporting_documents: manage_acts = [ self._newEntityAction, self._editEntityAction, self._removeEntityAction ] self.tbActions.insertActions(self._view_docs_act, manage_acts) #Add action separator self._act_sep = QAction(self) self._act_sep.setSeparator(True) self.tbActions.insertAction(self._view_docs_act, self._act_sep) else: self.tbActions.addAction(self._newEntityAction) self.tbActions.addAction(self._editEntityAction) self.tbActions.addAction(self._removeEntityAction) if isinstance(parent, EntityEditorDialog): if self.can_view_supporting_documents: self._view_docs_act.setVisible(False) self.parent_entity = parent._entity else: self.parent_entity = None # hide the add button and add layer preview for spatial entity if entity.has_geometry_column() and self.parent_entity is None: self.sp_unit_manager = SpatialUnitManagerDockWidget(iface) self.geom_cols = self.sp_unit_manager.geom_columns( self._entity) self.add_spatial_unit_layer() self.tbEntity.clicked.connect(self.on_select_attribute) self.tbEntity.entered.connect(self.on_select_attribute) self.shift_spatial_entity_browser() # Hide the add button from spatial tables # self._newEntityAction.setVisible(False) self._editor_dlg = EntityEditorDialog def onNewEntity(self): ''' Load editor dialog for adding a new record. ''' self._notifBar.clear() if not self._can_add_edit(): msg = QApplication.translate( 'EntityBrowserWithEditor', 'There are no user-defined columns for this entity.') self._notifBar.insertErrorNotification(msg) return if self._entity.has_geometry_column(): self.sp_unit_manager.active_layer_source() gps_tool = GPSToolDialog(iface, self._entity, self._entity.name, self.sp_unit_manager.active_sp_col, reload=False, entity_browser=self) editor_trans = self.tr('Editor') title = u'{0} {1}'.format(format_name(self._entity.short_name), editor_trans) gps_tool.setWindowTitle(title) result = gps_tool.exec_() result = False # a workaround to avoid duplicate model insert self.addEntityDlg = gps_tool.entity_editor else: self.addEntityDlg = self._editor_dlg( self._entity, parent=self, parent_entity=self.parent_entity) self.addEntityDlg.addedModel.connect(self.on_save_and_new) result = self.addEntityDlg.exec_() if result == QDialog.Accepted: model_obj = self.addEntityDlg.model() if self.addEntityDlg.is_valid: if self.parent_entity is None: self.addModelToView(model_obj) self.recomputeRecordCount() def on_save_and_new(self, model): """ A slot raised when save and new button is clicked. It updates the entity browser with the new model added. :param model: The model saved. :type model: SQL Alchemy Model """ if model is not None: insert_position = self.addModelToView(model) self.set_child_model(model, insert_position + 1) self.recomputeRecordCount() def _can_add_edit(self): """ Check if there are columns specified (apart from id) for the given entity. :return: Returns True if there are other columns apart from id, otherwise False. """ columns = self._entity.columns.values() if len(columns) < 2: return False return True def onEditEntity(self): ''' Slot raised to load the editor for the selected row. ''' self._notifBar.clear() if not self._can_add_edit(): msg = QApplication.translate( 'EntityBrowserWithEditor', 'There are no user-defined columns for this entity.') self._notifBar.insertErrorNotification(msg) return if self.tbEntity.selectionModel() is None: return selRowIndices = self.tbEntity.selectionModel().selectedRows(0) if len(selRowIndices) == 0: msg = QApplication.translate( "EntityBrowserWithEditor", "Please select a record in the table" " below for editing.") self._notifBar.insertWarningNotification(msg) return #Exit if more than one record has been selected if len(selRowIndices) > 1: msg = QApplication.translate( "EntityBrowserWithEditor", "Multiple selection detected, please choose one record " "only for editing.") self._notifBar.insertWarningNotification(msg) return rowIndex = self._proxyModel.mapToSource(selRowIndices[0]) recordid = rowIndex.data() self._load_editor_dialog(recordid, rowIndex.row()) def set_child_model(self, model, row_position): """ Sets the child model so that it can be used for editing and deleting if needed. :param model: The child model saved :type model: Object """ self.child_model[row_position, self.entity] = model def onRemoveEntity(self): ''' Load editor dialog for editing an existing record. ''' self._notifBar.clear() sel_row_indices = self.tbEntity.selectionModel().selectedRows(0) if len(sel_row_indices) == 0: msg = QApplication.translate( "EntityBrowserWithEditor", "Please select one or more records in the " "table below to be deleted.") self._notifBar.insertWarningNotification(msg) return msg = QApplication.translate( "EntityBrowserWithEditor", "Are you sure you want to delete the selected record(s)?\n" "This action cannot be undone.") response = QMessageBox.warning( self, QApplication.translate("EntityBrowserWithEditor", "Delete Record(s)"), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if response == QMessageBox.No: return while len(sel_row_indices) > 0: ri = sel_row_indices[0] source_row_index = self._proxyModel.mapToSource(ri) record_id = source_row_index.data() row_number = source_row_index.row() #Delete record result = self._delete_record(record_id, row_number) if not result: title = QApplication.translate('EntityBrowserWithEditor', 'Delete Record(s)') msg = QApplication.translate( 'EntityBrowserWithEditor', 'An error occurred while attempting to delete a record, this ' 'is most likely caused by a dependency issue.\nPlease check ' 'if the record has dependencies such as social tenure ' 'relationship or related entities. If it has then delete ' 'these dependencies first.') QMessageBox.critical(self, title, msg) break #Refresh list of selected records sel_row_indices = self.tbEntity.selectionModel().selectedRows(0) def remove_rows(self): """ Removes rows from the entity browser. """ if self.tbEntity.model() is not None: row_count = self.tbEntity.model().rowCount() self.tbEntity.model().removeRows(0, row_count) def _load_editor_dialog(self, recid, rownumber): ''' Load editor dialog based on the selected model instance with the given ID. ''' model_obj = self._model_from_id(recid, rownumber) # show GPS editor if geometry if self._entity.has_geometry_column(): self.sp_unit_manager.active_layer_source() gps_tool = GPSToolDialog(iface, self._entity, self._entity.name, self.sp_unit_manager.active_sp_col, model=model_obj, reload=False, row_number=rownumber, entity_browser=self) editor_trans = self.tr('Editor') title = u'{0} {1}'.format(format_name(self._entity.short_name), editor_trans) gps_tool.setWindowTitle(title) result = gps_tool.exec_() else: #Load editor dialog edit_entity_dlg = self._editor_dlg( self._entity, model=model_obj, parent=self, parent_entity=self.parent_entity) result = edit_entity_dlg.exec_() if result == QDialog.Accepted: if self._entity.has_geometry_column(): edit_entity_dlg = gps_tool.entity_editor updated_model_obj = edit_entity_dlg.model() if not edit_entity_dlg.is_valid: return for i, attr in enumerate(self._entity_attrs): prop_idx = self._tableModel.index(rownumber, i) attr_val = getattr(updated_model_obj, attr) ''' Check if there are display formatters and apply if one exists for the given attribute. ''' if attr in self._cell_formatters: formatter = self._cell_formatters[attr] attr_val = formatter.format_column_value(attr_val) self._tableModel.setData(prop_idx, attr_val) def _delete_record(self, rec_id, row_number): """ Delete the record with the given id and remove it from the table view. """ del_result = True if self.parent_entity is not None: del self.child_model[row_number + 1, self.entity] del self._parent.child_models[row_number + 1, self.entity] self._tableModel.removeRows(row_number, 1) # Update number of records self.recomputeRecordCount() #Clear previous notifications self._notifBar.clear() return del_result #Remove record from the database dbHandler = self._dbmodel() entity = dbHandler.queryObject().filter( self._dbmodel.id == rec_id).first() if entity: result = entity.delete() if not result: return False self._tableModel.removeRows(row_number, 1) #Clear previous notifications self._notifBar.clear() #Notify user delMsg = QApplication.translate("EntityBrowserWithEditor", "Record successfully deleted!") self._notifBar.insertInformationNotification(delMsg) #Update number of records self.recomputeRecordCount() return del_result def onDoubleClickView(self, modelindex): ''' Override for loading editor dialog. ''' rowIndex = self._proxyModel.mapToSource(modelindex) rowNumber = rowIndex.row() recordIdIndex = self._tableModel.index(rowNumber, 0) recordId = recordIdIndex.data() self._load_editor_dialog(recordId, recordIdIndex.row()) def shift_spatial_entity_browser(self): """ Shift records manager to the bottom left corner :rtype: NoneType :return: None """ parent_height = self.parent().geometry().height() parent_width = self.parent().geometry().width() parent_x = self.parent().geometry().x() parent_y = self.parent().geometry().y() dialog_width = self.width() dialog_height = self.height() self.setGeometry(parent_x, parent_y + parent_height - dialog_height - 40, dialog_width, dialog_height) def on_select_attribute(self): """ Slot raised when selecting a spatial entity row. """ sel_row_indices = self.tbEntity.\ selectionModel().selectedRows(0) record_ids = [] for sel_row_index in sel_row_indices: rowIndex = self._proxyModel.mapToSource(sel_row_index) record_id = rowIndex.data() record_ids.append(record_id) self.record_feature_highlighter(record_ids) def record_feature_highlighter(self, record_ids): """ Highlights a feature of a record. :param record_id: The id of a row :type record_id: Integer :return: None :rtype: NoneType """ if len(record_ids) < 1: return for geom in self.geom_cols: geom_wkb = entity_id_to_attr(self._entity, geom.name, record_ids[0]) if geom_wkb is not None: sel_lyr_name = self.sp_unit_manager. \ geom_col_layer_name( self._entity.name, geom ) self.add_spatial_unit_layer(sel_lyr_name) layers = QgsMapLayerRegistry.instance().\ mapLayersByName( sel_lyr_name ) if len(layers) > 0: layers[0].removeSelection() layers[0].select(record_ids) bounding_box = layers[0].boundingBoxOfSelected() iface.mapCanvas().setExtent(bounding_box) iface.mapCanvas().refresh() self.selection_layer = layers[0] def add_spatial_unit_layer(self, layer_name=None): """ Add the spatial unit layer into the map canvas for later use. :param layer_name: The name of the layer to be added to the map canvas. :type layer_name: String """ if layer_name is not None: self.sp_unit_manager.add_layer_by_name(layer_name) else: # As this is used for startup of # entity browser, just add the first geom layer. if len(self.geom_cols) > 0: layer_name_item = \ self.sp_unit_manager.geom_col_layer_name( self._entity.name, self.geom_cols[0] ) self.sp_unit_manager.\ add_layer_by_name(layer_name_item) def closeEvent(self, event): """ The event handler that is triggered when the dialog is closed. :param event: the event :type QCloseEvent :return: None """ if self._entity.has_geometry_column(): try: if self.selection_layer is not None: self.selection_layer.removeSelection() self.sp_unit_manager.zoom_to_layer() except RuntimeError: pass def hideEvent(self, hideEvent): """ The event handler that is triggered when the dialog is hidden. :param hideEvent: the event :type QCloseEvent :return: None """ if self._entity.has_geometry_column(): if self.selection_layer is not None: self.selection_layer.removeSelection() self.sp_unit_manager.zoom_to_layer()
def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self._plugin = plugin QTimer.singleShot( 300, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) #self.tbPropertyPreview.set_iface(self._plugin.iface) self.curr_profile = current_profile() self.spatial_unit = self.curr_profile.social_tenure.spatial_unit #Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin) self.geom_cols = self.sp_unit_manager.geom_columns(self.spatial_unit) self.toolBox.setStyleSheet(''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''') self.tvSTRResults.setStyleSheet(''' QTreeView:!active { selection-background-color: #72a6d9; } ''') # Configure notification bar self._notif_search_config = NotificationBar(self.vl_notification) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None #Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved) self._source_doc_manager.setEditPermissions(False) self.initGui()
class ViewSTRWidget(QMainWindow, Ui_frmManageSTR): """ Search and browse the social tenure relationship of all participating entities. """ def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self._plugin = plugin QTimer.singleShot( 300, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) #self.tbPropertyPreview.set_iface(self._plugin.iface) self.curr_profile = current_profile() self.spatial_unit = self.curr_profile.social_tenure.spatial_unit #Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin) self.geom_cols = self.sp_unit_manager.geom_columns(self.spatial_unit) self.toolBox.setStyleSheet(''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''') self.tvSTRResults.setStyleSheet(''' QTreeView:!active { selection-background-color: #72a6d9; } ''') # Configure notification bar self._notif_search_config = NotificationBar(self.vl_notification) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None #Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved) self._source_doc_manager.setEditPermissions(False) self.initGui() def add_tool_buttons(self): """ Add toolbar buttons of add, edit and delete buttons. :return: None :rtype: NoneType """ tool_buttons = QToolBar() tool_buttons.setObjectName('form_toolbar') tool_buttons.setIconSize(QSize(16, 16)) self.addSTR = QAction(QIcon(':/plugins/stdm/images/icons/add.png'), QApplication.translate('ViewSTRWidget', 'Add'), self) self.editSTR = QAction(QIcon(':/plugins/stdm/images/icons/edit.png'), QApplication.translate('ViewSTRWidget', 'Edit'), self) self.deleteSTR = QAction( QIcon(':/plugins/stdm/images/icons/remove.png'), QApplication.translate('ViewSTRWidget', 'Remove'), self) tool_buttons.addAction(self.addSTR) tool_buttons.addAction(self.editSTR) tool_buttons.addAction(self.deleteSTR) self.toolbarVBox.addWidget(tool_buttons) def initGui(self): """ Initialize widget """ self.init_progress_dialog() self.tb_actions.setVisible(False) QTimer.singleShot(30, self._load_entity_configurations) self.add_tool_buttons() #Connect signals self.tbSTREntity.currentChanged.connect(self.entityTabIndexChanged) self.btnSearch.clicked.connect(self.searchEntityRelations) self.btnClearSearch.clicked.connect(self.clearSearch) self.tvSTRResults.expanded.connect(self.onTreeViewItemExpanded) #Set the results treeview to accept requests for context menus self.tvSTRResults.setContextMenuPolicy(Qt.CustomContextMenu) self.tvSTRResults.customContextMenuRequested.connect( self.onResultsContextMenuRequested) if not self._can_create: self.addSTR.hide() if not self._can_edit: self.editSTR.hide() else: self.editSTR.setDisabled(True) if not self._can_delete: self.deleteSTR.hide() else: self.deleteSTR.setDisabled(True) self.addSTR.triggered.connect(self.load_new_str_editor) self.deleteSTR.triggered.connect(self.delete_str) self.editSTR.triggered.connect(self.load_edit_str_editor) #Load async for the current widget self.entityTabIndexChanged(0) def init_progress_dialog(self): """ Initializes the progress dialog. """ self.progress = QProgressBar(self) self.progress.resize(self.width(), 10) self.progress.setTextVisible(False) def add_spatial_unit_layer(self): """ Add the spatial unit layer into the map canvas for later use. """ # Used for startup of view STR, just add the first geom layer. if len(self.geom_cols) > 0: layer_name_item = \ self.sp_unit_manager.geom_col_layer_name( self.spatial_unit.name, self.geom_cols[0] ) self.sp_unit_manager.\ add_layer_by_name(layer_name_item) def _check_permissions(self): """ Enable/disable actions based on the permissions defined in the content group. """ if self._can_edit: self.tb_actions.addAction(self._new_str_action) else: self.tb_actions.removeAction(self._new_str_action) if len(self.tb_actions.actions()) == 0: self.tb_actions.setVisible(False) else: self.tb_actions.setVisible(True) def _load_entity_configurations(self): """ Specify the entity configurations. """ try: spatial_unit = [self.curr_profile.social_tenure.spatial_unit] self.parties = self.curr_profile.social_tenure.parties tb_str_entities = self.parties + spatial_unit self.progress.setRange(0, len(tb_str_entities) - 1) for i, t in enumerate(tb_str_entities): QApplication.processEvents() entity_cfg = self._entity_config_from_profile( str(t.name), t.short_name) if not entity_cfg is None: entity_widget = self.add_entity_config(entity_cfg) entity_widget.setNodeFormatter( EntityNodeFormatter(entity_cfg, self.tvSTRResults, self)) self.progress.setValue(i) self.progress.hide() except Exception as pe: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(unicode(pe)) def _entity_config_from_profile(self, table_name, short_name): """ Creates an EntityConfig object from the table name. :param table_name: Name of the database table. :type table_name: str :return: Entity configuration object. :rtype: EntityConfig """ table_display_name = format_name(short_name) entity = self.curr_profile.entity_by_name(table_name) model = entity_model(entity) if model is not None: #Entity configuration entity_cfg = EntityConfiguration() entity_cfg.Title = table_display_name entity_cfg.STRModel = model entity_cfg.data_source_name = table_name # Load filter and display columns # using only those which are of # numeric/varchar type searchable_columns = entity_searchable_columns(entity) display_columns = entity_display_columns(entity) for c in searchable_columns: if c != 'id': entity_cfg.filterColumns[c] = format_name(c) for c in display_columns: if c != 'id': entity_cfg.displayColumns[c] = format_name(c) return entity_cfg else: return None def add_entity_config(self, config): """ Set an entity configuration option and add it to the 'Search Entity' tab. """ entityWidg = STRViewEntityWidget(config) entityWidg.asyncStarted.connect(self._progressStart) entityWidg.asyncFinished.connect(self._progressFinish) tabIndex = self.tbSTREntity.addTab(entityWidg, config.Title) return entityWidg def entityTabIndexChanged(self, index): """ Raised when the tab index of the entity search tab widget changes. """ #Get the current widget in the tab container entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.loadAsync() def searchEntityRelations(self): """ Slot that searches for matching items for the specified entity and corresponding STR entities. """ self._reset_controls() entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): valid, msg = entityWidget.validate() if not valid: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(msg) return formattedNode, results, searchWord = entityWidget.executeSearch() #Show error message if len(results) == 0: noResultsMsg = QApplication.translate( 'ViewSTR', 'No results found for "{}"'.format(searchWord)) self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(noResultsMsg) return if formattedNode is not None: self._load_root_node(formattedNode) def clearSearch(self): """ Clear search input parameters (for current widget) and results. """ entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.reset() self._reset_controls() def _reset_controls(self): #Clear tree view self._resetTreeView() #Clear document listings self._deleteSourceDocTabs() #Remove spatial unit memory layer self.tbPropertyPreview.remove_layer() def on_select_results(self): """ Slot which is raised when the selection is changed in the tree view selection model. """ index = self.tvSTRResults.currentIndex() #Check type of node and perform corresponding action #for mi in selIndexes: if index.isValid(): node = index.internalPointer() self.editSTR.setDisabled(True) self.deleteSTR.setDisabled(True) if index.column() == 0: # Assert if node represents another # entity has been clicked self._on_node_reference_changed(node.rootHash()) if isinstance(node, SupportsDocumentsNode): src_docs = node.documents() if isinstance(src_docs, dict): self._load_source_documents(src_docs) # if there is supporting document, # expand supporting document tab if len(src_docs) > 0: self.toolBox.setCurrentIndex(1) if self._can_edit: self.deleteSTR.setDisabled(False) if self._can_delete: self.editSTR.setDisabled(False) if isinstance(node, SpatialUnitNode): # Expand the Spatial Unit preview self.toolBox.setCurrentIndex(0) self.draw_spatial_unit(node.model()) self.editSTR.setDisabled(True) self.deleteSTR.setDisabled(True) def str_party_column_obj(self, record): """ Gets the current party column name in STR table by finding party column with value other than None. :param record: The STR record or result. :type record: Dictionary :return: The party column name with value. :rtype: String """ for party in self.parties: party_name = party.short_name.lower() party_id = '{}_id'.format(party_name) if party_id not in record.__dict__: return None if record.__dict__[party_id] != None: party_id_obj = getattr(self.str_model, party_id) return party_id_obj def load_edit_str_editor(self): index = self.tvSTRResults.currentIndex() node = None if index.isValid(): node = index.internalPointer() if index.column() == 0: if isinstance(node, SupportsDocumentsNode): self.edit_str = EditSTREditor(node) status = self.edit_str.exec_() if status == 1: self.btnSearch.click() # if node._parent.typeInfo() == 'ENTITY_NODE': # node_party_id = self.str_party_column_obj( # node._model # ) # edit_str_party_id = self.str_party_column_obj( # self.edit_str.updated_str_obj # ) # if node_party_id is None or \ # edit_str_party_id is None: # return # if node_party_id == \ # edit_str_party_id: # self.btnSearch.click() # index = node.treeView().model().index(0, 0) # node._on_expand(index) # # if node._parent.typeInfo() == 'SPATIAL_UNIT_NODE': # if node._model.spatial_unit_id == \ # self.edit_str.updated_str_obj.spatial_unit_id: # self.btnSearch.click() # index = node.treeView().model().index(0, 0) # node._on_expand(index) def load_new_str_editor(self): try: # Check type of node and perform corresponding action add_str = STREditor() add_str.exec_() except Exception as ex: QMessageBox.critical( self._plugin.iface.mainWindow(), QApplication.translate("STDMPlugin", "Loading Error"), unicode(ex)) def delete_str(self): index = self.tvSTRResults.currentIndex() node = None if index.isValid(): node = index.internalPointer() if isinstance(node, SupportsDocumentsNode): node.onDelete(index) self.btnSearch.click() index = node.treeView().model().index(0, 0) node._on_expand(index) def onSourceDocumentRemoved(self, container_id, doc_uuid, removed_doc): """ Slot raised when a source document is removed from the container. If there are no documents in the specified container then remove the tab. """ curr_container = self.tbSupportingDocs.currentWidget() curr_doc_widget = curr_container.findChildren(DocumentWidget) for doc in curr_doc_widget: if doc.fileUUID == doc_uuid: doc.deleteLater() self.removed_docs = removed_doc def draw_spatial_unit(self, model): """ Render the geometry of the given spatial unit in the spatial view. :param row_id: Sqlalchemy object representing a feature. """ self.tbPropertyPreview.draw_spatial_unit(model) def onTreeViewItemExpanded(self, modelindex): """ Raised when a tree view item is expanded. Reset the document listing and map view if the hash of the parent node is different. """ if modelindex.isValid(): node = modelindex.internalPointer() #Assert if node representing another entity has been clicked self._on_node_reference_changed(node.rootHash()) def onResultsContextMenuRequested(self, pnt): """ Slot raised when the user right-clicks on a node item to request the corresponding context menu. """ #Get the model index at the specified point mi = self.tvSTRResults.indexAt(pnt) if mi.isValid(): node = mi.internalPointer() rMenu = QMenu(self) #Load node actions items into the context menu node.manageActions(mi, rMenu) rMenu.exec_(QCursor.pos()) def showEvent(self, event): """ (Re)load map layers in the viewer and main canvas. :param event: Window event :type event: QShowEvent """ self.setEnabled(True) QTimer.singleShot(200, self.init_mirror_map) #self.init_mirror_map() return QMainWindow.showEvent(self, event) def init_mirror_map(self): self._notify_no_base_layers() # Add spatial unit layer if it doesn't exist self.add_spatial_unit_layer() self.tbPropertyPreview.refresh_canvas_layers() self.tbPropertyPreview.load_web_map() def _notify_no_base_layers(self): """ Checks if there are any base layers that will be used when visualizing the spatial units. If there are no base layers then insert warning message. """ self._notif_search_config.clear() num_layers = len(self._plugin.iface.legendInterface().layers()) if num_layers == 0: msg = QApplication.translate( "ViewSTR", "No basemap layers are loaded in the " "current project. Basemap layers " "enhance the visualization of spatial units.") self._notif_search_config.insertWarningNotification(msg) def _deleteSourceDocTabs(self): """ Removes all source document tabs and deletes their references. """ tabCount = self.tbSupportingDocs.count() while tabCount != 0: srcDocWidget = self.tbSupportingDocs.widget(tabCount - 1) self.tbSupportingDocs.removeTab(tabCount - 1) del srcDocWidget tabCount -= 1 self._strID = None self._source_doc_manager.reset() def _resetTreeView(self): """ Clears the results tree view. """ #Reset tree view strModel = self.tvSTRResults.model() resultsSelModel = self.tvSTRResults.selectionModel() if strModel: strModel.clear() if resultsSelModel: self.disconnect( resultsSelModel, SIGNAL( "selectionChanged(const QItemSelection&,const QItemSelection&)" ), self.on_select_results) def _load_root_node(self, root): """ Load the search results (formatted into an object of type 'stdm.navigaion.STR') into the tree view. """ strTreeViewModel = STRTreeViewModel(root, view=self.tvSTRResults) self.tvSTRResults.setModel(strTreeViewModel) # Resize tree columns to fit contents self._resize_columns() #Capture selection changes signals when # results are returned in the tree view resultsSelModel = self.tvSTRResults.selectionModel() resultsSelModel.currentChanged.connect(self.on_select_results) def _resize_columns(self): """ Adjusts the column sizes to fit its contents """ qModel = self.tvSTRResults.model() columnCount = qModel.columnCount() for i in range(columnCount): self.tvSTRResults.resizeColumnToContents(i) #Once resized then slightly increase the width # of the first column so that text for 'No STR Defined' visible. currColWidth = self.tvSTRResults.columnWidth(0) newColWidth = currColWidth + 100 self.tvSTRResults.setColumnWidth(0, newColWidth) def _load_source_documents(self, source_docs): """ Load source documents into document listing widget. """ # Configure progress dialog progress_msg = QApplication.translate( "ViewSTR", "Loading supporting documents...") progress_dialog = QProgressDialog(self) if len(source_docs) > 0: progress_dialog.setWindowTitle(progress_msg) progress_dialog.setRange(0, len(source_docs)) progress_dialog.setWindowModality(Qt.WindowModal) progress_dialog.setFixedWidth(380) progress_dialog.show() progress_dialog.setValue(0) self._notif_search_config.clear() self.tbSupportingDocs.clear() self._source_doc_manager.reset() if len(source_docs) < 1: empty_msg = QApplication.translate( 'ViewSTR', 'No supporting document is uploaded ' 'for this social tenure relationship.') self._notif_search_config.clear() self._notif_search_config.insertWarningNotification(empty_msg) for i, (doc_type_id, doc_obj) in enumerate(source_docs.iteritems()): # add tabs, and container and widget for each tab tab_title = self._source_doc_manager.doc_type_mapping[doc_type_id] tab_widget = QWidget() tab_widget.setObjectName(tab_title) cont_layout = QVBoxLayout(tab_widget) cont_layout.setObjectName('widget_layout_' + tab_title) scrollArea = QScrollArea(tab_widget) scrollArea.setFrameShape(QFrame.NoFrame) scrollArea_contents = QWidget() scrollArea_contents.setObjectName('tab_scroll_area_' + tab_title) tab_layout = QVBoxLayout(scrollArea_contents) tab_layout.setObjectName('layout_' + tab_title) scrollArea.setWidgetResizable(True) scrollArea.setWidget(scrollArea_contents) cont_layout.addWidget(scrollArea) self._source_doc_manager.registerContainer(tab_layout, doc_type_id) for doc in doc_obj: try: # add doc widgets self._source_doc_manager.insertDocFromModel( doc, doc_type_id) except Exception as ex: LOGGER.debug(str(ex)) self.tbSupportingDocs.addTab(tab_widget, tab_title) progress_dialog.setValue(i + 1) def _on_node_reference_changed(self, rootHash): """ Method for resetting document listing and map preview if another root node and its children are selected then the documents are reset as well as the map preview control. """ if rootHash != self._curr_rootnode_hash: self._deleteSourceDocTabs() self._curr_rootnode_hash = rootHash def _progressStart(self): """ Load progress dialog window. For items whose durations is unknown, 'isindefinite' = True by default. If 'isindefinite' is False, then 'rangeitems' has to be specified. """ pass def _progressFinish(self): """ Hide progress dialog window. """ pass def _edit_permissions(self): """ Returns True/False whether the current logged in user has permissions to create new social tenure relationships. If true, then the system assumes that they can also edit STR records. """ canEdit = False userName = stdm.data.app_dbconn.User.UserName authorizer = Authorizer(userName) newSTRCode = "9576A88D-C434-40A6-A318-F830216CA15A" #Get the name of the content from the code cnt = Content() createSTRCnt = cnt.queryObject().filter( Content.code == newSTRCode).first() if createSTRCnt: name = createSTRCnt.name canEdit = authorizer.CheckAccess(name) return canEdit
def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self._plugin = plugin self.search_done = False # self.tbPropertyPreview.set_iface(self._plugin.iface) QTimer.singleShot( 100, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) self.curr_profile = current_profile() self.spatial_units = self.curr_profile.social_tenure.spatial_units #Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin ) self.geom_cols = [] for spatial_unit in self.spatial_units: each_geom_col = self.sp_unit_manager.geom_columns(spatial_unit) self.geom_cols.extend(each_geom_col) # Configure notification bar self._notif_search_config = NotificationBar( self.vl_notification ) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None #Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True ) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self ) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved ) self._source_doc_manager.setEditPermissions(False) self.initGui() # if self._plugin.details_tree_view is None: # self._plugin.details_dock.init_dock() self.add_spatial_unit_layer() self.details_tree_view = DetailsTreeView(iface, self._plugin, self) # else: # self.details_tree_view = self._plugin.details_tree_view self.details_tree_view.activate_feature_details(True) self.details_tree_view.add_tree_view() self.details_tree_view.model.clear() count = pg_table_count(self.curr_profile.social_tenure.name) self.setWindowTitle( self.tr(u'{}{}'.format( self.windowTitle(), '- ' + str(count) +' rows' )) ) self.toolBox.setStyleSheet( ''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''' ) self.details_tree_view.view.setStyleSheet( ''' QTreeView:!active { selection-background-color: #72a6d9; } ''' )
class ViewSTRWidget(QMainWindow, Ui_frmManageSTR): """ Search and browse the social tenure relationship of all participating entities. """ def __init__(self, plugin): QMainWindow.__init__(self, plugin.iface.mainWindow()) self.setupUi(self) self._plugin = plugin self.search_done = False # self.tbPropertyPreview.set_iface(self._plugin.iface) QTimer.singleShot( 100, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface)) self.curr_profile = current_profile() self.spatial_units = self.curr_profile.social_tenure.spatial_units #Center me self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.sp_unit_manager = SpatialUnitManagerDockWidget( self._plugin.iface, self._plugin ) self.geom_cols = [] for spatial_unit in self.spatial_units: each_geom_col = self.sp_unit_manager.geom_columns(spatial_unit) self.geom_cols.extend(each_geom_col) # Configure notification bar self._notif_search_config = NotificationBar( self.vl_notification ) # set whether currently logged in user has # permissions to edit existing STR records self._can_edit = self._plugin.STRCntGroup.canUpdate() self._can_delete = self._plugin.STRCntGroup.canDelete() self._can_create = self._plugin.STRCntGroup.canCreate() # Variable used to store a reference to the # currently selected social tenure relationship # when displaying documents in the supporting documents tab window. # This ensures that there are no duplicates # when the same item is selected over and over again. self._strID = None self.removed_docs = None #Used to store the root hash of the currently selected node. self._curr_rootnode_hash = "" self.str_model, self.str_doc_model = entity_model( self.curr_profile.social_tenure, False, True ) self._source_doc_manager = SourceDocumentManager( self.curr_profile.social_tenure.supporting_doc, self.str_doc_model, self ) self._source_doc_manager.documentRemoved.connect( self.onSourceDocumentRemoved ) self._source_doc_manager.setEditPermissions(False) self.initGui() # if self._plugin.details_tree_view is None: # self._plugin.details_dock.init_dock() self.add_spatial_unit_layer() self.details_tree_view = DetailsTreeView(iface, self._plugin, self) # else: # self.details_tree_view = self._plugin.details_tree_view self.details_tree_view.activate_feature_details(True) self.details_tree_view.add_tree_view() self.details_tree_view.model.clear() count = pg_table_count(self.curr_profile.social_tenure.name) self.setWindowTitle( self.tr(u'{}{}'.format( self.windowTitle(), '- ' + str(count) +' rows' )) ) self.toolBox.setStyleSheet( ''' QToolBox::tab { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #EDEDED, stop: 0.4 #EDEDED, stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3 ); border-radius: 2px; border-style: outset; border-width: 2px; height: 100px; border-color: #C3C3C3; } QToolBox::tab:selected { font: italic; } ''' ) self.details_tree_view.view.setStyleSheet( ''' QTreeView:!active { selection-background-color: #72a6d9; } ''' ) def add_tool_buttons(self): """ Add toolbar buttons of add, edit and delete buttons. :return: None :rtype: NoneType """ tool_buttons = QToolBar() tool_buttons.setObjectName('form_toolbar') tool_buttons.setIconSize(QSize(16, 16)) self.addSTR = QAction(QIcon( ':/plugins/stdm/images/icons/add.png'), QApplication.translate('ViewSTRWidget', 'Add'), self ) self.editSTR = QAction( QIcon(':/plugins/stdm/images/icons/edit.png'), QApplication.translate('ViewSTRWidget', 'Edit'), self ) self.deleteSTR = QAction( QIcon(':/plugins/stdm/images/icons/remove.png'), QApplication.translate('ViewSTRWidget', 'Remove'), self ) tool_buttons.addAction(self.addSTR) tool_buttons.addAction(self.editSTR) tool_buttons.addAction(self.deleteSTR) self.toolbarVBox.addWidget(tool_buttons) def initGui(self): """ Initialize widget """ self.tb_actions.setVisible(False) self._load_entity_configurations() self.add_tool_buttons() #Connect signals self.tbSTREntity.currentChanged.connect(self.entityTabIndexChanged) self.btnSearch.clicked.connect(self.searchEntityRelations) self.btnClearSearch.clicked.connect(self.clearSearch) # self.tvSTRResults.expanded.connect(self.onTreeViewItemExpanded) #Set the results treeview to accept requests for context menus # self.tvSTRResults.setContextMenuPolicy(Qt.CustomContextMenu) # self.tvSTRResults.customContextMenuRequested.connect( # self.onResultsContextMenuRequested # ) if not self._can_create: self.addSTR.hide() if not self._can_edit: self.editSTR.hide() else: self.editSTR.setDisabled(True) if not self._can_delete: self.deleteSTR.hide() else: self.deleteSTR.setDisabled(True) self.addSTR.triggered.connect(self.load_new_str_editor) self.deleteSTR.triggered.connect(self.delete_str) self.editSTR.triggered.connect(self.load_edit_str_editor) #Load async for the current widget self.entityTabIndexChanged(0) def init_progress_dialog(self): """ Initializes the progress dialog. """ self.progress = QProgressBar(self) self.progress.resize(self.width(), 10) self.progress.setTextVisible(False) def add_spatial_unit_layer(self): """ Add the spatial unit layer into the map canvas for later use. """ # Used for startup of view STR, just add the first geom layer. if len(self.geom_cols) > 0: for spatial_unit in self.spatial_units: layer_name_item = self.sp_unit_manager.geom_col_layer_name( spatial_unit.name, self.geom_cols[0] ) self.sp_unit_manager.add_layer_by_name(layer_name_item) def _check_permissions(self): """ Enable/disable actions based on the permissions defined in the content group. """ if self._can_edit: self.tb_actions.addAction(self._new_str_action) else: self.tb_actions.removeAction(self._new_str_action) if len(self.tb_actions.actions()) == 0: self.tb_actions.setVisible(False) else: self.tb_actions.setVisible(True) def _load_entity_configurations(self): """ Specify the entity configurations. """ try: self.parties = self.curr_profile.social_tenure.parties tb_str_entities = self.parties + self.spatial_units for i, t in enumerate(tb_str_entities): QApplication.processEvents() entity_cfg = self._entity_config_from_profile( str(t.name), t.short_name ) if not entity_cfg is None: entity_widget = self.add_entity_config(entity_cfg) # entity_widget.setNodeFormatter( # EntityNodeFormatter( # entity_cfg, self.tvSTRResults, self # ) # ) except Exception as pe: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(unicode(pe)) def _entity_config_from_profile(self, table_name, short_name): """ Creates an EntityConfig object from the table name. :param table_name: Name of the database table. :type table_name: str :return: Entity configuration object. :rtype: EntityConfig """ table_display_name = format_name(short_name) entity = self.curr_profile.entity_by_name(table_name) model = entity_model(entity) if model is not None: #Entity configuration entity_cfg = EntityConfiguration() entity_cfg.Title = table_display_name entity_cfg.STRModel = model entity_cfg.data_source_name = table_name for col, factory in self._get_widget_factory(entity): entity_cfg.LookupFormatters[col.name] = factory # Load filter and display columns # using only those which are of # numeric/varchar type searchable_columns = entity_searchable_columns(entity) display_columns = entity_display_columns(entity) for c in searchable_columns: if c != 'id': entity_cfg.filterColumns[c] = format_name(c) for c in display_columns: if c != 'id': entity_cfg.displayColumns[c] = format_name(c) return entity_cfg else: return None def _get_widget_factory(self, entity): """ Get widget factory for specific column type :param entity: Current column entity object :type entity: Entity :return c: Column object corresponding to the widget factory :rtype c: BaseColumn :return col_factory: Widget factory corresponding to the column type :rtype col_factory: ColumnWidgetRegistry """ for c in entity.columns.values(): col_factory = ColumnWidgetRegistry.factory(c.TYPE_INFO) if col_factory is not None: yield c, col_factory(c) def add_entity_config(self, config): """ Set an entity configuration option and add it to the 'Search Entity' tab. """ entityWidg = STRViewEntityWidget(config) entityWidg.asyncStarted.connect(self._progressStart) entityWidg.asyncFinished.connect(self._progressFinish) tabIndex = self.tbSTREntity.addTab(entityWidg, config.Title) return entityWidg def entityTabIndexChanged(self, index): """ Raised when the tab index of the entity search tab widget changes. """ #Get the current widget in the tab container entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.loadAsync() def searchEntityRelations(self): """ Slot that searches for matching items for the specified entity and corresponding STR entities. """ entityWidget = self.tbSTREntity.currentWidget() entity_name = entityWidget.config.data_source_name self._reset_controls() if isinstance(entityWidget,EntitySearchItem): valid, msg = entityWidget.validate() if not valid: self._notif_search_config.clear() self._notif_search_config.insertErrorNotification(msg) return results, searchWord = entityWidget.executeSearch() #Show error message if len(results) == 0: noResultsMsg = QApplication.translate( 'ViewSTR', 'No results found for "{}"'.format(searchWord) ) self._notif_search_config.clear() self._notif_search_config.insertErrorNotification( noResultsMsg ) return party_names = [e.name for e in self.curr_profile.social_tenure.parties] entity = self.curr_profile.entity_by_name(entity_name) result_ids = [r.id for r in results] if entity_name in party_names: self.details_tree_view.search_party( entity, result_ids ) else: self.details_tree_view.search_spatial_unit( entity, result_ids ) #self._load_root_node(entity_name, formattedNode) def clearSearch(self): """ Clear search input parameters (for current widget) and results. """ entityWidget = self.tbSTREntity.currentWidget() if isinstance(entityWidget, EntitySearchItem): entityWidget.reset() self._reset_controls() def _reset_controls(self): #Clear tree view self._resetTreeView() #Clear document listings self._deleteSourceDocTabs() #Remove spatial unit memory layer self.tbPropertyPreview.remove_layer() def on_select_results(self): """ Slot which is raised when the selection is changed in the tree view selection model. """ if len(self.details_tree_view.view.selectedIndexes()) < 1: self.disable_buttons() return self.search_done = True index = self.details_tree_view.view.selectedIndexes()[0] item = self.details_tree_view.model.itemFromIndex(index) QApplication.processEvents() # STR steam - edit social tenure relationship if item.text() == self.details_tree_view.str_text: entity = self.curr_profile.social_tenure str_model = self.details_tree_view.str_models[item.data()] documents = self.details_tree_view._supporting_doc_models( entity.name, str_model ) self._load_source_documents(documents) # if there is supporting document, # expand supporting document tab if len(documents) > 0: self.toolBox.setCurrentIndex(1) self.disable_buttons(False) # party steam - edit party elif item in self.details_tree_view.spatial_unit_items.keys(): self.toolBox.setCurrentIndex(0) entity = self.details_tree_view.spatial_unit_items[item] model = self.details_tree_view.feature_model(entity, item.data()) self.draw_spatial_unit(entity.name, model) self.disable_buttons() else: self.disable_buttons() def disable_buttons(self, status=True): if self._can_edit: self.deleteSTR.setDisabled(status) if self._can_delete: self.editSTR.setDisabled(status) def str_party_column_obj(self, record): """ Gets the current party column name in STR table by finding party column with value other than None. :param record: The STR record or result. :type record: Dictionary :return: The party column name with value. :rtype: String """ for party in self.parties: party_name = party.short_name.lower() party_id = '{}_id'.format(party_name) if party_id not in record.__dict__: return None if record.__dict__[party_id] != None: party_id_obj = getattr(self.str_model, party_id) return party_id_obj def load_edit_str_editor(self): self.details_tree_view.edit_selected_node() self.btnSearch.click() self.disable_buttons() def load_new_str_editor(self): try: # Check type of node and perform corresponding action add_str = STREditor() add_str.exec_() except Exception as ex: QMessageBox.critical( self._plugin.iface.mainWindow(), QApplication.translate( "STDMPlugin", "Loading Error" ), unicode(ex) ) def delete_str(self): self.details_tree_view.delete_selected_item() self.btnSearch.click() self.disable_buttons() def onSourceDocumentRemoved(self, container_id, doc_uuid, removed_doc): """ Slot raised when a source document is removed from the container. If there are no documents in the specified container then remove the tab. """ curr_container = self.tbSupportingDocs.currentWidget() curr_doc_widget = curr_container.findChildren(DocumentWidget) for doc in curr_doc_widget: if doc.fileUUID == doc_uuid: doc.deleteLater() self.removed_docs = removed_doc def draw_spatial_unit(self, entity_name, model): """ Render the geometry of the given spatial unit in the spatial view. :param row_id: Sqlalchemy object representing a feature. """ entity = self.curr_profile.entity_by_name(entity_name) self.tbPropertyPreview.draw_spatial_unit(entity, model) def showEvent(self, event): """ (Re)load map layers in the viewer and main canvas. :param event: Window event :type event: QShowEvent """ self.setEnabled(True) if QTimer is not None: QTimer.singleShot(200, self.init_mirror_map) return QMainWindow.showEvent(self, event) def init_mirror_map(self): self._notify_no_base_layers() # Add spatial unit layer if it doesn't exist self.tbPropertyPreview.refresh_canvas_layers() self.tbPropertyPreview.load_web_map() def _notify_no_base_layers(self): """ Checks if there are any base layers that will be used when visualizing the spatial units. If there are no base layers then insert warning message. """ self._notif_search_config.clear() num_layers = len(self._plugin.iface.legendInterface().layers()) if num_layers == 0: msg = QApplication.translate( "ViewSTR", "No basemap layers are loaded in the " "current project. Basemap layers " "enhance the visualization of spatial units." ) self._notif_search_config.insertWarningNotification(msg) def _deleteSourceDocTabs(self): """ Removes all source document tabs and deletes their references. """ tabCount = self.tbSupportingDocs.count() while tabCount != 0: srcDocWidget = self.tbSupportingDocs.widget(tabCount-1) self.tbSupportingDocs.removeTab(tabCount-1) del srcDocWidget tabCount -= 1 self._strID = None self._source_doc_manager.reset() def _resetTreeView(self): """ Clears the results tree view. """ #Reset tree view strModel = self.details_tree_view.view.model() resultsSelModel = self.details_tree_view.view.selectionModel() if strModel: strModel.clear() if resultsSelModel: if self.search_done: resultsSelModel.selectionChanged.disconnect(self.on_select_results) resultsSelModel.selectionChanged.connect(self.on_select_results) def _load_source_documents(self, source_docs): """ Load source documents into document listing widget. """ # Configure progress dialog progress_msg = QApplication.translate( "ViewSTR", "Loading supporting documents..." ) progress_dialog = QProgressDialog(self) if len(source_docs) > 0: progress_dialog.setWindowTitle(progress_msg) progress_dialog.setRange(0, len(source_docs)) progress_dialog.setWindowModality(Qt.WindowModal) progress_dialog.setFixedWidth(380) progress_dialog.show() progress_dialog.setValue(0) self._notif_search_config.clear() self.tbSupportingDocs.clear() self._source_doc_manager.reset() if len(source_docs) < 1: empty_msg = QApplication.translate( 'ViewSTR', 'No supporting document is uploaded ' 'for this social tenure relationship.' ) self._notif_search_config.clear() self._notif_search_config.insertWarningNotification(empty_msg) for i, (doc_type_id, doc_obj) in enumerate(source_docs.iteritems()): # add tabs, and container and widget for each tab tab_title = self._source_doc_manager.doc_type_mapping[doc_type_id] tab_widget = QWidget() tab_widget.setObjectName(tab_title) cont_layout = QVBoxLayout(tab_widget) cont_layout.setObjectName('widget_layout_' + tab_title) scrollArea = QScrollArea(tab_widget) scrollArea.setFrameShape(QFrame.NoFrame) scrollArea_contents = QWidget() scrollArea_contents.setObjectName('tab_scroll_area_' + tab_title) tab_layout = QVBoxLayout(scrollArea_contents) tab_layout.setObjectName('layout_' + tab_title) scrollArea.setWidgetResizable(True) scrollArea.setWidget(scrollArea_contents) cont_layout.addWidget(scrollArea) self._source_doc_manager.registerContainer( tab_layout, doc_type_id ) for doc in doc_obj: try: # add doc widgets self._source_doc_manager.insertDocFromModel( doc, doc_type_id ) except Exception as ex: LOGGER.debug(str(ex)) self.tbSupportingDocs.addTab( tab_widget, tab_title ) progress_dialog.setValue(i + 1) # def _on_node_reference_changed(self, rootHash): # """ # Method for resetting document listing and map preview # if another root node and its children # are selected then the documents are reset as # well as the map preview control. # """ # if rootHash != self._curr_rootnode_hash: # self._deleteSourceDocTabs() # self._curr_rootnode_hash = rootHash def _progressStart(self): """ Load progress dialog window. For items whose durations is unknown, 'isindefinite' = True by default. If 'isindefinite' is False, then 'rangeitems' has to be specified. """ pass def _progressFinish(self): """ Hide progress dialog window. """ pass def _edit_permissions(self): """ Returns True/False whether the current logged in user has permissions to create new social tenure relationships. If true, then the system assumes that they can also edit STR records. """ canEdit = False userName = stdm.data.app_dbconn.User.UserName authorizer = Authorizer(userName) newSTRCode = "9576A88D-C434-40A6-A318-F830216CA15A" #Get the name of the content from the code cnt = Content() createSTRCnt = cnt.queryObject().filter( Content.code == newSTRCode ).first() if createSTRCnt: name = createSTRCnt.name canEdit = authorizer.CheckAccess(name) return canEdit
def __init__(self,entity, parent=None, state=MANAGE, load_records=True, plugin=None): EntityBrowser.__init__(self, entity, parent, state, load_records, plugin) self.record_id = 0 self.highlight = None self.load_records = load_records self.selection_layer = None #Add action toolbar if the state contains Manage flag if (state & MANAGE) != 0: add = QApplication.translate("EntityBrowserWithEditor", "Add") edit = QApplication.translate("EntityBrowserWithEditor","Edit") remove = QApplication.translate("EntityBrowserWithEditor", "Remove") self._newEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/add.png"), add, self) self.connect(self._newEntityAction,SIGNAL("triggered()"),self.onNewEntity) self._editEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/edit.png"), edit,self) self._editEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "edit_tool") ) self.connect(self._editEntityAction,SIGNAL("triggered()"),self.onEditEntity) self._removeEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/remove.png"), remove, self) self._removeEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "remove_tool") ) self.connect(self._removeEntityAction,SIGNAL("triggered()"),self.onRemoveEntity) #Manage position of the actions based on whether the entity # supports documents. if self.can_view_supporting_documents: manage_acts = [self._newEntityAction, self._editEntityAction, self._removeEntityAction] self.tbActions.insertActions(self._view_docs_act, manage_acts) #Add action separator self._act_sep = QAction(self) self._act_sep.setSeparator(True) self.tbActions.insertAction(self._view_docs_act, self._act_sep) else: self.tbActions.addAction(self._newEntityAction) self.tbActions.addAction(self._editEntityAction) self.tbActions.addAction(self._removeEntityAction) if isinstance(parent, EntityEditorDialog): if self.can_view_supporting_documents: self._view_docs_act.setVisible(False) self.parent_entity = parent._entity else: self.parent_entity = None # hide the add button and add layer preview for spatial entity if entity.has_geometry_column(): #and self.parent_entity is None: self.sp_unit_manager = SpatialUnitManagerDockWidget( iface, self.plugin) #self.sp_unit_manager = self.plugin.spatialLayerManagerDockWidget self.geom_cols = self.sp_unit_manager.geom_columns( self._entity ) self.add_spatial_unit_layer() self.tbEntity.clicked.connect( self.on_select_attribute ) self.tbEntity.entered.connect( self.on_select_attribute ) self.shift_spatial_entity_browser() # Hide the add button from spatial tables # self._newEntityAction.setVisible(False) self._editor_dlg = EntityEditorDialog
class EntityBrowserWithEditor(EntityBrowser): """ Entity browser with added functionality for carrying out CRUD operations directly. """ def __init__(self,entity, parent=None, state=MANAGE, load_records=True, plugin=None): EntityBrowser.__init__(self, entity, parent, state, load_records, plugin) self.record_id = 0 self.highlight = None self.load_records = load_records self.selection_layer = None #Add action toolbar if the state contains Manage flag if (state & MANAGE) != 0: add = QApplication.translate("EntityBrowserWithEditor", "Add") edit = QApplication.translate("EntityBrowserWithEditor","Edit") remove = QApplication.translate("EntityBrowserWithEditor", "Remove") self._newEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/add.png"), add, self) self.connect(self._newEntityAction,SIGNAL("triggered()"),self.onNewEntity) self._editEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/edit.png"), edit,self) self._editEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "edit_tool") ) self.connect(self._editEntityAction,SIGNAL("triggered()"),self.onEditEntity) self._removeEntityAction = QAction(QIcon(":/plugins/stdm/images/icons/remove.png"), remove, self) self._removeEntityAction.setObjectName( QApplication.translate("EntityBrowserWithEditor", "remove_tool") ) self.connect(self._removeEntityAction,SIGNAL("triggered()"),self.onRemoveEntity) #Manage position of the actions based on whether the entity # supports documents. if self.can_view_supporting_documents: manage_acts = [self._newEntityAction, self._editEntityAction, self._removeEntityAction] self.tbActions.insertActions(self._view_docs_act, manage_acts) #Add action separator self._act_sep = QAction(self) self._act_sep.setSeparator(True) self.tbActions.insertAction(self._view_docs_act, self._act_sep) else: self.tbActions.addAction(self._newEntityAction) self.tbActions.addAction(self._editEntityAction) self.tbActions.addAction(self._removeEntityAction) if isinstance(parent, EntityEditorDialog): if self.can_view_supporting_documents: self._view_docs_act.setVisible(False) self.parent_entity = parent._entity else: self.parent_entity = None # hide the add button and add layer preview for spatial entity if entity.has_geometry_column(): #and self.parent_entity is None: self.sp_unit_manager = SpatialUnitManagerDockWidget( iface, self.plugin) #self.sp_unit_manager = self.plugin.spatialLayerManagerDockWidget self.geom_cols = self.sp_unit_manager.geom_columns( self._entity ) self.add_spatial_unit_layer() self.tbEntity.clicked.connect( self.on_select_attribute ) self.tbEntity.entered.connect( self.on_select_attribute ) self.shift_spatial_entity_browser() # Hide the add button from spatial tables # self._newEntityAction.setVisible(False) self._editor_dlg = EntityEditorDialog def onNewEntity(self): ''' Load editor dialog for adding a new record. ''' self._notifBar.clear() if not self._can_add_edit(): msg = QApplication.translate( 'EntityBrowserWithEditor', 'There are no user-defined columns for this entity.' ) self._notifBar.insertErrorNotification(msg) return if self._entity.has_geometry_column(): self.sp_unit_manager.active_layer_source() gps_tool = GPSToolDialog( iface, self._entity, self._entity.name, self.sp_unit_manager.active_sp_col, reload=False, entity_browser=self ) result = gps_tool.exec_() result = False # a workaround to avoid duplicate model insert self.addEntityDlg = gps_tool.entity_editor else: self.addEntityDlg = self._editor_dlg( self._entity, parent=self, parent_entity=self.parent_entity, plugin=self.plugin ) self.addEntityDlg.addedModel.connect(self.on_save_and_new) result = self.addEntityDlg.exec_() if result == QDialog.Accepted: model_obj = self.addEntityDlg.model() if self.addEntityDlg.is_valid: if self.parent_entity is None: self.addModelToView(model_obj) self.recomputeRecordCount() def on_save_and_new(self, model): """ A slot raised when save and new button is clicked. It updates the entity browser with the new model added. :param model: The model saved. :type model: SQL Alchemy Model """ if model is not None: insert_position = self.addModelToView(model) self.set_child_model(model, insert_position + 1) self.recomputeRecordCount() def _can_add_edit(self): """ Check if there are columns specified (apart from id) for the given entity. :return: Returns True if there are other columns apart from id, otherwise False. """ columns = self._entity.columns.values() if len(columns) < 2: return False return True def onEditEntity(self): ''' Slot raised to load the editor for the selected row. ''' self._notifBar.clear() if not self._can_add_edit(): msg = QApplication.translate( 'EntityBrowserWithEditor', 'There are no user-defined columns for this entity.' ) self._notifBar.insertErrorNotification(msg) return if self.tbEntity.selectionModel() is None: return selRowIndices = self.tbEntity.selectionModel().selectedRows(0) if len(selRowIndices) == 0: msg = QApplication.translate("EntityBrowserWithEditor", "Please select a record in the table" " below for editing.") self._notifBar.insertWarningNotification(msg) return #Exit if more than one record has been selected if len(selRowIndices) > 1: msg = QApplication.translate( "EntityBrowserWithEditor", "Multiple selection detected, please choose one record " "only for editing." ) self._notifBar.insertWarningNotification(msg) return rowIndex = self._proxyModel.mapToSource(selRowIndices[0]) recordid = rowIndex.data() self._load_editor_dialog(recordid, rowIndex.row()) def set_child_model(self, model, row_position): """ Sets the child model so that it can be used for editing and deleting if needed. :param model: The child model saved :type model: Object """ self.child_model[row_position] = model def onRemoveEntity(self): ''' Load editor dialog for editing an existing record. ''' self._notifBar.clear() sel_row_indices = self.tbEntity.selectionModel().selectedRows(0) if len(sel_row_indices) == 0: msg = QApplication.translate( "EntityBrowserWithEditor", "Please select one or more records in the " "table below to be deleted." ) self._notifBar.insertWarningNotification(msg) return msg = QApplication.translate( "EntityBrowserWithEditor", "Are you sure you want to delete the selected record(s)?\n" "This action cannot be undone." ) response = QMessageBox.warning( self, QApplication.translate( "EntityBrowserWithEditor", "Delete Record(s)"), msg, QMessageBox.Yes|QMessageBox.No, QMessageBox.No ) if response == QMessageBox.No: return while len(sel_row_indices) > 0: ri = sel_row_indices[0] source_row_index = self._proxyModel.mapToSource(ri) record_id = source_row_index.data() row_number = source_row_index.row() #Delete record result = self._delete_record(record_id, row_number) if not result: title = QApplication.translate( 'EntityBrowserWithEditor', 'Delete Record(s)' ) msg = QApplication.translate( 'EntityBrowserWithEditor', 'An error occurred while attempting to delete a record, this ' 'is most likely caused by a dependency issue.\nPlease check ' 'if the record has dependencies such as social tenure ' 'relationship or related entities. If it has then delete ' 'these dependencies first.' ) QMessageBox.critical(self, title, msg) break #Refresh list of selected records sel_row_indices = self.tbEntity.selectionModel().selectedRows(0) def remove_rows(self): """ Removes rows from the entity browser. """ if self.tbEntity.model() is not None: row_count = self.tbEntity.model().rowCount() self.tbEntity.model().removeRows(0, row_count) def _load_editor_dialog(self, recid, rownumber): ''' Load editor dialog based on the selected model instance with the given ID. ''' model_obj = self._model_from_id(recid, rownumber) # show GPS editor if geometry if self._entity.has_geometry_column(): self.sp_unit_manager.active_layer_source() gps_tool = GPSToolDialog( iface, self._entity, self._entity.name, self.sp_unit_manager.active_sp_col, model=model_obj, reload=False, row_number=rownumber, entity_browser=self ) result = gps_tool.exec_() else: #Load editor dialog edit_entity_dlg = self._editor_dlg(self._entity, model=model_obj, parent=self, parent_entity=self.parent_entity, plugin=self.plugin) result = edit_entity_dlg.exec_() if result == QDialog.Accepted: if self._entity.has_geometry_column(): edit_entity_dlg = gps_tool.entity_editor updated_model_obj = edit_entity_dlg.model() if not edit_entity_dlg.is_valid: return for i, attr in enumerate(self._entity_attrs): prop_idx = self._tableModel.index(rownumber, i) attr_val = getattr(updated_model_obj, attr) ''' Check if there are display formatters and apply if one exists for the given attribute. ''' if attr in self._cell_formatters: formatter = self._cell_formatters[attr] attr_val = formatter.format_column_value(attr_val) self._tableModel.setData(prop_idx, attr_val) def _delete_record(self, rec_id, row_number): """ Delete the record with the given id and remove it from the table view. """ del_result = True if self.parent_entity is not None: idx = row_number+1 if idx in self.child_model: del self.child_model[idx] del self._parent.child_models[idx, self.entity] self._tableModel.removeRows(row_number, 1) # Update number of records self.recomputeRecordCount() #Clear previous notifications self._notifBar.clear() #Remove record from the database dbHandler = self._dbmodel() entity = dbHandler.queryObject().filter( self._dbmodel.id == rec_id ).first() if entity: result = entity.delete() if not result: return False self._tableModel.removeRows(row_number, 1) #Clear previous notifications self._notifBar.clear() #Notify user delMsg = QApplication.translate( "EntityBrowserWithEditor", "Record successfully deleted!" ) self._notifBar.insertInformationNotification(delMsg) #Update number of records self.recomputeRecordCount() return del_result def onDoubleClickView(self, modelindex): ''' Override for loading editor dialog. ''' rowIndex = self._proxyModel.mapToSource(modelindex) rowNumber = rowIndex.row() recordIdIndex = self._tableModel.index(rowNumber, 0) recordId = recordIdIndex.data() self._load_editor_dialog(recordId,recordIdIndex.row()) def shift_spatial_entity_browser(self): """ Shift records manager to the bottom left corner :rtype: :return: """ parent_height = self.parent().geometry().height() parent_width = self.parent().geometry().width() parent_x = self.parent().geometry().x() parent_y = self.parent().geometry().y() dialog_width = self.width() dialog_height = self.height() self.setGeometry( parent_x, parent_y+parent_height-dialog_height-40, dialog_width, dialog_height ) def on_select_attribute(self): """ Slot raised when selecting a spatial entity row. """ sel_row_indices = self.tbEntity.\ selectionModel().selectedRows(0) record_ids = [] for sel_row_index in sel_row_indices: rowIndex = self._proxyModel.mapToSource( sel_row_index ) record_id = rowIndex.data() record_ids.append(record_id) self.record_feature_highlighter(record_ids) def record_feature_highlighter(self, record_ids): """ Highlights a feature of a record. :param record_id: The id of a row :type record_id: Integer :return: None :rtype: NoneType """ if len(record_ids) < 1: return for geom in self.geom_cols: geom_wkb = entity_id_to_attr( self._entity, geom.name, record_ids[0] ) if geom_wkb is not None: sel_lyr_name = self.sp_unit_manager. \ geom_col_layer_name( self._entity.name, geom ) self.add_spatial_unit_layer(sel_lyr_name) layers = QgsMapLayerRegistry.instance().mapLayersByName( sel_lyr_name ) if len(layers) > 0: layers[0].removeSelection() canvas = iface.mapCanvas() layers[0].blockSignals(True) # Get selection and extent while disabling signals of selection # especially for geometry tools. layers[0].select(record_ids) bounding_box = layers[0].boundingBoxOfSelected() layers[0].blockSignals(False) canvas.setCrsTransformEnabled(True) canvas.zoomToSelected(layers[0]) iface.mapCanvas().setExtent(bounding_box) layers[0].select(record_ids) canvas.refresh() self.selection_layer = layers[0] def add_spatial_unit_layer(self, layer_name=None): """ Add the spatial unit layer into the map canvas for later use. :param layer_name: The name of the layer to be added to the map canvas. :type layer_name: String """ if layer_name is not None: self.sp_unit_manager.add_layer_by_name(layer_name) else: # As this is used for startup of # entity browser, just add the first geom layer. if len(self.geom_cols) > 0: layer_name_item = \ self.sp_unit_manager.geom_col_layer_name( self._entity.name, self.geom_cols[0] ) self.sp_unit_manager.\ add_layer_by_name(layer_name_item) def closeEvent(self, event): """ The event handler that is triggered when the dialog is closed. :param event: the event :type QCloseEvent :return: None """ if self._entity.has_geometry_column(): try: if self.selection_layer is not None: self.selection_layer.removeSelection() except RuntimeError: pass def hideEvent(self, hideEvent): """ The event handler that is triggered when the dialog is hidden. :param hideEvent: the event :type QCloseEvent :return: None """ if self._entity.has_geometry_column(): if self.selection_layer is not None: self.selection_layer.removeSelection()