Example #1
0
    def on_bt_save_clicked(self):
        """
        Run when Qtablewidget button save is clicked
        """
        geom_list = []

        for key, vertex in self.vertex_dict.iteritems():
            if vertex[3] == Qt.Checked:
                geom_list.append(QgsPoint(vertex[1], vertex[2]))

        if self.active_layer_geometry_typ == 0:
            geom = QgsGeometry.fromPoint(geom_list[0])
        elif self.active_layer_geometry_typ == 1:
            geom = QgsGeometry.fromPolyline(geom_list)
        elif self.active_layer_geometry_typ == 2:
            geom = QgsGeometry.fromPolygon([geom_list])

        geom_wkb = geom.exportToWkt()

        non_sp_colms = non_spatial_table_columns(self.sp_table)

        for key, vertex in self.vertex_dict.iteritems():
            self.map_canvas.scene().removeItem(vertex[0])

        self.close()

        self.curr_layer = vector_layer(self.sp_table, geom_column=self.sp_col)

        self.iface.mapCanvas().setExtent(self.curr_layer.extent())

        self.iface.mapCanvas().refresh()

        srid = self.entity.columns[self.sp_col].srid
        #init form
        editor = EntityEditorDialog(self.entity, None, self.iface.mainWindow())

        model = editor.model()
        # add geometry into the model
        setattr(model, self.sp_col, 'SRID={};{}'.format(srid, geom_wkb))

        editor.exec_()
Example #2
0
    def edit_selected_steam(self, entity):
        id, item = self.steam_data('edit')

        feature_edit = True
        if id is None:
            return
        if isinstance(id, str):
            data_error = QApplication.translate('DetailsTreeView', id)
            QMessageBox.warning(
                self.iface.mainWindow(), "Edit Error", data_error
            )
            return

        if item.text() == 'Social Tenure Relationship':
            model = self.STR_models[id]

            feature_edit = False
            ##TODO add STR wizard edit mode here.
        elif item.text() == format_name(self.party.short_name):
            feature_edit = False

            model = self.party_models[id]
            editor = EntityEditorDialog(
                self.party, model, self.iface.mainWindow()
            )
            editor.exec_()
        else:
            model = self.feature_models[id]

            editor = EntityEditorDialog(
                entity, model, self.iface.mainWindow()
            )
            editor.exec_()
        #root = self.find_root(entity, id)
        self.view.expand(item.index())
        if feature_edit:
            self.update_edited_steam(entity, id)
        else:
            self.update_edited_steam(self.social_tenure, id)
