Exemple #1
0
    def _setup_str_tab(self, is_party_unit: bool):
        """
        Creates the STR relationship tab
        """
        from stdm.ui.feature_details import DetailsTreeView

        layout = QVBoxLayout()
        hl = QHBoxLayout()

        add_btn = QToolButton(self)
        add_btn.setText(self.tr('Create STR'))
        add_btn.setIcon(GuiUtils.get_icon('add.png'))
        hl.addWidget(add_btn)
        add_btn.clicked.connect(self._create_str)

        edit_btn = QToolButton(self)
        edit_btn.setText(self.tr('Edit'))
        edit_btn.setIcon(GuiUtils.get_icon('edit.png'))
        edit_btn.setDisabled(True)
        hl.addWidget(edit_btn)

        view_document_btn = QToolButton(self)
        view_document_btn.setText(self.tr('View Supporting Documents'))
        view_document_btn.setIcon(GuiUtils.get_icon('document.png'))
        view_document_btn.setDisabled(True)
        hl.addWidget(view_document_btn)

        hl.addStretch()
        layout.addLayout(hl)

        self.details_tree_view = DetailsTreeView(
            parent=self,
            plugin=self.plugin,
            edit_button=edit_btn,
            view_document_button=view_document_btn)
        self.details_tree_view.activate_feature_details(
            True, follow_layer_selection=False)
        self.details_tree_view.model.clear()

        if is_party_unit:
            self.details_tree_view.search_party(self.entity,
                                                [self.ent_model.id])
        else:
            self.details_tree_view.search_spatial_unit(self.entity,
                                                       [self.ent_model.id])

        layout.addWidget(self.details_tree_view)
        w = QWidget()
        w.setLayout(layout)

        self.entity_tab_widget.addTab(w, self.tr('STR'))
Exemple #2
0
    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;
            }
            ''')
Exemple #3
0
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
Exemple #4
0
    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;
            }
            '''
        )
