Beispiel #1
0
class MapperMixin(object):
    '''
    Mixin class for use in a dialog or widget, and manages attribute mapping.
    '''
    def __init__(self, model, entity):
        '''
        :param model: Callable (new instances) or instance (existing instance
        for updating) of STDM model.
        '''
        if callable(model):
            self._model = model()
            self._mode = SAVE
        else:
            self._model = model
            self._mode = UPDATE
        self.entity = entity
        self._attrMappers = []
        self._attr_mapper_collection = {}
        self._dirtyTracker = ControlDirtyTrackerCollection()
        self._notifBar = None
        self.is_valid = False
        self.saved_model = None

        # Get document objects

        self.entity_model = entity_model(entity)

        self.entity_model_obj = self.entity_model()
        #Initialize notification bar
        if hasattr(self, "vlNotification"):
            self._notifBar = NotificationBar(self.vlNotification)

        #Flag to indicate whether to close the widget or dialog once model has been submitted
        #self.closeOnSubmit = True

    def addMapping(self,
                   attributeName,
                   control,
                   isMandatory=False,
                   pseudoname='',
                   valueHandler=None,
                   preloadfunc=None):
        '''
        Specify the mapping configuration.
        '''
        attrMapper = _AttributeMapper(attributeName, control, self._model,
                                      pseudoname, isMandatory, valueHandler)
        self.addMapper(attrMapper, preloadfunc)

    def addMapper(self, attributeMapper, preloadfunc=None):
        '''
        Add an attributeMapper object to the collection.
        Preloadfunc specifies a function that can be used to prepopulate the control's value only when
        the control is on SAVE mode.
        '''
        if self._mode == SAVE and preloadfunc != None:
            attributeMapper.valueHandler().setValue(preloadfunc)

        if self._mode == UPDATE:
            #Set control value based on the model attribute value
            attributeMapper.bindControl()

        #Add control to dirty tracker collection after control value has been set
        self._dirtyTracker.addControl(attributeMapper.control(),
                                      attributeMapper.valueHandler())

        self._attrMappers.append(attributeMapper)
        self._attr_mapper_collection[
            attributeMapper.attributeName()] = attributeMapper

    def saveMode(self):
        '''
        Return the mode that the mapper is currently configured in.
        '''
        return self._mode

    def is_update_mode(self):
        """
        :return: Returns True if the form is in UPDATE mode, otherwise False
        if in SAVE mode when creating a new record.
        :rtype: bool
        """
        if self._mode == UPDATE:
            return True

        return False

    def attribute_mapper(self, attribute_name):
        """
        Returns attribute mapper object corresponding to the the given
        attribute.
        :param attribute_name: Name of the attribute
        :type attribute_name: str
        :return: Attribute mapper
        :rtype: _AttributeMapper
        """
        return self._attr_mapper_collection.get(attribute_name, None)

    def setSaveMode(self, mode):
        '''
        Set the mapper's save mode.
        '''
        self._mode = mode

    def setModel(self, stdmModel):
        '''
        Set the model to be used by the mapper.
        '''
        self._model = stdmModel

    def model(self):
        '''
        Returns the model configured for the mapper.
        '''
        return self._model

    def setNotificationLayout(self, layout):
        '''
        Set the vertical layout instance that will be used to display
        notification messages.
        '''
        self._notifBar = NotificationBar(layout)

    def insertNotification(self, message, mtype):
        '''
        There has to be a vertical layout, named 'vlNotification', that
        enables for notifications to be inserted.
        '''
        if self._notifBar:
            self._notifBar.insertNotification(message, mtype)

    def clearNotifications(self):
        '''
        Clears all messages in the notification bar.
        '''
        if self._notifBar:
            self._notifBar.clear()

    def checkDirty(self):
        '''
        Asserts whether the dialog contains dirty controls.
        '''
        isDirty = False
        msgResponse = None

        if self._dirtyTracker.isDirty():
            isDirty = True
            msg = QApplication.translate(
                "MappedDialog",
                "Would you like to save changes before closing?")
            msgResponse = QMessageBox.information(
                self, QApplication.translate("MappedDialog", "Save Changes"),
                msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

        return isDirty, msgResponse

    def cancel(self):
        '''
        Slot for closing the dialog.
        Checks the dirty state first before closing.
        '''
        isDirty, userResponse = self.checkDirty()

        if isDirty:
            if userResponse == QMessageBox.Yes:
                self.submit()
            elif userResponse == QMessageBox.No:
                self.reject()
            elif userResponse == QMessageBox.Cancel:
                pass
        else:
            self.reject()

    def preSaveUpdate(self):
        '''
        Mixin classes can override this method to specify any operations that
        need to be executed prior to saving or updating the model's values.
        It should return True prior to saving.
        '''
        return True

    def postSaveUpdate(self, dbmodel):
        '''
        Executed once a record has been saved or updated. 
        '''
        self.saved_model = dbmodel
        self._post_save(self.saved_model)

    def _post_save(self, model):
        """
        Enables sub-classes to incorporate additional logic after form data
        has been saved.
        """
        pass

    def validate_all(self):
        """
        Validate the entire form.
        :return:
        :rtype:
        """
        errors = []
        for attrMapper in self._attrMappers:
            error = self.validate(attrMapper)

            if error is not None:
                self._notifBar.insertWarningNotification(error)
                errors.append(error)
        return errors

    def validate(self, attrMapper, update=False):
        """
        Validate attribute.
        :param attrMapper: The attribute
        :type attrMapper: _AttributeMapper
        :param update: Whether the validation is on update or new entry
        :type update: Boolean
        :return: List of error messages or None
        :rtype: list or NoneType
        """
        error = None

        field = attrMapper.pseudoName()
        column_name = attrMapper.attributeName()
        if column_name in self.entity.columns.keys():
            column = self.entity.columns[column_name]
        else:
            return

        if column.unique:
            column_obj = getattr(self.entity_model, column_name, None)
            if not update:
                result = self.entity_model_obj.queryObject().filter(
                    column_obj == attrMapper.valueHandler().value()).first()
            else:
                id_obj = getattr(self.entity_model, 'id', None)
                result = self.entity_model_obj.queryObject().filter(
                    column_obj == attrMapper.valueHandler().value()).filter(
                        id_obj != self.model().id).first()

            if result is not None:
                msg = QApplication.translate("MappedDialog",
                                             "field value should be unique.")
                error = u'{} {}'.format(field, msg)

        if column.mandatory:
            if attrMapper.valueHandler().value() == \
                    attrMapper.valueHandler().default():
                # Notify user
                msg = QApplication.translate("MappedDialog",
                                             "is a required field.")
                error = u'{} {}'.format(field, msg)

        return error

    def _custom_validate(self):
        # Sub-classes can implement custom validation logic.
        return True

    def submit(self, collect_model=False, save_and_new=False):
        """
        Slot for saving or updating the model.
        This will close the dialog on successful submission.
        :param collect_model: If set to True only returns
        the model without saving it to the database.
        :type collect_model: Boolean
        :param save_and_new: A Boolean indicating it is
        triggered by save and new button.
        :type save_and_new: Boolean
        """
        if not self.preSaveUpdate():
            return

        self.clearNotifications()
        self.is_valid = True

        # Validate mandatory fields have been entered by the user.
        errors = []

        for attrMapper in self._attrMappers:

            if self._mode == 'SAVE':
                error = self.validate(attrMapper)

            else:  # update mode
                error = self.validate(attrMapper, True)

            if error is not None:
                self._notifBar.insertWarningNotification(error)
                errors.append(error)

        if len(errors) > 0 or not self._custom_validate():
            self.is_valid = False

        if not self.is_valid:
            return

        # Bind model once all attributes are valid
        for attrMapper in self._attrMappers:
            control = attrMapper.valueHandler().control
            if isinstance(control, AutoGeneratedLineEdit):

                if control.column.prefix_source == control.column.columns_name:
                    if attrMapper.valueHandler().value() is None:
                        control.on_load_foreign_key_browser()

            attrMapper.set_model(self.model())
            attrMapper.bindModel()

        if not collect_model:
            self._persistModel(save_and_new)

    def _persistModel(self, save_and_new):
        """
        Saves the model to the database and shows a success message.
        :param save_and_new: A Boolean indicating it is triggered by save and
        new button.
        :type save_and_new: Boolean
        """
        try:
            # Persist the model to its corresponding store.
            if self._mode == SAVE:
                self._model.save()
                if not save_and_new:
                    QMessageBox.information(
                        self,
                        QApplication.translate("MappedDialog", "Record Saved"),
                        QApplication.translate(
                            "MappedDialog",
                            "New record has been successfully saved."))

            else:
                self._model.update()
                QMessageBox.information(
                    self,
                    QApplication.translate("MappedDialog", "Record Updated"),
                    QApplication.translate(
                        "MappedDialog",
                        "Record has been successfully updated."))

            STDMDb.instance().session.flush()

            for attrMapper in self._attrMappers:
                control = attrMapper.valueHandler().control
                if isinstance(control, ExpressionLineEdit):

                    value = control.on_expression_triggered()
                    print attrMapper._attrName, value
                    setattr(self.model(), attrMapper._attrName, value)
            self._model.update()
            # STDMDb.instance().session.flush()

        except Exception as ex:
            QMessageBox.critical(
                self,
                QApplication.translate("MappedDialog", "Data Operation Error"),
                QApplication.translate(
                    "MappedDialog", u'The data could not be saved due to '
                    u'the error: \n{}'.format(ex.args[0])))
            self.is_valid = False

        # Close the dialog
        if isinstance(self, QDialog) and self.is_valid:
            self.postSaveUpdate(self._model)
            if not save_and_new:
                self.accept()

    def clear(self):
        """
        Clears the form values.
        """
        for attrMapper in self._attrMappers:
            attrMapper.valueHandler().clear()
class TenureCustomAttributesEditor(QDialog, Ui_EntityAttributesEditor):
    """
    Dialog for editing an entity's attributes.
    """
    def __init__(self,
                 profile,
                 tenure_custom_entities,
                 parent=None,
                 editable=True,
                 exclude_columns=None):
        """
        Class constructor.
        :param profile: Profile object.
        :type profile: Profile
        :param tenure_custom_entities: Collection of tenure types and 
        corresponding custom attribute entities.
        :type tenure_custom_entities: OrderedDict
        :param proxy_name: Name of the entity which will be used to create 
        a proxy table.The attributes of the entity can then be copied to the 
        target entity which shares the same name as the proxy entity.
        :type proxy_name: str
        :param parent: Parent object.
        :type parent: QWidget
        :param editable: True if the attributes can be edited, otherwise 
        False.
        :type editable: bool
        :param exclude_columns: List of column names to exclude.
        :type exclude_columns: list
        """
        super(TenureCustomAttributesEditor, self).__init__(parent)
        self.setupUi(self)

        self._notifBar = NotificationBar(self.vlNotification)

        self._profile = profile
        self._exclude_names = exclude_columns
        if self._exclude_names is None:
            self._exclude_names = []

        # Attributes for each tenure type
        # Tenure type: list of attributes
        self._tenure_custom_entities = tenure_custom_entities

        # Populate attributes minus excluded columns
        self._tenure_custom_attrs = {}
        for tt, custom_ent in self._tenure_custom_entities.iteritems():
            attrs = custom_ent.columns.values()
            self._tenure_custom_attrs[tt] = [
                a for a in attrs if a.name not in self._exclude_names
            ]

        # Column types to omit
        self._exc_cols = ['GEOMETRY', 'FOREIGN_KEY']

        # Connect signals
        self.btnAddColumn.clicked.connect(self.on_new_column)
        self.btnEditColumn.clicked.connect(self.on_edit_column)
        self.btnDeleteColumn.clicked.connect(self.on_delete_column)

        self._editable = editable
        #if not editable:
        #self._disable_editing()

        # Update excluded columns
        self._update_excluded_columns()

        # Load tenure types
        self._load_tenure_types()

        # Connect tenure type signal
        self.cbo_tenure_type.currentIndexChanged.connect(
            self._on_tenure_type_changed)

        # Load attributes for current tenure type
        if self.cbo_tenure_type.count() > 0:
            c_tenure_type = self.cbo_tenure_type.currentText()
            self._tenure_custom_attrs_entity(c_tenure_type)

    @property
    def custom_tenure_attributes(self):
        """
        :return: Returns a collection containing the attributes for each 
        entity corresponding to the tenure types. Key is tenure type lookup 
        name, value is a list of tenure attributes.
        :rtype: dict(str, list)
        """
        return self._tenure_custom_attrs

    def exclude_column_names(self, names):
        """
        Set the list of columns to be excluded from the view only, not from 
        the collection.
        :param names: List containing column names that will be excluded 
        from the view. If there are already existing columns in the view 
        which are also in the list of excluded columns then they will be 
        removed from the view but reference will remain in the collection.
        :type names: list
        """
        self._exclude_names = names

    def _load_tenure_types(self):
        # Load tenure types
        t_types = self._tenure_custom_attrs.keys()

        if len(t_types) > 0:
            self.cbo_tenure_type.clear()

        for t_name in t_types:
            self.cbo_tenure_type.addItem(t_name)

    def _on_tenure_type_changed(self, idx):
        # Slot raised when the index of the tenure type combo changes
        if idx == -1:
            return

        t_type = self.cbo_tenure_type.itemText(idx)
        if t_type:
            self._tenure_custom_attrs_entity(t_type)

    def _tenure_custom_attrs_entity(self, tenure_type):
        # Loads the custom attributes entity. Creates if it does not exist.
        c_ent_attrs = self._tenure_custom_attrs.get(tenure_type, None)
        if c_ent_attrs is None:
            QMessageBox.critical(
                self, self.tr('Custom Tenure Attributes'),
                self.tr('Attributes entity is not available.'))

            return

        self.load_attributes(c_ent_attrs)

    def _update_excluded_columns(self):
        # Remove excluded columns.
        for n in self._exclude_names:
            self.tb_view.remove_item(n)

    def _get_current_entity(self):
        # Returns the custom attributes entity corresponding to the
        # currently selected tenure type.
        tenure_type = self.cbo_tenure_type.currentText()

        return self._tenure_custom_entities.get(tenure_type, None)

    def _get_attribute(self, tenure_type, name):
        # Get attribute by name and index otherwise None.
        attr, idx = None, -1
        attrs = self._tenure_custom_attrs.get(tenure_type, None)
        if attrs is None:
            return attr, idx

        for i, a in enumerate(attrs):
            if a.name == name:
                attr = a
                idx = i

                break

        return attr, idx

    #def _disable_editing(self):
    # Disable editing
    #self.btnAddColumn.setEnabled(False)
    #self.btnEditColumn.setEnabled(False)
    #self.btnDeleteColumn.setEnabled(False)
    #self.tb_view.setEnabled(False)
    #ok_btn = self.buttonBox.button(QDialogButtonBox.Ok)
    #ok_btn.setEnabled(False)

    def _column_editor_params(self):
        # Constructor params for column editor
        params = {}
        params['parent'] = self
        params['entity'] = self._get_current_entity()
        params['profile'] = self._profile

        return params

    def _on_column_added(self, column):
        # Slot raised when a new column has been added to the entity.
        if self._validate_excluded_column(column.name):
            self.add_column(column)

    def _get_tenure_type_attrs(self, tenure_type):
        # Returns a list of attributes matching to the given tenure type.
        # Creates an empty list if None is found.
        if not tenure_type in self._tenure_custom_attrs:
            self._tenure_custom_attrs[tenure_type] = []

        return self._tenure_custom_attrs[tenure_type]

    def add_column(self, column):
        """
        Adds a new column to the view.
        :param tenure_type: Name of tenure type lookup.
        :type tenure_type: str
        :param column: Column object.
        :type column: BaseColumn
        """
        tenure_type = self.cbo_tenure_type.currentText()

        attrs = self._get_tenure_type_attrs(tenure_type)

        # Check if the column is already in the list
        attr, idx = self._get_attribute(tenure_type, column.name)
        if idx == -1:
            attrs.append(column)

        self.tb_view.add_item(column)

    def edit_column(self, original_name, column):
        """
        Updates the edited column in the view.
        :param original_name: Original name of the column.
        :type original_name: str
        :param column: Column object.
        :type column: BaseColumn
        :return: Returns True if the operation succeeded, otherwise False 
        if the column does not exist.
        :rtype: bool
        """
        tenure_type = self.cbo_tenure_type.currentText()

        col, idx = self._get_attribute(tenure_type, original_name)
        if idx == -1:
            return False

        attrs = self._get_tenure_type_attrs(tenure_type)
        col = attrs.pop(idx)
        attrs.insert(idx, column)

        self.tb_view.update_item(original_name, column)

    def on_new_column(self):
        """
        Slot for showing New column dialog.
        """
        editor_params = self._column_editor_params()
        editor_params['is_new'] = True

        editor = ColumnEditor(**editor_params)
        editor.exclude_column_types(self._exc_cols)
        editor_title = self.tr('Create New Column')
        editor.setWindowTitle(editor_title)

        if editor.exec_() == QDialog.Accepted:
            # Do nothing since we are connecting to the signal
            self.add_column(editor.column)

    def selected_column(self):
        """
        :return: Returns the selected column object in the view otherwise 
        None if there is no row selected.
        :rtype: BaseColumn
        """
        tenure_type = self.cbo_tenure_type.currentText()

        sel_col_name = self.tb_view.selected_column()
        if not sel_col_name:
            return None

        col, idx = self._get_attribute(tenure_type, sel_col_name)
        if idx == -1:
            return None

        return col

    def on_edit_column(self):
        """
        Slot for showing a dialog for editing the selected column.
        """
        self._notifBar.clear()

        sel_col = self.selected_column()
        if sel_col is None:
            msg = self.tr('Please select a column to edit.')
            self._notifBar.insertWarningNotification(msg)

            return

        original_name = sel_col.name

        editor_params = self._column_editor_params()
        editor_params['column'] = sel_col
        editor_params['is_new'] = False
        editor_params['in_db'] = not self._editable

        editor = ColumnEditor(**editor_params)
        editor.exclude_column_types(self._exc_cols)
        editor_title = self.tr('Edit Column')
        editor.setWindowTitle(editor_title)

        if editor.exec_() == QDialog.Accepted:
            if self._validate_excluded_column(editor.column.name):
                self.edit_column(original_name, editor.column)

    def _validate_excluded_column(self, name):
        # Check name against list of excluded names
        if name in self._exclude_names:
            msg = self.tr(
                'column has been defined as an excluded column, it will not '
                'be added.')
            msg = '\'{0}\' {1}'.format(name, msg)
            QMessageBox.critical(self, self.tr('Excluded column'), msg)

            return False

        return True

    def on_delete_column(self):
        """
        Slot for deleting the selected column.
        """
        sel_col = self.selected_column()
        if sel_col is None:
            self._notifBar.clear()
            msg = self.tr('Please select a column to delete.')
            self._notifBar.insertWarningNotification(msg)

            return

        msg = self.tr(
            'Are you sure you want to permanently delete the attribute?')
        result = QMessageBox.warning(self, self.tr('Delete Attribute'), msg,
                                     QMessageBox.Yes | QMessageBox.No)

        if result == QMessageBox.Yes:
            self.delete_column(sel_col.name)

    def delete_column(self, name):
        """
        Removes the column with the given name from the view.
        :param name: Column name.
        :type name: str
        :return: Return True if the column was successfully deleted, 
        otherwise False if the column does not exist.
        :rtype: bool
        """
        tenure_type = self.cbo_tenure_type.currentText()
        attr, idx = self._get_attribute(tenure_type, name)
        if idx == -1:
            return False

        attrs = self._get_tenure_type_attrs(tenure_type)
        col = attrs.pop(idx)

        return self.tb_view.remove_item(attr.name)

    def load_attributes_from_entity(self, entity):
        """
        Loads the view with the attributes of the specified entity. Any 
        previous attributes will be removed.
        :param entity: Entity whose attributes will be loaded.
        :type entity: Entity
        """
        self.load_attributes(entity.columns)

    def load_attributes(self, attributes):
        """
        Loads the collection of attibutes to the view. Any previous 
        attributes will be removed. The collection should not be empty.
        :param attributes: Collection of attributes to be loaded to te view.
        :type attributes: OrderedDict
        """
        # Clear view
        self.tb_view.clear_view()

        if len(attributes) > 0:
            for a in attributes:
                self.add_column(a)

            # Refresh excluded columns
            self._update_excluded_columns()
Beispiel #3
0
class LookupDialog(QDialog, Ui_LookupTranslatorDialog, TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the lookup translation
    implementation.
    """
    def __init__(self, parent, source_cols, dest_table, dest_col, src_col):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col,
                                      src_col)

        self._notif_bar = NotificationBar(self.vl_notification)

        # Populate controls
        self._load_lookup_tables()

        # Connect signals
        self.cbo_lookup.currentIndexChanged.connect(
            self._on_lookup_table_name_changed)

    def _load_lookup_tables(self):
        # Load lookup table names
        c_profile = self._current_profile
        if c_profile is None:
            msg = QApplication.translate(
                'LookupDialog', 'Current profile could not be determined.')
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return

        lookups = [e.name for e in c_profile.value_lists()]

        self.cbo_lookup.clear()
        self.cbo_lookup.addItem('')
        self.cbo_lookup.addItems(lookups)

    def _on_lookup_table_name_changed(self, idx):
        # Slot raised when the lookup table name changes.
        self.cbo_default.clear()

        if idx == -1:
            return

        t_name = self.cbo_lookup.currentText()
        self.load_lookup_values(t_name)

    def load_lookup_values(self, table_name):
        """
        Load the default value combobox with values from the specified lookup
        table name.
        :param table_name: Lookup table name.
        :type table_name: str
        """
        self.cbo_default.clear()

        if not table_name:
            return

        # Get lookup entity
        lk_ent = self._current_profile.entity_by_name(table_name)
        if lk_ent is None:
            msg = QApplication.translate('LookupDialog',
                                         'Lookup values could not be loaded.')
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return

        lk_values = lk_ent.lookups()

        self.cbo_default.addItem('')

        for lk_value in lk_values:
            text_value = None
            for k, v in lk_ent.values.items():
                if v.value == lk_value:
                    text_value = v.value
            if text_value is not None:
                self.cbo_default.addItem(text_value)

    def value_translator(self):
        """
        :return: Returns the lookup value translator object.
        :rtype: LookupValueTranslator
        """
        lookup_translator = LookupValueTranslator()
        lookup_translator.set_referencing_table(self._dest_table)
        lookup_translator.set_referencing_column(self._dest_col)
        lookup_translator.set_referenced_table(self.cbo_lookup.currentText())
        lookup_translator.add_source_reference_column(self._src_col,
                                                      self._dest_col)
        lookup_translator.default_value = self.cbo_default.currentText()

        return lookup_translator

    def validate(self):
        """
        Check user configuration and validate if they are correct.
        :return: Returns True if user configuration is correct, otherwise
        False.
        :rtype: bool
        """
        if not self.cbo_lookup.currentText():
            msg = QApplication.translate(
                'LookupDialog', 'Please select the referenced lookup table.')
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(LookupDialog, self).accept()
Beispiel #4
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
Beispiel #5
0
class MapperMixin(object):
    '''
    Mixin class for use in a dialog or widget, and manages attribute mapping.
    '''
    def __init__(self, model):
        '''
        :param model: Callable (new instances) or instance (existing instance
        for updating) of STDM model.
        '''
        if callable(model):
            self._model = model()
            self._mode = SAVE
        else:
            self._model = model
            self._mode = UPDATE
        self._attrMappers = []
        self._attr_mapper_collection = {}
        self._dirtyTracker = ControlDirtyTrackerCollection()
        self._notifBar = None
        self.is_valid = False

        #Initialize notification bar
        if hasattr(self, "vlNotification"):
            self._notifBar = NotificationBar(self.vlNotification)

        #Flag to indicate whether to close the widget or dialog once model has been submitted
        #self.closeOnSubmit = True

    def addMapping(self,
                   attributeName,
                   control,
                   isMandatory=False,
                   pseudoname='',
                   valueHandler=None,
                   preloadfunc=None):
        '''
        Specify the mapping configuration.
        '''
        attrMapper = _AttributeMapper(attributeName, control, self._model,
                                      pseudoname, isMandatory, valueHandler)
        self.addMapper(attrMapper, preloadfunc)

    def addMapper(self, attributeMapper, preloadfunc=None):
        '''
        Add an attributeMapper object to the collection.
        Preloadfunc specifies a function that can be used to prepopulate the control's value only when
        the control is on SAVE mode.
        '''
        if self._mode == SAVE and preloadfunc != None:
            attributeMapper.valueHandler().setValue(preloadfunc)

        if self._mode == UPDATE:
            #Set control value based on the model attribute value
            attributeMapper.bindControl()

        #Add control to dirty tracker collection after control value has been set
        self._dirtyTracker.addControl(attributeMapper.control(),
                                      attributeMapper.valueHandler())

        self._attrMappers.append(attributeMapper)
        self._attr_mapper_collection[
            attributeMapper.attributeName()] = attributeMapper

    def saveMode(self):
        '''
        Return the mode that the mapper is currently configured in.
        '''
        return self._mode

    def attribute_mapper(self, attribute_name):
        """
        Returns attribute mapper object corresponding to the the given
        attribute.
        :param attribute_name: Name of the attribute
        :type attribute_name: str
        :return: Attribute mapper
        :rtype: _AttributeMapper
        """
        return self._attr_mapper_collection.get(attribute_name, None)

    def setSaveMode(self, mode):
        '''
        Set the mapper's save mode.
        '''
        self._mode = mode

    def setModel(self, stdmModel):
        '''
        Set the model to be used by the mapper.
        '''
        self._model = stdmModel

    def model(self):
        '''
        Returns the model configured for the mapper.
        '''
        return self._model

    def setNotificationLayout(self, layout):
        '''
        Set the vertical layout instance that will be used to display
        notification messages.
        '''
        self._notifBar = NotificationBar(layout)

    def insertNotification(self, message, mtype):
        '''
        There has to be a vertical layout, named 'vlNotification', that
        enables for notifications to be inserted.
        '''
        if self._notifBar:
            self._notifBar.insertNotification(message, mtype)

    def clearNotifications(self):
        '''
        Clears all messages in the notification bar.
        '''
        if self._notifBar:
            self._notifBar.clear()

    def checkDirty(self):
        '''
        Asserts whether the dialog contains dirty controls.
        '''
        isDirty = False
        msgResponse = None

        if self._dirtyTracker.isDirty():
            isDirty = True
            msg = QApplication.translate(
                "MappedDialog",
                "Would you like to save changes before closing?")
            msgResponse = QMessageBox.information(
                self, QApplication.translate("MappedDialog", "Save Changes"),
                msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

        return isDirty, msgResponse

    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.
        '''
        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 cancel(self):
        '''
        Slot for closing the dialog.
        Checks the dirty state first before closing.
        '''
        isDirty, userResponse = self.checkDirty()

        if isDirty:
            if userResponse == QMessageBox.Yes:
                self.submit()
            elif userResponse == QMessageBox.No:
                self.reject()
            elif userResponse == QMessageBox.Cancel:
                pass
        else:
            self.reject()

    def preSaveUpdate(self):
        '''
        Mixin classes can override this method to specify any operations that
        need to be executed prior to saving or updating the model's values.
        It should return True prior to saving.
        '''
        return True

    def postSaveUpdate(self, dbmodel):
        '''
        Executed once a record has been saved or updated. 
        '''
        pass

    def submit(self, collect_model=False, save_and_new=False):
        """
        Slot for saving or updating the model.
        This will close the dialog on successful submission.
        :param collect_model: If set to True only returns
        the model without saving it to the database.
        :type collect_model: Boolean
        :param save_and_new: A Boolean indicating it is
        triggered by save and new button.
        :type save_and_new: Boolean
        """
        if not self.preSaveUpdate():
            return

        self.clearNotifications()
        self.is_valid = True

        # Validate mandatory fields have been entered by the user.
        for attrMapper in self._attrMappers:

            if attrMapper.isMandatory() and \
                    attrMapper.valueHandler().supportsMandatory():
                if attrMapper.valueHandler().value() == \
                        attrMapper.valueHandler().default():
                    #Notify user
                    msg = QApplication.translate("MappedDialog",
                                                 "'%s' is a required field.")\
                          %unicode(attrMapper.pseudoName())
                    self._notifBar.insertWarningNotification(msg)
                    self.is_valid = False

        if not self.is_valid:
            return

        # Bind model once all attributes are valid
        for attrMapper in self._attrMappers:
            attrMapper.set_model(self.model())
            attrMapper.bindModel()

        if not collect_model:
            self._persistModel(save_and_new)

    def _persistModel(self, save_and_new):
        """
        Saves the model to the database and shows a success message.
        :param save_and_new: A Boolean indicating it is triggered by save and
        new button.
        :type save_and_new: Boolean
        """
        try:
            # Persist the model to its corresponding store.
            if self._mode == SAVE:
                self._model.save()
                if not save_and_new:
                    QMessageBox.information(
                        self,
                        QApplication.translate("MappedDialog", "Record Saved"),
                        QApplication.translate(
                            "MappedDialog",
                            "New record has been successfully saved."))

            else:
                self._model.update()
                QMessageBox.information(
                    self,
                    QApplication.translate("MappedDialog", "Record Updated"),
                    QApplication.translate(
                        "MappedDialog",
                        "Record has been successfully updated."))

        except Exception as ex:
            QMessageBox.critical(
                self,
                QApplication.translate("MappedDialog", "Data Operation Error"),
                QApplication.translate(
                    "MappedDialog", u'The data could not be saved due to '
                    u'the error: \n{}'.format(ex.args[0])))
            self.is_valid = False

        # Close the dialog
        if isinstance(self, QDialog) and self.is_valid:
            self.postSaveUpdate(self._model)
            if not save_and_new:
                self.accept()

    def clear(self):
        """
        Clears the form values.
        """
        for attrMapper in self._attrMappers:
            attrMapper.valueHandler().clear()
Beispiel #6
0
class RelatedTableDialog(QDialog, Ui_RelatedTableTranslatorDialog,
                         TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the
    RelatedTableTranslator class implementation.
    """
    def __init__(self, parent, source_cols, dest_table, dest_col, src_col):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col,
                                      src_col)

        self._notif_bar = NotificationBar(self.vl_notification)

        self._set_source_table_headers()

        #Set UI values
        self.txt_table_name.setText(dest_table)
        self.txt_column_name.setText(dest_col)

        #Load STDM tables exluding views
        self._load_tables()

        #Connect signals
        self.cbo_source_tables.currentIndexChanged.connect(
            self._on_source_table_changed)

    def _load_tables(self):
        """
        Loads both textual and spatial tables into the user list.
        """
        self.cbo_source_tables.addItem("")
        self.cbo_source_tables.addItems(self.db_tables())

    def value_translator(self):
        rel_tab_translator = RelatedTableTranslator()
        rel_tab_translator.set_referencing_table(self.txt_table_name.text())
        rel_tab_translator.set_referencing_column(self.txt_column_name.text())
        rel_tab_translator.set_referenced_table(
            self.cbo_source_tables.currentText())
        rel_tab_translator.set_output_reference_column(
            self.cbo_output_column.currentText())
        rel_tab_translator.set_input_referenced_columns(self.column_pairings())

        return rel_tab_translator

    def _set_source_table_headers(self):
        labels = [
            QApplication.translate("RelatedTableDialog", "Source Table"),
            QApplication.translate("RelatedTableDialog", "Referenced Table")
        ]
        self.tb_source_trans_cols.set_header_labels(labels)

    def _on_source_table_changed(self, index):
        source_table = self.cbo_source_tables.currentText()

        self.cbo_output_column.clear()

        if source_table:
            ref_table_cols = table_column_names(source_table)

            self.tb_source_trans_cols.set_combo_selection(
                [self._source_cols, ref_table_cols])

            #self.cbo_output_column.addItem("")
            self.cbo_output_column.addItems(ref_table_cols)

        else:
            self.tb_source_trans_cols.clear_view()

    def column_pairings(self):
        """
        :return: Source and reference table column matchings.
        :rtype: dict
        """
        return self.tb_source_trans_cols.column_pairings()

    def validate(self):
        """
        :return: Check user entries.
        :rtype: bool
        """
        if self.cbo_source_tables.currentText() == "":
            msg = QApplication.translate(
                "RelatedTableDialog", "Please select "
                "the reference table name.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        if self.cbo_output_column.currentText() == "":
            msg = QApplication.translate(
                "RelatedTableDialog", "Please select "
                "the output column name.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        if len(self.column_pairings()) == 0:
            msg = QApplication.translate(
                "RelatedTableDialog", "Please specify "
                "at least one column pairing.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(RelatedTableDialog, self).accept()
Beispiel #7
0
class AdminUnitManager(WIDGET, BASE):
    '''
    Administrative Unit Manager Widget
    '''
    # Signal raised when the state (view/manage) of the widet changes.
    stateChanged = pyqtSignal('bool')

    def __init__(self, parent=None, State=VIEW):
        QWidget.__init__(self, parent)
        self.setupUi(self)

        self.btnRemove.setIcon(GuiUtils.get_icon('remove.png'))
        self.btnClear.setIcon(GuiUtils.get_icon('reset.png'))
        self.btnAdd.setIcon(GuiUtils.get_icon('add.png'))

        self._defaultEditTriggers = self.tvAdminUnits.editTriggers()

        self._state = State
        self._onStateChange()

        self._notifBar = NotificationBar(self.vlNotification)

        # Configure validating line edit controls
        # invalidMsg = "{} already exists."
        # self.txtUnitCode.setModelAttr(AdminSpatialUnitSet,"Code")
        # self.txtUnitCode.setInvalidMessage(invalidMsg)
        # self.txtUnitCode.setNotificationBar(self._notifBar)
        '''
        Initialize formatter for the rendering the admin unit nodes and insert
        the root node into the tree view model.
        '''
        self._adminUnitNodeFormatter = AdminUnitFormatter(
            self.tvAdminUnits, self)
        self._rtNode = self._adminUnitNodeFormatter.rootNode

        self._adminUnitTreeModel = STRTreeViewModel(
            self._adminUnitNodeFormatter.root(), view=self.tvAdminUnits)
        self.tvAdminUnits.setModel(self._adminUnitTreeModel)
        self.tvAdminUnits.hideColumn(2)
        self.tvAdminUnits.setColumnWidth(0, 220)
        self.tvAdminUnits.expandAll()
        # Connects slots
        self.btnAdd.clicked.connect(self.onCreateAdminUnit)
        self.btnClear.clicked.connect(self.onClearSelection)
        self.btnRemove.clicked.connect(self.onDeleteSelection)
        self._adminUnitTreeModel.dataChanged.connect(self.onModelDataChanged)

    def model(self):
        """
        :return: Returns the model associated with the administrative unit
        view.
        :rtype: STRTreeViewModel
        """
        return self._adminUnitTreeModel

    def selection_model(self):
        """
        :return: Returns the selection model associated with the
        administrative unit tree view.
        :rtype: QItemSelectionModel
        """
        return self.tvAdminUnits.selectionModel()

    def state(self):
        '''
        Returns the current state that the widget has been configured in.
        '''
        return self._state

    def setState(self, state):
        '''
        Set the state of the widget.
        '''
        self._state = state
        self._onStateChange()

    def notificationBar(self):
        '''
        Returns the application notification widget.
        '''
        return self._notifBar

    def _onStateChange(self):
        '''
        Configure controls upon changing the state of the widget.
        '''
        manageControls = False if self._state == VIEW else True

        self.btnRemove.setVisible(manageControls)
        self.btnClear.setVisible(manageControls)
        self.gbManage.setVisible(manageControls)

        if manageControls:
            self.tvAdminUnits.setEditTriggers(self._defaultEditTriggers)

        else:
            self.tvAdminUnits.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.stateChanged.emit(manageControls)

    def onCreateAdminUnit(self):
        '''
        Slot raised on clicking to add a new administrative unit.
        '''
        self._notifBar.clear()

        if self.txtUnitName.text() == "":
            msg = QApplication.translate(
                "AdminUnitManager",
                "Name of the administrative unit cannot be empty.")
            self._notifBar.insertErrorNotification(msg)
            self.txtUnitName.setFocus()
            return

        if not self.txtUnitName.validate():
            return

        if self.txtUnitCode.text() == "":
            msg = QApplication.translate(
                "AdminUnitManager",
                "Code of the administrative unit cannot be empty.")
            self._notifBar.insertErrorNotification(msg)
            self.txtUnitCode.setFocus()
            return

        # if not self.txtUnitCode.validate():
        #     return

        # Get current row selection
        selIndexes = self.tvAdminUnits.selectionModel().selectedRows(0)

        if len(selIndexes) == 0:
            # Get the number of items in the tree view
            rootIndex = self.tvAdminUnits.rootIndex()
            rowCount = self._adminUnitTreeModel.rowCount(rootIndex)

            if rowCount > 0:
                msg = QApplication.translate("AdminUnitManager",
                                             "You have not selected any parent node for the new administrative unit. Do " \
                                             "you want to add it as one of the topmost administrative units?\nClick Yes to " \
                                             "proceed or No to cancel.")
                selOption = QMessageBox.warning(
                    self,
                    QApplication.translate("AdminUnitManager",
                                           "No Parent Node Selected"), msg,
                    QMessageBox.Yes | QMessageBox.No)

                if selOption == QMessageBox.Yes:
                    parentNode = self._rtNode
                    # We are interested in the model index of the root node
                    parentModelIndex = rootIndex
                    parentModel = None

                else:
                    return

            # Do not prompt user and immediately add the administrative unit to the root node.
            else:
                parentNode = self._rtNode
                parentModelIndex = rootIndex
                parentModel = None

        else:
            # Get model index for the first column as this is where the new node will be added as the child
            parentModelIndex = selIndexes[0]
            parentNode = self._adminUnitTreeModel._getNode(parentModelIndex)

            parentID = parentNode.data(2)
            ausModel = AdminSpatialUnitSet()
            parentModel = ausModel.queryObject().filter(
                AdminSpatialUnitSet.id == parentID).first()

        adminUnitModel = AdminSpatialUnitSet(self.txtUnitName.text(),
                                             self.txtUnitCode.text(),
                                             parentModel)

        # Commit transaction to the database
        adminUnitModel.save()

        # Extract properties from the model
        ausProps = self._adminUnitNodeFormatter._extractAdminUnitSetInfo(
            adminUnitModel)

        childNode = BaseSTRNode(ausProps, parentNode)

        # Insert row into the view
        self._adminUnitTreeModel.insertRows(parentNode.childCount(), 1,
                                            parentModelIndex)

        self.clearInputs()

    def onClearSelection(self):
        '''
        Slot that removes any existing selections in the tree view.
        '''
        self.tvAdminUnits.selectionModel().clearSelection()

    def onModelDataChanged(self, oldindex, newindex):
        '''
        Slot raised when the model data is changed.
        '''
        # Get model index containing ID property
        refNode = self._adminUnitTreeModel._getNode(newindex)
        ausID = refNode.data(2)

        ausHandler = AdminSpatialUnitSet()
        ausObj = ausHandler.queryObject().filter(
            AdminSpatialUnitSet.id == ausID).first()

        if ausObj != None:
            attrColumn = newindex.column()
            if attrColumn == 0:
                ausObj.Name = refNode.data(0)
            elif attrColumn == 1:
                ausObj.Code = refNode.data(1)

            ausObj.update()

    def onDeleteSelection(self):
        '''
        Slot raised to delete current selection of administrative unit.
        '''
        self._notifBar.clear()
        # Get current row selection
        selIndexes = self.tvAdminUnits.selectionModel().selectedRows(2)

        if len(selIndexes) == 0:
            msg = QApplication.translate(
                "AdminUnitManager",
                "Please select the administrative unit to delete.")
            self._notifBar.insertWarningNotification(msg)

        else:
            delmsg = QApplication.translate("AdminUnitManager",
                                            "This action will delete the selected administrative unit plus any " \
                                            "existing children under it. It cannot be undone.\nClick Yes to " \
                                            "delete or No to cancel.")
            selOption = QMessageBox.warning(
                self,
                QApplication.translate("AdminUnitManager", "Confirm deletion"),
                delmsg, QMessageBox.Yes | QMessageBox.No)

            if selOption == QMessageBox.Yes:
                # Get the node in the current selection
                delIndex = selIndexes[0]
                ausNode = self._adminUnitTreeModel._getNode(delIndex)
                ausId = ausNode.data(2)
                ausHandler = AdminSpatialUnitSet()
                ausObj = ausHandler.queryObject().filter(
                    AdminSpatialUnitSet.id == ausId).first()

                if not ausObj is None:
                    ausObj.delete()

                    # Remove item in tree view
                    self._adminUnitTreeModel.removeRows(
                        delIndex.row(), 1, delIndex.parent())

                    # Notify user
                    self._notifBar.clear()
                    successmsg = QApplication.translate(
                        "AdminUnitManager",
                        "Administrative unit successfully deleted.")
                    self._notifBar.insertSuccessNotification(successmsg)

    def selectedAdministrativeUnit(self):
        '''
        Returns the selected administrative unit object otherwise, if there is no
        selection then it returns None.
        '''
        selIndexes = self.tvAdminUnits.selectionModel().selectedRows(2)

        if len(selIndexes) == 0:
            selAdminUnit = None

        else:
            selIndex = selIndexes[0]
            ausNode = self._adminUnitTreeModel._getNode(selIndex)
            ausId = ausNode.data(2)
            ausHandler = AdminSpatialUnitSet()
            selAdminUnit = ausHandler.queryObject().filter(
                AdminSpatialUnitSet.id == ausId).first()

        return selAdminUnit

    def clearInputs(self):
        '''
        Clears the input controls.
        '''
        self.txtUnitCode.clear()
        self.txtUnitName.clear()
Beispiel #8
0
class LookupValueSelector(WIDGET, BASE):
    """
    A dialog that enables to select a value and code from a lookup.
    .. versionadded:: 1.5
    """
    def __init__(self, parent, lookup_entity_name, profile=None):
        """
        Initializes LookupValueSelector.
        :param parent: The parent of the dialog.
        :type parent: QWidget
        :param lookup_entity_name: The lookup entity name
        :type lookup_entity_name: String
        :param profile: The current profile object
        :type profile: Object
        """
        QDialog.__init__(self, parent,
                         Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        self.setupUi(self)
        self.value_and_code = None
        if profile is None:
            self._profile = current_profile()
        else:
            self._profile = profile

        self.lookup_entity = self._profile.entity_by_name('{}_{}'.format(
            self._profile.prefix, lookup_entity_name))

        self.notice = NotificationBar(self.notice_bar)
        self._view_model = QStandardItemModel()
        self.value_list_box.setModel(self._view_model)
        header_item = QStandardItem(lookup_entity_name)
        self._view_model.setHorizontalHeaderItem(0, header_item)
        self.populate_value_list_view()

        self.selected_code = None
        self.selected_value_code = None

        self.value_list_box.clicked.connect(self.validate_selected_code)

    def populate_value_list_view(self):
        """
        Populates the lookup values and codes.
        """
        self.value_and_code = self.lookup_entity.values

        for value, code in self.value_and_code.items():
            u_value = str(value)
            code_value = self.lookup_entity.values[u_value]

            value_code = QStandardItem('{} ({})'.format(
                code_value.value, code.code))
            value_code.setData(code.code)
            self._view_model.appendRow(value_code)

    def validate_selected_code(self):
        """
        Validate the selected code for the presence of Code or not.
        """
        self.notice.clear()
        self.selected_code_value()
        if self.selected_code == '':
            notice = QApplication.tr(self, 'The selected value has no code.')
            self.notice.insertWarningNotification(notice)

    def selected_code_value(self):
        """
        Get the selected lookup value.
        """
        index = self.value_list_box.currentIndex()
        item = self._view_model.itemFromIndex(index)
        self.selected_code = item.data()
        self.selected_value_code = item.text()

    def accept(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code_value()
        self.done(1)

    def reject(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code = None
        self.selected_value_code = None
        self.done(0)
Beispiel #9
0
class DirDocumentTypeSelector(QDialog):
    """
    Dialog for selecting supporting documents from a given directory. Default
    filter searches for PDF files only.
    """
    def __init__(self, dir, doc_types, parent=None, filters=None):
        super(DirDocumentTypeSelector, self).__init__(parent)
        self.setWindowTitle(
            self.tr('Documents in Folder')
        )
        self._filters = filters
        # Use PDF as default filter
        if not self._filters:
            self._filters = ['*.pdf']

        self._init_ui()
        self._dir = QDir(dir)
        self._dir.setNameFilters(self._filters)
        self._doc_types = doc_types

        self._attr_model = QStandardItemModel(self)
        self._sel_doc_types = OrderedDict()

        # Notification bar
        self._notif_bar = NotificationBar(self.vl_notif)

        self.resize(320, 350)

        # Load documents
        self.load_document_types()

    @property
    def selected_document_types(self):
        """
        :return: Returns a dictionary of the document types and the
        corresponding file paths as selected by the user.
        :rtype: dict
        """
        return self._sel_doc_types

    def _init_ui(self):
        # Draw UI widgets
        layout = QVBoxLayout()

        # Add layout for notification bar
        self.vl_notif = QVBoxLayout()
        layout.addLayout(self.vl_notif)
        self.lbl_info = QLabel()
        self.lbl_info.setObjectName('lbl_info')
        self.lbl_info.setText(self.tr(
            'The selected document types have been found in the directory, '
            'check/uncheck to specify which ones to upload.'
        ))
        self.lbl_info.setWordWrap(True)
        layout.addWidget(self.lbl_info)
        self.lst_docs = QListView()
        layout.addWidget(self.lst_docs)
        self.lbl_warning = QLabel()
        self.lbl_warning.setTextFormat(Qt.RichText)
        self.lbl_warning.setText(self.tr(
            '<html><head/><body><p><span style=" font-style:italic;">'
            '* Previously uploaded documents will be replaced.</span></p>'
            '</body></html>'
        ))
        self.lbl_warning.setWordWrap(True)
        layout.addWidget(self.lbl_warning)
        self.btn_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        layout.addWidget(self.btn_box)
        self.setLayout(layout)

        # Connect signals
        self.btn_box.accepted.connect(
            self.set_selected_document_types
        )
        self.btn_box.rejected.connect(
            self.reject
        )

    def set_selected_document_types(self):
        """
        Sets the collections of accepted document types and their
        corresponding file paths and accepts the dialog.
        """
        self._sel_doc_types = OrderedDict()
        for i in range(self._attr_model.rowCount()):
            doc_type_item = self._attr_model.item(i, 0)

            if doc_type_item.checkState() == Qt.Checked:
                path_item = self._attr_model.item(i, 1)
                self._sel_doc_types[doc_type_item.text()] = path_item.text()

        if len(self._sel_doc_types) == 0:
            self._notif_bar.clear()
            msg = self.tr('No matching documents found or selected.')
            self._notif_bar.insertWarningNotification(msg)

            return

        self.accept()

    def load_document_types(self):
        """
        Load all document types to the list view and enable/check the items
        for those types that have been found.
        """
        self._attr_model.clear()
        self._attr_model.setColumnCount(2)

        file_infos = self._dir.entryInfoList(
            QDir.Readable | QDir.Files,
            QDir.Name
        )

        # Index file info based on name
        idx_file_infos = {fi.completeBaseName().lower(): fi for fi in file_infos}

        for d in self._doc_types:
            doc_type_item = QStandardItem(d)
            doc_type_item.setCheckable(True)
            path_item = QStandardItem()

            item_enabled = False
            check_state = Qt.Unchecked
            dl = d.lower()
            if dl in idx_file_infos:
                item_enabled = True
                check_state = Qt.Checked
                path = idx_file_infos[dl].filePath()
                path_item.setText(path)
                doc_type_item.setToolTip(path)

            doc_type_item.setEnabled(item_enabled)
            doc_type_item.setCheckState(check_state)

            self._attr_model.appendRow([doc_type_item, path_item])

        self.lst_docs.setModel(self._attr_model)
Beispiel #10
0
class FltsSearchWidget(QWidget, Ui_FltsSearchWidget):
    """
    Widget that provides an interface for searching data from a given data
    source specified in the search configuration.
    """
    def __init__(self, search_config):
        """
        :param search_config: Search configuration object.
        :type search_config: FltsSearchConfiguration
        """
        super(FltsSearchWidget, self).__init__(None)
        self.setupUi(self)
        self.notif_bar = NotificationBar(self.vlNotification)
        self._config = search_config
        self._ds_mgr = FltsSearchConfigDataSourceManager(self._config)

        # Sort dialog and mapping
        self._sort_dialog = None
        self._sort_map = None

        # Check validity
        self._check_validity()
        if not self._ds_mgr.is_valid:
            return

        # Initialize UI
        self._init_gui()

    def _enable_controls(self, enable):
        # Enables or disables UI controls
        self.cbo_column.setEnabled(enable)
        self.cbo_expression.setEnabled(enable)
        self.txt_keyword.setEnabled(enable)
        self.btn_search.setEnabled(enable)
        self.btn_advanced_search.setEnabled(enable)
        self.btn_clear.setEnabled(enable)
        self.tb_results.setEnabled(enable)
        self.btn_sort.setEnabled(enable)

    def _check_validity(self):
        # Notify is the data source is invalid.
        if not self._ds_mgr.is_valid:
            self._enable_controls(False)
            self.notif_bar.insertErrorNotification(
                u'\'{0}\' data source does not exist in the database.'.format(
                    self._config.data_source))

    def _init_gui(self):
        # Connect signals
        self.btn_search.clicked.connect(self.on_basic_search)
        self.cbo_column.currentIndexChanged.connect(
            self._on_filter_col_changed)
        self.btn_clear.clicked.connect(self.clear_results)
        self.btn_advanced_search.clicked.connect(self.on_advanced_search)
        self.btn_sort.clicked.connect(self.on_sort_columns)
        self.txt_keyword.returnPressed.connect(self.on_basic_search)

        # Set filter columns
        self.cbo_column.clear()
        col_ico = QIcon(':/plugins/stdm/images/icons/column.png')
        for col, disp_col in self._ds_mgr.filter_column_mapping.iteritems():
            self.cbo_column.addItem(col_ico, disp_col, col)

        # Set model
        self._res_model = SearchResultsModel(self._ds_mgr)
        self.tb_results.setModel(self._res_model)
        self.tb_results.hideColumn(0)

        # Connect to item selection changed signal
        selection_model = self.tb_results.selectionModel()
        selection_model.selectionChanged.connect(self.on_selection_changed)

        self.txt_keyword.setFocus()

    def _on_filter_col_changed(self, idx):
        # Set the valid expressions based on the type of the filter column.
        self.cbo_expression.clear()
        self.txt_keyword.clearValue()
        if idx == -1:
            return

        filter_col = self.cbo_column.itemData(idx)
        filter_exp = self._ds_mgr.column_type_expression(filter_col)
        exp_ico = QIcon(':/plugins/stdm/images/icons/math_operators.png')
        for disp, exp in filter_exp.iteritems():
            self.cbo_expression.addItem(exp_ico, disp, exp)

        # Update the search completer
        self._set_search_completer()

    def _set_search_completer(self):
        # Set the completer for the search line edit for showing
        # previously saved searches.
        ds = self._config.data_source
        filter_col = self.cbo_column.itemData(self.cbo_column.currentIndex())
        searches = column_searches(ds, filter_col)

        # Create and set completer
        completer = QCompleter(searches, self)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.txt_keyword.setCompleter(completer)

    def clear_results(self):
        """
        Removes any previous search results in the view.
        """
        self._res_model.clear_results()
        self._update_search_status(-1)

    def on_basic_search(self):
        """
        Slot raised to execute basic search.
        """
        # Validate if input parameters have been specified
        filter_col = ''
        msgs = []
        if not self.cbo_column.currentText():
            msgs.append('Filter column has not been specified.')
        else:
            filter_col = self.cbo_column.itemData(
                self.cbo_column.currentIndex())

        filter_exp = None
        if not self.cbo_expression.currentText():
            msgs.append('Filter expression has not been specified.')
        else:
            filter_exp = self.cbo_expression.itemData(
                self.cbo_expression.currentIndex())

        search_term = self.txt_keyword.value()
        if not search_term:
            msgs.append('Please specify the search keyword.')

        # Clear any previous notifications
        self.notif_bar.clear()

        # Insert warning messages
        for msg in msgs:
            self.notif_bar.insertWarningNotification(msg)

        if len(msgs) > 0:
            return

        # Save search and update completer with historical searches
        save_column_search(self._config.data_source, filter_col, search_term)
        self._set_search_completer()

        # Format the input value depending on the selected operator
        fm_search_term = self._ds_mgr.format_value_by_operator(
            filter_exp, search_term)

        # Build search query object
        search_query = BasicSearchQuery()
        search_query.filter_column = filter_col
        search_query.expression = filter_exp
        search_query.search_term = fm_search_term
        search_query.quote_column_value = self._ds_mgr.quote_column_value(
            filter_col)

        exp_text = search_query.expression_text()
        self.exec_search(exp_text)

    def exec_search(self, search_expression):
        """
        Execute a search operation based on the specified filter expression.
        :param search_expression: Filter expression.
        :type search_expression: str
        """
        self.clear_results()
        if not search_expression:
            msg = 'Search expression cannot be empty.'
            self.notif_bar.insertWarningNotification(msg)
            return

        try:
            results = self._ds_mgr.search_data_source(search_expression,
                                                      self._sort_map)
            self._update_search_status(len(results))
            # Update model
            self._res_model.set_results(results)

            # Notify user if there are no results
            if len(results) == 0:
                self.notif_bar.insertInformationNotification(
                    'No results found matching the search keyword.')
        except FltsSearchException as fe:
            self.notif_bar.insertWarningNotification(str(fe))

    def _update_search_status(self, count=-1):
        # Updates search count label.
        txt = ''
        suffix = 'record' if count == 1 else 'records'
        if count != -1:
            # Separate thousand using comma
            cs_count = format(count, ',')
            txt = '{0} {1}'.format(cs_count, suffix)

        self.lbl_results_count.setText(txt)

    def on_advanced_search(self):
        # Slot raised to show the expression editor.
        filter_col = self.cbo_column.itemData(self.cbo_column.currentIndex())
        start_txt = '"{0}" = '.format(filter_col)
        exp_dlg = QgsExpressionBuilderDialog(self._ds_mgr.vector_layer,
                                             start_txt, self,
                                             self._config.display_name)
        exp_dlg.setWindowTitle('{0} Expression Editor'.format(
            self._config.display_name))
        if exp_dlg.exec_() == QDialog.Accepted:
            exp_text = exp_dlg.expressionText()
            self.exec_search(exp_text)

    def on_sort_columns(self):
        # Slot raised to show the sort column dialog
        col_mapping = self._ds_mgr.valid_column_mapping
        if not self._sort_dialog:
            self._sort_dialog = SortColumnDialog(col_mapping, self)

        if self._sort_dialog.exec_() == QDialog.Accepted:
            sort_map = self._sort_dialog.sort_mapping()
            if len(sort_map) > 0:
                self._sort_map = sort_map
            else:
                self._sort_map = None

    def selected_rows(self):
        """
        :return: Returns the row numbers of the selected results.
        :rtype: list
        """
        return [
            idx.row()
            for idx in self.tb_results.selectionModel().selectedRows()
        ]

    def selected_features(self):
        """
        :return: Returns a list of QgsFeatures corresponding to the selected
        results.
        :rtype: list
        """
        features = []
        for r in self.selected_rows():
            feat = self._res_model.row_to_feature(r)
            if feat:
                features.append(feat)

        return features

    def on_selection_changed(self, previous_selection, current_selection):
        # Slot raised when the selection changes in the results table.
        features = self.selected_features()
Beispiel #11
0
class ComposerChartConfigEditor(WIDGET, BASE):
    def __init__(self, composer_wrapper, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)

        self._composer_wrapper = composer_wrapper

        self._notif_bar = NotificationBar(self.vl_notification)

        self.cbo_chart_type.currentIndexChanged[int].connect(self._on_chart_type_changed)

        '''
        Register chartname to the positional index of the corresponding editor
        '''
        self._short_name_idx = {}

        # Add registered chart types
        self._load_chart_type_settings()

        # Load legend positions
        self._load_legend_positions()

        self.groupBox_2.setCollapsed(True)
        self.groupBox_2.collapsedStateChanged.connect(self._on_series_properties_collapsed)

        # Load fields if the data source has been specified
        ds_name = self._composer_wrapper.selectedDataSource()
        self.ref_table.load_data_source_fields(ds_name)

        # Load referenced table list
        self.ref_table.load_link_tables()

        # Connect signals
        self._composer_wrapper.dataSourceSelected.connect(self.ref_table.on_data_source_changed)
        self.ref_table.referenced_table_changed.connect(self.on_referenced_table_changed)

    def _load_legend_positions(self):
        from stdm.composer import legend_positions

        self.cbo_legend_pos.clear()
        for k, v in legend_positions.items():
            self.cbo_legend_pos.addItem(k, v)

        # Select 'Automatic' option
        setComboCurrentIndexWithText(self.cbo_legend_pos,
                                     QApplication.translate("ChartConfiguration", "Automatic"))

    def _load_chart_type_settings(self):
        for cts in ChartTypeUISettings.registry:
            self.add_chart_type_setting(cts)

    def add_chart_type_setting(self, cts):
        """
        Adds a chart type and corresponding series properties editor to the
        collection.
        :param cts: Chart type setting containing a display name and
        corresponding editor.
        :type cts: ChartTypeUISettings
        """
        cts_obj = cts(self)

        widget_idx = self.series_type_container.addWidget(cts_obj.editor())
        self.cbo_chart_type.insertItem(widget_idx, cts_obj.icon(),
                                       cts_obj.title())

        # Register short_name index
        if cts_obj.short_name():
            self._short_name_idx[cts_obj.short_name()] = widget_idx

    def _on_series_properties_collapsed(self, state):
        """
        Slot to check whether the user has specified a chart type and collapse
         if none has been selected.
        :param state: True if group box is collapsed, false if not.
        :type state: bool
        """
        if not state and not self.cbo_chart_type.currentText():
            self._notif_bar.clear()
            msg = QApplication.translate("ComposerChartConfigEditor",
                                         "Please select a chart type from "
                                         "the drop down list below")
            self._notif_bar.insertWarningNotification(msg)

            self.groupBox_2.setCollapsed(True)

    def _on_chart_type_changed(self, index):
        """
        Slot raised when the chart type selection changes.
        :param index: Index of the chart type in the combobox
        :type index: int
        """
        if index >= 0:
            self.series_type_container.setCurrentIndex(index)

    def on_referenced_table_changed(self, table):
        """
        Slot raised when the referenced table name changes. This notifies
        series properties editors of the need to update the fields.
        :param table: Current table name.
        :type table: str
        """
        curr_editor = self.series_type_container.currentWidget()
        if not curr_editor is None:
            if isinstance(curr_editor, DataSourceNotifier):
                curr_editor.on_table_name_changed(table)

    def configuration(self):
        # Return chart configuration settings
        config = None

        curr_editor = self.series_type_container.currentWidget()
        if not curr_editor is None:
            try:
                config = curr_editor.configuration()
            except AttributeError:
                raise AttributeError(QApplication.translate("ComposerChartConfigEditor",
                                                            "Series editor does not contain a method for "
                                                            "returning a ChartConfigurationSettings object."))

        else:
            raise Exception(QApplication.translate("ComposerChartConfigEditor",
                                                   "No series editor found."))

        if not config is None:
            ref_table_config = self.ref_table.properties()
            config.extract_from_linked_table_properties(ref_table_config)
            config.set_insert_legend(self.gb_legend.isChecked())
            config.set_title(self.txt_plot_title.text())
            config.set_legend_position(self.cbo_legend_pos.itemData
                                       (self.cbo_legend_pos.currentIndex()))

        return config

    def composer_item(self):
        return self._picture_item

    def _set_graph_properties(self, config):
        # Set the general graph properties from the config object
        self.txt_plot_title.setText(config.title())
        self.gb_legend.setChecked(config.insert_legend())
        setComboCurrentIndexWithItemData(self.cbo_legend_pos,
                                         config.legend_position())

    def set_configuration(self, configuration):
        # Load configuration settings
        short_name = configuration.plot_type

        if short_name:
            if short_name in self._short_name_idx:
                plot_type_idx = self._short_name_idx[short_name]
                self.cbo_chart_type.setCurrentIndex(plot_type_idx)

                # Set linked table properties
                self.ref_table.set_properties(configuration.linked_table_props())

                # Set series editor properties
                curr_editor = self.series_type_container.currentWidget()
                if not curr_editor is None:
                    try:
                        curr_editor.set_configuration(configuration)
                        self._set_graph_properties(configuration)

                    except AttributeError:
                        msg = QApplication.translate("ComposerChartConfigEditor",
                                                     "Configuration could not be set for series editor.")
                        self._notif_bar.clear()
                        self._notif_bar.insertErrorNotification(msg)

        else:
            msg = QApplication.translate("ComposerChartConfigEditor",
                                         "Configuration failed to load. Plot type cannot be determined.")
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)
Beispiel #12
0
class RelatedTableDialog(QDialog, Ui_RelatedTableTranslatorDialog, TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the
    RelatedTableTranslator class implementation.
    """
    def __init__(self, parent, source_cols, dest_table, dest_col, src_col):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col)

        self._notif_bar = NotificationBar(self.vl_notification)

        self._set_source_table_headers()

        #Set UI values
        self.txt_table_name.setText(dest_table)
        self.txt_column_name.setText(dest_col)

        #Load STDM tables exluding views
        self._load_tables()

        #Connect signals
        self.cbo_source_tables.currentIndexChanged.connect(self._on_source_table_changed)

    def _load_tables(self):
        """
        Loads both textual and spatial tables into the user list.
        """
        self.cbo_source_tables.addItem("")
        self.cbo_source_tables.addItems(self.db_tables())

    def value_translator(self):
        rel_tab_translator = RelatedTableTranslator()
        rel_tab_translator.set_referencing_table(self.txt_table_name.text())
        rel_tab_translator.set_referencing_column(self.txt_column_name.text())
        rel_tab_translator.set_referenced_table(self.cbo_source_tables.currentText())
        rel_tab_translator.set_output_reference_column(self.cbo_output_column.currentText())
        rel_tab_translator.set_input_referenced_columns(self.column_pairings())

        return rel_tab_translator

    def _set_source_table_headers(self):
        labels = [QApplication.translate("RelatedTableDialog","Source Table"),
                  QApplication.translate("RelatedTableDialog",
                                         "Referenced Table")]
        self.tb_source_trans_cols.set_header_labels(labels)

    def _on_source_table_changed(self, index):
        source_table = self.cbo_source_tables.currentText()

        self.cbo_output_column.clear()

        if source_table:
            ref_table_cols = table_column_names(source_table)

            self.tb_source_trans_cols.set_combo_selection([self._source_cols,
            ref_table_cols])

            #self.cbo_output_column.addItem("")
            self.cbo_output_column.addItems(ref_table_cols)

        else:
            self.tb_source_trans_cols.clear_view()

    def column_pairings(self):
        """
        :return: Source and reference table column matchings.
        :rtype: dict
        """
        return self.tb_source_trans_cols.column_pairings()

    def validate(self):
        """
        :return: Check user entries.
        :rtype: bool
        """
        if self.cbo_source_tables.currentText() == "":
            msg = QApplication.translate("RelatedTableDialog", "Please select "
                    "the reference table name.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        if self.cbo_output_column.currentText() == "":
            msg = QApplication.translate("RelatedTableDialog", "Please select "
                    "the output column name.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        if len(self.column_pairings()) == 0:
            msg = QApplication.translate("RelatedTableDialog", "Please specify "
                    "at least one column pairing.")
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(RelatedTableDialog, self).accept()
Beispiel #13
0
class MapperMixin(object):
    '''
    Mixin class for use in a dialog or widget, and does the heavy lifting when it comes to managing attribute mapping.
    '''
    def __init__(self,model):
        '''
        :param model: Callable (new instances) or instance (existing instance for updating) of STDM model.
        '''
        if callable(model):
            self._model = model()
            self._mode = SAVE
        else:
            self._model = model
            self._mode = UPDATE
        
        self._attrMappers = []
        self._dirtyTracker = ControlDirtyTrackerCollection()
        self._notifBar = None
        
        #Initialize notification bar
        if hasattr(self,"vlNotification"):
            self._notifBar = NotificationBar(self.vlNotification)
        
        #Flag to indicate whether to close the widget or dialog once model has been submitted
        #self.closeOnSubmit = True
        
    def addMapping(self,attributeName,control,isMandatory = False,pseudoname = "",valueHandler = None,preloadfunc = None):
        '''
        Specify the mapping configuration.
        '''
        attrMapper = _AttributeMapper(attributeName,control,self._model,pseudoname,isMandatory,valueHandler)
        self.addMapper(attrMapper,preloadfunc)
        
    def addMapper(self,attributeMapper,preloadfunc = None):
        '''
        Add an attributeMapper object to the collection.
        Preloadfunc specifies a function that can be used to prepopulate the control's value only when
        the control is on SAVE mode.
        '''
        if self._mode == SAVE and preloadfunc != None:
            attributeMapper.valueHandler().setValue(preloadfunc)
        
        if self._mode == UPDATE:
            #Set control value based on the model attribute value
            attributeMapper.bindControl()
            
        #Add control to dirty tracker collection after control value has been set
        self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler())
            
        self._attrMappers.append(attributeMapper)
        
    def saveMode(self):
        '''
        Return the mode that the mapper is currently configured in.
        '''
        return self._mode
    
    def setSaveMode(self,mode):
        '''
        Set the mapper's save mode.
        '''
        self._mode = mode
        
    def setModel(self,stdmModel):
        '''
        Set the model to be used by the mapper.
        '''
        self._model = stdmModel
        
    def model(self):
        '''
        Returns the model configured for the mapper.
        '''
        return self._model
        
    def setNotificationLayout(self,layout):
        '''
        Set the vertical layout instance that will be used to display notification messages.
        '''
        self._notifBar = NotificationBar(layout)
        
    def insertNotification(self,message,mtype):
        '''
        There has to be a vertical layout, named 'vlNotification', that
        enables for notifications to be inserted.
        '''
        if self._notifBar:
            self._notifBar.insertNotification(message, mtype)   
            
    def clearNotifications(self):         
        '''
        Clears all messages in the notification bar.
        '''
        if self._notifBar:
            self._notifBar.clear()
            
    def checkDirty(self):
        '''
        Asserts whether the dialog contains dirty controls.
        '''
        isDirty = False
        msgResponse = None
        
        if self._dirtyTracker.isDirty():
            isDirty = True
            msg = QApplication.translate("MappedDialog","Would you like to save changes before closing?")
            msgResponse = QMessageBox.information(self, QApplication.translate("MappedDialog","Save Changes"), msg, 
                                             QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            
        return isDirty,msgResponse
    
    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.
        ''' 
        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 cancel(self):
        '''
        Slot for closing the dialog.
        Checks the dirty state first before closing.
        '''
        isDirty,userResponse = self.checkDirty()
        
        if isDirty:
            if userResponse == QMessageBox.Yes:
                self.submit()
            elif userResponse == QMessageBox.No:
                self.reject()
            elif userResponse == QMessageBox.Cancel:
                pass
        else:
            self.reject()
    
    def preSaveUpdate(self):
        '''
        Mixin classes can override this method to specify any operations that need to be executed
        prior to saving or updating the model's values.
        It should return True prior to saving.
        '''
        return True
    
    def postSaveUpdate(self,dbmodel):
        '''
        Executed once a record has been saved or updated. 
        '''
        pass
    
    def submit(self):
        '''
        Slot for saving or updating the model. This will close the dialog on successful submission.
        '''
        if not self.preSaveUpdate():
            return
        
        self.clearNotifications()
        isValid= True
        
        #Validate mandatory fields have been entered by the user.
        for attrMapper in self._attrMappers:
            if attrMapper.isMandatory() and attrMapper.valueHandler().supportsMandatory():
                if attrMapper.valueHandler().value() == attrMapper.valueHandler().default():
                    #Notify user
                    msg = "{0} is a required field.".format(attrMapper.pseudoName())
                    self._notifBar.insertWarningNotification(msg)
                    isValid = False
                else:
                    attrMapper.bindModel()
                    
            else:
                attrMapper.bindModel()
        
        if not isValid:
            return
        
        self._persistModel()
            
    def _persistModel(self):
        #Persist the model to its corresponding store.
        if self._mode == SAVE:
            self._model.save()
            QMessageBox.information(self, QApplication.translate("MappedDialog","Record Saved"), \
                                    QApplication.translate("MappedDialog","New record has been successfully saved"))
            
        else:
            self._model.update()
            QMessageBox.information(self, QApplication.translate("MappedDialog","Record Updated"), \
                                    QApplication.translate("MappedDialog","Record has been successfully updated"))
            
        #Close the dialog
        if isinstance(self, QDialog):
            self.postSaveUpdate(self._model)
            self.accept()
Beispiel #14
0
class LookupValueSelector(QDialog, Ui_LookupValueSelector):
    """
    A dialog that enables to select a value and code from a lookup.
    .. versionadded:: 1.5
    """

    def __init__(self, parent, lookup_entity_name, profile=None):
        """

        """
        QDialog.__init__(self, parent, Qt.WindowTitleHint|Qt.WindowCloseButtonHint)
        self.setupUi(self)
        self.value_and_code = None
        if profile is None:
            self._profile = current_profile()
        else:
            self._profile = profile

        self.lookup_entity = self._profile.entity_by_name(
            '{}_{}'.format(self._profile.prefix, lookup_entity_name)
        )

        self.notice = NotificationBar(self.notice_bar)
        self._view_model = QStandardItemModel()
        self.value_list_box.setModel(self._view_model)
        header_item = QStandardItem(lookup_entity_name)
        self._view_model.setHorizontalHeaderItem(0, header_item)
        self.populate_value_list_view()

        self.selected_code = None
        self.selected_value_code = None

        self.value_list_box.clicked.connect(self.validate_selected_code)


    def populate_value_list_view(self):
        self.value_and_code = self.lookup_entity.values
        for value, code in self.value_and_code.iteritems():
            value_code = QStandardItem('{} ({})'.format(value, code.code))
            value_code.setData(code.code)
            self._view_model.appendRow(value_code)

    def validate_selected_code(self):
        self.notice.clear()
        self.selected_code_value()
        if self.selected_code == '':
            notice = QApplication.tr(self, 'The selected value has no code.')
            self.notice.insertWarningNotification(notice)

    def selected_code_value(self):
        index = self.value_list_box.currentIndex()
        item = self._view_model.itemFromIndex(index)
        self.selected_code = item.data()
        self.selected_value_code = item.text()

    def accept(self):
        self.selected_code_value()
        self.done(1)

    def reject(self):
        self.selected_code = None
        self.selected_value_code = None
        self.done(0)
Beispiel #15
0
class OptionsDialog(QDialog, Ui_DlgOptions):
    """
    Dialog for editing STDM settings.
    """
    def __init__(self, iface):
        QDialog.__init__(self, iface.mainWindow())
        self.setupUi(self)
        self.iface = iface

        self.notif_bar = NotificationBar(self.vlNotification, 6000)
        self._apply_btn = self.buttonBox.button(QDialogButtonBox.Apply)
        self._reg_config = RegistryConfig()
        self._db_config = DatabaseConfig()

        # Connect signals
        self._apply_btn.clicked.connect(self.apply_settings)
        self.buttonBox.accepted.connect(self.on_accept)
        self.chk_pg_connections.toggled.connect(self._on_use_pg_connections)
        self.cbo_pg_connections.currentIndexChanged.connect(
            self._on_pg_profile_changed)
        self.btn_db_conn_clear.clicked.connect(self.clear_properties)
        self.btn_test_db_connection.clicked.connect(
            self._on_test_db_connection)
        self.btn_template_folder.clicked.connect(
            self._on_choose_doc_designer_template_path)
        self.btn_composer_out_folder.clicked.connect(
            self._on_choose_doc_generator_output_path)
        self.btn_test_docs_repo_conn.clicked.connect(
            self._on_test_cmis_connection)
        self.txt_atom_pub_url.textChanged.connect(self._on_cmis_url_changed)
        self.btn_holders_conf_file.clicked.connect(
            self._on_choose_holders_config_file)

        self._config = StdmConfiguration.instance()
        self.init_gui()

    def init_gui(self):
        # Set integer validator for the port number
        int_validator = QIntValidator(1024, 49151)
        self.txtPort.setValidator(int_validator)

        # Load profiles
        self.load_profiles()

        # Set current profile in the combobox
        curr_profile = current_profile()
        if not curr_profile is None:
            setComboCurrentIndexWithText(self.cbo_profiles, curr_profile.name)

        # Load current database connection properties
        self._load_db_conn_properties()

        # Load existing PostgreSQL connections
        self._load_qgis_pg_connections()

        # Load directory paths
        self._load_directory_paths()

        # Load document repository-related settings
        self._load_cmis_config()

        # Load holders configuration file path
        self._load_holders_configuration_file()

        # Debug logging
        lvl = debug_logging()
        if lvl:
            self.chk_logging.setCheckState(Qt.Checked)
        else:
            self.chk_logging.setCheckState(Qt.Unchecked)

        # Shortcut dialog
        self._check_shortcuts_settings()

        self.chk_shortcut_dlg.toggled.connect(self._on_check_shortcut_checkbox)

    def _load_cmis_config(self):
        """
        Load configuration names and IDs in the combobox then select
        the user specified ID.
        """
        # Load CMIS atom pub URL
        self.txt_atom_pub_url.setText(cmis_atom_pub_url())

        self.cbo_auth_config_name.clear()
        self.cbo_auth_config_name.addItem('')

        config_ids = config_entries()
        for ci in config_ids:
            id = ci[0]
            name = ci[1]
            display = u'{0} ({1})'.format(name, id)
            self.cbo_auth_config_name.addItem(display, id)

        # Then select the user specific config ID if specified
        conf_id = cmis_auth_config_id()
        if conf_id:
            id_idx = self.cbo_auth_config_name.findData(conf_id)
            if id_idx != -1:
                self.cbo_auth_config_name.setCurrentIndex(id_idx)

    def _load_holders_configuration_file(self):
        # Load the path of the holders configuration file.
        holders_config = holders_config_path()
        if not holders_config:
            # Use the default path
            holders_config = DEF_HOLDERS_CONFIG_PATH

        self.txt_holders_config.setText(holders_config)

    def _on_choose_holders_config_file(self):
        # Slot raised to browse the holders config file
        holders_config = self.txt_holders_config.text()
        if not holders_config:
            holders_config = QDir.home().path()

        conf_file = QFileDialog.getOpenFileName(
            self, self.tr('Browse Holders Configuration File'), holders_config,
            'Holders INI file (*.ini)')
        if conf_file:
            self.txt_holders_config.setText(conf_file)

    def _on_cmis_url_changed(self, new_text):
        # Slot raised when text for CMIS URL changes. Basically updates
        # the tooltip so as to display long URLs
        self.txt_atom_pub_url.setToolTip(new_text)

    def _check_shortcuts_settings(self):
        """
        Checks the state of the show dialog checkbox
        :return: Bool
        """
        # Check registry values for shortcut dialog
        show_dlg = 1
        dlg_key = self._reg_config.read([SHOW_SHORTCUT_DIALOG])

        if len(dlg_key) > 0:
            show_dlg = dlg_key[SHOW_SHORTCUT_DIALOG]

        if show_dlg == 1 or show_dlg == unicode(1):
            self.chk_shortcut_dlg.setChecked(True)
        elif show_dlg == 0 or show_dlg == unicode(0):
            self.chk_shortcut_dlg.setChecked(False)

    def _on_check_shortcut_checkbox(self, toggled):
        """
        Slot raised when the checkbox for showing shortcut window
        is checked/unchecked.
        :param toggled: Toggle status
        :type toggled: bool
        """
        if toggled:
            self._reg_config.write({SHOW_SHORTCUT_DIALOG: 1})
            self.notif_bar.clear()
            msg = self.tr(u"Shortcuts window will show after login")
            self.notif_bar.insertWarningNotification(msg)
        else:
            self._reg_config.write({SHOW_SHORTCUT_DIALOG: 0})
            self.notif_bar.clear()
            msg = self.tr(u"Shortcuts window will not show after login")
            self.notif_bar.insertWarningNotification(msg)

    def load_profiles(self):
        """
        Load existing profiles into the combobox.
        """
        profile_names = self._config.profiles.keys()

        self.cbo_profiles.clear()
        self.cbo_profiles.addItem('')
        self.cbo_profiles.addItems(profile_names)

    def _load_db_conn_properties(self):
        # Load database connection properties from the registry.
        db_conn = self._db_config.read()

        if not db_conn is None:
            self.txtHost.setText(db_conn.Host)
            self.txtPort.setText(db_conn.Port)
            self.txtDatabase.setText(db_conn.Database)

    def _load_qgis_pg_connections(self):
        """
        Load QGIS postgres connections.
        """
        self.cbo_pg_connections.addItem('')

        profiles = pg_profile_names()
        for profile in profiles:
            self.cbo_pg_connections.addItem(profile[0], profile[1])

    def _load_directory_paths(self):
        # Load paths to various directory settings.
        comp_out_path = composer_output_path()
        comp_temp_path = composer_template_path()

        if not comp_out_path is None:
            self.txt_output_dir.setText(comp_out_path)

        if not comp_temp_path is None:
            self.txt_template_dir.setText(comp_temp_path)

    def _on_use_pg_connections(self, state):
        # Slot raised when to (not) use existing pg connections
        if not state:
            self.cbo_pg_connections.setCurrentIndex(0)
            self.cbo_pg_connections.setEnabled(False)

            # Restore current connection in registry
            self._load_db_conn_properties()

        else:
            self.cbo_pg_connections.setEnabled(True)

    def _on_pg_profile_changed(self, index):
        """
        Slot raised when the index of the pg profile changes. If the
        selection is valid then the system will attempt to extract
        the database connection properties of the selected profile
        stored in the registry.
        """
        if index == 0:
            return

        profile_path = self.cbo_pg_connections.itemData(index)

        q_config = QGISRegistryConfig(profile_path)
        db_items = q_config.read(['Database', 'Host', 'Port'])

        if len(db_items) > 0:
            self.txtDatabase.setText(db_items['Database'])
            self.txtHost.setText(db_items['Host'])
            self.txtPort.setText(db_items['Port'])

    def clear_properties(self):
        """
        Clears the host, database name and port number values from the
        respective controls.
        """
        self.txtDatabase.clear()
        self.txtHost.clear()
        self.txtPort.clear()

    def _on_choose_doc_designer_template_path(self):
        # Slot raised to select directory for document designer templates.
        self._set_selected_directory(
            self.txt_template_dir,
            self.tr('Document Designer Templates Directory'))

    def _on_choose_doc_generator_output_path(self):
        # Slot raised to select directory for doc generator outputs.
        self._set_selected_directory(
            self.txt_output_dir,
            self.tr('Document Generator Output Directory'))

    def _set_selected_directory(self, txt_box, title):
        def_path = txt_box.text()
        sel_doc_path = QFileDialog.getExistingDirectory(self, title, def_path)

        if sel_doc_path:
            normalized_path = QDir.fromNativeSeparators(sel_doc_path)
            txt_box.clear()
            txt_box.setText(normalized_path)

    def _validate_db_props(self):
        # Test if all properties have been specified
        status = True

        self.notif_bar.clear()

        if not self.txtHost.text():
            msg = self.tr('Please specify the database host address.')
            self.notif_bar.insertErrorNotification(msg)

            status = False

        if not self.txtPort.text():
            msg = self.tr('Please specify the port number.')
            self.notif_bar.insertErrorNotification(msg)

            status = False

        if not self.txtDatabase.text():
            msg = self.tr('Please specify the database name.')
            self.notif_bar.insertErrorNotification(msg)

            status = False

        return status

    def _database_connection(self):
        # Creates a databaase connection object from the specified args
        host = self.txtHost.text()
        port = self.txtPort.text()
        database = self.txtDatabase.text()

        # Create database connection object
        db_conn = DatabaseConnection(host, port, database)

        return db_conn

    def _on_test_db_connection(self):
        """
        Slot raised to test database connection.
        """
        status = self._validate_db_props()

        if not status:
            return

        login_dlg = loginDlg(self, True)
        db_conn = self._database_connection()
        login_dlg.set_database_connection(db_conn)

        res = login_dlg.exec_()
        if res == QDialog.Accepted:
            msg = self.tr(u"Connection to '{0}' database was "
                          "successful.".format(db_conn.Database))
            self.notif_bar.insertSuccessNotification(msg)

    def _on_test_cmis_connection(self):
        # Slot raised to test connection to CMIS service
        status = self._validate_cmis_properties()
        if not status:
            return

        self.notif_bar.clear()

        atom_pub_url = self.txt_atom_pub_url.text()
        auth_conf_id = self.cbo_auth_config_name.itemData(
            self.cbo_auth_config_name.currentIndex())

        cmis_mgr = CmisManager(url=atom_pub_url, auth_config_id=auth_conf_id)

        QgsApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        status = cmis_mgr.connect()
        QgsApplication.restoreOverrideCursor()

        if status:
            msg = self.tr('Connection to the CMIS server succeeded.')
            self.notif_bar.insertSuccessNotification(msg)
        else:
            msg = self.tr(
                'Failed to connect to the CMIS server. Check URL and/or '
                'credentials.')
            self.notif_bar.insertErrorNotification(msg)

    def set_current_profile(self):
        """
        Saves the given profile name as the current profile.
        """
        profile_name = self.cbo_profiles.currentText()

        if not profile_name:
            self.notif_bar.clear()

            msg = self.tr('Profile name is empty, current profile will not '
                          'be set.')
            self.notif_bar.insertErrorNotification(msg)

            return False

        save_current_profile(profile_name)

        return True

    def set_cmis_properties(self):
        """
        Saves the CMIS atom pub URL and authentication configuration ID.
        """
        if not self._validate_cmis_properties():
            return False

        atom_pub_url = self.txt_atom_pub_url.text()
        set_cmis_atom_pub_url(atom_pub_url)

        curr_idx = self.cbo_auth_config_name.currentIndex()
        config_id = self.cbo_auth_config_name.itemData(curr_idx)
        set_cmis_auth_config_id(config_id)

        return True

    def _validate_cmis_properties(self):
        # Assert if user has specified URL and authentication config ID.
        atom_pub_url = self.txt_atom_pub_url.text()
        config_name_id = self.cbo_auth_config_name.currentText()

        status = True

        if not atom_pub_url:
            msg = self.tr(
                'Please specify the URL of the CMIS atom pub service.')
            self.notif_bar.insertErrorNotification(msg)
            status = False

        if not config_name_id:
            msg = self.tr(
                'Please select the configuration ID containing the CMIS authentication.'
            )
            self.notif_bar.insertErrorNotification(msg)
            status = False

        return status

    def set_holders_config_file(self):
        # Save the path to the holders configuration file
        holders_config_file = self.txt_holders_config.text()
        if not holders_config_file:
            msg = self.tr(
                'Please set the path to the holders configuration file.')
            self.notif_bar.insertErrorNotification(msg)
            return False

        set_holders_config_path(holders_config_file)

        return True

    def save_database_properties(self):
        """
        Saves the specified database connection properties to the registry.
        :return: True if the connection properties were successfully saved.
        :rtype: bool
        """
        if not self._validate_db_props():
            return False

        # Create a database object and write it to the registry
        db_conn = self._database_connection()
        self._db_config.write(db_conn)

        return True

    def set_document_templates_path(self):
        """
        Set the directory of document designer templates.
        :return: True if the directory was set in the registry, otherwise
        False.
        :rtype: bool
        """
        path = self.txt_template_dir.text()

        if not path:
            msg = self.tr('Please set the document designer templates '
                          'directory.')
            self.notif_bar.insertErrorNotification(msg)

            return False

        # Validate path
        if not self._check_path_exists(path, self.txt_template_dir):
            return False

        # Commit to registry
        self._reg_config.write({COMPOSER_TEMPLATE: path})

        return True

    def set_document_output_path(self):
        """
        Set the directory of document generator outputs.
        :return: True if the directory was set in the registry, otherwise
        False.
        :rtype: bool
        """
        path = self.txt_output_dir.text()

        if not path:
            msg = self.tr('Please set the document generator output directory'
                          '.')
            self.notif_bar.insertErrorNotification(msg)

            return False

        # Validate path
        if not self._check_path_exists(path, self.txt_output_dir):
            return False

        # Commit to registry
        self._reg_config.write({COMPOSER_OUTPUT: path})

        return True

    def _check_path_exists(self, path, text_box):
        # Validates if the specified folder exists
        dir = QDir()

        if not dir.exists(path):
            msg = self.tr(u"'{0}' directory does not exist.".format(path))
            self.notif_bar.insertErrorNotification(msg)

            # Highlight textbox control
            text_box.setStyleSheet(INVALIDATESTYLESHEET)

            timer = QTimer(self)
            # Sync interval with that of the notification bar
            timer.setInterval(self.notif_bar.interval)
            timer.setSingleShot(True)

            # Remove previous connected slots (if any)
            receivers = timer.receivers(SIGNAL('timeout()'))
            if receivers > 0:
                self._timer.timeout.disconnect()

            timer.start()
            timer.timeout.connect(lambda: self._restore_stylesheet(text_box))

            return False

        return True

    def _restore_stylesheet(self, textbox):
        # Slot raised to restore the original stylesheet of the textbox control
        textbox.setStyleSheet(self._default_style_sheet)

        # Get reference to timer and delete
        sender = self.sender()
        if not sender is None:
            sender.deleteLater()

    def apply_debug_logging(self):
        # Save debug logging
        logger = logging.getLogger('stdm')

        if self.chk_logging.checkState() == Qt.Checked:
            logger.setLevel(logging.DEBUG)
            set_debug_logging(True)
        else:
            logger.setLevel(logging.ERROR)
            set_debug_logging(False)

    def apply_settings(self):
        """
        Save settings.
        :return: True if the settings were successfully applied, otherwise
        False.
        :rtype: bool
        """
        # Set current profile
        if not self.set_current_profile():
            return False

        # Set db connection properties
        if not self.save_database_properties():
            return False

        # Set CMIS properties
        if not self.set_cmis_properties():
            return False

        # Set holders configuration file path
        if not self.set_holders_config_file():
            return False

        # Set document designer templates path
        if not self.set_document_templates_path():
            return False

        # Set document generator output path
        if not self.set_document_output_path():
            return False

        self.apply_debug_logging()

        msg = self.tr('Settings successfully saved.')
        self.notif_bar.insertSuccessNotification(msg)

        return True

    def on_accept(self):
        """
        Slot raised to save the settings of the current widget and close the
        widget.
        """
        if not self.apply_settings():
            return

        self.accept()
Beispiel #16
0
class LookupValueSelector(QDialog, Ui_LookupValueSelector):
    """
    A dialog that enables to select a value and code from a lookup.
    .. versionadded:: 1.5
    """

    def __init__(self, parent, lookup_entity_name, profile=None):
        """
        Initializes LookupValueSelector.
        :param parent: The parent of the dialog.
        :type parent: QWidget
        :param lookup_entity_name: The lookup entity name
        :type lookup_entity_name: String
        :param profile: The current profile object
        :type profile: Object
        """
        QDialog.__init__(self, parent, Qt.WindowTitleHint |
                         Qt.WindowCloseButtonHint)
        self.setupUi(self)
        self.value_and_code = None
        if profile is None:
            self._profile = current_profile()
        else:
            self._profile = profile

        self.lookup_entity = self._profile.entity_by_name(
            '{}_{}'.format(self._profile.prefix, lookup_entity_name)
        )

        self.notice = NotificationBar(self.notice_bar)
        self._view_model = QStandardItemModel()
        self.value_list_box.setModel(self._view_model)
        header_item = QStandardItem(lookup_entity_name)
        self._view_model.setHorizontalHeaderItem(0, header_item)
        self.populate_value_list_view()

        self.selected_code = None
        self.selected_value_code = None

        self.value_list_box.clicked.connect(self.validate_selected_code)

    def populate_value_list_view(self):
        """
        Populates the lookup values and codes.
        """
        self.value_and_code = self.lookup_entity.values

        for value, code in self.value_and_code.iteritems():
            u_value = unicode(value)
            code_value = self.lookup_entity.values[u_value]

            value_code = QStandardItem('{} ({})'.format(
                    code_value.value, code.code
                )
            )
            value_code.setData(code.code)
            self._view_model.appendRow(value_code)

    def validate_selected_code(self):
        """
        Validate the selected code for the presence of Code or not.
        """
        self.notice.clear()
        self.selected_code_value()
        if self.selected_code == '':
            notice = QApplication.tr(self, 'The selected value has no code.')
            self.notice.insertWarningNotification(notice)

    def selected_code_value(self):
        """
        Get the selected lookup value.
        """
        index = self.value_list_box.currentIndex()
        item = self._view_model.itemFromIndex(index)
        self.selected_code = item.data()
        self.selected_value_code = item.text()

    def accept(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code_value()
        self.done(1)

    def reject(self):
        """
        Overridden QDialog accept method.
        """
        self.selected_code = None
        self.selected_value_code = None
        self.done(0)
Beispiel #17
0
class LookupDialog(QDialog, Ui_LookupTranslatorDialog, TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the lookup translation
    implementation.
    """
    def __init__(self, parent, source_cols, dest_table, dest_col, src_col):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        TranslatorDialogBase.__init__(
            self,
            source_cols,
            dest_table,
            dest_col,
            src_col
        )

        self._notif_bar = NotificationBar(self.vl_notification)

        # Populate controls
        self._load_lookup_tables()

        # Connect signals
        self.cbo_lookup.currentIndexChanged.connect(
            self._on_lookup_table_name_changed
        )

    def _load_lookup_tables(self):
        # Load lookup table names
        c_profile = self._current_profile
        if c_profile is None:
            msg = QApplication.translate(
                'LookupDialog',
                'Current profile could not be determined.'
            )
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return

        lookups = [e.name for e in c_profile.value_lists()]

        self.cbo_lookup.clear()
        self.cbo_lookup.addItem('')
        self.cbo_lookup.addItems(lookups)

    def _on_lookup_table_name_changed(self, idx):
        # Slot raised when the lookup table name changes.
        self.cbo_default.clear()

        if idx == -1:
            return

        t_name = self.cbo_lookup.currentText()
        self.load_lookup_values(t_name)

    def load_lookup_values(self, table_name):
        """
        Load the default value combobox with values from the specified lookup
        table name.
        :param table_name: Lookup table name.
        :type table_name: str
        """
        self.cbo_default.clear()

        if not table_name:
            return

        # Get lookup entity
        lk_ent = self._current_profile.entity_by_name(table_name)
        if lk_ent is None:
            msg = QApplication.translate(
                'LookupDialog',
                'Lookup values could not be loaded.'
            )
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return

        lk_values = lk_ent.lookups()


        self.cbo_default.addItem('')

        for lk_value in lk_values:
            text_value = None
            for k, v in lk_ent.values.items():
                if v.value == lk_value:
                    text_value = v.value
            if text_value is not None:
                self.cbo_default.addItem(text_value)

    def value_translator(self):
        """
        :return: Returns the lookup value translator object.
        :rtype: LookupValueTranslator
        """
        lookup_translator = LookupValueTranslator()
        lookup_translator.set_referencing_table(self._dest_table)
        lookup_translator.set_referencing_column(self._dest_col)
        lookup_translator.set_referenced_table(self.cbo_lookup.currentText())
        lookup_translator.add_source_reference_column(
            self._src_col,
            self._dest_col
        )
        lookup_translator.default_value = self.cbo_default.currentText()

        return lookup_translator

    def validate(self):
        """
        Check user configuration and validate if they are correct.
        :return: Returns True if user configuration is correct, otherwise
        False.
        :rtype: bool
        """
        if not self.cbo_lookup.currentText():
            msg = QApplication.translate(
                'LookupDialog',
                'Please select the referenced lookup table.'
            )
            self._notif_bar.clear()
            self._notif_bar.insertWarningNotification(msg)

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(LookupDialog, self).accept()