Example #3
0
class STDMFieldWidget():
    # Instantiate the singleton QgsEditorWidgetRegistry
    widgetRegistry = QgsEditorWidgetRegistry.instance()

    def __init__(self):
        self.entity = None
        self.widget_mapping = {}
        self.layer = None
        self.feature_models = OrderedDict()
        self.removed_feature_models = OrderedDict()
        self.current_feature = None
        self.editor = None

    def init_form(self, table, spatial_column, curr_layer):
        """
        Initialize required methods and slots
        to be used in form initialization.
        :param table: The table name of the layer
        :type table: String
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :param curr_layer: The current layer of form.
        :type curr_layer: QgsVectorLayer
        :return: None
        :rtype: NoneTYpe
        """
        try:
            # init form
            self.set_entity(table)
            self.set_widget_mapping()
            self.register_factory()
            self.set_widget_type(curr_layer)

            curr_layer.editFormConfig().setSuppress(1)
            try:

                curr_layer.featureAdded.connect(
                    lambda feature_id: self.load_stdm_form(
                        feature_id, spatial_column))
            except Exception:
                pass
            curr_layer.featureDeleted.connect(self.on_feature_deleted)

            curr_layer.beforeCommitChanges.connect(self.on_digitizing_saved)

        except Exception as ex:
            LOGGER.debug(ex)

    def set_entity(self, source):
        """
        Sets the layer entity of the layer
        based on a table name.
        :param source: Table name that acts as a layer source.
        :type source: String
        :return: None
        :rtype: NoneType
        """
        curr_profile = current_profile()
        self.entity = curr_profile.entity_by_name(source)

    def _set_widget_type(self, layer, column, widget_type_id):
        """
        Sets the widget type for each field into
        QGIS form configuration.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :param column: STDM column object
        :type column: Object
        :param widget_type_id: The widget type id which could be
         the default QGIS or the custom STDM widget id which is
         based on column.TYPE_INFO.
        :type widget_type_id: String
        :return: None
        :rtype:NoneType
        """
        idx = layer.fieldNameIndex(column.name)
        # Set Alias/ Display names for the column names
        layer.addAttributeAlias(idx, column.header())

        try:
            layer.editFormConfig().setWidgetType(idx, widget_type_id)

        except Exception:
            layer.setEditorWidgetV2(idx, widget_type_id)

    def set_widget_mapping(self):
        """
        Maps each column to QGIS or STDM editor widgets.
        :return: None
        :rtype:NoneType
        """
        self.widget_mapping.clear()
        for c in self.entity.columns.values():

            if c.TYPE_INFO == 'SERIAL':
                self.widget_mapping[c] = ['Hidden', None]
            elif c.TYPE_INFO == 'GEOMETRY':
                self.widget_mapping[c] = ['TextEdit', None]
            else:
                stdm = QApplication.translate('STDMFieldWidget', u'STDM')
                self.widget_mapping[c] = [
                    u'stdm_{}'.format(c.TYPE_INFO.lower()),
                    u'{} {}'.format(stdm, c.display_name())
                ]

    def register_factory(self):
        """
        Registers each widget type
        to a QGIS widget factory registry.
        :return: None
        :rtype: NoneType
        """
        # The destructor has no effects. It is QGIS bug.
        # So restarting QGIS is required to destroy
        # registered stdm widgets.
        for widget_id_name in self.widget_mapping.values():
            # add and register stdm widget type only
            if not widget_id_name[1] is None:
                widget_name = widget_id_name[1]

                if widget_id_name[0] not in \
                        self.widgetRegistry.factories().keys():

                    widget_factory = QGISFieldWidgetFactory(widget_name)

                    self.widgetRegistry.registerWidget(widget_id_name[0],
                                                       widget_factory)

    def set_widget_type(self, layer):
        """
        Sets widget type for each fields in a layer.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :return: None
        :rtype: NoneType
        """
        self.layer = layer
        for col, widget_id_name in \
                self.widget_mapping.iteritems():

            self._set_widget_type(layer, col, widget_id_name[0])

    def load_stdm_form(self, feature_id, spatial_column):
        """
        Loads STDM Form and collects the model added
        into the form so that it is saved later.
        :param feature_id: the ID of a feature that
        is last added
        :type feature_id: Integer
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :return: None
        :rtype:NoneType
        """
        srid = None
        self.current_feature = feature_id

        # If the digitizing save button is clicked,
        # the featureAdded signal is called but the
        # feature ids value is over 0. Return to prevent
        # the dialog from popping up for every feature.
        if feature_id > 0:
            return

        # if the feature is already in the OrderedDict don't
        # show the form as the model of the feature is
        # already populated by the form
        if feature_id in self.feature_models.keys():
            return
        # If the feature is removed by the undo button, don't
        # load the form for it but add it
        # back to feature_models and don't show the form.
        # This happens when redo button(add feature back) is
        # clicked after an undo button(remove feature)
        if feature_id in self.removed_feature_models.keys():
            self.feature_models[feature_id] = \
                self.removed_feature_models[feature_id]
            return
        # If the feature is not valid, geom_wkt will be None
        # So don't launch form for invalid feature and delete feature
        geom_wkt = self.get_wkt(feature_id)

        if geom_wkt is None:
            title = QApplication.translate('STDMFieldWidget',
                                           u'Spatial Entity Form Error', None,
                                           QCoreApplication.UnicodeUTF8)
            msg = QApplication.translate(
                'STDMFieldWidget', u'The feature you have added is invalid. \n'
                'To fix this issue, check if the feature '
                'is digitized correctly.  \n'
                'Make sure you have added a base layer to digitize on.', None,
                QCoreApplication.UnicodeUTF8)
            # Message: Spatial column information
            # could not be found
            QMessageBox.critical(iface.mainWindow(), title, msg)
            return
        # init form
        self.editor = EntityEditorDialog(self.entity,
                                         None,
                                         parent=iface.mainWindow(),
                                         manage_documents=True,
                                         collect_model=True)

        self.model = self.editor.model()
        self.editor.addedModel.connect(self.on_form_saved)

        # get srid with EPSG text
        full_srid = self.layer.crs().authid().split(':')

        if len(full_srid) > 0:
            # Only extract the number
            srid = full_srid[1]
        if not geom_wkt is None:
            # add geometry into the model
            setattr(self.model, spatial_column,
                    'SRID={};{}'.format(srid, geom_wkt))

        # open editor
        result = self.editor.exec_()
        if result < 1:
            self.removed_feature_models[feature_id] = None
            self.layer.deleteFeature(feature_id)

    def get_wkt(self, feature_id):
        """
        Gets feature geometry in Well-Known Text
        format and returns it.
        :param feature_id: Feature id
        :type feature_id: Integer
        :return: Well-Known Text format of a geometry
        :rtype: WKT
        """
        geom_wkt = None
        fid = feature_id
        request = QgsFeatureRequest()
        request.setFilterFid(fid)
        features = self.layer.getFeatures(request)

        # get the wkt of the geometry
        for feature in features:
            geometry = feature.geometry()
            if geometry.isGeosValid():
                geom_wkt = feature.geometry().exportToWkt()

        return geom_wkt

    def on_form_saved(self, model):
        """
        A slot raised when the save button is clicked
        in spatial unit form. It adds the feature model
        in feature_models ordered dictionary to be saved
        later.
        :param model: The model holding feature geometry
        and attributes obtained from the form
        :type model: SQLAlchemy Model
        :return: None
        :rtype: NoneType
        """
        if not model is None:
            self.feature_models[self.current_feature] = model
            if self.editor.is_valid:
                self.editor.accept()

    def on_feature_deleted(self, feature_id):
        """
        A slot raised when a feature is deleted
        in QGIS map canvas via the undo button.
        It deletes the associated model of the feature.
        :param feature_id: The id that is removed.
        :type feature_id: Integer
        :return: None
        :rtype: NoneType
        """
        if feature_id in self.feature_models.keys():
            self.removed_feature_models[feature_id] = \
                self.feature_models[feature_id]
            del self.feature_models[feature_id]

    def on_digitizing_saved(self):
        """
        A slot raised when the save button is clicked
        on Digitizing Toolbar of QGIS. It saves feature
        models created by the digitizer and STDM form to
        the Database.
        :return: None
        :rtype: NoneType
        """
        ent_model = entity_model(self.entity)
        entity_obj = ent_model()
        entity_obj.saveMany(self.feature_models.values())

        # Save child models
        if self.editor is not None:
            self.editor.save_children()
        # undo each feature created so that qgis
        # don't try to save the same feature again.
        # It will also clear all the models from
        # self.feature_models as on_feature_deleted
        # is raised when a feature is removed.
        for i in range(len(self.feature_models)):
            self.layer.undoStack().undo()