Exemple #5
0
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
Exemple #6
0
class EntityEditorDialog(MapperMixin):
    """
    Dialog for editing entity attributes.
    """
    addedModel = pyqtSignal(object)

    def __init__(self,
                 entity,
                 model=None,
                 parent=None,
                 manage_documents=True,
                 collect_model=False,
                 parent_entity=None,
                 exclude_columns=None,
                 plugin=None,
                 allow_str_creation=True):
        """
        Class constructor.
        :param entity: Entity object corresponding to a table object.
        :type entity: Entity
        :param model: Data object for loading data into the form widgets.
        If the model is set, then the editor dialog is assumed to be in edit
        mode.
        :type model: object
        :param parent: Parent widget that the form belongs to.
        :type parent: QWidget
        :param manage_documents: True if the dialog should provide controls
        for managing supporting documents. Only applicable if the entity
        allows for supporting documents to be attached.
        :type manage_documents: bool
        :param collect_model: If set to True only returns
        the filled form model without saving it to the database.
        :type collect_model: Boolean
        :param parent_entity: The parent entity of the editor
        :type parent_entity: Object
        :param exclude_columns: List of columns to be excluded if in a list.
        :type exclude_columns: List
        :return: If collect_model, returns SQLAlchemy Model
        """
        super().__init__(parent=parent, model=model, entity=entity)

        QgsGui.enableAutoGeometryRestore(self)

        self.collection_suffix = self.tr('Collection')

        # Set minimum width
        self.setMinimumWidth(450)

        self.plugin = plugin

        # Flag for mandatory columns
        self.has_mandatory = False
        self.reload_form = False
        self._entity = entity
        self.edit_model = model
        self.column_widgets = OrderedDict()
        self.columns = {}
        self._parent = parent
        self.exclude_columns = exclude_columns or []
        self.entity_tab_widget = None
        self._disable_collections = False
        self.filter_val = None
        self.parent_entity = parent_entity
        self.child_models = OrderedDict()
        self.entity_scroll_area = None
        self.entity_editor_widgets = OrderedDict()
        self.details_tree_view = None
        # Set notification layout bar
        self.vlNotification = QVBoxLayout()
        self.vlNotification.setObjectName('vlNotification')
        self._notifBar = NotificationBar(self.vlNotification)
        self.do_not_check_dirty = False
        # Set manage documents only if the entity supports documents
        if self._entity.supports_documents:
            self._manage_documents = manage_documents
        else:
            self._manage_documents = False

        # Setup entity model
        self._ent_document_model = None
        if self._entity.supports_documents:
            self.ent_model, self._ent_document_model = entity_model(
                self._entity, with_supporting_document=True)
        else:
            self.ent_model = entity_model(self._entity)
        if model is not None:
            self.ent_model = model

        MapperMixin.__init__(self, self.ent_model, entity)

        self.collect_model = collect_model

        self.register_column_widgets()

        try:
            if isinstance(parent._parent, EntityEditorDialog):
                # hide collections form child editor
                self._disable_collections = True
        except AttributeError:
            self._parent._parent = None

        # Set title
        editor_trans = self.tr('Editor')
        if self._entity.label is not None:
            if self._entity.label != '':
                title_str = self._entity.label
            else:
                title_str = format_name(self._entity.short_name)
        else:
            title_str = format_name(self._entity.short_name)

        self.title = '{0} {1}'.format(title_str, editor_trans)

        self.setWindowTitle(self.title)

        # determine whether the entity is part of the STR relationship
        curr_profile = current_profile()
        self.participates_in_str, self.is_party_unit = curr_profile.social_tenure.entity_participates_in_str(
            self.entity)

        self._init_gui(show_str_tab=allow_str_creation
                       and self.participates_in_str,
                       is_party_unit=self.is_party_unit)
        self.adjustSize()

        self._get_entity_editor_widgets()

        if isinstance(parent._parent, EntityEditorDialog):
            self.parent_entity = parent.parent_entity
            self.set_parent_values()
            # make the size smaller to differentiate from parent and as it
            # only has few tabs.
            self.adjustSize()

        self.attribute_mappers = self._attr_mapper_collection

        # Exception title for editor extension exceptions
        self._ext_exc_msg = self.tr(
            'An error has occured while executing Python code in the editor '
            'extension:')

        # Register custom editor extension if specified
        self._editor_ext = entity_dlg_extension(self)
        if self._editor_ext is not None:
            self._editor_ext.post_init()

            # Initialize CascadingFieldContext objects
            self._editor_ext.connect_cf_contexts()

    def _init_gui(self, show_str_tab: bool, is_party_unit: bool):
        # Setup base elements
        self.gridLayout = QGridLayout(self)
        self.gridLayout.setObjectName('glMain')
        self.gridLayout.addLayout(self.vlNotification, 0, 0, 1, 1)

        # set widgets values
        column_widget_area = self._setup_columns_content_area()

        self.gridLayout.addWidget(column_widget_area, 1, 0, 1, 1)

        if show_str_tab:
            self._setup_str_tab(is_party_unit=is_party_unit)

        # Add notification for mandatory columns if applicable
        next_row = 2
        if self.has_mandatory:
            self.required_fields_lbl = QLabel(self)
            msg = self.tr('Please fill out all required (*) fields.')
            msg = self._highlight_asterisk(msg)
            self.required_fields_lbl.setText(msg)
            self.gridLayout.addWidget(self.required_fields_lbl, next_row, 0, 1,
                                      2)
            # Bump up row reference
            next_row += 1

        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setObjectName('buttonBox')
        self.gridLayout.addWidget(self.buttonBox, next_row, 0, 1, 1)

        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Save)

        if self.edit_model is None:
            if not self.collect_model:
                self.save_new_button = QPushButton(
                    QApplication.translate('EntityEditorDialog',
                                           'Save and New'))
                self.buttonBox.addButton(self.save_new_button,
                                         QDialogButtonBox.ActionRole)

        # edit model, collect model
        # adding new record for child

        # Saving in parent editor
        if not isinstance(self._parent._parent, EntityEditorDialog):
            # adding a new record
            if self.edit_model is None:
                # saving when digitizing.
                if self.collect_model:
                    self.buttonBox.accepted.connect(self.on_model_added)
                # saving parent editor
                else:
                    self.buttonBox.accepted.connect(self.save_parent_editor)
                    self.save_new_button.clicked.connect(self.save_and_new)
            # updating existing record
            else:
                if not self.collect_model:
                    # updating existing record of the parent editor
                    self.buttonBox.accepted.connect(self.save_parent_editor)
                else:
                    self.buttonBox.accepted.connect(self.on_model_added)
        # Saving in child editor
        else:
            # save and new record
            if self.edit_model is None:
                self.buttonBox.accepted.connect(self.on_child_saved)
                self.save_new_button.clicked.connect(
                    lambda: self.on_child_saved(True))

            else:
                # When updating an existing child editor save to the db
                self.buttonBox.accepted.connect(self.on_child_saved)
                # self.buttonBox.accepted.connect(self.submit)

        self.buttonBox.rejected.connect(self.cancel)

    @property
    def notification_bar(self):
        """
        :return: Returns the dialog's notification bar.
        :rtype: NotificationBar
        """
        return self._notifBar

    def save_parent_editor(self):
        """
        Saves the parent editor and its children.
        """
        self.submit()
        self.save_children()

    def set_parent_values(self):
        """
        Sets the parent display column for the child.
        """
        if self.parent_entity is None:
            return
        for col in self._entity.columns.values():
            if col.TYPE_INFO == 'FOREIGN_KEY':
                parent_entity = col.parent
                if parent_entity == self.parent_entity:
                    self.parent_widgets_value_setter(self._parent._parent, col)

    def parent_widgets_value_setter(self, parent, col):
        """
        Finds and sets the value from parent widget and set it to the column
        widget of a child using the child column.
        :param parent: The parent widget
        :type parent: QWidget
        :param col: The child column object
        :type col: Object
        """
        for parent_col_name, parent_widget in parent.column_widgets.items():
            parent_col = parent.columns[parent_col_name]
            if parent_col.name == col.name:
                self.single_parent_value_setter(col, parent_widget)
                break
            if parent_col.name in col.entity_relation.display_cols:
                self.single_parent_value_setter(col, parent_widget)
                break

    def single_parent_value_setter(self, col, parent_widget):
        """
        Gets value from parent widget and set it to the column widget of a
        child using the child column.
        :param parent: The parent widget
        :type parent: QWidget
        :param col: The child column object
        :type col: Object
        """
        local_widget = self.column_widgets[col.name]
        local_widget.show_clear_button()
        self.filter_val = parent_widget.text()
        local_widget.setText(self.filter_val)

    def save_and_new(self):
        """
        A slot raised when Save and New button is click. It saves the form
        without showing a success message. Then it sets reload_form property
        to True so that entity_browser can re-load the form.
        """
        from stdm.ui.entity_browser import (EntityBrowserWithEditor)
        self.submit(False, True)
        self.save_children()

        if self.is_valid:
            self.addedModel.emit(self.model())
            self.setModel(self.ent_model())
            self.clear()
            self.child_models.clear()
            for index in range(0, self.entity_tab_widget.count() - 1):
                if isinstance(self.entity_tab_widget.widget(index),
                              EntityBrowserWithEditor):
                    child_browser = self.entity_tab_widget.widget(index)
                    child_browser.remove_rows()

    def on_model_added(self):
        """
        A slot raised when a form is submitted with collect model set to True.
        There will be no success message and the form does not close.
        """
        self.submit(True)
        self.addedModel.emit(self.model())

    def closeEvent(self, event):
        """
        Raised when a request to close the window is received.
        Check the dirty state of input controls and prompt user to
        save if dirty.
        """

        if self.do_not_check_dirty:
            event.accept()
            return
        isDirty, userResponse = self.checkDirty()

        if isDirty:
            if userResponse == QMessageBox.Yes:
                # We need to ignore the event so that validation and
                # saving operations can be executed
                event.ignore()
                self.submit()
            elif userResponse == QMessageBox.No:
                event.accept()
            elif userResponse == QMessageBox.Cancel:
                event.ignore()
        else:
            event.accept()

    def on_child_saved(self, save_and_new=False):
        """
        A slot raised when the save or save and new button is clicked. It sets
        the child_models dictionary of the parent when saved.
        :param save_and_new: A boolean indicating the save and new button is
        clicked to trigger the slot.
        :type save_and_new: Boolean
        """
        if self.parent_entity is None:
            return

        self.submit(True)

        insert_pos = self._parent.tbEntity.model().rowCount() + 1
        # Save to parent editor so that it is persistent.
        self._parent._parent.child_models[insert_pos, self._entity.name] = \
            (self._entity, self.model())
        self.addedModel.emit(self.model())
        if not save_and_new:
            self.accept()

        else:
            if self.is_valid:
                # self.addedModel.emit(self.model())
                self.setModel(self.ent_model())

                self.clear()
                self.set_parent_values()

    def save_children(self):
        """
        Saves children models into the database by assigning the the id of the
        parent for foreign key column.
        """
        if len(self.child_models) < 1:
            return
        children_obj = []
        for row_entity, row_value in self.child_models.items():
            entity, model = row_value

            ent_model = entity_model(entity)
            entity_obj = ent_model()
            for col in entity.columns.values():
                if col.TYPE_INFO == 'FOREIGN_KEY':
                    if col.parent.name == self._entity.name:
                        setattr(model, col.name, self.model().id)
                        children_obj.append(model)
            entity_obj.saveMany(children_obj)

    def register_column_widgets(self):
        """
        Registers the column widgets.
        """
        # Append column labels and widgets
        table_name = self._entity.name
        columns = table_column_names(table_name)
        self.scroll_widget_contents = QWidget()
        self.scroll_widget_contents.setObjectName('scrollAreaWidgetContents')
        for c in self._entity.columns.values():
            if c.name in self.exclude_columns:
                continue
            if c.name not in columns and not isinstance(c, VirtualColumn):
                continue
            # Get widget factory
            column_widget = ColumnWidgetRegistry.create(
                c, self.scroll_widget_contents, host=self)
            self.columns[c.name] = c
            self.column_widgets[c.name] = column_widget

    def _setup_columns_content_area(self):
        # Only use this if entity supports documents
        # self.entity_tab_widget = None
        self.doc_widget = None

        self.entity_scroll_area = QScrollArea(self)
        self.entity_scroll_area.setFrameShape(QFrame.NoFrame)
        self.entity_scroll_area.setWidgetResizable(True)
        self.entity_scroll_area.setObjectName('scrollArea')

        # Grid layout for controls
        self.gl = QGridLayout(self.scroll_widget_contents)
        self.gl.setObjectName('gl_widget_contents')

        # Append column labels and widgets
        table_name = self._entity.name
        columns = table_column_names(table_name)
        # Iterate entity column and assert if they exist
        row_id = 0
        for column_name, column_widget in self.column_widgets.items():
            c = self.columns[column_name]
            if c.name in self.exclude_columns:
                continue
            if c.name not in columns and not isinstance(c, VirtualColumn):
                continue

            if column_widget is not None:
                header = c.ui_display()
                self.c_label = QLabel(self.scroll_widget_contents)

                # Format label text if it is a mandatory field
                if c.mandatory:
                    header = '{0} *'.format(c.ui_display())
                    # Highlight asterisk
                    header = self._highlight_asterisk(header)

                self.c_label.setText(header)
                self.gl.addWidget(self.c_label, row_id, 0, 1, 1)

                self.column_widget = column_widget
                self.gl.addWidget(self.column_widget, row_id, 1, 1, 1)

                # Add user tip if specified for the column configuration
                if c.user_tip:
                    self.tip_lbl = UserTipLabel(user_tip=c.user_tip)
                    self.gl.addWidget(self.tip_lbl, row_id, 2, 1, 1)

                if c.mandatory and not self.has_mandatory:
                    self.has_mandatory = True

                col_name = c.name
                # Replace name accordingly based on column type
                if isinstance(c, MultipleSelectColumn):
                    col_name = c.model_attribute_name

                # Add widget to MapperMixin collection
                self.addMapping(col_name,
                                self.column_widget,
                                c.mandatory,
                                pseudoname=c.ui_display())

                # Bump up row_id
                row_id += 1

        self.entity_scroll_area.setWidget(self.scroll_widget_contents)

        if self.entity_tab_widget is None:
            self.entity_tab_widget = QTabWidget(self)
        # Check if there are children and add foreign key browsers

        # Add primary tab if necessary
        self._add_primary_attr_widget()

        if not self._disable_collections:
            ch_entities = self.children_entities()

            for col, ch in ch_entities:
                if hasattr(col.entity_relation, 'show_in_parent'):
                    if col.entity_relation.show_in_parent != '0':
                        self._add_fk_browser(ch, col)
                else:
                    self._add_fk_browser(ch, col)

        # Add tab widget if entity supports documents
        if self._entity.supports_documents:
            self.doc_widget = SupportingDocumentsWidget(
                self._entity.supporting_doc, self._ent_document_model, self)

            # Map the source document manager object
            self.addMapping('documents',
                            self.doc_widget.source_document_manager)

            #
            # # Add attribute tab
            # self._add_primary_attr_widget()

            # Add supporting documents tab
            self.entity_tab_widget.addTab(self.doc_widget,
                                          self.tr('Supporting Documents'))

        # Return the correct widget
        if self.entity_tab_widget is not None:
            return self.entity_tab_widget

        return self.entity_scroll_area

    def _add_primary_attr_widget(self):
        # Check if the primary entity
        # exists and add if it does not
        pr_txt = self.tr('Primary')
        if self.entity_tab_widget is not None:
            tab_txt = self.entity_tab_widget.tabText(0)
            if not tab_txt == pr_txt:
                self.entity_tab_widget.addTab(self.entity_scroll_area, pr_txt)

    def _setup_str_tab(self, is_party_unit: bool):
        """
        Creates the STR relationship tab
        """
        from stdm.ui.feature_details import DetailsTreeView

        layout = QVBoxLayout()
        hl = QHBoxLayout()

        add_btn = QToolButton(self)
        add_btn.setText(self.tr('Create STR'))
        add_btn.setIcon(GuiUtils.get_icon('add.png'))
        hl.addWidget(add_btn)
        add_btn.clicked.connect(self._create_str)

        edit_btn = QToolButton(self)
        edit_btn.setText(self.tr('Edit'))
        edit_btn.setIcon(GuiUtils.get_icon('edit.png'))
        edit_btn.setDisabled(True)
        hl.addWidget(edit_btn)

        view_document_btn = QToolButton(self)
        view_document_btn.setText(self.tr('View Supporting Documents'))
        view_document_btn.setIcon(GuiUtils.get_icon('document.png'))
        view_document_btn.setDisabled(True)
        hl.addWidget(view_document_btn)

        hl.addStretch()
        layout.addLayout(hl)

        self.details_tree_view = DetailsTreeView(
            parent=self,
            plugin=self.plugin,
            edit_button=edit_btn,
            view_document_button=view_document_btn)
        self.details_tree_view.activate_feature_details(
            True, follow_layer_selection=False)
        self.details_tree_view.model.clear()

        if is_party_unit:
            self.details_tree_view.search_party(self.entity,
                                                [self.ent_model.id])
        else:
            self.details_tree_view.search_spatial_unit(self.entity,
                                                       [self.ent_model.id])

        layout.addWidget(self.details_tree_view)
        w = QWidget()
        w.setLayout(layout)

        self.entity_tab_widget.addTab(w, self.tr('STR'))

    def _create_str(self):
        """
        Opens the dialog to create a new STR
        """
        from stdm.ui.social_tenure.str_editor import STREditor
        add_str_window = STREditor()

        #if is_party_unit:
        #    add_str.set_party_data(self.entity_model_obj)

        if add_str_window.exec_():
            # STR created - refresh STR view
            if self.is_party_unit:
                self.details_tree_view.search_party(self.entity,
                                                    [self.ent_model.id])
            else:
                self.details_tree_view.search_spatial_unit(
                    self.entity, [self.ent_model.id])

    def _add_fk_browser(self, child_entity, column):
        # Create and add foreign key
        # browser to the collection
        from stdm.ui.entity_browser import (ContentGroupEntityBrowser)

        attr = '{0}_collection'.format(child_entity.name)

        # Return if the attribute does not exist
        if not hasattr(self._model, attr):
            return

        table_content = TableContentGroup(User.CURRENT_USER.UserName,
                                          child_entity.short_name)

        if self.edit_model is not None:
            parent_id = self.edit_model.id
        else:
            parent_id = 0

        entity_browser = ContentGroupEntityBrowser(child_entity,
                                                   table_content,
                                                   rec_id=parent_id,
                                                   parent=self,
                                                   plugin=self.plugin,
                                                   load_recs=False)

        # entity_browser = EntityBrowserWithEditor(
        # child_entity,
        # self,
        # MANAGE,
        # False,
        # plugin=self.plugin
        # )

        entity_browser.buttonBox.setVisible(False)
        entity_browser.record_filter = []

        if len(child_entity.label) > 2:
            column_label = child_entity.label
        else:
            # Split and join  to filter out entity name prefix
            # e.g. 'lo_parcel' to 'parcel'
            column_label = format_name(" ".join(
                child_entity.name.split("_", 1)[1:]))

        self.set_filter(child_entity, entity_browser)

        self.entity_tab_widget.addTab(entity_browser,
                                      '{0}'.format(column_label))

    def set_filter(self, entity, browser):
        col = self.filter_col(entity)
        child_model = entity_model(entity)
        child_model_obj = child_model()
        col_obj = getattr(child_model, col.name)

        browser.filtered_records = []

        if self.model() is not None:
            if self.model().id is not None:
                browser.filtered_records = child_model_obj.queryObject(
                ).filter(col_obj == self.model().id).all()

        if self.edit_model is not None:
            browser.filtered_records = child_model_obj.queryObject().filter(
                col_obj == self.edit_model.id).all()

    def filter_col(self, child_entity):
        for col in child_entity.columns.values():
            if col.TYPE_INFO == 'FOREIGN_KEY':
                parent_entity = col.parent
                if parent_entity == self._entity:
                    return col

    def children_entities(self):
        """
        :return: Returns a list of children entities (by name)
        that refer to the main entity as the parent.
        :rtype: OrderedDict
        """
        child_columns = []
        for ch in self._entity.children():
            if ch.TYPE_INFO == Entity.TYPE_INFO:
                for col in ch.columns.values():
                    if hasattr(col, 'entity_relation'):

                        if col.parent.name == self._entity.name:
                            child_columns.append((col, ch))
        return child_columns

    def document_widget(self):
        """
        :return: Returns the widget for managing
        the supporting documents for an entity if enabled.
        :rtype: SupportingDocumentsWidget
        """
        return self.doc_widget

    def source_document_manager(self):
        """
        :return: Returns an instance of the
        SourceDocumentManager only if supporting
        documents are enabled for the given entity. Otherwise,
        None if supporting documents are not enabled.
        :rtype: SourceDocumentManager
        """
        if self.doc_widget is None:
            return None

        return self.doc_widget.source_document_manager

    def _highlight_asterisk(self, text):
        # Highlight asterisk in red
        c = '*'

        # Do not format if there is no asterisk
        if text.find(c) == -1:
            return text

        asterisk_highlight = '<span style=\" color:#ff0000;\">*</span>'
        text = text.replace(c, asterisk_highlight)

        return '<html><head/><body><p>{0}</p></body></html>'.format(text)

    def _custom_validate(self):
        """
        Override of the MapperMixin which enables custom editor extensions to
        inject additional validation before saving form data.
        :return: Return True if the validation was successful,
        otherwise False.
        :rtype: bool
        """
        if self._editor_ext is not None:
            return self._editor_ext.validate()

        # Return True if there is no custom editor extension specified
        return True

    def _post_save(self, model):
        """
        Include additional post-save logic by custom extensions.
        :param model: SQLAlchemy model
        :type model: object
        """
        if self._editor_ext is not None:
            self._editor_ext.post_save(model)

    def _get_entity_editor_widgets(self):
        """
        Gets entity editor widgets and appends them to a dictionary
        """
        if self.entity_tab_widget:
            tab_count = self.entity_tab_widget.count()
            for i in range(tab_count):
                tab_object = self.entity_tab_widget.widget(i)
                tab_text = self.entity_tab_widget.tabText(i)
                self.entity_editor_widgets[tab_text] = tab_object
        else:
            self.entity_editor_widgets['no_tab'] = self.entity_scroll_area