Example #4
0
class STDMFieldWidget:
    # Instantiate the singleton QgsEditorWidgetRegistry
    widgetRegistry = QgsGui.editorWidgetRegistry()

    def __init__(self, plugin):
        self.entity = None
        self.widget_mapping = {}
        self.column_mapping = {}
        self.layer = None
        self.feature_models = OrderedDict()
        self.removed_feature_models = OrderedDict()
        self.current_feature = None
        self.editor = None
        self.plugin = plugin

    def init_form(self, table, spatial_column, curr_layer):
        """
        Initialize required methods and slots
        to be used in form initialization.
        :param table: The table name of the layer
        :type table: String
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :param curr_layer: The current layer of form.
        :type curr_layer: QgsVectorLayer
        :return: None
        :rtype: NoneTYpe
        """

        # init form
        self.set_entity(table)
        self.set_widget_mapping()
        self.register_factory()
        self.set_widget_type(curr_layer)

        form_config = curr_layer.editFormConfig()
        form_config.setSuppress(QgsEditFormConfig.SuppressOn)
        curr_layer.setEditFormConfig(form_config)

        curr_layer.featureAdded.connect(
            lambda feature_id: self.load_stdm_form(
                feature_id, spatial_column
            )
        )

        curr_layer.featureDeleted.connect(
            self.on_feature_deleted
        )

        curr_layer.beforeCommitChanges.connect(
            self.on_digitizing_saved
        )

    def set_entity(self, source):
        """
        Sets the layer entity of the layer
        based on a table name.
        :param source: Table name that acts as a layer source.
        :type source: String
        :return: None
        :rtype: NoneType
        """
        curr_profile = current_profile()
        self.entity = curr_profile.entity_by_name(
            source
        )

    def _set_widget_type(self, layer, column, widget_type_id):
        """
        Sets the widget type for each field into
        QGIS form configuration.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :param column: STDM column object
        :type column: Object
        :param widget_type_id: The widget type id which could be
         the default QGIS or the custom STDM widget id which is
         based on column.TYPE_INFO.
        :type widget_type_id: String
        :return: None
        :rtype:NoneType
        """
        idx = layer.fields().indexFromName(column.name)
        # Set Alias/ Display names for the column names
        layer.setFieldAlias(
            idx,
            column.header()
        )

        setup = QgsEditorWidgetSetup(widget_type_id, {})
        layer.setEditorWidgetSetup(idx, setup)

    def set_widget_mapping(self):
        """
        Maps each column to QGIS or STDM editor widgets.
        :return: None
        :rtype:NoneType
        """
        self.widget_mapping.clear()
        self.column_mapping.clear()
        for c in self.entity.columns.values():
            self.column_mapping[c.name] = c

            if c.TYPE_INFO == 'SERIAL':
                self.widget_mapping[c.name] = ['Hidden', None]
            elif c.TYPE_INFO == 'GEOMETRY':
                self.widget_mapping[c.name] = ['TextEdit', None]
            else:
                stdm = QApplication.translate(
                    'STDMFieldWidget', 'STDM'
                )
                self.widget_mapping[c.name] = [
                    'stdm_{}'.format(
                        c.TYPE_INFO.lower()
                    ),
                    '{} {}'.format(
                        stdm, c.display_name()
                    )
                ]

    def register_factory(self):
        """
        Registers each widget type
        to a QGIS widget factory registry.
        :return: None
        :rtype: NoneType
        """
        # The destructor has no effects. It is QGIS bug.
        # So restarting QGIS is required to destroy
        # registered stdm widgets.
        for widget_id_name in self.widget_mapping.values():
            # add and register stdm widget type only
            if not widget_id_name[1] is None:
                widget_name = widget_id_name[1]

                if widget_id_name[0] not in \
                        self.widgetRegistry.factories().keys():
                    widget_factory = QGISFieldWidgetFactory(
                        widget_name
                    )

                    self.widgetRegistry.registerWidget(
                        widget_id_name[0], widget_factory
                    )

    def set_widget_type(self, layer):
        """
        Sets widget type for each fields in a layer.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :return: None
        :rtype: NoneType
        """
        self.layer = layer
        for col_name, widget_id_name in \
                self.widget_mapping.items():
            col = self.column_mapping[col_name]
            self._set_widget_type(
                layer, col, widget_id_name[0]
            )

    def feature_to_model(self, feature_id):
        """
        Converts feature to db model.
        :param feature_id: The feature id
        :type feature_id: Integer
        :return: The model and number of columns with data.
        :rtype: Tuple
        """
        ent_model = entity_model(self.entity)
        model_obj = ent_model()

        iterator = self.layer.getFeatures(
            QgsFeatureRequest().setFilterFid(feature_id))
        feature = next(iterator)
        field_names = [field.name() for field in self.layer.fields()]
        attribute = feature.attributes()
        if isinstance(attribute[0], QgsField):
            return None, 0
        mapped_data = OrderedDict(list(zip(field_names, feature.attributes())))
        col_with_data = []

        for col, value in mapped_data.items():
            if col == 'id':
                continue
            if value is None:
                continue
            if value == NULL:
                continue
            setattr(model_obj, col, value)
            col_with_data.append(col)

        return model_obj, len(col_with_data)

    def load_stdm_form(self, feature_id, spatial_column):
        """
        Loads STDM Form and collects the model added
        into the form so that it is saved later.
        :param feature_id: the ID of a feature that
        is last added
        :type feature_id: Integer
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :return: None
        :rtype:NoneType
        """

        srid = None

        self.current_feature = feature_id

        # If the digitizing save button is clicked,
        # the featureAdded signal is called but the
        # feature ids value is over 0. Return to prevent
        # the dialog from popping up for every feature.
        if feature_id > 0:
            return

        # if the feature is already in the OrderedDict don't
        # show the form as the model of the feature is
        # already populated by the form
        if feature_id in self.feature_models.keys():
            return

        # If the feature is removed by the undo button, don't
        # load the form for it but add it
        # back to feature_models and don't show the form.
        # This happens when redo button(add feature back) is
        # clicked after an undo button(remove feature)
        if feature_id in self.removed_feature_models.keys():
            self.feature_models[feature_id] = \
                self.removed_feature_models[feature_id]
            return
        # If the feature is not valid, geom_wkt will be None
        # So don't launch form for invalid feature and delete feature

        geom_wkt = self.get_wkt(spatial_column, feature_id)

        if geom_wkt is None:
            title = QApplication.translate(
                'STDMFieldWidget',
                'Spatial Entity Form Error',
                None
            )
            msg = QApplication.translate(
                'STDMFieldWidget',
                'The feature you have added is invalid. \n'
                'To fix this issue, check if the feature '
                'is digitized correctly.  \n'
                'Make sure you have added a base layer to digitize on.',
                None
            )
            # Message: Spatial column information
            # could not be found
            QMessageBox.critical(
                iface.mainWindow(),
                title,
                msg
            )
            return
        # init form
        feature_model, col_with_data = self.feature_to_model(feature_id)

        if col_with_data == 0:
            feature_model = None
        self.editor = EntityEditorDialog(
            self.entity,
            model=feature_model,
            parent=iface.mainWindow(),
            manage_documents=True,
            collect_model=True,
            plugin=self.plugin
        )
        self.model = self.editor.model()
        self.editor.addedModel.connect(self.on_form_saved)

        # get srid with EPSG text
        full_srid = self.layer.crs().authid().split(':')

        if len(full_srid) > 0:
            # Only extract the number
            srid = full_srid[1]
        if geom_wkt is not None:
            # add geometry into the model

            setattr(
                self.model,
                spatial_column,
                'SRID={};{}'.format(srid, geom_wkt)
            )

        # open editor
        result = self.editor.exec_()
        if result < 1:
            self.removed_feature_models[feature_id] = None
            self.layer.deleteFeature(feature_id)

    def get_wkt(self, spatial_column, feature_id):
        """
        Gets feature geometry in Well-Known Text
        format and returns it.
        :param spatial_column: The spatial column name.
        :type spatial_column: String
        :param feature_id: Feature id
        :type feature_id: Integer
        :return: Well-Known Text format of a geometry
        :rtype: WKT
        """
        geom_wkt = None
        fid = feature_id
        request = QgsFeatureRequest()
        request.setFilterFid(fid)
        features = self.layer.getFeatures(request)

        geom_col_obj = self.entity.columns[spatial_column]
        geom_type = geom_col_obj.geometry_type()

        # get the wkt of the geometry
        for feature in features:
            geometry = feature.geometry()
            if geometry.isGeosValid():
                if geom_type in ['MULTIPOLYGON', 'MULTILINESTRING']:
                    geometry.convertToMultiType()

                geom_wkt = geometry.asWkt()

        return geom_wkt

    def on_form_saved(self, model):
        """
        A slot raised when the save button is clicked
        in spatial unit form. It adds the feature model
        in feature_models ordered dictionary to be saved
        later.
        :param model: The model holding feature geometry
        and attributes obtained from the form
        :type model: SQLAlchemy Model
        :return: None
        :rtype: NoneType
        """
        if model is not None:
            self.feature_models[self.current_feature] = model
            if self.editor.is_valid:
                self.editor.accept()

    def on_feature_deleted(self, feature_id):
        """
        A slot raised when a feature is deleted
        in QGIS map canvas via the undo button.
        It deletes the associated model of the feature.
        :param feature_id: The id that is removed.
        :type feature_id: Integer
        :return: None
        :rtype: NoneType
        """
        if feature_id in self.feature_models.keys():
            self.removed_feature_models[feature_id] = \
                self.feature_models[feature_id]
            del self.feature_models[feature_id]

    def on_digitizing_saved(self):
        """
        A slot raised when the save button is clicked
        on Digitizing Toolbar of QGIS. It saves feature
        models created by the digitizer and STDM form to
        the Database.
        :return: None
        :rtype: NoneType
        """
        ent_model = entity_model(self.entity)
        entity_obj = ent_model()
        entity_obj.saveMany(
            list(self.feature_models.values())
        )
        for model in self.feature_models.values():
            STDMDb.instance().session.flush()

            for attrMapper in self.editor._attrMappers:
                control = attrMapper.valueHandler().control
                if isinstance(control, ExpressionLineEdit):
                    value = control.on_expression_triggered(model)
                    print(attrMapper._attrName, value)
                    setattr(model, attrMapper._attrName, value)
            model.update()

        # Save child models
        if self.editor is not None:
            self.editor.save_children()
        # undo each feature created so that qgis
        # don't try to save the same feature again.
        # It will also clear all the models from
        # self.feature_models as on_feature_deleted
        # is raised when a feature is removed.
        feature_ids_to_delete = list(self.feature_models.keys())
        for f_id in feature_ids_to_delete:
            iface.mainWindow().blockSignals(True)
            self.layer.deleteFeature(f_id)
            self.on_feature_deleted(f_id)
            iface.mainWindow().blockSignals(True)

        for i in range(len(self.feature_models)):
            self.layer.undoStack().undo()
Example #5
0
class STDMFieldWidget():
    # Instantiate the singleton QgsEditorWidgetRegistry
    widgetRegistry = QgsEditorWidgetRegistry.instance()

    def __init__(self, plugin):
        self.entity = None
        self.widget_mapping = {}
        self.layer = None
        self.feature_models = OrderedDict()
        self.removed_feature_models = OrderedDict()
        self.current_feature = None
        self.editor = None
        self.plugin = plugin

    def init_form(self, table, spatial_column, curr_layer):
        """
        Initialize required methods and slots
        to be used in form initialization.
        :param table: The table name of the layer
        :type table: String
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :param curr_layer: The current layer of form.
        :type curr_layer: QgsVectorLayer
        :return: None
        :rtype: NoneTYpe
        """
        try:
            # init form
            self.set_entity(table)
            self.set_widget_mapping()
            self.register_factory()
            self.set_widget_type(curr_layer)

            curr_layer.editFormConfig().setSuppress(1)
            try:

                curr_layer.featureAdded.connect(
                    lambda feature_id: self.load_stdm_form(
                        feature_id, spatial_column
                    )
                )
            except Exception:
                pass
            curr_layer.featureDeleted.connect(
                self.on_feature_deleted
            )

            curr_layer.beforeCommitChanges.connect(
                self.on_digitizing_saved
            )

        except Exception as ex:
            LOGGER.debug(ex)

    def set_entity(self, source):
        """
        Sets the layer entity of the layer
        based on a table name.
        :param source: Table name that acts as a layer source.
        :type source: String
        :return: None
        :rtype: NoneType
        """
        curr_profile = current_profile()
        self.entity = curr_profile.entity_by_name(
            source
        )

    def _set_widget_type(self, layer, column, widget_type_id):
        """
        Sets the widget type for each field into
        QGIS form configuration.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :param column: STDM column object
        :type column: Object
        :param widget_type_id: The widget type id which could be
         the default QGIS or the custom STDM widget id which is
         based on column.TYPE_INFO.
        :type widget_type_id: String
        :return: None
        :rtype:NoneType
        """
        idx = layer.fieldNameIndex(column.name)
        # Set Alias/ Display names for the column names
        layer.addAttributeAlias(
            idx,
            column.header()
        )

        try:
            layer.editFormConfig().setWidgetType(
                idx, widget_type_id
            )

        except Exception:
            layer.setEditorWidgetV2(
                idx, widget_type_id
            )

    def set_widget_mapping(self):
        """
        Maps each column to QGIS or STDM editor widgets.
        :return: None
        :rtype:NoneType
        """
        self.widget_mapping.clear()
        for c in self.entity.columns.values():

            if c.TYPE_INFO == 'SERIAL':
                self.widget_mapping[c] = ['Hidden', None]
            elif c.TYPE_INFO == 'GEOMETRY':
                self.widget_mapping[c] = ['TextEdit', None]
            else:
                stdm = QApplication.translate(
                    'STDMFieldWidget', u'STDM'
                )
                self.widget_mapping[c] = [
                    u'stdm_{}'.format(
                        c.TYPE_INFO.lower()
                    ),
                    u'{} {}'.format(
                        stdm, c.display_name()
                    )
                ]

    def register_factory(self):
        """
        Registers each widget type
        to a QGIS widget factory registry.
        :return: None
        :rtype: NoneType
        """
        # The destructor has no effects. It is QGIS bug.
        # So restarting QGIS is required to destroy
        # registered stdm widgets.
        for widget_id_name in self.widget_mapping.values():
            # add and register stdm widget type only
            if not widget_id_name[1] is None:
                widget_name = widget_id_name[1]

                if widget_id_name[0] not in \
                        self.widgetRegistry.factories().keys():
                    widget_factory = QGISFieldWidgetFactory(
                        widget_name
                    )

                    self.widgetRegistry.registerWidget(
                        widget_id_name[0], widget_factory
                    )

    def set_widget_type(self, layer):
        """
        Sets widget type for each fields in a layer.
        :param layer: The layer to which the widget type is set.
        :type layer: QgsVectorLayer
        :return: None
        :rtype: NoneType
        """
        self.layer = layer
        for col, widget_id_name in \
                self.widget_mapping.iteritems():
            self._set_widget_type(
                layer, col, widget_id_name[0]
            )

    def feature_to_model(self, feature_id):
        """
        Converts feature to db model.
        :param feature_id: The feature id
        :type feature_id: Integer
        :return: The model and number of columns with data.
        :rtype: Tuple
        """
        ent_model = entity_model(self.entity)
        model_obj = ent_model()

        iterator = self.layer.getFeatures(
            QgsFeatureRequest().setFilterFid(feature_id))
        feature = next(iterator)
        field_names = [field.name() for field in self.layer.pendingFields()]
        attribute = feature.attributes()
        if isinstance(attribute[0], QgsField):
            return None, 0
        mapped_data = OrderedDict(zip(field_names, feature.attributes()))
        col_with_data = []

        for col, value in mapped_data.iteritems():
            if col == 'id':
                continue
            if value is None:
                continue
            if value == NULL:
                continue
            setattr(model_obj, col, value)
            col_with_data.append(col)

        return model_obj, len(col_with_data)

    def load_stdm_form(self, feature_id, spatial_column):
        """
        Loads STDM Form and collects the model added
        into the form so that it is saved later.
        :param feature_id: the ID of a feature that
        is last added
        :type feature_id: Integer
        :param spatial_column: The spatial column name
        of the layer
        :type spatial_column: String
        :return: None
        :rtype:NoneType
        """

        srid = None

        self.current_feature = feature_id

        # If the digitizing save button is clicked,
        # the featureAdded signal is called but the
        # feature ids value is over 0. Return to prevent
        # the dialog from popping up for every feature.
        if feature_id > 0:
            return

        # if the feature is already in the OrderedDict don't
        # show the form as the model of the feature is
        # already populated by the form
        if feature_id in self.feature_models.keys():
            return

        # If the feature is removed by the undo button, don't
        # load the form for it but add it
        # back to feature_models and don't show the form.
        # This happens when redo button(add feature back) is
        # clicked after an undo button(remove feature)
        if feature_id in self.removed_feature_models.keys():
            self.feature_models[feature_id] = \
                self.removed_feature_models[feature_id]
            return
        # If the feature is not valid, geom_wkt will be None
        # So don't launch form for invalid feature and delete feature

        geom_wkt = self.get_wkt(spatial_column, feature_id)

        if geom_wkt is None:
            title = QApplication.translate(
                'STDMFieldWidget',
                u'Spatial Entity Form Error',
                None,
                QCoreApplication.UnicodeUTF8
            )
            msg = QApplication.translate(
                'STDMFieldWidget',
                u'The feature you have added is invalid. \n'
                'To fix this issue, check if the feature '
                'is digitized correctly.  \n'
                'Make sure you have added a base layer to digitize on.',
                None,
                QCoreApplication.UnicodeUTF8
            )
            # Message: Spatial column information
            # could not be found
            QMessageBox.critical(
                iface.mainWindow(),
                title,
                msg
            )
            return
        # init form
        feature_model, col_with_data = self.feature_to_model(feature_id)

        if col_with_data == 0:
            feature_model = None
        self.editor = EntityEditorDialog(
            self.entity,
            model=feature_model,
            parent=iface.mainWindow(),
            manage_documents=True,
            collect_model=True,
            plugin=self.plugin
        )
        self.model = self.editor.model()
        self.editor.addedModel.connect(self.on_form_saved)

        # get srid with EPSG text
        full_srid = self.layer.crs().authid().split(':')

        if len(full_srid) > 0:
            # Only extract the number
            srid = full_srid[1]
        if not geom_wkt is None:
            # add geometry into the model

            setattr(
                self.model,
                spatial_column,
                'SRID={};{}'.format(srid, geom_wkt)
            )

        # open editor
        result = self.editor.exec_()
        if result < 1:
            self.removed_feature_models[feature_id] = None
            self.layer.deleteFeature(feature_id)

    def get_wkt(self, spatial_column, feature_id):
        """
        Gets feature geometry in Well-Known Text
        format and returns it.
        :param spatial_column: The spatial column name.
        :type spatial_column: String
        :param feature_id: Feature id
        :type feature_id: Integer
        :return: Well-Known Text format of a geometry
        :rtype: WKT
        """
        geom_wkt = None
        fid = feature_id
        request = QgsFeatureRequest()
        request.setFilterFid(fid)
        features = self.layer.getFeatures(request)

        geom_col_obj = self.entity.columns[spatial_column]
        geom_type = geom_col_obj.geometry_type()

        # get the wkt of the geometry
        for feature in features:
            geometry = feature.geometry()
            if geometry.isGeosValid():
                if geom_type in ['MULTIPOLYGON', 'MULTILINESTRING']:
                    geometry.convertToMultiType()

                geom_wkt = geometry.exportToWkt()

        return geom_wkt

    def on_form_saved(self, model):
        """
        A slot raised when the save button is clicked
        in spatial unit form. It adds the feature model
        in feature_models ordered dictionary to be saved
        later.
        :param model: The model holding feature geometry
        and attributes obtained from the form
        :type model: SQLAlchemy Model
        :return: None
        :rtype: NoneType
        """
        if not model is None:
            self.feature_models[self.current_feature] = model
            if self.editor.is_valid:
                self.editor.accept()

    def on_feature_deleted(self, feature_id):
        """
        A slot raised when a feature is deleted
        in QGIS map canvas via the undo button.
        It deletes the associated model of the feature.
        :param feature_id: The id that is removed.
        :type feature_id: Integer
        :return: None
        :rtype: NoneType
        """
        if feature_id in self.feature_models.keys():
            self.removed_feature_models[feature_id] = \
                self.feature_models[feature_id]
            del self.feature_models[feature_id]

    def on_digitizing_saved(self):
        """
        A slot raised when the save button is clicked
        on Digitizing Toolbar of QGIS. It saves feature
        models created by the digitizer and STDM form to
        the Database.
        :return: None
        :rtype: NoneType
        """
        ent_model = entity_model(self.entity)
        entity_obj = ent_model()
        entity_obj.saveMany(
            self.feature_models.values()
        )
        for model in self.feature_models.values():
            STDMDb.instance().session.flush()

            for attrMapper in self.editor._attrMappers:
                control = attrMapper.valueHandler().control
                if isinstance(control, ExpressionLineEdit):
                    value = control.on_expression_triggered(model)
                    print attrMapper._attrName, value
                    setattr(model, attrMapper._attrName, value)
            model.update()

        # Save child models
        if self.editor is not None:
            self.editor.save_children()
        # undo each feature created so that qgis
        # don't try to save the same feature again.
        # It will also clear all the models from
        # self.feature_models as on_feature_deleted
        # is raised when a feature is removed.
        for f_id in self.feature_models.keys():
            iface.mainWindow().blockSignals(True)
            self.layer.deleteFeature(f_id)
            self.on_feature_deleted(f_id)
            iface.mainWindow().blockSignals(True)

        for i in range(len(self.feature_models)):
            self.layer.undoStack().undo()