Beispiel #1
0
    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.vlNotification)

        #Assert if the entity supports documents
        self._assert_entity_supports_documents()

        #Set the source document directory
        self.source_document_directory = None

        #Document type name
        self._document_type_name = self._dest_col

        #Document type ID
        self._document_type_id = None

        #Set document type ID
        self._set_document_type_id()

        #Connect slots
        self.btn_source_doc_folder.clicked.connect(
            self._load_source_document_directory_selector
        )
Beispiel #2
0
    def __init__(self, parent=None):
        """Class Constructor."""
        super(GeoODKConverter, self).__init__(parent)

        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-aut o-connect
        self.connect_action = pyqtSignal(str)
        self.setupUi(self)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.set_entity_model_view(self.entity_model)
        self.stdm_config = None
        self.parent = parent
        self.load_profiles()
        self.check_state_on()

        self.check_geoODK_path_exist()

        self.chk_all.stateChanged.connect(self.check_state_on)
        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)
        #self.btn_upload.clicked.connect(self.upload_generated_form)

        self._notif_bar_str = NotificationBar(self.vlnotification)
Beispiel #3
0
    def __init__(self, parent=None):
        """
        initailize class variables here
        """
        super(ProfileInstanceRecords, self).__init__(parent)
        self.setupUi(self)

        self.path = None
        self.instance_list = []
        self.relations = OrderedDict()
        self.parent_ids = {}
        self.importlogger = ImportLogger()
        self._notif_bar_str = NotificationBar(self.vlnotification)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.uuid_extractor = InstanceUUIDExtractor(self.path)
        self.btn_chang_dir.setIcon(QIcon(":/plugins/stdm/images/icons/open_file.png"))
        self.btn_refresh.setIcon(QIcon(":/plugins/stdm/images/icons/update.png"))

        self.chk_all.stateChanged.connect(self.change_check_state)
        #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed)
        self.btn_chang_dir.clicked.connect(self.on_directory_search)
        self.lst_widget.itemClicked.connect(self.user_selected_entities)
        self.btn_refresh.clicked.connect(self.update_files_with_custom_filter)

        self.buttonBox.button(QDialogButtonBox.Save).setText('Import')

        #self.load_config()
        self.init_file_path()
        self.current_profile_changed()
        self.change_check_state(self.chk_all.checkState())
        self.instance_dir()
Beispiel #4
0
    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)
Beispiel #5
0
    def __init__(self, parent):
        QDialog.__init__(self, parent)

        self.profile_name = ''
        self.desc = ''
        
        self.setupUi(self)
        self.init_controls()
        self.notice_bar = NotificationBar(self.notif_bar)
Beispiel #6
0
    def __init__(self, parent, lookup, code_value=None):
        """
        :param parent: Owner of this dialog window
        :type parent: QWidget
        :param lookup: A value list object to add the value
        :type lookup: ValueList
        :param code_value: A value object to add to the lookup,
        if None this is a new value, else its an edit.
        :type code_value: CodeValue
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.lookup = lookup
        self.code_value = code_value
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()
Beispiel #7
0
    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)
Beispiel #8
0
 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)
Beispiel #9
0
    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
        )
Beispiel #10
0
    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()

        version = version_from_metadata()
        upgrade_label_text = self.label_9.text().replace('1.4', version)
        self.label_9.setText(upgrade_label_text)

        #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_connection)
        self.btn_supporting_docs.clicked.connect(
            self._on_choose_supporting_docs_path
        )
        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.upgradeButton.toggled.connect(
            self.manage_upgrade
        )

        self._config = StdmConfiguration.instance()
        self._default_style_sheet = self.txtRepoLocation.styleSheet()

        self.manage_upgrade()

        self.init_gui()
Beispiel #11
0
    def __init__(self, parent, profile, lookup=None):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param profile: A profile to add/edit lookup
        :type profile: Profile
        :type inplace: Flag to check if lookup creation is initiated from the
                       'normal' lookup creation process -inplace = False,
                       this is the normal state. If 'inplace' = True, then
                       creation is initiated from the the lookup selection dialog
        :param lookup: Value list to create, if None this is a new value list
         else its an edit
        :type lookup: ValueList
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.profile = profile
        self.lookup = lookup
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()
Beispiel #12
0
    def __init__(self, parent=None, **kwargs):
        super(ImageExportSettings, self).__init__(parent)
        self.setupUi(self)

        self._image_tr = self.tr('Image')

        self.notif_bar = NotificationBar(self.vl_notification, 6000)

        #Connect signals
        self.btn_path.clicked.connect(self._on_choose_image_path)
        self.buttonBox.accepted.connect(self.on_accept)
        self.sb_resolution.valueChanged.connect(self._on_resolution_changed)

        #Set color button defaults
        self._default_color = Qt.white
        self.btn_color.setDefaultColor(self._default_color)
        self.btn_color.setColor(self._default_color)
        self.btn_color.setAllowAlpha(True)

        self.path = kwargs.get('image_path', '')
        self.resolution = kwargs.get('resolution', '96')
        self.background_color = kwargs.get('background', Qt.transparent)

        self._update_controls()
Beispiel #13
0
class TenureCustomAttributesEditor(WIDGET, BASE):
    """
    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.btnAddColumn.setIcon(GuiUtils.get_icon('add.png'))
        self.btnEditColumn.setIcon(GuiUtils.get_icon('edit.png'))
        self.btnDeleteColumn.setIcon(GuiUtils.get_icon('delete.png'))

        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 list(self._tenure_custom_entities.items()):
            attrs = list(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 = list(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 _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)

        col.entity.remove_column(col.name)

        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 #14
0
class GeoODKConverter(QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Class Constructor."""
        super(GeoODKConverter, self).__init__(parent)

        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-aut o-connect
        self.connect_action = pyqtSignal(str)
        self.setupUi(self)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.set_entity_model_view(self.entity_model)
        self.stdm_config = None
        self.parent = parent
        self.load_profiles()
        self.check_state_on()

        self.check_geoODK_path_exist()

        self.chk_all.stateChanged.connect(self.check_state_on)
        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)
        #self.btn_upload.clicked.connect(self.upload_generated_form)

        self._notif_bar_str = NotificationBar(self.vlnotification)

    def onShowOutputFolder(self):
        output_path = FORM_HOME

        # windows
        if sys.platform.startswith('win32'):
            os.startfile(output_path)

        # *nix systems
        if sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_path])

        # macOS
        if sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_path])

    def check_state_on(self):
        """
        Ensure all the items in the list are checked
        :return:
        """
        if self.entity_model.rowCount() > 0:
            for row in range(self.entity_model.rowCount()):
                item = self.entity_model.item(row)
                if self.chk_all.isChecked():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)

    def load_profiles(self):
        """
        Read and load profiles from StdmConfiguration instance
        """
        self.populate_view_models(current_profile())

    def profiles(self):
        """
        Get all profiles
        :return:
        """
        return self.load_config().values()

    def populate_view_models(self, profile):
        for entity in profile.entities.values():
            if entity.action == DbItem.DROP:
                continue

            if hasattr(entity,
                       'user_editable') and entity.TYPE_INFO <> 'VALUE_LIST':
                if entity.user_editable == False:
                    continue

            if entity.TYPE_INFO not in [
                    'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE',
                    'ADMINISTRATIVE_SPATIAL_UNIT',
                    'ENTITY_SUPPORTING_DOCUMENT', 'ASSOCIATION_ENTITY',
                    'AUTO_GENERATE_CODE'
            ]:

                if entity.TYPE_INFO == 'VALUE_LIST':
                    pass
                else:
                    self.entity_model.add_entity(entity)
        self.set_model_items_selectable()

    def set_entity_model_view(self, entity_model):
        """
        Set our list view to the default model
        :return:
        """
        self.trentities.setModel(entity_model)

    def set_model_items_selectable(self):
        """
        Ensure that the entities  are checkable
        :return:
        """
        if self.entity_model.rowCount() > 0:
            for row in range(self.entity_model.rowCount()):
                index = self.entity_model.index(row, 0)
                item_index = self.entity_model.itemFromIndex(index)
                item_index.setCheckable(True)

    def selected_entities_from_Model(self):
        """
        Get selected entities for conversion
        to Xform from the user selection
        :return:
        """
        entity_list = []
        if self.entity_model.rowCount() > 0:
            for row in range(self.entity_model.rowCount()):
                item = self.entity_model.item(row)
                if item.isCheckable() and item.checkState() == Qt.Checked:
                    entity_list.append(item.text())
        return entity_list

    def check_geoODK_path_exist(self):
        """
        Check if the geoodk paths are there in the directory
        Otherwise create them
        :return:
        """
        if not os.access(FORM_HOME, os.F_OK):
            os.makedirs(unicode(FORM_HOME))

    def upload_generated_form(self):
        """
        Upload the generated Xform file to mobile phone.
        This eliminates the process of copying the file
        manually to the mobile device
        :return:
        """
        form_uploader = FormUploader(self)
        form_uploader.exec_()

    def generate_mobile_form(self, selected_entities):
        """
        Generate mobile form based on the selected entities.
        :return:
        """
        try:
            self._notif_bar_str.clear()

            if len(selected_entities) == 0:
                self._notif_bar_str.insertErrorNotification(
                    'No entity selected. Please select at least one entity...')
                return
            if len(selected_entities) > 0:
                geoodk_writer = GeoodkWriter(selected_entities,
                                             self.str_supported)
                geoodk_writer.write_data_to_xform()
                msg = 'File saved ' \
                      'in: {}'
                self._notif_bar_str.insertInformationNotification(
                    msg.format(FORM_HOME))
        except Exception as ex:
            self._notif_bar_str.insertErrorNotification(
                ex.message + ': Unable to generate Mobile Form')
            return

    def accept(self):
        """
        Generate mobile forms based on user selected entities.
        Check if str is enabled, then ensure str tables are enabled.
        :return:
        """
        user_entities = self.selected_entities_from_Model()
        self.str_supported = False
        if self.ck_social_tenure.isChecked():
            self.str_supported = True
            str_definition = current_profile().social_tenure
            str_definition_party = str_definition.parties[0].short_name
            str_definition_spatial = str_definition.spatial_units[0].short_name
            if str_definition_party not in user_entities or str_definition_spatial not in user_entities:
                self._notif_bar_str.insertErrorNotification(
                    'One of the entities required to define str is not selected. Form not saved'
                )
                return
            #else:
            #self.generate_mobile_form(user_entities)
        #else:
        self.generate_mobile_form(user_entities)
Beispiel #15
0
 def setNotificationLayout(self,layout):
     '''
     Set the vertical layout instance that will be used to display notification messages.
     '''
     self._notifBar = NotificationBar(layout)
Beispiel #16
0
class ComposerTableDataSourceEditor(WIDGET, BASE):
    def __init__(self, composer_wrapper, frame_item, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)

        self._composer_wrapper = composer_wrapper
        if isinstance(frame_item, QgsComposerFrame):
            self._composer_table_item = frame_item.multiFrame()
        else:
            self._composer_table_item = frame_item

        self._notif_bar = NotificationBar(self.vl_notification)

        # 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 source tables
        self.ref_table.load_link_tables()

        # Connect signals
        self._composer_wrapper.dataSourceSelected.connect(self.ref_table.on_data_source_changed)
        # self.ref_table.cbo_ref_table.currentIndexChanged[str].connect(self.set_table_vector_layer)

    def composer_item(self):
        return self._composer_table_item

    def set_table_vector_layer(self, table_name):
        """
        Creates a vector layer and appends it to the composer table item.
        :param table_name: Name of the linked table containing tabular
        information.
        :type table_name: str
        """
        self._notif_bar.clear()

        if not table_name:
            return

        v_layer = vector_layer(table_name)
        if v_layer is None:
            msg = QApplication.translate("ComposerTableDataSourceEditor",
                                         "A vector layer could not be created from the table.")
            self._notif_bar.insertErrorNotification(msg)

            return

        if not v_layer.isValid():
            msg = QApplication.translate("ComposerTableDataSourceEditor",
                                         "Invalid vector layer, the table will not be added.")
            self._notif_bar.insertErrorNotification(msg)

            return

        # No need to add the layer in the legend
        QgsMapLayerRegistry.instance().addMapLayer(v_layer, False)

        if len(self.composer_item().columns()) > 0:
            self._composer_table_item.setVectorLayer(v_layer)  # _composer_table_item is QgsComposerAttributeTable
        self._composer_table_item.update()

    def configuration(self):
        from stdm.stdm.composer import TableConfiguration

        linked_table_props = self.ref_table.properties()

        table_config = TableConfiguration()
        table_config.set_linked_table(linked_table_props.linked_table)
        table_config.set_source_field(linked_table_props.source_field)
        table_config.set_linked_column(linked_table_props.linked_field)

        return table_config

    def set_configuration(self, configuration):
        # Load referenced table editor with item configuration settings.
        table_props = LinkedTableProps(linked_table=configuration.linked_table(),
                                       source_field=configuration.source_field(),
                                       linked_field=configuration.linked_field())

        self.ref_table.set_properties(table_props)
Beispiel #17
0
class ProfileEditor(QDialog, Ui_Profile):
    def __init__(self, parent):
        QDialog.__init__(self, parent)

        self.profile_name = ''
        self.desc = ''
        
        self.setupUi(self)
        self.init_controls()
        self.notice_bar = NotificationBar(self.notif_bar)
        
    def init_controls(self):
        self.edtProfile.clear()
        self.edtDesc.clear()
        self.edtProfile.setFocus()
        self.edtProfile.textChanged.connect(self.validate_text)

    def format_name(self, txt):
        ''''remove any trailing spaces in the name and replace them underscore'''
        formatted_name = txt.strip().replace(' ', "_")
        return formatted_name
    
    def add_profile(self):
        self.profile_name = self.format_name(unicode(self.edtProfile.text()))
        self.desc = unicode(self.edtDesc.text())

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        msg = self.trUtf8(message)
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(msg)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return
        locale = QSettings().value("locale/userLocale")[0:2]

        if locale == 'en':
            name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
            name_validator = QRegExpValidator(name_regex)
            text_edit.setValidator(name_validator)
            QApplication.processEvents()
            last_character = text[-1:]
            state = name_validator.validate(text, text.index(last_character))[0]
            if state != QValidator.Acceptable:
                msg = u'\'{0}\' is not allowed at this position.'.format(
                    last_character
                )
                self.show_notification(msg)
                text = text[:-1]

        # remove space and underscore at the beginning of the text
        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def accept(self):
        '''listen to user action on the dialog'''
        if self.edtProfile.text() == '' or self.edtProfile.text() == ' ':
            self.error_info_message(
                QApplication.translate(
                    "ProfileEditor", "Please enter a valid Profile name."
                )
            )
            return

        self.add_profile()
        self.done(1)

    def reject(self):
        self.done(0)
        
    def error_info_message(self, Message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(Message)
        msg.exec_()  
Beispiel #18
0
class TemplateDocumentSelector(QDialog,Ui_frmDocumentSelector):
    """
    Dialog for selecting a document template from the saved list.
    """
    def __init__(self, parent=None,selectMode=True, filter_data_source=''):
        QDialog.__init__(self,parent)
        self.setupUi(self)
        
        self.notifBar = NotificationBar(self.vlNotification)

        self._mode = selectMode

        #Filter templates by the specified table name
        self._filter_data_source = filter_data_source

        #Document templates in current profile
        self._profile_templates = []

        self._current_profile = current_profile()

        #Load current profile templates
        self._load_current_profile_templates()
        
        if selectMode:
            self.buttonBox.setVisible(True)
            self.manageButtonBox.setVisible(False)
            currHeight = self.size().height()
            self.resize(200,currHeight)
            
        else:
            self.buttonBox.setVisible(False)
            self.manageButtonBox.setVisible(True)
            self.setWindowTitle(
                QApplication.translate(
                    "TemplateDocumentSelector",
                    "Template Manager"
                )
            )
            
        #Configure manage buttons
        btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok)
        btnEdit.setText(QApplication.translate("TemplateDocumentSelector","Edit..."))
        btnEdit.setIcon(QIcon(":/plugins/stdm/images/icons/edit.png"))
        
        btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save)
        btnDelete.setText(QApplication.translate("TemplateDocumentSelector","Delete"))
        btnDelete.setIcon(QIcon(":/plugins/stdm/images/icons/delete.png"))
        
        #Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        btnEdit.clicked.connect(self.onEditTemplate)
        btnDelete.clicked.connect(self.onDeleteTemplate)
        
        #Get saved document templates then add to the model
        templates = documentTemplates()

        self._docItemModel = QStandardItemModel(parent)
        self._docItemModel.setColumnCount(2)

        #Append current profile templates to the model.
        for dt in self._profile_templates:

            if self._template_contains_filter_table(dt):
                doc_name_item = self._createDocNameItem(dt.name)
                file_path_item = QStandardItem(dt.path)
                self._docItemModel.appendRow([doc_name_item,file_path_item])

        self.lstDocs.setModel(self._docItemModel)

    def _load_current_profile_templates(self):
        # Loads only those templates that refer to tables in the current
        # profile.
        if self._current_profile is None:
            return

        #Get saved document templates then add to the model
        templates = documentTemplates()

        profile_tables = self._current_profile.table_names()

        #Get templates for the current profile
        for name, path in templates.iteritems():
            doc_temp = _DocumentTemplate.build_from_path(name, path)
            if doc_temp.data_source is None:
                continue

            #Assert data source is in the current profile
            if doc_temp.data_source.referenced_table_name in profile_tables:
                self._add_doc_temp(doc_temp)
                #self._profile_templates.append(doc_temp)

            if doc_temp.data_source._dataSourceName in user_non_profile_views():
                self._add_doc_temp(doc_temp)
                #self._profile_templates.append(doc_temp)

    def _add_doc_temp(self, doc_temp):
        found = False
        for template in self._profile_templates:
            if template.name == doc_temp.name:
                found = True
                break
        if not found:
            self._profile_templates.append(doc_temp)

    def _template_contains_filter_table(self, document_template):
        #Returns true if the template refers to the filter data source

        #If no filter data source defined then always return True

        if document_template.data_source._dataSourceName in user_non_profile_views():
            return True

        if not self._filter_data_source:
            return True

        referenced_table = document_template.referenced_table_name

        if referenced_table == self._filter_data_source:
            return True

        return False

    @property
    def mode(self):
        return self._mode

    @property
    def filter_data_source(self):
        return self._filter_data_source
        
    def _createDocNameItem(self,docName):
        """
        Create a template document standard item.
        """
        #Set icon
        icon = QIcon()
        icon.addPixmap(
            QPixmap(
                ":/plugins/stdm/images/icons/document.png"
            ),
            QIcon.Normal,
            QIcon.Off
        )
        
        dnItem = QStandardItem(icon,docName)
        
        return dnItem
    
    def onEditTemplate(self):
        """
        Slot raised to edit document template.
        """
        self.notifBar.clear()
        
        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to edit"))
            return
        
        templateName,filePath = self.documentMapping()
        
        docName,ok = QInputDialog.getText(self, \
                                              QApplication.translate("TemplateDocumentSelector","Edit Template"), \
                                              QApplication.translate("TemplateDocumentSelector","Please enter the new template name below"), \
                                              text = templateName)
        if ok and docName == "":
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Template name cannot be empty"))
            return
        
        elif docName == templateName:
            return
        
        elif ok and docName != "":
            result,newTemplatePath = self._editTemplate(filePath, docName)
            
            if result:
                #Update view
                mIndices = self._selectedMappings()
        
                docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
                filePathItem = self._docItemModel.itemFromIndex(mIndices[1])
                
                docNameItem.setText(docName)
                filePathItem.setText(newTemplatePath)
                
                self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "'{0}' template has been successfully updated".format(docName)))
                
            else:
                self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Error: '{0}' template could not be updated".format(templateName)))
            
    def onDeleteTemplate(self):
        """
        Slot raised to delete document template.
        """
        self.notifBar.clear()
        
        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to delete"))
            return
        
        templateName,filePath = self.documentMapping()
        
        result = QMessageBox.warning(self, QApplication.translate("TemplateDocumentSelector", \
                                                                         "Confirm delete"), 
                                     QApplication.translate("TemplateDocumentSelector", \
                                                                         "Are you sure you want to delete '{0}' template?" \
                                                                         "This action cannot be undone.\nClick Yes to proceed " \
                                                                         "or No to cancel.".format(templateName)), 
                                     QMessageBox.Yes|QMessageBox.No)
        
        if result == QMessageBox.No:
            return
        
        status = self._deleteDocument(filePath)
        
        if status:
            #Remove item from list using model index row number
            selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0)
            row = selectedDocNameIndices[0].row()
            self._docItemModel.removeRow(row)
            self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "'{0}' template has been successfully removed".format(templateName)))
        
        else:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Error: '{0}' template could not be removed".format(templateName)))
    
    def onAccept(self):
        """
        Slot raised to close the dialog only when a selection has been made by the user.
        """
        self.notifBar.clear()
        
        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document"))
            return
        
        self.accept()
        
    def _selectedMappings(self):
        """
        Returns the model indices for the selected row.
        """
        selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0)
        selectedFilePathIndices = self.lstDocs.selectionModel().selectedRows(1)
        
        if len(selectedDocNameIndices) == 0:
            return None
        
        docNameIndex = selectedDocNameIndices[0]
        filePathIndex = selectedFilePathIndices[0]
        
        return (docNameIndex,filePathIndex)
    
    def documentMapping(self):
        """
        Returns a tuple containing the selected document name and the corresponding file name.
        """
        mIndices = self._selectedMappings()
        
        if mIndices == None:
            return None
        
        docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
        filePathItem = self._docItemModel.itemFromIndex(mIndices[1])
        
        return (docNameItem.text(),filePathItem.text())
    
    def _editTemplate(self,templatePath,newName):
        """
        Updates the template document to use the new name.
        """
        templateFile = QFile(templatePath)
        
        if not templateFile.open(QIODevice.ReadOnly):
            QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Open Operation Error"), \
                                            "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector","Cannot read template file."), \
                                                      templateFile.errorString()
                                                      ))
            return (False,"")
         
        templateDoc = QDomDocument()
        
        if templateDoc.setContent(templateFile):
            composerElement = templateDoc.documentElement()
            titleAttr = composerElement.attributeNode("_title")
            if not titleAttr.isNull():
                titleAttr.setValue(newName)
                
            #Try remove file
            status = templateFile.remove()
            
            if not status:
                return (False,"")
            
            #Create new file
            newTemplatePath = self._composerTemplatesPath() + "/" + newName + ".sdt"  
            newTemplateFile = QFile(newTemplatePath)
            
            if not newTemplateFile.open(QIODevice.WriteOnly):
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Save Operation Error"), \
                                                "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector","Could not save template file."), \
                                                          newTemplateFile.errorString()
                                                          ))
                return (False,"")
            
            if newTemplateFile.write(templateDoc.toByteArray()) == -1:
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Save Error"), \
                                                QApplication.translate("TemplateDocumentSelector","Could not save template file."))
                return (False,"")
            
            newTemplateFile.close()  
            
            return (True,newTemplatePath)
    
    def _deleteDocument(self,templatePath):
        """
        Delete the document template from the file system.
        """
        docFile = QFile(templatePath)
        
        return docFile.remove()
    
    def _composerTemplatesPath(self):
        """
        Reads the path of composer templates in the registry.
        """
        regConfig = RegistryConfig()
        keyName = "ComposerTemplates"
        
        valueCollection = regConfig.read([keyName])
        
        if len(valueCollection) == 0:
            return None
        
        else:
            return valueCollection[keyName]
Beispiel #19
0
class DocumentGeneratorDialog(WIDGET, BASE):
    """
    Dialog that enables a user to generate documents by using configuration
    information for different entities.
    """
    def __init__(self, iface, access_templates, parent=None, plugin=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.btnSelectTemplate.setIcon(GuiUtils.get_icon('document.png'))

        self._iface = iface
        self.plugin = plugin
        self._docTemplatePath = ""
        self._outputFilePath = ""
        self.curr_profile = current_profile()
        self.last_data_source = None
        self._config_mapping = OrderedDict()

        self._notif_bar = NotificationBar(self.vlNotification)

        self._doc_generator = DocumentGenerator(self._iface, self)

        self._data_source = ""

        self.access_templates = access_templates

        enable_drag_sort(self.lstDocNaming)

        # Configure generate button
        generateBtn = self.buttonBox.button(QDialogButtonBox.Ok)
        if not generateBtn is None:
            generateBtn.setText(
                QApplication.translate("DocumentGeneratorDialog", "Generate"))

        # Load supported image types
        supportedImageTypes = QImageWriter.supportedImageFormats()
        for imageType in supportedImageTypes:
            imageTypeStr = imageType.data().decode()
            self.cboImageType.addItem(imageTypeStr)

        self._init_progress_dialog()
        # Connect signals
        self.btnSelectTemplate.clicked.connect(self.onSelectTemplate)
        self.buttonBox.accepted.connect(self.onGenerate)
        self.chkUseOutputFolder.stateChanged.connect(
            self.onToggledOutputFolder)
        self.rbExpImage.toggled.connect(self.onToggleExportImage)
        self.tabWidget.currentChanged.connect(self.on_tab_index_changed)
        self.chk_template_datasource.stateChanged.connect(
            self.on_use_template_datasource)

        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)

    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_entity_configuration(self, **kwargs):
        ent_config = EntityConfig(**kwargs)
        self.add_entity_config(ent_config)

    def add_entity_config(self, ent_config, progress_value=0):
        QApplication.processEvents()
        if not self._config_mapping.get(ent_config.title(), ""):
            fk_mapper = self._create_fk_mapper(ent_config)
            self.tabWidget.addTab(fk_mapper, ent_config.title())
            self._config_mapping[ent_config.title()] = ent_config

            # Force list of column names to be loaded
            if self.tabWidget.currentIndex() != 0:
                self.tabWidget.setCurrentIndex(0)

            else:
                self.on_tab_index_changed(0)

        self.progress.setValue(progress_value)

    def on_tab_index_changed(self, index):
        if index == -1:
            return

        config = self.config(index)

        if not config is None:
            # Set data source name
            self._data_source = config.data_source()

    def on_use_template_datasource(self, state):
        if state == Qt.Checked:
            self.tabWidget.setEnabled(False)
            self.chkUseOutputFolder.setEnabled(False)
            self.chkUseOutputFolder.setChecked(False)

        elif state == Qt.Unchecked:
            self.tabWidget.setEnabled(True)
            self.chkUseOutputFolder.setEnabled(True)
            self.chkUseOutputFolder.setChecked(False)
            self.on_tab_index_changed(self.tabWidget.currentIndex())

    def onShowOutputFolder(self):
        reg_config = RegistryConfig()
        path = reg_config.read([COMPOSER_OUTPUT])
        output_path = path.get(COMPOSER_OUTPUT, '')

        # windows
        if sys.platform.startswith('win32'):
            os.startfile(output_path)

        # *nix systems
        if sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_path])

        # macOS
        if sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_path])

    def notification_bar(self):
        """
        :return: Returns an instance of the notification bar.
        :rtype: NotificationBar
        """
        return self._notif_bar

    def config(self, index):
        """
        Returns the configuration for the current mapper in the tab widget.
        """
        tab_key = self.tabWidget.tabText(index)

        return self._config_mapping.get(tab_key, None)

    def current_config(self):
        """
        Returns the configuration corresponding to the current widget in the
        tab.
        """
        return self.config(self.tabWidget.currentIndex())

    def _load_model_columns(self, config):
        """
        Load model columns into the view for specifying file output name.
        Only those columns of display type variants will be
        used.
        """
        model_attr_mapping = OrderedDict()

        for c in config.data_source_columns():
            model_attr_mapping[c] = format_name(c)

        self.lstDocNaming.load_mapping(model_attr_mapping)

    def _load_data_source_columns(self, entity):
        """
        Load the columns of a data source for use in the file naming.
        """
        table_cols = entity_display_columns(entity, True)

        attr_mapping = OrderedDict()

        for c, header in table_cols.items():
            attr_mapping[c] = header

        self.lstDocNaming.load_mapping(attr_mapping)

    def _create_fk_mapper(self, config):
        fk_mapper = ForeignKeyMapper(config.ds_entity,
                                     self.tabWidget,
                                     self._notif_bar,
                                     enable_list=True,
                                     can_filter=True,
                                     plugin=self.plugin)

        fk_mapper.setDatabaseModel(config.model())
        fk_mapper.setSupportsList(True)
        fk_mapper.setDeleteonRemove(False)
        fk_mapper.setNotificationBar(self._notif_bar)

        return fk_mapper

    def onSelectTemplate(self):
        """
        Slot raised to load the template selector dialog.
        """
        current_config = self.current_config()
        if current_config is None:
            msg = QApplication.translate(
                'DocumentGeneratorDialog',
                'An error occured while trying to determine the data source '
                'for the current entity.\nPlease check your current profile '
                'settings.')
            QMessageBox.critical(
                self,
                QApplication.translate('DocumentGeneratorDialog',
                                       'Template Selector'), msg)
            return

        # Set the template selector to only load those templates that
        # reference the current data source.
        filter_table = current_config.data_source()
        templateSelector = TemplateDocumentSelector(
            self,
            filter_data_source=filter_table,
            access_templates=self.access_templates)

        if templateSelector.exec_() == QDialog.Accepted:
            docName, docPath = templateSelector.documentMapping()

            self.lblTemplateName.setText(docName)
            self._docTemplatePath = docPath
            if filter_table != self.last_data_source:
                # Load template data source fields
                self._load_template_datasource_fields()

    def _load_template_datasource_fields(self):
        # If using template data source
        template_doc, err_msg = self._doc_generator.template_document(
            self._docTemplatePath)
        if template_doc is None:
            QMessageBox.critical(
                self, "Error Generating documents",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))

            return

        composer_ds, err_msg = self._doc_generator.composer_data_source(
            template_doc)

        if composer_ds is None:
            QMessageBox.critical(
                self, "Error Generating documents",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))

            return

        # Load data source columns
        self._data_source = self.current_config().data_source()

        self.ds_entity = self.curr_profile.entity_by_name(self._data_source)

        self._load_data_source_columns(self.ds_entity)

    def onToggledOutputFolder(self, state):
        """
        Slot raised to enable/disable the generated output documents to be
        written to the plugin composer output folder using the specified
        naming convention.
        """
        if state == Qt.Checked:
            self.gbDocNaming.setEnabled(True)

        elif state == Qt.Unchecked:
            self.gbDocNaming.setEnabled(False)

    def reset(self, success_status=False):
        """
        Clears/resets the dialog from user-defined options.
        """
        self._docTemplatePath = ""

        self._data_source = ""

        self.lblTemplateName.setText("")
        # reset form only if generation is successful
        if success_status:
            fk_table_view = self.tabWidget.currentWidget(). \
                findChild(QTableView)
            while fk_table_view.model().rowCount() > 0:
                fk_table_view.model().rowCount(0)
                fk_table_view.model().removeRow(0)

            if self.tabWidget.count() > 0 and \
                    not self.chk_template_datasource.isChecked():
                self.on_tab_index_changed(0)

            if self.cboImageType.count() > 0:
                self.cboImageType.setCurrentIndex(0)

    def onToggleExportImage(self, state):
        """
        Slot raised to enable/disable the image formats combobox.
        """
        if state:
            self.cboImageType.setEnabled(True)

        else:
            self.cboImageType.setEnabled(False)

    def onGenerate(self):
        """
        Slot raised to initiate the certificate generation process.
        """
        self._notif_bar.clear()
        success_status = True
        config = self.current_config()
        self.last_data_source = config.data_source()
        if config is None:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                           "The entity configuration could not be extracted."))
            return

        # Get selected records and validate
        records = self.tabWidget.currentWidget().entities()

        if self.chk_template_datasource.isChecked():
            records = self._dummy_template_records()

        if len(records) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                           "Please load at least one entity record"))
            return

        if not self._docTemplatePath:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                           "Please select a document template to use"))
            return

        documentNamingAttrs = self.lstDocNaming.selectedMappings()

        if self.chkUseOutputFolder.checkState() == Qt.Checked and len(
                documentNamingAttrs) == 0:
            self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \
                                                                           "Please select at least one field for naming the output document"))

            return

        # Set output file properties
        if self.rbExpImage.isChecked():
            outputMode = DocumentGenerator.Image
            fileExtension = self.cboImageType.currentText()
            saveAsText = "Image File"

        else:
            outputMode = DocumentGenerator.PDF
            fileExtension = "pdf"
            saveAsText = "PDF File"

        # Show save file dialog if not using output folder
        if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
            docDir = source_document_location()

            if self._outputFilePath:
                fileInfo = QFileInfo(self._outputFilePath)
                docDir = fileInfo.dir().path()

            self._outputFilePath, _ = QFileDialog.getSaveFileName(
                self,
                QApplication.translate("DocumentGeneratorDialog",
                                       "Save Document"), docDir,
                "{0} (*.{1})".format(
                    QApplication.translate("DocumentGeneratorDialog",
                                           saveAsText), fileExtension))

            if not self._outputFilePath:
                self._notif_bar.insertErrorNotification(
                    QApplication.translate(
                        "DocumentGeneratorDialog",
                        "Process aborted. No output file was specified."))

                return

            # Include extension in file name
            self._outputFilePath = self._outputFilePath  # + "." + fileExtension

        # else:
        # Multiple files to be generated.
        # pass

        self._doc_generator.set_link_field(config.link_field())

        self._doc_generator.clear_attr_value_formatters()

        if not self.chk_template_datasource.isChecked():
            # Apply cell formatters for naming output files
            self._doc_generator.set_attr_value_formatters(config.formatters())

        entity_field_name = "id"

        # Iterate through the selected records
        progressDlg = QProgressDialog(self)
        progressDlg.setMaximum(len(records))

        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

            for i, record in enumerate(records):
                progressDlg.setValue(i)

                if progressDlg.wasCanceled():
                    success_status = False
                    break

                # User-defined location
                if self.chkUseOutputFolder.checkState() == Qt.Unchecked:
                    status, msg = self._doc_generator.run(
                        self._docTemplatePath,
                        entity_field_name,
                        record.id,
                        outputMode,
                        data_source=self.ds_entity.name,
                        filePath=self._outputFilePath)
                    self._doc_generator.clear_temporary_layers()
                # Output folder location using custom naming
                else:

                    status, msg = self._doc_generator.run(
                        self._docTemplatePath,
                        entity_field_name,
                        record.id,
                        outputMode,
                        dataFields=documentNamingAttrs,
                        fileExtension=fileExtension,
                        data_source=self.ds_entity.name)
                    self._doc_generator.clear_temporary_layers()

                if not status:
                    result = QMessageBox.warning(
                        self,
                        QApplication.translate("DocumentGeneratorDialog",
                                               "Document Generate Error"), msg,
                        QMessageBox.Ignore | QMessageBox.Abort)

                    if result == QMessageBox.Abort:
                        progressDlg.close()
                        success_status = False

                        # Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                    # If its the last record and user has selected to ignore
                    if i + 1 == len(records):
                        progressDlg.close()
                        success_status = False

                        # Restore cursor
                        QApplication.restoreOverrideCursor()

                        return

                else:
                    progressDlg.setValue(len(records))

            QApplication.restoreOverrideCursor()

            QMessageBox.information(
                self,
                QApplication.translate("DocumentGeneratorDialog",
                                       "Document Generation Complete"),
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Document generation has successfully completed."))

        except DummyException as ex:
            LOGGER.debug(str(ex))
            err_msg = sys.exc_info()[1]
            QApplication.restoreOverrideCursor()

            QMessageBox.critical(
                self, "STDM",
                QApplication.translate(
                    "DocumentGeneratorDialog",
                    "Error Generating documents - %s" % (err_msg)))
            success_status = False

        # Reset UI
        self.reset(success_status)

    def _dummy_template_records(self):
        """
        This is applied when records from a template data source are to be
        used to generate the documents where no related entity will be used
        to filter matching records in the data source. The iteration of the
        data source records will be done internally within the
        DocumentGenerator class.
        """
        class _DummyRecord:
            id = 1

        return [_DummyRecord()]

    def showEvent(self, event):
        """
        Notifies if there are not entity configuration objects defined.
        :param event: Window event
        :type event: QShowEvent
        """
        QTimer.singleShot(500, self.check_entity_config)

        return QDialog.showEvent(self, event)

    def check_entity_config(self):
        if len(self._config_mapping) == 0:
            self._notif_bar.clear()

            msg = QApplication.translate(
                "DocumentGeneratorDialog", "Table "
                "configurations do not exist or have not been configured properly"
            )
            self._notif_bar.insertErrorNotification(msg)
Beispiel #20
0
class EntityEditor(WIDGET, BASE):
    """
    Dialog to add and edit entities
    """
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :param profile : current profile
        :param entity : current entity
        :param in_db : Boolean flag to check if entity exist in database
        """
        self.form_parent = kwargs.get('parent', self)
        self.profile = kwargs.get('profile', None)
        self.entity = kwargs.get('entity', None)
        self.in_db = kwargs.get('in_db', False)

        QDialog.__init__(self, self.form_parent)
        self.setupUi(self)
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui_controls()

    def init_gui_controls(self):
        self.edtTable.setFocus()
        self.setTabOrder(self.edtTable, self.edtDesc)
        if self.entity:
            self.edtTable.setText(self.entity.short_name)
            self.edtDesc.setText(self.entity.description)
            self.txt_display_name.setText(self.entity.label)

            self.cbSupportDoc.setCheckState(
                self.bool_to_check(self.entity.supports_documents))

            if self.entity.supports_documents and self.supporting_document_exists(
            ):
                self.cbSupportDoc.setEnabled(False)

        self.edtTable.textChanged.connect(self.validate_text)
        self.edtTable.setEnabled(not self.in_db)

    def supporting_document_exists(self):
        sd_name = '{0}_{1}_{2}'.format(self.profile.prefix,
                                       self.entity.short_name.lower(),
                                       'supporting_document')
        return pg_table_exists(sd_name)

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """

        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(message)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return
        locale = (QSettings().value("locale/userLocale") or 'en-US')[0:2]

        name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
        name_validator = QRegExpValidator(name_regex)
        text_edit.setValidator(name_validator)
        QApplication.processEvents()
        last_character = text[-1:]
        state = name_validator.validate(text, text.index(last_character))[0]
        msg = QApplication.translate('EntityEditor',
                                     'is not allowed at this position.')

        if state != QValidator.Acceptable:
            self.show_notification('"{}" {}'.format(last_character, msg))
            text = text[:-1]

        # remove space and underscore at the beginning of the text
        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def bool_to_check(self, state):
        """
        Returns a check state given a boolean value
        :param state : Boolean value
        :type state: Boolean
        """
        if state:
            return Qt.Checked
        else:
            return Qt.Unchecked

    def accept(self):
        if self.edtTable.text() == '' or self.edtTable.text() == ' ':
            self.show_message(self.tr("Please enter a valid entity name."))
            return

        sn = str(self.edtTable.text().strip())
        short_name = sn[0].upper() + sn[1:]

        if self.entity is None:  # New entity
            if self.duplicate_check(short_name):
                self.show_message(
                    self.tr("Entity with the same name already "
                            "exist in the current profile!"))
                return
            else:
                self.add_entity(short_name)
                self.done(0)
        else:
            self.edit_entity(short_name)
            self.done(1)

    def add_entity(self, short_name):
        """
        Creates and adds a new entity to a profile
        :param entity_name: name of the new entity
        :type entity_name: str
        """
        self.entity = self._create_entity(short_name)
        self.profile.add_entity(self.entity)
        # return True

    def _create_entity(self, short_name):
        entity = self.profile.create_entity(
            short_name, entity_factory, supports_documents=self.support_doc())
        entity.description = self.edtDesc.text()
        entity.label = self.txt_display_name.text()
        entity.column_added.connect(self.form_parent.add_column_item)
        entity.column_removed.connect(self.form_parent.delete_column_item)

        return entity

    def edit_entity(self, short_name):
        # remove old entity
        old_short_name = self.entity.short_name

        if old_short_name != short_name:
            status = self.profile.rename(old_short_name, short_name)
            self.entity.short_name = short_name

        self.entity.description = self.edtDesc.text()
        self.entity.label = self.txt_display_name.text()
        self.entity.supports_documents = self.support_doc()

    def duplicate_check(self, name):
        """
        Return True if we have an entity in the current profile with same 'name'
        :param name: entity short_name
        :type name: str
        """
        return name in self.profile.entities

    def support_doc(self):
        """
        Return boolean value representing the check state of supporting
        document checkbox
        """
        values = [False, None, True]
        cs = values[self.cbSupportDoc.checkState()]
        return cs

    def format_internal_name(self, short_name):
        """
        Returns a table name used internally by the entity
        :param short_name: Entity name entered by user
        :type short_name: str
        :rtype: str
        """
        name = str(short_name).strip()
        name = name.replace(' ', "_")
        name = name.lower()
        # Ensure prefix is not duplicated in the names
        prfx = self.profile.prefix
        prefix_idx = name.find(prfx, 0, len(prfx))

        # If there is no prefix then append
        if prefix_idx == -1:
            name = '{0}_{1}'.format(self.profile.prefix, name)

        return name

    def reject(self):
        self.done(0)

    def show_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(message)
        msg.exec_()
Beispiel #21
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 #22
0
class ProfileEditor(QDialog, Ui_Profile):
    def __init__(self, parent):
        QDialog.__init__(self, parent)

        self.profile_name = ''
        self.desc = ''

        self.setupUi(self)
        self.init_controls()
        self.notice_bar = NotificationBar(self.notif_bar)

    def init_controls(self):
        self.edtProfile.clear()
        self.edtDesc.clear()
        self.edtProfile.setFocus()
        self.edtProfile.textChanged.connect(self.validate_text)

    def format_name(self, txt):
        ''''remove any trailing spaces in the name and replace them underscore'''
        formatted_name = txt.strip().replace(' ', "_")
        return formatted_name

    def add_profile(self):
        self.profile_name = self.format_name(unicode(self.edtProfile.text()))
        self.desc = unicode(self.edtDesc.text())

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        msg = self.trUtf8(message)
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(msg)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return
        locale = QSettings().value("locale/userLocale")[0:2]

        if locale == 'en':
            name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
            name_validator = QRegExpValidator(name_regex)
            text_edit.setValidator(name_validator)
            QApplication.processEvents()
            last_character = text[-1:]
            state = name_validator.validate(text,
                                            text.index(last_character))[0]
            if state != QValidator.Acceptable:
                msg = u'\'{0}\' is not allowed at this position.'.format(
                    last_character)
                self.show_notification(msg)
                text = text[:-1]

        # remove space and underscore at the beginning of the text
        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def accept(self):
        '''listen to user action on the dialog'''
        if self.edtProfile.text() == '' or self.edtProfile.text() == ' ':
            self.error_info_message(
                QApplication.translate("ProfileEditor",
                                       "Please enter a valid Profile name."))
            return

        self.add_profile()
        self.done(1)

    def reject(self):
        self.done(0)

    def error_info_message(self, Message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(Message)
        msg.exec_()
class ProfileInstanceRecords(QDialog, FORM_CLASS):
    """
    class constructor
    The class handles all the instances that the user has collected
    and saved in the folder and saved in a computer. The class will
    construct the path to the folder and enumerate all available instances
    and return the count. It will also rename all the file based on instance
    unique GUUID for easier management and future updates.
    """
    def __init__(self, parent=None):
        """
        initailize class variables here
        """
        super(ProfileInstanceRecords, self).__init__(parent)
        self.setupUi(self)

        self.path = None
        self.instance_list = []
        self.relations = {}
        self.parent_ids = {}
        self.importlogger = ImportLogger()
        self._notif_bar_str = NotificationBar(self.vlnotification)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.uuid_extractor = InstanceUUIDExtractor(self.path)
        self.btn_chang_dir.setIcon(
            QIcon(":/plugins/stdm/images/icons/open_file.png"))
        self.btn_refresh.setIcon(
            QIcon(":/plugins/stdm/images/icons/update.png"))
        self.btn_srid.setIcon(QIcon(":/plugins/stdm/images/icons/edit24.png"))

        self.chk_all.stateChanged.connect(self.check_state_on)
        #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed)
        self.btn_chang_dir.clicked.connect(self.on_directory_search)
        self.lst_widget.itemClicked.connect(self.user_selected_entities)
        self.btn_srid.clicked.connect(self.projection_settings)
        self.btn_refresh.clicked.connect(self.update_files_with_custom_filter)

        #self.load_config()
        self.on_filepath()
        self.current_profile_changed()
        self.check_state_on()
        self.instance_dir()

    def load_config(self):
        """
        Load STDM configuration
        :return:
        """
        stdm_config = None
        if QFile.exists(HOME + "/stdm/configuration.stc"):
            stdm_config = QFile(CONFIG_FILE)
        ConfigurationFileSerializer(stdm_config)
        profiles = StdmConfiguration.instance().profiles
        return profiles

    def check_state_on(self):
        """
        Ensure all the items in the list are checked
        :return:
        """
        count = self.lst_widget.count()
        if count > 0:
            for i in range(count):
                item = self.lst_widget.item(i)
                if self.chk_all.isChecked():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)

    def profiles(self):
        """
        Get all profiles
        :return:
        """
        return self.load_config().values()

    def current_profile_changed(self):
        """
        Get the current profile so that it is the one selected at the combo box
        :return:
        """
        self.instance_list = []
        self.active_profile()
        self.on_filepath()
        self.available_records()
        self.on_dir_path()
        self.profile_instance_entities()

    def active_profile(self):
        """
        get the user selected profile
        :return:p
        """
        self.profile = current_profile().name
        return self.profile

    def instance_dir(self):
        """
        Create a path where imported instance will be kept
        :return:
        """
        self.inst_path = self.path + "imported_instance"
        if not os.access(self.inst_path, os.F_OK):
            os.makedirs(unicode(self.inst_path))
        else:
            return self.inst_path

    def instance_path(self):
        """
        :return:
        """
        self.instance_dir()
        return self.inst_path

    def on_filepath(self):
        """
        Access the file directory with geoodk files by constructing the full path
        :return: path
        :rtype: string
        """
        if self.txt_directory.text() != '':
            self.path = self.txt_directory.text()
        else:
            self.path = GEOODK_FORM_HOME
            if not os.access(self.path, os.F_OK):
                os.makedirs(unicode(self.path))
            self.txt_directory.setText(self.path)
        return self.path

    def xform_xpaths(self):
        """
        Return the full path to the default config path and filter geoodk
        instance that matches the current profile path
        :return: directories
        :rtype: list
        """
        dirs = []
        return [
            os.path.join(self.path, name) for name in os.listdir(self.path)
            if os.path.isdir(os.path.join(self.path, name))
            if name.startswith(self.profile_formater())
        ]

    def on_dir_path(self):
        """
        Extract the specific folder information and rename the file
        :return:
        """
        self.uuid_extractor.new_list = []
        if self.record_count() > 0:
            directories = self.xform_xpaths()
            for directory in directories:
                self.extract_guuid_and_rename_file(directory)

    def extract_guuid_and_rename_file(self, path):
        """
        Extract teh unique Guuid and rename the file
        so that we can uniquely identify each file
        :return:
        """
        for f in os.listdir(path):
            if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'):
                file_instance = os.path.join(path, f)
                self.rename_file_to_UUID(file_instance)

    def rename_file_to_UUID(self, file):
        """
        Extract the UUID from each folder and file
        :return:
        """
        self.uuid_extractor.set_file_path(file)
        self.uuid_extractor.on_file_passed()
        self.instance_list = self.uuid_extractor.new_list

    def move_imported_file(self, file):
        """
        Moves the imported files to avoid repetition
        :return:
        """
        instance_path = self.instance_path()
        try:
            basename = os.path.basename(os.path.dirname(file))
            if not os.path.isdir(os.path.join(self.instance_path(), basename)):
                shutil.move(os.path.dirname(file), instance_path)
            else:
                pass

        except Exception as ex:
            return ex

    def profile_instance_entities(self):
        """
        Add the user entities that are in the form to be imported into database
        into a list view widget
        :return: model
        """
        self.lst_widget.clear()
        entity_list = self.instance_entities()
        if entity_list is not None and len(entity_list) > 0:
            for entity in entity_list:
                list_widget = QListWidgetItem(
                    current_profile().entity_by_name(entity).short_name,
                    self.lst_widget)
                list_widget.setCheckState(Qt.Checked)
        else:
            return

    def user_selected_entities(self):
        """

        :return:
        """
        user_list = []
        count = self.lst_widget.count()
        if count > 0:
            for i in range(count):
                item = self.lst_widget.item(i)
                if item.checkState() == Qt.Checked:
                    user_list.append(current_profile().entity(
                        item.text()).name)
            return user_list
        else:
            return None

    def instance_entities(self):
        """
        Enumerate the entities that are in the current profile
         and also part of the form so that we are only importing relevant entities to database
        :return: entities
        """
        dirs = self.xform_xpaths()
        current_etities = []
        if len(dirs) > 0:
            dir_f = dirs[0]
            instance_file = [
                f for f in os.listdir(dir_f) if f.endswith('.xml')
            ]
            if len(instance_file) > 0:
                self.uuid_extractor.set_file_path(
                    os.path.join(dir_f, instance_file[0]))
                entity_list = self.check_profile_with_custom_name()
                for entity_name in entity_list:
                    if current_profile().entity_by_name(
                            entity_name) is not None:
                        current_etities.append(entity_name)
                if len(current_etities) > 0:
                    return current_etities

    def check_profile_with_custom_name(self):
        """
        Try extract mobile instance with custom filter name.
        Assumption is that there is a profile that bears that name
        :return:
        """
        mismatch_profile = 'Please set current profile based on the data to be imported'
        entity_attr = []
        if self.txt_filter.text() != '':
            for obj in self.profiles():
                if obj.name.startswith(self.txt_filter.text()):
                    if obj.name != current_profile().name:
                        self._notif_bar_str.insertErrorNotification(
                            mismatch_profile)
                        return
        return self.uuid_extractor.document_entities(self.profile)

    def entity_attribute_to_database(self, entity_info):
        """
        Get the user selected entities and insert tehm into database
        params: selected entities
        rtype: list
        :return:Object
        :type: dbObject
        """
        cu_obj = ''
        import_status = False
        self.txt_feedback.clear()
        self._notif_bar_str.clear()
        has_relations = self.has_foreign_keys_parent(entity_info)
        if len(self.parent_table_isselected()) > 0:
            if QMessageBox.information(
                    self,
                    QApplication.translate('GeoODKMobileSettings',
                                           " Import Warning"),
                    QApplication.translate(
                        'GeoODKMobileSettings',
                        'Some of dependent tables (entities)'
                        'which may not be part of the selected tables '
                        'I.e: {} will be imported'.format(
                            self.parent_table_isselected())),
                    QMessageBox.Ok | QMessageBox.No) == QMessageBox.No:
                return
        try:
            parents_info = []
            counter = 0
            if len(self.instance_list) > 0:
                self.pgbar.setRange(counter, len(self.instance_list))
                self.pgbar.setValue(0)
                for instance in self.instance_list:
                    import_status = False
                    counter = counter + 1
                    self.parent_ids = {}
                    entity_importer = EntityImporter(instance)
                    group_identifier = entity_importer.instance_group_id()
                    #set the geometry coordinate system
                    entity_importer.geomsetter(self.on_projection_select())
                    self.archive_this_import_file(counter, instance)
                    if has_relations:
                        #Import parents table first
                        for parent_table in self.relations.keys():
                            cu_obj = parent_table
                            if parent_table in self.instance_entities():
                                ref_id, import_status = entity_importer.process_parent_entity_import(
                                    parent_table)
                                if group_identifier:
                                    self.parent_ids[parent_table] = [
                                        ref_id, group_identifier
                                    ]
                                else:
                                    self.parent_ids[parent_table] = [
                                        ref_id, parent_table
                                    ]
                                log_timestamp = '{0} -- parent table import succeeded: {1}'\
                                    .format(parent_table, str(import_status))
                                self.log_table_entry(log_timestamp)
                                parents_info.append(parent_table)
                                if parent_table[1] in entity_info:
                                    entity_info.remove(parent_table)
                    for table in entity_info:
                        cu_obj = table
                        if table not in parents_info:
                            table_id, status = entity_importer.process_import_to_db(
                                table, self.parent_ids)
                            if table in self.parent_ids:
                                continue
                            else:
                                self.parent_ids[table] = [
                                    table_id, group_identifier
                                ]
                            self.log_table_entry(
                                " -- {0} import succeeded: ".format(cu_obj) +
                                str(status))
                    self.txt_feedback.append(
                        'saving record "{0}" to database'.format(counter))
                    if self.uuid_extractor.has_str_captured_in_instance():
                        if self.parent_ids is not None:
                            entity_importer.process_social_tenure(
                                self.parent_ids)
                            self.log_table_entry(
                                " -- saving social tenure relationship")
                    self.pgbar.setValue(counter)

                self.txt_feedback.append(
                    'Number of record successfully imported:  {}'.format(
                        counter))
            else:
                self._notif_bar_str.insertErrorNotification(
                    "No user selected entities to import")
                self.pgbar.setValue(0)
                return

        except Exception as ex:
            self.log_table_entry(
                unicode(ex.message) +
                '-- {0} import succeeded: '.format(cu_obj) +
                unicode(import_status))
            self.feedback_message(unicode(ex.message))
            return

    def has_foreign_keys_parent(self, select_entities):
        """
        Ensure we check that the table is not parent else
        import parent table first
        :return:
        """
        self.relations = {}
        str_tables = current_profile().social_tenure
        party_tbl = str_tables.parties[0].name
        sp_tbl = str_tables.spatial_units[0].name
        has_relations = False
        for table in select_entities:
            table_object = current_profile().entity_by_name(table)
            cols = table_object.columns.values()
            for col in cols:
                if col.TYPE_INFO == 'FOREIGN_KEY':
                    parent_object = table_object.columns[col.name]
                    if parent_object.parent:
                        self.relations[parent_object.parent.name] = [
                            table, col.name
                        ]
                        has_relations = True
                    else:
                        self.feedback_message(
                            'unable to read foreign key properties for "{0}"'.
                            format(parent_object.name))
                        return
        has_str_defined = self.uuid_extractor.has_str_captured_in_instance()

        if has_str_defined:

            if party_tbl not in self.relations.keys():
                self.relations[party_tbl] = [
                    'social_tenure_relationship',
                    str_tables.parties[0].short_name.lower() + '_id'
                ]
            if sp_tbl not in self.relations.keys():
                self.relations[sp_tbl] = [
                    'social_tenure_relationship',
                    str_tables.spatial_units[0].short_name.lower() + '_id'
                ]
        else:

            return has_relations

    def parent_table_isselected(self):
        """
        Take note that the user selected tables may or may not be imported
        based on parent child table relationship
        :return:
        """
        try:
            silent_list = []
            if self.user_selected_entities() > 0:
                for table in self.relations.keys():
                    if table not in self.user_selected_entities():
                        silent_list.append(table[1])
            return silent_list
        except Exception as ex:
            self._notif_bar_str.insertErrorNotification(ex.message)

    def archive_this_import_file(self, counter, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            self.importlogger.logger_sections()
            file_info = 'File instance ' + str(counter) + ' : \n' + instance
            self.importlogger.onlogger_action(file_info)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def log_table_entry(self, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            current_time = QDateTime()
            import_time = current_time.currentDateTime()
            log_entry = instance + ' ' + str(import_time.toPyDateTime())
            self.importlogger.onlogger_action(log_entry)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def check_previous_import(self):
        """
        Ensure we are importing files once
        :return:
        """
        try:
            self.importlogger.add_log_info()
            for files in self.instance_list:
                current_dir = os.path.basename(files)
                exist = self.importlogger.check_file_exist(current_dir)
                if exist:
                    self.instance_list.remove(files)
            self.txt_count.setText(str(len(self.instance_list)))
            if self.record_count() != len(self.instance_list):
                msg = 'Some files have been already imported and therefore ' \
                   'not enumerated'
                self._notif_bar_str.insertErrorNotification(msg)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def available_records(self):
        """
        Let the user know how many records have been collected and are available
         for inport process
        :return:
        """
        self.txt_count.setText(unicode(self.record_count()))

    def record_count(self):
        """
        get the count of instance dir in the selected directory
        :return: integer
        """
        return len([
            name for name in os.listdir(self.path)
            if os.path.isdir(os.path.join(self.path, name))
            if name.startswith(self.profile_formater())
        ])

    def profile_formater(self):
        """
        Format the profile name by removing underscore character
        :return:
        """
        if self.txt_filter.text() != '':
            filter_text = self.txt_filter.text()
            return filter_text
        else:
            return self.profile

    def update_files_with_custom_filter(self):
        """
        Get the new file count with the user custom filter text
        :return: file count
        """
        self.available_records()
        self.on_dir_path()
        self.profile_instance_entities()

    def projection_settings(self):
        """
        let user select the projections for the data
        :return:
        """
        project_select = ProjectionSelector(self)
        projection = project_select.loadAvailableSystems()
        self.txt_srid.setText(str(projection))

    def on_projection_select(self):
        """
        Get the selected projection and set it during data import
        :return:
        """
        vals = self.txt_srid.text().split(":")
        return vals[1]

    def on_directory_search(self):
        """
        Let the user choose the directory with instances
        :return:
        """
        home_path = 'home'
        if self.txt_directory.text() != '':
            home_path = self.txt_directory.text()

        dir_name = QFileDialog.getExistingDirectory(self, 'Open Directory',
                                                    home_path,
                                                    QFileDialog.ShowDirsOnly)
        if dir_name:
            self.txt_directory.setText(str(dir_name))
            self.current_profile_changed()
        self.check_state_on()

    def feedback_message(self, msg):
        """
        Create a dialog box to capture and display errrors related to db
        while importing data
        :param: msg
        :type: string
        :return:Qdialog
        """
        msgbox = QMessageBox()
        msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No)
        msgbox.setWindowTitle("Data Import")
        msgbox.setText(msg)
        msgbox.exec_()
        msgbox.show()
        return msgbox

    def accept(self):
        """
        Execute the import dialog once the save button has been clicked
        :return:
        """
        # if self.tab_widget.currentIndex() == 1:
        #     host = self.txt_host.text()
        #     passwd = self.txt_pass.text()
        #     db = self.cbo_dbname.currentText()
        #     user = self.txt_username.text()
        #     port = self.txt_port.text()
        #     json_extractor = JSONEXTRACTOR(user,passwd,host,port,db, self.profile)
        #     json_conn = json_extractor.create_orphan_connection()
        #     self.importlogger.onlogger_action(json_conn)
        #     self.txt_svlog.append(json_conn)
        #else:

        self.buttonBox.setEnabled(False)
        try:
            if self.lst_widget.count() < 1:
                msg = 'No mobile records could be found for the current profile'
                self._notif_bar_str.insertErrorNotification(msg)
                self.buttonBox.setEnabled(True)
                return
            entities = self.user_selected_entities()
            if len(entities) < 1:
                if QMessageBox.information(
                        self,
                        QApplication.translate('MobileForms',
                                               'Import Warning'),
                        QApplication.translate(
                            'MobileForms', 'You have not '
                            'selected any entity for import. All entities '
                            'will be imported'),
                        QMessageBox.Ok | QMessageBox.No) == QMessageBox.Ok:
                    entities = self.instance_entities()
                else:
                    return
            self.entity_attribute_to_database(entities)
            self.buttonBox.setEnabled(True)
        except Exception as ex:
            self._notif_bar_str.insertErrorNotification(ex.message)
            self.feedback_message(str(ex.message))
            self.buttonBox.setEnabled(True)
Beispiel #24
0
class ColumnEditor(QDialog, Ui_ColumnEditor):
    """
    Dialog to add/edit entity columns
    """
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param kwargs: Keyword dictionary of the following parameters;
         column  - Column you editing, None if its a new column
         entity  - Entity you are adding the column to
         profile - Current profile
         in_db   - Boolean flag to indicate if a column has been created in 
                   the database
        """

        self.form_parent = kwargs.get('parent', self)
        self.column = kwargs.get('column', None)
        self.entity = kwargs.get('entity', None)
        self.profile = kwargs.get('profile', None)
        self.in_db = kwargs.get('in_db', False)
        self.is_new = kwargs.get('is_new', True)

        QDialog.__init__(self, self.form_parent)

        self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set']

        self.EX_TYPE_INFO = [
            'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE',
            'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT',
            'VALUE_LIST', 'ASSOCIATION_ENTITY'
        ]

        self.setupUi(self)
        self.dtypes = {}

        self.type_info = ''

        # dictionary to hold default attributes for each data type
        self.type_attribs = {}
        self.init_type_attribs()

        # dictionary to act as a work area for the form fields.
        self.form_fields = {}
        self.init_form_fields()

        self.fk_entities = []
        self.lookup_entities = []

        if self.is_new:
            self.prop_set = None
        else:
            self.prop_set = True

        # the current entity should not be part of the foreign key parent table,
        # add it to the exclusion list
        self.FK_EXCLUDE.append(self.entity.short_name)

        self.type_names = \
                [unicode(name) for name in BaseColumn.types_by_display_name().keys()]

        self.cboDataType.currentIndexChanged.connect(self.change_data_type)
        self.btnColProp.clicked.connect(self.data_type_property)

        self.init_controls()

        self.notice_bar = NotificationBar(self.notif_bar)
        self.show_notification()

    def show_notification(self):
        msg = self.tr('Column names should be in lower case with no spaces.')
        self.notice_bar.clear()
        self.notice_bar.insertNotification(msg, INFORMATION)

    def init_controls(self):
        """
        Initialize GUI controls default state when the dialog window is opened.
        """
        self.popuplate_data_type_cbo()

        name_regex = QtCore.QRegExp('^[a-z][a-z0-9_]*$')
        name_validator = QtGui.QRegExpValidator(name_regex)
        self.edtColName.setValidator(name_validator)

        #if self.column:
        if not self.column is None:
            self.column_to_form(self.column)
            self.column_to_wa(self.column)

        self.edtColName.setFocus()

        self.edtColName.setEnabled(not self.in_db)
        self.cboDataType.setEnabled(not self.in_db)

        self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(
            self.cancel)

    def column_to_form(self, column):
        """
        Initializes form controls with Column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        text = column.display_name()
        self.cboDataType.setCurrentIndex(self.cboDataType.findText(text))

        self.edtColName.setText(column.name)
        self.edtColDesc.setText(column.description)
        self.edtUserTip.setText(column.user_tip)
        self.cbMandt.setChecked(column.mandatory)
        self.cbSearch.setCheckState(self.bool_to_check(column.searchable))
        self.cbUnique.setCheckState(self.bool_to_check(column.unique))
        self.cbIndex.setCheckState(self.bool_to_check(column.index))

        ti = self.current_type_info()
        ps = self.type_attribs[ti].get('prop_set', None)
        if ps is not None:
            self.type_attribs[ti]['prop_set'] = self.prop_set

    def column_to_wa(self, column):
        """
        Initialize 'work area' form_fields with column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        if column is not None:
            self.form_fields['colname'] = column.name
            self.form_fields['value'] = None
            self.form_fields['mandt'] = column.mandatory
            self.form_fields['search'] = column.searchable
            self.form_fields['unique'] = column.unique
            self.form_fields['index'] = column.index

            if hasattr(column, 'minimum'):
                self.form_fields['minimum'] = column.minimum
                self.form_fields['maximum'] = column.maximum

            if hasattr(column, 'srid'):
                self.form_fields['srid'] = column.srid
                self.form_fields['geom_type'] = column.geom_type

            if hasattr(column, 'entity_relation'):
                self.form_fields['entity_relation'] = column.entity_relation

            if hasattr(column, 'association'):
                self.form_fields[
                    'first_parent'] = column.association.first_parent
                self.form_fields[
                    'second_parent'] = column.association.second_parent

            if hasattr(column, 'min_use_current_date'):
                self.form_fields[
                    'min_use_current_date'] = column.min_use_current_date
                self.form_fields[
                    'max_use_current_date'] = column.max_use_current_date

            if hasattr(column, 'min_use_current_datetime'):
                self.form_fields['min_use_current_datetime'] = \
                        column.min_use_current_datetime
                self.form_fields['max_use_current_datetime'] = \
                        column.max_use_current_datetime

    def bool_to_check(self, state):
        """
        Converts a boolean to a Qt checkstate.
        :param state: True/False
        :type state: boolean
        :rtype: Qt.CheckState
        """
        if state:
            return Qt.Checked
        else:
            return Qt.Unchecked

    def init_form_fields(self):
        """
        Initializes work area 'form_fields' dictionary with default values.
        Used when creating a new column.
        """
        self.form_fields['colname'] = ''
        self.form_fields['value'] = None
        self.form_fields['mandt'] = False
        self.form_fields['search'] = False
        self.form_fields['unique'] = False
        self.form_fields['index'] = False
        self.form_fields['minimum'] = self.type_attribs.get('minimum', 0)
        self.form_fields['maximum'] = self.type_attribs.get('maximum', 0)
        self.form_fields['srid'] = self.type_attribs.get('srid', "")
        self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0)
        self.form_fields['in_db'] = self.in_db

        self.form_fields['entity_relation'] = \
                self.type_attribs['FOREIGN_KEY'].get('entity_relation', None)

        self.form_fields['entity_relation'] = \
                self.type_attribs['LOOKUP'].get('entity_relation', None)

        self.form_fields['first_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None)

        self.form_fields['second_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None)

        self.form_fields['min_use_current_date'] = \
                self.type_attribs['DATE'].get('min_use_current_date', None)

        self.form_fields['max_use_current_date'] = \
                self.type_attribs['DATE'].get('max_use_current_date', None)

        self.form_fields['min_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('min_use_current_datetime', None)

        self.form_fields['max_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('max_use_current_datetime', None)

    def init_type_attribs(self):
        """
        Initializes data type attributes. The attributes are used to
        set the form controls state when a particular data type is selected.
        mandt - enables/disables checkbox 'Mandatory'
        search - enables/disables checkbox 'Searchable'
        unique - enables/disables checkbox 'Unique'
        index - enables/disables checkbox 'Index'
        *property - function to execute when a data type is selected.
        """
        self.type_attribs['VARCHAR'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': True
            },
            'maximum': 30,
            'property': self.varchar_property
        }

        self.type_attribs['INT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': 0,
            'maximum': 0,
            'property': self.bigint_property
        }

        self.type_attribs['TEXT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
        }

        self.type_attribs['DOUBLE'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': True
            },
            'minimum': 0.0,
            'maximum': 0.0,
            'property': self.double_property
        }

        self.type_attribs['DATE'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': datetime.date.min,
            'maximum': datetime.date.max,
            'min_use_current_date': False,
            'max_use_current_date': False,
            'property': self.date_property
        }

        self.type_attribs['DATETIME'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': datetime.datetime.min,
            'maximum': datetime.datetime.max,
            'min_use_current_datetime': False,
            'max_use_current_datetime': False,
            'property': self.dtime_property
        }

        self.type_attribs['FOREIGN_KEY'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': None,
            'property': self.fk_property,
            'prop_set': False
        }

        self.type_attribs['LOOKUP'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': {},
            'property': self.lookup_property,
            'prop_set': False
        }

        self.type_attribs['GEOMETRY'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': False
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': True,
                'enabled_state': False
            },
            'index': {
                'check_state': True,
                'enabled_state': False
            },
            'srid': "",
            'geom_type': 0,
            'property': self.geometry_property,
            'prop_set': False
        }

        self.type_attribs['BOOL'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': False
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            }
        }

        self.type_attribs['ADMIN_SPATIAL_UNIT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': None
        }

        self.type_attribs['MULTIPLE_SELECT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'first_parent': None,
            'second_parent': self.entity,
            'property': self.multi_select_property,
            'prop_set': False
        }

    def data_type_property(self):
        """
        Executes the function assigned to the property attribute of 
        the current selected data type.
        """
        self.type_attribs[self.current_type_info()]['property']()

    def varchar_property(self):
        """
        Opens the property editor for the Varchar data type.
        If successfull, set a minimum column in work area 'form fields'
        """

        editor = VarcharProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['maximum'] = editor.max_len()

    def bigint_property(self):
        """
        Opens a property editor for the BigInt data type.
        """
        editor = BigintProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()

    def double_property(self):
        """
        Opens a property editor for the Double data type.
        """
        editor = DoubleProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()

    def date_property(self):
        """
        Opens a property editor for the Date data type.
        """
        editor = DateProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_date'] = \
                    editor.min_use_current_date
            self.form_fields['max_use_current_date'] = \
                    editor.max_use_current_date

    def dtime_property(self):
        """
        Opens a property editor for the DateTime data type.
        """
        editor = DTimeProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_datetime'] = \
                    editor.min_use_current_datetime
            self.form_fields['max_use_current_datetime'] = \
                    editor.max_use_current_datetime

    def geometry_property(self):
        """
        Opens a property editor for the Geometry data type.
        If successfull, set the srid(projection), geom_type (LINE, POLYGON...)
        and prop_set which is boolean flag to verify that all the geometry
        properties are set. 
        Constraint - If 'prop_set' is False column cannot be saved.
        """
        editor = GeometryProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['srid'] = editor.coord_sys()
            self.form_fields['geom_type'] = editor.geom_type()
            self.property_set()

    def admin_spatial_unit_property(self):
        """
        Sets entity relation property used when creating column of type
        ADMIN_SPATIAL_UNIT
        """
        er_fields = {}
        er_fields['parent'] = self.entity
        er_fields['parent_column'] = None
        er_fields['display_columns'] = []
        er_fields['child'] = None
        er_fields['child_column'] = None
        self.form_fields['entity_relation'] = EntityRelation(
            self.profile, **er_fields)

    def fk_property(self):
        """
        Opens a property editor for the ForeignKey data type.
        """
        if len(self.edtColName.displayText()) == 0:
            self.show_message("Please enter column name!")
            return

        # filter list of lookup tables, don't show internal
        # tables in list of lookups
        fk_ent = [entity for entity in self.profile.entities.items() \
                if entity[1].TYPE_INFO not in self.EX_TYPE_INFO]

        fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \
                not in self.FK_EXCLUDE]

        relation = {}
        relation['form_fields'] = self.form_fields
        relation['fk_entities'] = fk_ent
        relation['profile'] = self.profile
        relation['entity'] = self.entity
        relation['column_name'] = unicode(self.edtColName.text())

        editor = FKProperty(self, relation)
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            self.property_set()

    def lookup_property(self):
        """
        Opens a lookup type property editor
        """
        editor = LookupProperty(self, self.form_fields, profile=self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            self.property_set()

    def multi_select_property(self):
        """
        Opens a multi select property editor
        """
        if len(self.edtColName.displayText()) == 0:
            self.show_message("Please enter column name!")
            return

        editor = MultiSelectProperty(self, self.form_fields, self.entity,
                                     self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['first_parent'] = editor.lookup()
            self.form_fields['second_parent'] = self.entity
            self.property_set()

    def create_column(self):
        """
        Creates a new BaseColumn.
        """
        column = None

        if self.type_info <> "":
            if self.type_info == 'ADMIN_SPATIAL_UNIT':
                self.admin_spatial_unit_property()
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity, **self.form_fields)
                return column

            if self.is_property_set(self.type_info):
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity,
                                self.form_fields['geom_type'],
                                self.entity, **self.form_fields)
            else:
                self.show_message(self.tr('Please set column properties.'))
                return
        else:
            raise self.tr("No type to create!")

        return column

    def property_set(self):
        self.prop_set = True
        self.type_attribs[self.current_type_info()]['prop_set'] = True

    def is_property_set(self, ti):
        """
        Checks if column property is set by reading the value of
        attribute 'prop_set'
        :param ti: Type info to check for prop set
        :type ti: BaseColumn.TYPE_INFO
        :rtype: boolean
        """
        return self.type_attribs[ti].get('prop_set', True)

        #if self.prop_set is None:
        #return self.type_attribs[ti].get('prop_set', True)
        #else:
        #return self.prop_set

    def property_by_name(self, ti, name):
        try:
            return self.dtype_property(ti)['property'][name]
        except:
            return None

    def popuplate_data_type_cbo(self):
        """
        Fills the data type combobox widget with BaseColumn type names
        """
        self.cboDataType.clear()
        self.cboDataType.insertItems(0,
                                     BaseColumn.types_by_display_name().keys())
        self.cboDataType.setCurrentIndex(0)

    def change_data_type(self, index):
        """
        Called by type combobox when you select a different data type.
        """

        #ti = self.current_type_info()
        #if ti=='':
        #return

        text = self.cboDataType.itemText(index)
        ti = BaseColumn.types_by_display_name()[text].TYPE_INFO

        self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property'))
        self.type_info = ti
        opts = self.type_attribs[ti]
        self.set_optionals(opts)
        self.set_min_max_defaults(ti)

        #self.column_to_form(self.column, text)
        #self.column_to_wa(self.column)

    def set_optionals(self, opts):
        """
        Enable/disables form controls based on selected 
        column data type attributes
        param opts: Dictionary type properties of selected column
        type opts: dict
        """
        self.cbMandt.setEnabled(opts['mandt']['enabled_state'])
        self.cbSearch.setEnabled(opts['search']['enabled_state'])
        self.cbUnique.setEnabled(opts['unique']['enabled_state'])
        self.cbIndex.setEnabled(opts['index']['enabled_state'])

        self.cbMandt.setCheckState(
            self.bool_to_check(opts['mandt']['check_state']))
        self.cbSearch.setCheckState(
            self.bool_to_check(opts['search']['check_state']))
        self.cbUnique.setCheckState(
            self.bool_to_check(opts['unique']['check_state']))
        self.cbIndex.setCheckState(
            self.bool_to_check(opts['index']['check_state']))

    def set_min_max_defaults(self, type_info):
        """
        sets the work area 'form_fields' default values (minimum/maximum)
        from the column's type attribute dictionary
        :param type_info: BaseColumn.TYPE_INFO
        :type type_info: str
        """
        self.form_fields['minimum'] = \
                self.type_attribs[type_info].get('minimum', 0)

        self.form_fields['maximum'] = \
                self.type_attribs[type_info].get('maximum', 0)

    def current_type_info(self):
        """
        Returns a TYPE_INFO of a data type
        :rtype: str
        """
        text = self.cboDataType.itemText(self.cboDataType.currentIndex())
        try:
            return BaseColumn.types_by_display_name()[text].TYPE_INFO
        except:
            return ''

    def fill_work_area(self):
        """
        Sets work area 'form_fields' with form control values
        """
        self.form_fields['colname'] = unicode(self.edtColName.text())
        self.form_fields['description'] = unicode(self.edtColDesc.text())
        self.form_fields['index'] = self.cbIndex.isChecked()
        self.form_fields['mandatory'] = self.cbMandt.isChecked()
        self.form_fields['searchable'] = self.cbSearch.isChecked()
        self.form_fields['unique'] = self.cbUnique.isChecked()
        self.form_fields['user_tip'] = unicode(self.edtUserTip.text())

    def show_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM"))
        msg.setText(message)
        msg.exec_()

    def accept(self):
        col_name = unicode(self.edtColName.text()).strip()
        # column name is not empty
        if len(col_name) == 0:
            self.show_message(self.tr('Please enter the column name!'))
            return False

        # check for STDM reserved keywords
        if col_name in RESERVED_KEYWORDS:
            self.show_message(self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\
                "Please choose another column name.".format(col_name)) )
            return False

        new_column = self.make_column()

        if new_column is None:
            LOGGER.debug("Error creating column!")
            self.show_message('Unable to create column!')
            return False

        if self.column is None:  # new column
            if self.duplicate_check(col_name):
                self.show_message(
                    self.tr("Column with the same name already "
                            "exist in this entity!"))
                return False

            self.entity.add_column(new_column)
            self.done(1)
        else:  # editing a column
            self.column = new_column
            self.done(1)

    def cancel(self):
        self.done(0)

    def make_column(self):
        """
        Returns a newly created column
        :rtype: BaseColumn
        """
        self.fill_work_area()
        col = self.create_column()
        return col

    def duplicate_check(self, name):
        """
        Return True if we have a column in the current entity with same name
        as our new column
        :param col_name: column name
        :type col_name: str
        """
        # check if another column with the same name exist in the current entity
        if self.entity.columns.has_key(name):
            return True
        else:
            return False

    def rejectAct(self):
        self.done(0)
Beispiel #25
0
class ColumnEditor(QDialog, Ui_ColumnEditor):
    """
    Dialog to add/edit entity columns
    """
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param kwargs: Keyword dictionary of the following parameters;
         column  - Column you editing, None if its a new column
         entity  - Entity you are adding the column to
         profile - Current profile
         in_db   - Boolean flag to indicate if a column has been created in 
                   the database
         auto_add- True to automatically add a new column to the entity, 
                   default is False.
        """
        
        self.form_parent = kwargs.get('parent', self)
        self.column  = kwargs.get('column', None)
        self.entity  = kwargs.get('entity', None)
        self.profile = kwargs.get('profile', None)
        self.in_db = kwargs.get('in_db', False)
        self.is_new = kwargs.get('is_new', True)
        self.auto_entity_add = kwargs.get('auto_add', False)

        QDialog.__init__(self, self.form_parent)

        self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set']

        self.EX_TYPE_INFO =  ['SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 
                'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT',
                'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED']

        self.setupUi(self)
        self.dtypes = {}

        self.type_info = ''
        
        # dictionary to hold default attributes for each data type
        self.type_attribs = {}
        self.init_type_attribs()

        # dictionary to act as a work area for the form fields.
        self.form_fields = {}
        self.init_form_fields()

        self.fk_entities = []
        self.lookup_entities = []

        # Exclude column type info in the list
        self._exclude_col_type_info = []

        if self.is_new:
            self.prop_set = None  # why not False??
        else:
            self.prop_set = True

        # the current entity should not be part of the foreign key parent table,
        # add it to the exclusion list
        self.FK_EXCLUDE.append(self.entity.short_name)

        self.type_names = \
                [unicode(name) for name in BaseColumn.types_by_display_name().keys()]

        self.cboDataType.currentIndexChanged.connect(self.change_data_type)
        self.btnColProp.clicked.connect(self.data_type_property)
        self.edtColName.textChanged.connect(self.validate_text)

        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_controls()

    def exclude_column_types(self, type_info):
        """
        Exclude the column types with the given type_info.
        :param type_info: List of TYPE_INFO of columns to exclude.
        :type type_info: list
        """
        self._exclude_col_type_info = type_info

        # Block index change signal of combobox
        self.cboDataType.blockSignals(True)

        # Reload column data types
        self.populate_data_type_cbo()

        # Select column type if it had been specified
        if not self.column is None:
            text = self.column.display_name()
            self.cboDataType.setCurrentIndex(self.cboDataType.findText(text))

        # Re-enable signals
        self.cboDataType.blockSignals(False)

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(message)

    def _column_type_info(self, column):
        """
        Check if column has TYPE_INFO attribute
        :param column: Entity column object
        :return: Column type. Otherwise None
        :rtype: String or None
        """
        try:
            return column.TYPE_INFO
        except AttributeError:
            return None

    def init_controls(self):
        """
        Initialize GUI controls default state when the dialog window is opened.
        """
        self.populate_data_type_cbo()

        if not self.column is None:
            self.column_to_form(self.column)
            self.column_to_wa(self.column)

        self.edtColName.setFocus()

        self.edtColName.setEnabled(not self.in_db)

        self.cboDataType.setEnabled(not self.in_db)

        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.accept)
        self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.cancel)

        col_type = self._column_type_info(self.column)
        if not self.in_db and col_type == 'GEOMETRY':
            opts = self.type_attribs[col_type]
            self.cbMandt.setEnabled(opts['mandt']['enabled_state'])
            self.cbUnique.setEnabled(opts['unique']['enabled_state'])
            self.cbIndex.setEnabled(opts['index']['enabled_state'])
        else:
            self.cbMandt.setEnabled(not self.in_db)
            self.cbUnique.setEnabled(not self.in_db)
            self.cbIndex.setEnabled(not self.in_db)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return

        name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
        name_validator = QtGui.QRegExpValidator(name_regex)
        text_edit.setValidator(name_validator)
        QApplication.processEvents()
        last_character = text[-1:]
        locale = QSettings().value("locale/userLocale")[0:2]

        #if locale == 'en':
        state = name_validator.validate(text, text.index(last_character))[0]
        if state != QValidator.Acceptable:
            self.show_notification(u'"{}" is not allowed at this position.'.
                format(last_character)
            )
            text = text[:-1]

        # fix caps, _, and spaces
        if last_character.isupper():
            text = text.lower()
        if last_character == ' ':
            text = text.replace(' ', '_')
        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]
            text = text.replace(' ', '_').lower()

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def column_to_form(self, column):
        """
        Initializes form controls with Column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        text = column.display_name()
        self.cboDataType.setCurrentIndex(self.cboDataType.findText(text))

        self.edtColName.setText(column.name)
        self.edtColDesc.setText(column.description)
        self.txt_form_label.setText(column.label)
        self.edtUserTip.setText(column.user_tip)
        self.cbMandt.setChecked(column.mandatory)
        self.cbSearch.setCheckState(self.bool_to_check(column.searchable))
        self.cbUnique.setCheckState(self.bool_to_check(column.unique))
        self.cbIndex.setCheckState(self.bool_to_check(column.index))

        ti = self.current_type_info()
        ps = self.type_attribs[ti].get('prop_set', None)
        if ps is not None:
            self.type_attribs[ti]['prop_set']= self.prop_set

    def column_to_wa(self, column):
        """
        Initialize 'work area' form_fields with column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        if column is not None:
            self.form_fields['colname'] = column.name
            self.form_fields['value']  = None
            self.form_fields['mandt']  = column.mandatory
            self.form_fields['search'] = column.searchable
            self.form_fields['unique'] = column.unique
            self.form_fields['index']  = column.index

            if hasattr(column, 'minimum'):
                self.form_fields['minimum'] = column.minimum
                self.form_fields['maximum'] = column.maximum

            if hasattr(column, 'srid'):
                self.form_fields['srid'] = column.srid
                self.form_fields['geom_type'] = column.geom_type

            if hasattr(column, 'entity_relation'):
                self.form_fields['entity_relation'] = column.entity_relation

            if hasattr(column, 'association'):
                self.form_fields['first_parent'] = column.association.first_parent
                self.form_fields['second_parent'] = column.association.second_parent

            if hasattr(column, 'min_use_current_date'):
                self.form_fields['min_use_current_date'] = column.min_use_current_date
                self.form_fields['max_use_current_date'] = column.max_use_current_date

            if hasattr(column, 'min_use_current_datetime'):
                self.form_fields['min_use_current_datetime'] = \
                        column.min_use_current_datetime
                self.form_fields['max_use_current_datetime'] = \
                        column.max_use_current_datetime

            if hasattr(column, 'prefix_source'):
                self.form_fields['prefix_source'] = column.prefix_source
                self.form_fields['columns'] = column.columns
                self.form_fields['column_separators'] = column.column_separators
                self.form_fields['leading_zero'] = column.leading_zero
                self.form_fields['separator'] = column.separator
                self.form_fields['colname'] = column.name
                self.form_fields['enable_editing'] = column.enable_editing
                self.form_fields['disable_auto_increment'] = column.disable_auto_increment
                self.form_fields['hide_prefix'] = column.hide_prefix

            # Decimal properties
            if hasattr(column, 'precision'):
                self.form_fields['precision'] = column.precision
                self.form_fields['scale'] = column.scale

            # Expression column
            if hasattr(column, 'expression'):

                self.form_fields['expression'] = column.expression
                self.form_fields['output_data_type'] = column.output_data_type

    def bool_to_check(self, state):
        """
        Converts a boolean to a Qt checkstate.
        :param state: True/False
        :type state: boolean
        :rtype: Qt.CheckState
        """
        return Qt.Checked if state else Qt.Unchecked

    def init_form_fields(self):
        """
        Initializes work area 'form_fields' dictionary with default values.
        Used when creating a new column.
        """
        none = QApplication.translate('CodeProperty', 'None')
        self.form_fields['colname'] = ''
        self.form_fields['value']  = None
        self.form_fields['mandt']  = False
        self.form_fields['search'] = False
        self.form_fields['unique'] = False
        self.form_fields['index']  = False
        self.form_fields['minimum'] = self.type_attribs.get('minimum', 0) 
        self.form_fields['maximum'] = self.type_attribs.get('maximum', 0)
        self.form_fields['srid'] = self.type_attribs.get('srid', "")
        self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0)
        self.form_fields['in_db'] = self.in_db
        self.form_fields['prefix_source'] = self.type_attribs.get(
            'prefix_source', none
        )
        self.form_fields['columns'] = self.type_attribs.get(
            'columns', []
        )
        self.form_fields['column_separators'] = self.type_attribs.get(
            'column_separators', []
        )
        self.form_fields['leading_zero'] = self.type_attribs.get(
            'leading_zero', ''
        )
        self.form_fields['separator'] = self.type_attribs.get(
            'separator', ''
        )
        self.form_fields['enable_editing'] = self.type_attribs.get(
            'enable_editing', ''
        )
        self.form_fields['disable_auto_increment'] = self.type_attribs.get(
            'disable_auto_increment', ''
        )
        self.form_fields['hide_prefix'] = self.type_attribs.get(
            'hide_prefix', ''
        )
        self.form_fields['precision'] = self.type_attribs.get(
            'precision', 18
        )
        self.form_fields['scale'] = self.type_attribs.get(
            'scale', 6
        )


        self.form_fields['entity_relation'] = \
                self.type_attribs['FOREIGN_KEY'].get('entity_relation', None)

        self.form_fields['entity_relation'] = \
                self.type_attribs['LOOKUP'].get('entity_relation', None)

        self.form_fields['first_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None)

        self.form_fields['second_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None)

        self.form_fields['min_use_current_date'] = \
                self.type_attribs['DATE'].get('min_use_current_date', None)

        self.form_fields['max_use_current_date'] = \
                self.type_attribs['DATE'].get('max_use_current_date', None)

        self.form_fields['min_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('min_use_current_datetime', None)

        self.form_fields['max_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('max_use_current_datetime', None)

        self.form_fields['expression'] = self.type_attribs.get(
            'expression', ''
        )
        self.form_fields['output_data_type'] = self.type_attribs.get(
            'output_data_type', ''
        )

    def init_type_attribs(self):
        """
        Initializes data type attributes. The attributes are used to
        set the form controls state when a particular data type is selected.
        mandt - enables/disables checkbox 'Mandatory'
        search - enables/disables checkbox 'Searchable'
        unique - enables/disables checkbox 'Unique'
        index - enables/disables checkbox 'Index'
        *property - function to execute when a data type is selected.
        """
        self.type_attribs['VARCHAR'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':True, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':True},
                'index':{'check_state':False, 'enabled_state':True},
                'maximum':30,'property': self.varchar_property }

        self.type_attribs['INT'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':True, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':True},
                'index':{'check_state':False, 'enabled_state':False},
                'minimum':0, 'maximum':0,
                'property':self.bigint_property }

        self.type_attribs['TEXT'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':False, 'enabled_state':False},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                } 

        self.type_attribs['DOUBLE' ] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':True, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':True},
                'index':{'check_state':False, 'enabled_state':True},
                'minimum':0.0, 'maximum':0.0,
                'precision': 18, 'scale': 6,
                'property':self.double_property }

        self.type_attribs['DATE'] =  {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':False, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'minimum':datetime.date.min,
                'maximum':datetime.date.max,
                'min_use_current_date':False,
                'max_use_current_date':False,
                'property':self.date_property }
               
        self.type_attribs['DATETIME'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':False, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'minimum':datetime.datetime.min,
                'maximum':datetime.datetime.max,
                'min_use_current_datetime':False,
                'max_use_current_datetime':False,
                'property':self.dtime_property }

        self.type_attribs['FOREIGN_KEY'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':False, 'enabled_state':False},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'entity_relation':None,
                'show_in_parent': True, 'show_in_child': True,
                'property':self.fk_property, 'prop_set':False }

        self.type_attribs['LOOKUP'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':True, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'entity_relation':{},
                'property':self.lookup_property, 'prop_set':False }

        self.type_attribs['GEOMETRY'] = {
                'mandt':{'check_state':False, 'enabled_state':False},
                'search':{'check_state':False, 'enabled_state':False},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'srid':"", 'geom_type':0,
                'property':self.geometry_property, 'prop_set':False }

        self.type_attribs['BOOL'] = {
                'mandt':{'check_state':False, 'enabled_state':False},
                'search':{'check_state':False, 'enabled_state':False},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False}
                }
        self.type_attribs['PERCENT'] = {
            'mandt': {'check_state': False, 'enabled_state': False},
            'search': {'check_state': False, 'enabled_state': True},
            'unique': {'check_state': False, 'enabled_state': False},
            'index': {'check_state': False, 'enabled_state': False}
        }

        self.type_attribs['ADMIN_SPATIAL_UNIT'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':True, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'entity_relation':None}

        self.type_attribs['MULTIPLE_SELECT'] = {
                'mandt':{'check_state':False, 'enabled_state':True},
                'search':{'check_state':False, 'enabled_state':True},
                'unique':{'check_state':False, 'enabled_state':False},
                'index':{'check_state':False, 'enabled_state':False},
                'first_parent':None, 'second_parent':self.entity,
                'property':self.multi_select_property, 'prop_set':False }

        self.type_attribs['AUTO_GENERATED'] = {
            'mandt': {'check_state': False, 'enabled_state': True},
            'search': {'check_state': True, 'enabled_state': True},
            'unique': {'check_state': True, 'enabled_state': True},
            'index': {'check_state': True, 'enabled_state': True},
            'prefix_source': '', 'columns':[], 'column_separators':[],
            'leading_zero': '', 'separator':'',
            'disable_auto_increment': False, 'enable_editing': False,
            'property': self.code_property, 'hide_prefix': False, 'prop_set': True}

        self.type_attribs['EXPRESSION'] = {
            'mandt': {'check_state': False, 'enabled_state': True},
            'search': {'check_state': False, 'enabled_state': True},
            'unique': {'check_state': False, 'enabled_state': True},
            'index': {'check_state': False, 'enabled_state': True},
            'output_data_type': '', 'expression':'',
            'property': self.expression_property,
            'prop_set': False}

    def data_type_property(self):
        """
        Executes the function assigned to the property attribute of 
        the current selected data type.
        """
        self.type_attribs[self.current_type_info()]['property']()

    def varchar_property(self):
        """
        Opens the property editor for the Varchar data type.
        If successful, set a minimum column in work area 'form fields'
        """
        editor = VarcharProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['maximum'] = editor.max_len()

    def bigint_property(self):
        """
        Opens a property editor for the BigInt data type.
        """
        editor = BigintProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()

    def double_property(self):
        """
        Opens a property editor for the Double data type.
        """
        editor = DoubleProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['precision'] = editor.precision
            self.form_fields['scale'] = editor.scale

    def date_property(self):
        """
        Opens a property editor for the Date data type.
        """
        editor = DateProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_date'] = \
                    editor.min_use_current_date
            self.form_fields['max_use_current_date'] = \
                    editor.max_use_current_date

    def dtime_property(self):
        """
        Opens a property editor for the DateTime data type.
        """
        editor = DTimeProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_datetime'] = \
                    editor.min_use_current_datetime
            self.form_fields['max_use_current_datetime'] = \
                    editor.max_use_current_datetime

    def geometry_property(self):
        """
        Opens a property editor for the Geometry data type.
        If successful, set the srid(projection), geom_type (LINE, POLYGON...)
        and prop_set which is boolean flag to verify that all the geometry
        properties are set. 
        Constraint - If 'prop_set' is False column cannot be saved.
        """
        editor = GeometryProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['srid'] = editor.coord_sys()
            self.form_fields['geom_type'] = editor.geom_type()
            self.property_set()

    def admin_spatial_unit_property(self):
        """
        Sets entity relation property used when creating column of type
        ADMIN_SPATIAL_UNIT
        """
        er_fields = {}
        er_fields['parent'] = self.entity
        er_fields['parent_column'] = None
        er_fields['display_columns'] = []
        er_fields['child'] = None
        er_fields['child_column'] = None
        self.form_fields['entity_relation'] = EntityRelation(self.profile, **er_fields)

    def fk_property(self):
        """
        Opens a property editor for the ForeignKey data type.
        """
        if len(self.edtColName.displayText())==0:
            self.show_message("Please enter column name!")
            return

        # filter list of lookup tables, don't show internal 
        # tables in list of lookups
        fk_ent = [entity for entity in self.profile.entities.items() \
                if entity[1].TYPE_INFO not in self.EX_TYPE_INFO]

        fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \
                not in self.FK_EXCLUDE]

        relation = {}
        relation['form_fields'] = self.form_fields
        relation['fk_entities'] = fk_ent
        relation['profile'] = self.profile
        relation['entity'] = self.entity
        relation['column_name'] = unicode(self.edtColName.text())
        relation['show_in_parent'] = '1'
        relation['show_in_child'] = '1'
        editor = FKProperty(self, relation)
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            relation['show_in_parent'] = editor.show_in_parent()
            relation['show_in_child'] = editor.show_in_child()

            self.property_set()

    def lookup_property(self):
        """
        Opens a lookup type property editor
        """
        editor = LookupProperty(self, self.form_fields, profile=self.profile) 
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            self.property_set()

    def multi_select_property(self):
        """
        Opens a multi select property editor
        """
        if len(self.edtColName.displayText())==0:
           self.show_message("Please enter column name!")
           return
       
        editor = MultiSelectProperty(self, self.form_fields, self.entity, self.profile) 
        result = editor.exec_()
        if result == 1:
            self.form_fields['first_parent'] = editor.lookup()
            self.form_fields['second_parent'] = self.entity
            self.property_set()

    def code_property(self):
        """
        Opens the code data type property editor
        """
        editor = CodeProperty(self, self.form_fields, entity=self.entity, profile=self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['prefix_source'] = editor.prefix_source()
            self.form_fields['columns'] = editor.columns()
            self.form_fields['leading_zero'] = editor.leading_zero()
            self.form_fields['separator'] = editor.separator()
            self.form_fields['disable_auto_increment'] = editor.disable_auto_increment()
            self.form_fields['enable_editing'] = editor.enable_editing()
            self.form_fields['column_separators'] = editor.column_separators()
            self.form_fields['hide_prefix'] = editor.hide_prefix()

            self.property_set()

    def expression_property(self):
        """
        Opens the code data type property editor
        """
        layer = self.create_layer()

        editor = ExpressionProperty(layer, self.form_fields, self)
        result = editor.exec_()
        if result == 1:
            self.form_fields['expression'] = editor.expression_text()
            self.form_fields['output_data_type'] = editor.get_output_data_type()
            self.property_set()

    def create_layer(self):
        srid = None
        column = ''
        if self.entity.has_geometry_column():
            geom_cols = [col.name for col in self.entity.columns.values()
                         if col.TYPE_INFO == 'GEOMETRY']
            column = geom_cols[0]
            geom_col_obj = self.entity.columns[column]

            if geom_col_obj.srid >= 100000:
                srid = geom_col_obj.srid
        layer = vector_layer(self.entity.name, geom_column=column,
                             proj_wkt=srid)
        return layer

    def create_column(self):
        """
        Creates a new BaseColumn.
        """
        column = None

        if self.type_info <> "":
            if self.type_info == 'ADMIN_SPATIAL_UNIT':
                self.admin_spatial_unit_property()
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity, **self.form_fields)
                return column

            if self.is_property_set(self.type_info):
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity, 
                                self.form_fields['geom_type'],
                                self.entity, **self.form_fields)
            else:
                self.show_message(self.tr('Please set column properties.'))
                return
        else:
            raise self.tr("No type to create.")

        return column

    def property_set(self):
        self.prop_set = True
        self.type_attribs[self.current_type_info()]['prop_set'] = True

    def is_property_set(self, ti):
        """
        Checks if column property is set by reading the value of
        attribute 'prop_set'
        :param ti: Type info to check for prop set
        :type ti: BaseColumn.TYPE_INFO
        :rtype: boolean
        """
        return self.type_attribs[ti].get('prop_set', True)

    def property_by_name(self, ti, name):
        try:
            return self.dtype_property(ti)['property'][name]
        except:
            return None

    def populate_data_type_cbo(self):
        """
        Fills the data type combobox widget with BaseColumn type names.
        """
        self.cboDataType.clear()

        for name, col in BaseColumn.types_by_display_name().iteritems():
            # Specify columns to exclude
            if col.TYPE_INFO not in self._exclude_col_type_info:
                self.cboDataType.addItem(name)

        if self.cboDataType.count() > 0:
            self.cboDataType.setCurrentIndex(0)

    def change_data_type(self, index):
        """
        Called by type combobox when you select a different data type.
        """
        text = self.cboDataType.itemText(index)
        col_cls = BaseColumn.types_by_display_name().get(text, None)
        if col_cls is None:
            return

        ti = col_cls.TYPE_INFO
        if ti not in self.type_attribs:
            msg = self.tr('Column type attributes could not be found.')
            self.notice_bar.clear()
            self.notice_bar.insertErrorNotification(msg)

            return

        self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property'))
        self.type_info = ti
        opts = self.type_attribs[ti]
        self.set_optionals(opts)
        self.set_min_max_defaults(ti)

    def set_optionals(self, opts):
        """
        Enable/disables form controls based on selected 
        column data type attributes
        param opts: Dictionary type properties of selected column
        type opts: dict
        """
        self.cbMandt.setEnabled(opts['mandt']['enabled_state'])
        self.cbSearch.setEnabled(opts['search']['enabled_state'])
        self.cbUnique.setEnabled(opts['unique']['enabled_state'])
        self.cbIndex.setEnabled(opts['index']['enabled_state'])

        self.cbMandt.setCheckState(self.bool_to_check(opts['mandt']['check_state']))
        self.cbSearch.setCheckState(self.bool_to_check(opts['search']['check_state']))
        self.cbUnique.setCheckState(self.bool_to_check(opts['unique']['check_state']))
        self.cbIndex.setCheckState(self.bool_to_check(opts['index']['check_state']))

    def set_min_max_defaults(self, type_info):
        """
        sets the work area 'form_fields' default values (minimum/maximum)
        from the column's type attribute dictionary
        :param type_info: BaseColumn.TYPE_INFO
        :type type_info: str
        """
        self.form_fields['minimum'] = \
                self.type_attribs[type_info].get('minimum', 0)

        self.form_fields['maximum'] = \
                self.type_attribs[type_info].get('maximum', 0)

    def current_type_info(self):
        """
        Returns a TYPE_INFO of a data type
        :rtype: str
        """
        text = self.cboDataType.itemText(self.cboDataType.currentIndex())
        try:
            return BaseColumn.types_by_display_name()[text].TYPE_INFO
        except:
            return ''

    def fill_work_area(self):
        """
        Sets work area 'form_fields' with form control values
        """
        self.form_fields['colname'] = unicode(self.edtColName.text())
        self.form_fields['description'] = unicode(self.edtColDesc.text())
        self.form_fields['label'] = unicode(self.txt_form_label.text())
        self.form_fields['index'] = self.cbIndex.isChecked()
        self.form_fields['mandatory'] = self.cbMandt.isChecked()
        self.form_fields['searchable'] = self.cbSearch.isChecked()
        self.form_fields['unique'] = self.cbUnique.isChecked()
        self.form_fields['user_tip'] = unicode(self.edtUserTip.text())

    def show_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM"))
        msg.setText(message)
        msg.exec_()  

    def accept(self):
        col_name = unicode(self.edtColName.text()).strip()
        # column name is not empty
        if len(col_name)==0 or col_name == '_':
            self.show_message(self.tr('Please enter a valid column name.'))
            return False

        # check for STDM reserved keywords
        if col_name in RESERVED_KEYWORDS:
            self.show_message(
                self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\
                "Please choose another column name.".format(col_name)) )
            return False

        new_column = self.make_column()

        if new_column is None:
            LOGGER.debug("Error creating column!")
            self.show_message('Unable to create column!')
            return False

        if self.column is None:  # new column
            if self.duplicate_check(col_name):
                self.show_message(self.tr("Column with the same name already "
                "exist in this entity!"))

                return False
            if self.auto_entity_add:
                self.entity.add_column(new_column)

            self.column = new_column
            self.done(1)
        else:  # editing a column 
            self.column = new_column
            self.done(1)

    def cancel(self):
        self.done(0)

    def make_column(self):
        """
        Returns a newly created column
        :rtype: BaseColumn
        """
        self.fill_work_area()
        col = self.create_column()
        return col

    def duplicate_check(self, name):
        """
        Return True if we have a column in the current entity with same name
        as our new column
        :param col_name: column name
        :type col_name: str
        """
        # check if another column with the same name exist in the current entity
        if name in self.entity.columns:
            return True
        else:
            return False

    def rejectAct(self):
        self.done(0)
Beispiel #26
0
class GeoODKConverter(QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Class Constructor."""
        super(GeoODKConverter, self).__init__(parent)

        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-aut o-connect
        self.connect_action = pyqtSignal(str)
        self.setupUi(self)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.set_entity_model_view(self.entity_model)
        self.stdm_config = None
        self.parent = parent
        self.load_profiles()
        self.check_state_on()

        self.check_geoODK_path_exist()

        self.chk_all.stateChanged.connect(self.check_state_on)
        self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder)
        #self.btn_upload.clicked.connect(self.upload_generated_form)

        self._notif_bar_str = NotificationBar(self.vlnotification)

    def onShowOutputFolder(self):
        output_path = FORM_HOME

        # windows
        if sys.platform.startswith('win32'):
            os.startfile(output_path)

        # *nix systems
        if sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_path])
        
        # macOS
        if sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_path])


    def check_state_on(self):
        """
        Ensure all the items in the list are checked
        :return:
        """
        if self.entity_model.rowCount() > 0:
            for row in range(self.entity_model.rowCount()):
                item = self.entity_model.item(row)
                if self.chk_all.isChecked():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)

    def load_profiles(self):
        """
        Read and load profiles from StdmConfiguration instance
        """
        self.populate_view_models(current_profile())

    def profiles(self):
        """
        Get all profiles
        :return:
        """
        return self.load_config().values()

    def populate_view_models(self, profile):
        for entity in profile.entities.values():
            if entity.action == DbItem.DROP:
                continue
            
            if hasattr(entity, 'user_editable') and entity.TYPE_INFO <> 'VALUE_LIST':
                if entity.user_editable == False:
                    continue

            if entity.TYPE_INFO not in ['SUPPORTING_DOCUMENT',
                    'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT',
                    'ENTITY_SUPPORTING_DOCUMENT', 'ASSOCIATION_ENTITY', 'AUTO_GENERATE_CODE']:

                if entity.TYPE_INFO == 'VALUE_LIST':
                    pass
                else:
                    self.entity_model.add_entity(entity)
        self.set_model_items_selectable()

    def set_entity_model_view(self, entity_model):
        """
        Set our list view to the default model
        :return:
        """
        self.trentities.setModel(entity_model)

    def set_model_items_selectable(self):
        """
        Ensure that the entities  are checkable
        :return:
        """
        if self.entity_model.rowCount() >0:
            for row in range(self.entity_model.rowCount()):
                index = self.entity_model.index(row,0)
                item_index = self.entity_model.itemFromIndex(index)
                item_index.setCheckable(True)

    def selected_entities_from_Model(self):
        """
        Get selected entities for conversion
        to Xform from the user selection
        :return:
        """
        entity_list =[]
        if self.entity_model.rowCount() > 0:
            for row in range(self.entity_model.rowCount()):
                item = self.entity_model.item(row)
                if item.isCheckable() and item.checkState() == Qt.Checked:
                    entity_list.append(item.text())
        return entity_list

    def check_geoODK_path_exist(self):
        """
        Check if the geoodk paths are there in the directory
        Otherwise create them
        :return:
        """
        if not os.access(FORM_HOME, os.F_OK):
            os.makedirs(unicode(FORM_HOME))

    def upload_generated_form(self):
        """
        Upload the generated Xform file to mobile phone.
        This eliminates the process of copying the file
        manually to the mobile device
        :return:
        """
        form_uploader = FormUploader(self)
        form_uploader.exec_()

    def generate_mobile_form(self, selected_entities):
        """
        Generate mobile form based on the selected entities.
        :return:
        """
        #try:
        self._notif_bar_str.clear()
        if len(selected_entities) == 0:
            self._notif_bar_str.insertErrorNotification(
                'No entity selected. Please select at least one entity...'
            )
            return
        if len(selected_entities) > 0:
            geoodk_writer = GeoodkWriter(selected_entities, self.str_supported)
            geoodk_writer.write_data_to_xform()
            msg = 'File saved in: {}'
            self._notif_bar_str.insertInformationNotification(msg.format(FORM_HOME))
        #except Exception as ex:
        #    self._notif_bar_str.insertErrorNotification(ex.message +
        #                                                ': Unable to generate Mobile Form')
        #   return

    def accept(self):
        """
        Generate mobile forms based on user selected entities.
        Check if str is enabled, then ensure str tables are enabled.
        :return:
        """
        user_entities = self.selected_entities_from_Model()
        self.str_supported = False
        if self.ck_social_tenure.isChecked():
            self.str_supported = True
            str_definition = current_profile().social_tenure
            str_definition_party = str_definition.parties[0].short_name
            str_definition_spatial = str_definition.spatial_units[0].short_name
            if str_definition_party not in user_entities or str_definition_spatial not in user_entities:
                self._notif_bar_str.insertErrorNotification(
                    'One of the entities required to define str is not selected. Form not saved'
                )
                return
            #else:
                #self.generate_mobile_form(user_entities)
        #else:
        self.generate_mobile_form(user_entities)
Beispiel #27
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 #28
0
class RelatedTableDialog(TranslatorDialogBase, WIDGET, BASE):
    """
    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 #29
0
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param kwargs: Keyword dictionary of the following parameters;
         column  - Column you editing, None if its a new column
         entity  - Entity you are adding the column to
         profile - Current profile
         in_db   - Boolean flag to indicate if a column has been created in 
                   the database
         auto_add- True to automatically add a new column to the entity, 
                   default is False.
        """

        self.form_parent = kwargs.get('parent', self)
        self.column = kwargs.get('column', None)
        self.entity = kwargs.get('entity', None)
        self.profile = kwargs.get('profile', None)
        self.in_db = kwargs.get('in_db', False)
        self.is_new = kwargs.get('is_new', True)
        self.auto_entity_add = kwargs.get('auto_add', False)

        QDialog.__init__(self, self.form_parent)

        self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set']

        self.EX_TYPE_INFO = [
            'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE',
            'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT',
            'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED'
        ]

        self.setupUi(self)
        self.dtypes = {}

        self.type_info = ''

        # dictionary to hold default attributes for each data type
        self.type_attribs = {}
        self.init_type_attribs()

        # dictionary to act as a work area for the form fields.
        self.form_fields = {}
        self.init_form_fields()

        self.fk_entities = []
        self.lookup_entities = []

        # Exclude column type info in the list
        self._exclude_col_type_info = []

        if self.is_new:
            self.prop_set = None  # why not False??
        else:
            self.prop_set = True

        # the current entity should not be part of the foreign key parent table,
        # add it to the exclusion list
        self.FK_EXCLUDE.append(self.entity.short_name)

        self.type_names = \
                [unicode(name) for name in BaseColumn.types_by_display_name().keys()]

        self.cboDataType.currentIndexChanged.connect(self.change_data_type)
        self.btnColProp.clicked.connect(self.data_type_property)
        self.edtColName.textChanged.connect(self.validate_text)

        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_controls()
Beispiel #30
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 #31
0
class ImageExportSettings(QDialog, Ui_ImageExportSettings):
    """A dialog for settings options for exporting an image."""
    def __init__(self, parent=None, **kwargs):
        super(ImageExportSettings, self).__init__(parent)
        self.setupUi(self)

        self._image_tr = self.tr('Image')

        self.notif_bar = NotificationBar(self.vl_notification, 6000)

        #Connect signals
        self.btn_path.clicked.connect(self._on_choose_image_path)
        self.buttonBox.accepted.connect(self.on_accept)
        self.sb_resolution.valueChanged.connect(self._on_resolution_changed)

        #Set color button defaults
        self._default_color = Qt.white
        self.btn_color.setDefaultColor(self._default_color)
        self.btn_color.setColor(self._default_color)
        self.btn_color.setAllowAlpha(True)

        self.path = kwargs.get('image_path', '')
        self.resolution = kwargs.get('resolution', '96')
        self.background_color = kwargs.get('background', Qt.transparent)

        self._update_controls()

    def _update_controls(self):
        #Update input controls with export settings' values
        if self.background_color == Qt.transparent:
            self.rb_transparent.setChecked(True)
            self.btn_color.setVisible(False)

        else:
            self.rb_fill.setChecked(True)
            self.btn_color.setColor(self.background_color)

        self.sb_resolution.setValue(int(self.resolution))
        self.txt_path.setText(self.path)

        #Set image size just in case value does not change
        self._set_image_size()

    def _on_resolution_changed(self, value):
        #Slot raised when the resolution changes
        self._set_image_size()

    def _update_export_vars(self):
        #Update export variables based on user values.
        self.path = self.txt_path.text()
        self.resolution = self.sb_resolution.value()

        if self.rb_transparent.isChecked():
            self.background_color = Qt.transparent

        else:
            self.background_color = self.btn_color.color()

    def _set_image_size(self):
        #Set image size based on the resolution using A4 paper size
        res = self.sb_resolution.value()
        if res == 0:
            return

        #To mm
        res_mm = res / 25.4

        #A4 landscape size
        width = int(297 * res_mm)
        height = int(210 * res_mm)

        units = 'px'
        width_display = u'{0} {1}'.format(width, units)
        height_display = u'{0} {1}'.format(height, units)
        self.lbl_width.setText(width_display)
        self.lbl_height.setText(height_display)

    def _image_filters(self):
        #Return supported image formats for use in a QFileDialog filter
        formats = []

        for f in QImageWriter.supportedImageFormats():
            f_type = f.data()
            filter_format = u'{0} {1} (*.{2})'.format(
                f_type.upper(),
                self._image_tr,
                f_type
            )
            formats.append(filter_format)

        return ';;'.join(formats)

    def _on_choose_image_path(self):
        #Slot raised to choose image path
        img_path = self.txt_path.text()
        title = self.tr('Specify image location')
        sel_image_path = QFileDialog.getSaveFileName(
            self,
            title,
            img_path,
            self._image_filters()
        )

        if sel_image_path:
            self.txt_path.setText(sel_image_path)

    def on_accept(self):
        """
        Slot raised to save the settings and close the dialog.
        """
        if not self.txt_path.text():
            msg = self.tr('Please specify the image path.')
            self.notif_bar.insertErrorNotification(msg)
            self.txt_path.setFocus()

            return

        self._update_export_vars()

        self.accept()
Beispiel #32
0
class TemplateDocumentSelector(WIDGET, BASE):
    """
    Dialog for selecting a document template from the saved list.
    """
    def __init__(self,
                 parent=None,
                 selectMode=True,
                 filter_data_source='',
                 access_templates=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.notifBar = NotificationBar(self.vlNotification)

        self._mode = selectMode

        # Filter templates by the specified table name
        self._filter_data_source = filter_data_source

        # Document templates in current profile
        self._profile_templates = []

        self._current_profile = current_profile()

        # Load current profile templates
        self._load_current_profile_templates()

        self.access_templates = access_templates or []

        if selectMode:
            self.buttonBox.setVisible(True)
            self.manageButtonBox.setVisible(False)
            currHeight = self.size().height()
            self.resize(200, currHeight)

        else:
            self.buttonBox.setVisible(False)
            self.manageButtonBox.setVisible(True)
            self.setWindowTitle(
                QApplication.translate("TemplateDocumentSelector",
                                       "Template Manager"))

        # Configure manage buttons
        btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok)
        btnEdit.setText(
            QApplication.translate("TemplateDocumentSelector", "Edit..."))
        btnEdit.setIcon(GuiUtils.get_icon("edit.png"))

        btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save)
        btnDelete.setText(
            QApplication.translate("TemplateDocumentSelector", "Delete"))
        btnDelete.setIcon(GuiUtils.get_icon("delete.png"))

        # Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        btnEdit.clicked.connect(self.onEditTemplate)
        btnDelete.clicked.connect(self.onDeleteTemplate)

        # Get saved document templates then add to the model
        templates = documentTemplates()

        self._docItemModel = QStandardItemModel(parent)
        self._docItemModel.setColumnCount(2)

        # Append current profile templates to the model.
        for dt in self._profile_templates:

            if self._template_contains_filter_table(
                    dt):  # and dt.name in self.access_templates:
                doc_name_item = self._createDocNameItem(dt.name)
                file_path_item = QStandardItem(dt.path)
                self._docItemModel.appendRow([doc_name_item, file_path_item])

        self.lstDocs.setModel(self._docItemModel)

    def _load_current_profile_templates(self):
        # Loads only those templates that refer to tables in the current
        # profile.
        if self._current_profile is None:
            return

        # Get saved document templates then add to the model
        templates = documentTemplates()

        profile_tables = self._current_profile.table_names()

        # Get templates for the current profile
        for name, path in templates.items():
            doc_temp = DocumentTemplate.build_from_path(name, path)
            if doc_temp.data_source is None:
                continue

            # Assert data source is in the current profile
            if doc_temp.data_source.referenced_table_name in profile_tables or \
                    doc_temp.data_source.name() in user_non_profile_views():
                self._add_doc_temp(doc_temp)
                # self._profile_templates.append(doc_temp)

    def _add_doc_temp(self, doc_temp):
        found = False
        for template in self._profile_templates:
            if template.name == doc_temp.name:
                found = True
                break
        if not found:
            self._profile_templates.append(doc_temp)

    def _template_contains_filter_table(self, document_template):
        # Returns true if the template refers to the filter data source

        # If no filter data source defined then always return True

        if document_template.data_source._dataSourceName in user_non_profile_views(
        ):
            return True

        if not self._filter_data_source:
            return True

        referenced_table = document_template.referenced_table_name

        if referenced_table == self._filter_data_source:
            return True

        return False

    @property
    def mode(self):
        return self._mode

    @property
    def filter_data_source(self):
        return self._filter_data_source

    def _createDocNameItem(self, docName):
        """
        Create a template document standard item.
        """
        # Set icon
        icon = QIcon()
        icon.addPixmap(GuiUtils.get_icon_pixmap("document.png"), QIcon.Normal,
                       QIcon.Off)

        dnItem = QStandardItem(icon, docName)

        return dnItem

    def onEditTemplate(self):
        """
        Slot raised to edit document template.
        """
        self.notifBar.clear()

        if self.documentMapping() is None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to edit"))
            return

        templateName, filePath = self.documentMapping()

        docName, ok = QInputDialog.getText(self, \
                                           QApplication.translate("TemplateDocumentSelector", "Edit Template"), \
                                           QApplication.translate("TemplateDocumentSelector",
                                                                  "Please enter the new template name below"), \
                                           text=templateName)
        if ok and docName == "":
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Template name cannot be empty"))
            return

        elif docName == templateName:
            return

        elif ok and docName != "":
            result, newTemplatePath = self._editTemplate(filePath, docName)

            if result:
                # Update view
                mIndices = self._selectedMappings()

                docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
                filePathItem = self._docItemModel.itemFromIndex(mIndices[1])

                docNameItem.setText(docName)
                filePathItem.setText(newTemplatePath)

                self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                               "'{0}' template has been successfully updated".format(
                                                                                   docName)))

            else:
                self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                             "Error: '{0}' template could not be updated".format(
                                                                                 templateName)))

    def onDeleteTemplate(self):
        """
        Slot raised to delete document template.
        """
        self.notifBar.clear()

        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document template to delete"))
            return

        templateName, filePath = self.documentMapping()

        result = QMessageBox.warning(self, QApplication.translate("TemplateDocumentSelector", \
                                                                  "Confirm delete"),
                                     QApplication.translate("TemplateDocumentSelector", \
                                                            "Are you sure you want to delete '{0}' template?" \
                                                            "This action cannot be undone.\nClick Yes to proceed " \
                                                            "or No to cancel.".format(templateName)),
                                     QMessageBox.Yes | QMessageBox.No)

        if result == QMessageBox.No:
            return

        status = self._deleteDocument(filePath)

        if status:
            # Remove item from list using model index row number
            selectedDocNameIndices = self.lstDocs.selectionModel(
            ).selectedRows(0)
            row = selectedDocNameIndices[0].row()
            self._docItemModel.removeRow(row)
            self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                           "'{0}' template has been successfully removed".format(
                                                                               templateName)))

        else:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Error: '{0}' template could not be removed".format(
                                                                             templateName)))

    def onAccept(self):
        """
        Slot raised to close the dialog only when a selection has been made by the user.
        """
        self.notifBar.clear()

        if self.documentMapping() == None:
            self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \
                                                                         "Please select a document"))
            return

        self.accept()

    def _selectedMappings(self):
        """
        Returns the model indices for the selected row.
        """
        selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0)
        selectedFilePathIndices = self.lstDocs.selectionModel().selectedRows(1)

        if len(selectedDocNameIndices) == 0:
            return None

        docNameIndex = selectedDocNameIndices[0]
        filePathIndex = selectedFilePathIndices[0]

        return (docNameIndex, filePathIndex)

    def documentMapping(self):
        """
        Returns a tuple containing the selected document name and the corresponding file name.
        """
        mIndices = self._selectedMappings()

        if mIndices == None:
            return None

        docNameItem = self._docItemModel.itemFromIndex(mIndices[0])
        filePathItem = self._docItemModel.itemFromIndex(mIndices[1])

        return (docNameItem.text(), filePathItem.text())

    def _editTemplate(self, templatePath, newName):
        """
        Updates the template document to use the new name.
        """
        templateFile = QFile(templatePath)

        if not templateFile.open(QIODevice.ReadOnly):
            QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Open Operation Error"), \
                                 "{0}\n{1}".format(
                                     QApplication.translate("TemplateDocumentSelector", "Cannot read template file."), \
                                     templateFile.errorString()
                                 ))
            return (False, "")

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            composerElement = templateDoc.documentElement()
            titleAttr = composerElement.attributeNode("_title")
            if not titleAttr.isNull():
                titleAttr.setValue(newName)

            # Try remove file
            status = templateFile.remove()

            if not status:
                return (False, "")

            # Create new file
            newTemplatePath = self._composerTemplatesPath(
            ) + "/" + newName + ".sdt"
            newTemplateFile = QFile(newTemplatePath)

            if not newTemplateFile.open(QIODevice.WriteOnly):
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Operation Error"), \
                                     "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector",
                                                                              "Could not save template file."), \
                                                       newTemplateFile.errorString()
                                                       ))
                return (False, "")

            if newTemplateFile.write(templateDoc.toByteArray()) == -1:
                QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Error"), \
                                     QApplication.translate("TemplateDocumentSelector",
                                                            "Could not save template file."))
                return (False, "")

            newTemplateFile.close()

            return (True, newTemplatePath)

    def _deleteDocument(self, templatePath):
        """
        Delete the document template from the file system.
        """
        docFile = QFile(templatePath)

        return docFile.remove()

    def _composerTemplatesPath(self):
        """
        Reads the path of composer templates in the registry.
        """
        regConfig = RegistryConfig()
        keyName = "ComposerTemplates"

        valueCollection = regConfig.read([keyName])

        if len(valueCollection) == 0:
            return None

        else:
            return valueCollection[keyName]
Beispiel #33
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 #34
0
    def __init__(self,
                 parent=None,
                 selectMode=True,
                 filter_data_source='',
                 access_templates=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.notifBar = NotificationBar(self.vlNotification)

        self._mode = selectMode

        # Filter templates by the specified table name
        self._filter_data_source = filter_data_source

        # Document templates in current profile
        self._profile_templates = []

        self._current_profile = current_profile()

        # Load current profile templates
        self._load_current_profile_templates()

        self.access_templates = access_templates or []

        if selectMode:
            self.buttonBox.setVisible(True)
            self.manageButtonBox.setVisible(False)
            currHeight = self.size().height()
            self.resize(200, currHeight)

        else:
            self.buttonBox.setVisible(False)
            self.manageButtonBox.setVisible(True)
            self.setWindowTitle(
                QApplication.translate("TemplateDocumentSelector",
                                       "Template Manager"))

        # Configure manage buttons
        btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok)
        btnEdit.setText(
            QApplication.translate("TemplateDocumentSelector", "Edit..."))
        btnEdit.setIcon(GuiUtils.get_icon("edit.png"))

        btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save)
        btnDelete.setText(
            QApplication.translate("TemplateDocumentSelector", "Delete"))
        btnDelete.setIcon(GuiUtils.get_icon("delete.png"))

        # Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        btnEdit.clicked.connect(self.onEditTemplate)
        btnDelete.clicked.connect(self.onDeleteTemplate)

        # Get saved document templates then add to the model
        templates = documentTemplates()

        self._docItemModel = QStandardItemModel(parent)
        self._docItemModel.setColumnCount(2)

        # Append current profile templates to the model.
        for dt in self._profile_templates:

            if self._template_contains_filter_table(
                    dt):  # and dt.name in self.access_templates:
                doc_name_item = self._createDocNameItem(dt.name)
                file_path_item = QStandardItem(dt.path)
                self._docItemModel.appendRow([doc_name_item, file_path_item])

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

        self.entity_table_model = {}

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

        #Set minimum width
        self.setMinimumWidth(450)

        self.plugin = plugin

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

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

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

        self.collect_model = collect_model

        self.register_column_widgets()

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

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

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

        self.setWindowTitle(self.title)

        self._init_gui()
        self.adjustSize()

        self._get_entity_editor_widgets()

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

        self.attribute_mappers = self._attr_mapper_collection

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

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

            # Initialize CascadingFieldContext objects
            self._editor_ext.connect_cf_contexts()
Beispiel #37
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()

        version = version_from_metadata()
        upgrade_label_text = self.label_9.text().replace('1.4', version)
        self.label_9.setText(upgrade_label_text)

        #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_connection)
        self.btn_supporting_docs.clicked.connect(
            self._on_choose_supporting_docs_path
        )
        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.upgradeButton.toggled.connect(
            self.manage_upgrade
        )

        self._config = StdmConfiguration.instance()
        self._default_style_sheet = self.txtRepoLocation.styleSheet()

        self.manage_upgrade()

        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()

        self.edtEntityRecords.setMaximum(MAX_LIMIT)
        self.edtEntityRecords.setValue(get_entity_browser_record_limit())

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

    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()
        source_doc_path = source_documents_path()

        if not source_doc_path is None:
            self.txtRepoLocation.setText(source_doc_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_supporting_docs_path(self):
        #Slot raised to select directory for supporting documents.
        self._set_selected_directory(self.txtRepoLocation, self.tr(
            'Supporting Documents Directory')
        )

    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_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))
            QMessageBox.information(self, self.tr('Database Connection'), 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 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_supporting_documents_path(self):
        """
        Set the directory of supporting documents.
        :return: True if the directory was set in the registry, otherwise
        False.
        :rtype: bool
        """
        path = self.txtRepoLocation.text()

        if not path:
            msg = self.tr('Please set the supporting documents directory.')
            self.notif_bar.insertErrorNotification(msg)

            return False

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

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

        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 supporting documents directory
        if not self.set_supporting_documents_path():
            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()

        # Set Entity browser record limit
        save_entity_browser_record_limit(self.edtEntityRecords.value())

        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()

    def manage_upgrade(self):
        """
        A slot raised when the upgrade button is clicked.
        It disables or enables the upgrade
        button based on the ConfigUpdated registry value.
        """

        self.config_updated_dic = self._reg_config.read(
            [CONFIG_UPDATED]
        )

        # if config file exists, check if registry key exists
        if len(self.config_updated_dic) > 0:
            config_updated_val = self.config_updated_dic[
                CONFIG_UPDATED
            ]
            # If failed to upgrade, enable the upgrade button
            if config_updated_val == '0' or config_updated_val == '-1':
                self.upgradeButton.setEnabled(True)

            # disable the button if any other value.
            else:
                self.upgradeButton.setEnabled(False)
        else:
            self.upgradeButton.setEnabled(False)
class MultipleEnumerationDialog(TranslatorDialogBase, WIDGET, BASE):
    """
    Dialog for defining configuration settings for the MultipleEnumerationTranslator
    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)

        # Container of column names for an enumeration table
        self._enum_col_names = []

        self._load_separators()

        # Init user selection to the corresponding UI controls
        self.txt_source_col.setText(self._src_col)

    def _load_separators(self):
        separators = []

        comma_sep = (QApplication.translate("MultipleEnumerationDialog",
                                            "Comma (,)"), ",")
        separators.append(comma_sep)

        colon_sep = (QApplication.translate("MultipleEnumerationDialog",
                                            "Colon (:)"), ":")
        separators.append(colon_sep)

        semi_colon_sep = (QApplication.translate("MultipleEnumerationDialog",
                                                 "Semi-colon (;)"), ";")
        separators.append(semi_colon_sep)

        asterisk_sep = (QApplication.translate("MultipleEnumerationDialog",
                                               "Asterisk (*)"), "*")
        separators.append(asterisk_sep)

        self.cbo_separator.addItem("")

        for sep in separators:
            self.cbo_separator.addItem(sep[0], sep[1])

    def validate(self):
        """
        :return: Check user entries.
        :rtype: bool
        """
        if not self.txt_source_col.text():
            msg = QApplication.translate("MultipleEnumerationDialog",
                                         "Source column does not exist.")
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return False

        if not self.cbo_separator.currentText():
            msg = QApplication.translate(
                "MultipleEnumerationDialog",
                "Please specify a separator for the multiple select data.")
            self._notif_bar.clear()
            self._notif_bar.insertErrorNotification(msg)

            return False

        return True

    def separator(self):
        sep_idx = self.cbo_separator.currentIndex()

        if sep_idx == 0:
            return ""

        else:
            return self.cbo_separator.itemData(sep_idx)

    def column_pairings(self):
        """
        Format of dict - source column name: matching enumeration table column.
        Primary enum column is the first item in the dictionary.
        """
        col_pairs = OrderedDict()
        col_pairs[self.txt_source_col.text()] = self._dest_col

        return col_pairs

    def value_translator(self):
        enum_translator = MultipleEnumerationTranslator()
        enum_translator.set_referencing_table(self._dest_table)
        enum_translator.set_referencing_column(self._dest_col)
        enum_translator.set_input_referenced_columns(self.column_pairings())
        enum_translator.set_separator(self.separator())

        return enum_translator

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(MultipleEnumerationDialog, self).accept()
Beispiel #39
0
class SourceDocumentTranslatorDialog(QDialog, Ui_SourceDocumentTranslatorDialog,
    TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the
    SourceDocumentTranslator 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.vlNotification)

        #Assert if the entity supports documents
        self._assert_entity_supports_documents()

        #Set the source document directory
        self.source_document_directory = None

        #Document type name
        self._document_type_name = self._dest_col

        #Document type ID
        self._document_type_id = None

        #Set document type ID
        self._set_document_type_id()

        #Connect slots
        self.btn_source_doc_folder.clicked.connect(
            self._load_source_document_directory_selector
        )

    @property
    def document_type_id(self):
        """
        :return: Returns source document type id. Otherwise None if not set.
        :rtype: int
        """
        return self._document_type_id

    @property
    def document_type(self):
        """
        :return: Returns the name of the selected document type.
        :rtype: str
        """
        return self._document_type_name

    @property
    def documents_directory(self):
        """
        :return: Returns the specified root directory for supporting
        documents. Otherwise None if not specified by the user.
        :rtype: str
        """
        return self.txtRootFolder.text()

    def _assert_entity_supports_documents(self):
        #Check if the entity supports documents and close automatically if not.
        entity = self.entity()

        if entity is None:
            msg = self.tr('Invalid table object')

            raise RuntimeError(msg)

        if not entity.supports_documents:
            msg = self.tr('The selected destination table does not support '
                          'documents.\nHence, this translator is not '
                          'applicable.')

            raise RuntimeError(msg)

    def _set_document_type_id(self):
        #Load document id based on the name
        entity = self.entity()

        if entity is None:
            return

        vl_cls = entity_model(
            entity.supporting_doc.document_type_entity,
            entity_only=True
        )

        vl_obj = vl_cls()
        res = vl_obj.queryObject().all()
        for r in res:
            if r.value == self._dest_col:
                self._document_type_id = r.id

                break

        if not entity.supports_documents:
            msg = self.tr('The selected column does not correspond to a '
                          'document type.\nHence, this translator is not '
                          'applicable.')
            title = self.tr('Invalid Document Type')
            QMessageBox.critical(self, title, msg)

            #Close dialog
            self.reject()

    def _load_source_document_directory_selector(self):
        #Load file dialog for selecting source documents directory
        title = self.tr('Select Source Document Directory')
        def_path = self.txtRootFolder.text()

        #Use the last set source document directory
        if not def_path:
            def_path = last_document_path()

        sel_doc_path = QFileDialog.getExistingDirectory(self, title, def_path)

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

    def value_translator(self):
        source_doc_translator = SourceDocumentTranslator()
        source_doc_translator.set_referencing_table(self._dest_table)
        source_doc_translator.set_referencing_column(self._dest_col)

        #Just use the source column for getting the relative image path
        # and name
        source_doc_translator.add_source_reference_column(
            self._src_col,
            self._dest_col
        )
        source_doc_translator.entity = self.entity()
        source_doc_translator.document_type_id = self._document_type_id
        source_doc_translator.document_type = self._document_type_name
        source_doc_translator.source_directory = self.documents_directory

        return source_doc_translator

    def validate(self):
        """
        :return: Return True if the source document directory exists,
        otherwise False.
        :rtype: bool
        """
        source_doc_path = self.txtRootFolder.text()

        #Clear previous notifications
        self._notif_bar.clear()

        if not source_doc_path:
            msg = self.tr(
                'Please set the root directory of source documents.'
            )
            self._notif_bar.insertErrorNotification(msg)

            return False

        dir = QDir()

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

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(SourceDocumentTranslatorDialog, self).accept()
Beispiel #40
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:
            vt = unicode(lk_value)
            text_value = lk_ent.values[vt]
            self.cbo_default.addItem(text_value.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 #41
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()

        version = version_from_metadata()
        upgrade_label_text = self.label_9.text().replace('1.4', version)
        self.label_9.setText(upgrade_label_text)

        #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_connection)
        self.btn_supporting_docs.clicked.connect(
            self._on_choose_supporting_docs_path)
        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.upgradeButton.toggled.connect(self.manage_upgrade)

        self._config = StdmConfiguration.instance()
        self._default_style_sheet = self.txtRepoLocation.styleSheet()

        self.manage_upgrade()

        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()

        self.edtEntityRecords.setMaximum(MAX_LIMIT)
        self.edtEntityRecords.setValue(get_entity_browser_record_limit())

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

    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()
        source_doc_path = source_documents_path()

        if not source_doc_path is None:
            self.txtRepoLocation.setText(source_doc_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_supporting_docs_path(self):
        #Slot raised to select directory for supporting documents.
        self._set_selected_directory(self.txtRepoLocation,
                                     self.tr('Supporting Documents Directory'))

    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_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))
            QMessageBox.information(self, self.tr('Database Connection'), 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 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_supporting_documents_path(self):
        """
        Set the directory of supporting documents.
        :return: True if the directory was set in the registry, otherwise
        False.
        :rtype: bool
        """
        path = self.txtRepoLocation.text()

        if not path:
            msg = self.tr('Please set the supporting documents directory.')
            self.notif_bar.insertErrorNotification(msg)

            return False

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

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

        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 supporting documents directory
        if not self.set_supporting_documents_path():
            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()

        # Set Entity browser record limit
        save_entity_browser_record_limit(self.edtEntityRecords.value())

        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()

    def manage_upgrade(self):
        """
        A slot raised when the upgrade button is clicked.
        It disables or enables the upgrade
        button based on the ConfigUpdated registry value.
        """

        self.config_updated_dic = self._reg_config.read([CONFIG_UPDATED])

        # if config file exists, check if registry key exists
        if len(self.config_updated_dic) > 0:
            config_updated_val = self.config_updated_dic[CONFIG_UPDATED]
            # If failed to upgrade, enable the upgrade button
            if config_updated_val == '0' or config_updated_val == '-1':
                self.upgradeButton.setEnabled(True)

            # disable the button if any other value.
            else:
                self.upgradeButton.setEnabled(False)
        else:
            self.upgradeButton.setEnabled(False)
Beispiel #42
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 #43
0
    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.btnAddColumn.setIcon(GuiUtils.get_icon('add.png'))
        self.btnEditColumn.setIcon(GuiUtils.get_icon('edit.png'))
        self.btnDeleteColumn.setIcon(GuiUtils.get_icon('delete.png'))

        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 list(self._tenure_custom_entities.items()):
            attrs = list(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)
Beispiel #44
0
class ProfileInstanceRecords(QDialog, FORM_CLASS):
    """
    class constructor
    The class handles all the instances that the user has collected
    and saved in the folder and saved in a computer. The class will
    construct the path to the folder and enumerate all available instances
    and return the count. It will also rename all the file based on instance
    unique GUUID for easier management and future updates.
    """
    def __init__(self, parent=None):
        """
        initailize class variables here
        """
        super(ProfileInstanceRecords, self).__init__(parent)
        self.setupUi(self)

        self.path = None
        self.instance_list = []
        self.relations = OrderedDict()
        self.parent_ids = {}
        self.importlogger = ImportLogger()
        self._notif_bar_str = NotificationBar(self.vlnotification)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.uuid_extractor = InstanceUUIDExtractor(self.path)
        self.btn_chang_dir.setIcon(QIcon(":/plugins/stdm/images/icons/open_file.png"))
        self.btn_refresh.setIcon(QIcon(":/plugins/stdm/images/icons/update.png"))

        self.chk_all.stateChanged.connect(self.change_check_state)
        #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed)
        self.btn_chang_dir.clicked.connect(self.on_directory_search)
        self.lst_widget.itemClicked.connect(self.user_selected_entities)
        self.btn_refresh.clicked.connect(self.update_files_with_custom_filter)

        self.buttonBox.button(QDialogButtonBox.Save).setText('Import')

        #self.load_config()
        self.init_file_path()
        self.current_profile_changed()
        self.change_check_state(self.chk_all.checkState())
        self.instance_dir()

    def load_config(self):
        """
        Load STDM configuration
        :return:
        """
        stdm_config = None
        if QFile.exists(HOME+"/stdm/configuration.stc"):
            stdm_config = QFile(CONFIG_FILE)
        ConfigurationFileSerializer(stdm_config)
        profiles = StdmConfiguration.instance().profiles
        return profiles

    def change_check_state(self, state):
        """
        Change the check state of items in a list widget
        """
        for i in range(self.lst_widget.count()):
            self.lst_widget.item(i).setCheckState(state)

    def profiles(self):
        """
        Return a list of all profiles
        :rtype: list
        """
        return self.load_config().values()

    def current_profile_changed(self):
        """
        Get the current profile so that it is the one selected at the combo box
        :return:
        """
        self.instance_list = []
        self.active_profile()
        self.init_file_path()
        self.available_records()
        self.on_dir_path()
        self.populate_entities_widget()

    def active_profile(self):
        """
        get the user selected profile
        :return:p
        """
        self.profile = current_profile().name
        return self.profile

    def instance_dir(self):
        """
        Create a path where imported instance will be kept
        :return:
        """
        self.inst_path = CONFIG_FILE+"_imported"
        if not os.access(self.inst_path, os.F_OK):
            os.makedirs(unicode(self.inst_path))
        else:
            return self.inst_path

    def imported_instance_path(self):
        """
        :return:
        """
        self.instance_dir()
        return self.inst_path


    def init_file_path(self):
        """
        Initialize GeoODK file path
        """
        self.path = self.geoODK_file_path(self.txt_directory.text())
        self.txt_directory.setText(self.path)

    def geoODK_file_path(self, path=''):
        """
        Check if geoODK file path has been configured, if not configure default
        and return it.
        :rtype: string
        """
        if not path.strip():
            path = self.make_path(GEOODK_FORM_HOME)
        return path

    def make_path(self, path):
        """
        Create and return a file path if is not available.
        :rtype: string
        """
        if not os.access(path, os.F_OK):
            os.makedirs(unicode(path))
        return path

    def xform_xpaths(self):
        """
        Return the full path to the default config path and filter geoodk
        instance that matches the current profile path
        :return: directories
        :rtype: list
        """
        return [os.path.join(self.path, name) for name in os.listdir(self.path)
                if os.path.isdir(os.path.join(self.path, name))
                if name.startswith(self.profile_formater())]

    def on_dir_path(self):
        """
        Extract the specific folder information and rename the file
        :return:
        """
        self.uuid_extractor.new_list = []
        if self.record_count() > 0:
            directories = self.xform_xpaths()
            for directory in directories:
                self.extract_guuid_and_rename_file(directory)
        inst_count = len(self.instance_list)
        rm_count = self.remove_imported_instances()
        diff = inst_count - rm_count
        self.txt_count.setText(unicode(diff))

    def extract_guuid_and_rename_file(self, path):
        """
        Extract the unique Guuid and rename the file
        so that we can uniquely identify each file
        :return:
        """
        for f in os.listdir(path):
            if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'):
                file_instance = os.path.join(path, f)
                self.rename_file_to_UUID(file_instance)

    def read_instance_data(self):
        """Read all instance data once and store them in a dict
        :rtype: dict
        """
        mobile_data = OrderedDict()
        social_tenure_info = OrderedDict()
        for instance in self.instance_list:
            self.uuid_extractor.set_file_path(instance)

            field_data_nodes = self.uuid_extractor.document_entities_with_data(current_profile().name.replace(' ', '_'),
                                                                         self.user_selected_entities())
            str_data_nodes = self.uuid_extractor.document_entities_with_data(current_profile().name.replace(' ', '_'),
                                                                       ['social_tenure'])
            mobile_data[instance] = [field_data_nodes, str_data_nodes]

            self.uuid_extractor.close_document()
        return mobile_data

    def rename_file_to_UUID(self, file):
        """
        Extract the UUID from each folder and file
        :return:
        """
        self.uuid_extractor.set_file_path(file)
        self.uuid_extractor.on_file_passed()
        self.instance_list = self.uuid_extractor.new_list

    def move_imported_file(self, file):
        """
        Moves the imported files to avoid repetition
        :return:
        """
        instance_path = self.imported_instance_path()
        try:
            basename = os.path.basename(os.path.dirname(file))
            if not os.path.isdir(os.path.join(self.imported_instance_path(), basename)):
               shutil.move(os.path.dirname(file), instance_path)
        except Exception as ex:
            return ex

    def populate_entities_widget(self):
        """
        Add entities in the instance file into a list view widget
        """
        self.lst_widget.clear()
        entities = self.instance_entities()
        if len(entities) > 0:
            for entity in entities:
                list_widget = QListWidgetItem(
                    current_profile().entity_by_name(entity).short_name, self.lst_widget)
                list_widget.setCheckState(Qt.Checked)

    def user_selected_entities(self):
        """
        :rtype: list
        """
        entities= []
        count = self.lst_widget.count()
        if count > 0:
            for i in range(count):
                item = self.lst_widget.item(i)
                if item.checkState() == Qt.Checked:
                    entities.append(current_profile().entity(item.text()).name)
        return entities

    def instance_entities(self):
        """
        Enumerate the entities that are in the current profile
         and also that are captured in the form so that we are only importing relevant entities to database
        :return: entities
        """
        current_entities = []
        entity_collections = []
        instance_collections = self.instance_collection()
        if len(instance_collections) > 0:
            for entity_name in self.profile_entities_names(current_profile()):
                if current_profile().entity_by_name(entity_name) is not None:
                    current_entities.append(entity_name)
        return current_entities

    def instance_collection(self):
        """
        Enumerate all the instances found in the instance directory
        rtype: list
        """
        dirs = self.xform_xpaths()
        instance_collections = []
        if len(dirs) > 0:
            for dir_f in dirs:
                xml_files = [dir_f.replace("\\", "/")+'/'+f for f in os.listdir(dir_f) if f.endswith('.xml')]
                if len(xml_files)>0:
                    instance_collections.append(xml_files[0])
        return instance_collections

    def check_profile_with_custom_name(self):
        """
        Try extract mobile instance with custom filter name.
        Assumption is that there is a profile that bears that name
        :return:
        """
        mismatch_profile = 'Nothing found to import. \n' \
                           ' Ensure the current filter text or profile is correct'
        entity_attr = []
        if self.txt_filter.text()!= '':
            for obj in self.profiles():
                if obj.name.startswith(self.txt_filter.text()):
                    if obj.name != current_profile().name:
                        self._notif_bar_str.insertErrorNotification(mismatch_profile)
                        return
        return self.uuid_extractor.document_entities(self.profile)

    def profile_entities_names(self, profile):
        """
        Return names of all entities in a profile
        :rtype: list
        """
        entities_names = []
        for entity in profile.user_entities():
            entities_names.append(entity.name)
        return entities_names

    def has_foreign_keys_parent(self, select_entities):
        """
        Ensure we check that the table is not parent else
        import parent table first
        Revised in version 1.7. It explicitly assumes str is captured. before it was optional.
        :return:
        """
        has_relations = False
        str_tables = current_profile().social_tenure
        party_tbls = str_tables.parties
        sp_tbls = str_tables.spatial_units
        self.relations = OrderedDict()
        if len(self.instance_list) > 0:
            if self.uuid_extractor.has_str_captured_in_instance(self.instance_list[0]):
                for party_tbl in party_tbls:
                    self.relations[party_tbl.name] = ['social_tenure_relationship',
                                                     party_tbl.short_name.lower() + '_id']
                for sp_tbl in sp_tbls:
                    self.relations[sp_tbl.name] = ['social_tenure_relationship',
                                                  sp_tbl.short_name.lower() + '_id']
           # print self.relations

        for table in select_entities:
            table_object = current_profile().entity_by_name(table)
            cols = table_object.columns.values()
            for col in cols:
                if col.TYPE_INFO == 'FOREIGN_KEY':
                    parent_object = table_object.columns[col.name]
                    if parent_object.parent:
                        if parent_object.parent.name in self.relations:
                            self.relations[parent_object.parent.name].append([table, col.name])
                        else:

                            self.relations[parent_object.parent.name] = [table, col.name]
                            #self.relations[parent_object.parent.name].append([table, col.name])
                    has_relations = True
                else:
                    continue

            return has_relations

    def parent_table_isselected(self):
        """
        Take note that the user selected tables may or may not be imported
        based on parent child table relationship.
        Add those table silently so that we can show them to the user
        :return:
        """
        try:
            silent_list = []
            entities = self.user_selected_entities()
            if len(entities) > 0:
                for table in self.relations.keys():
                    if table not in entities:
                        silent_list.append(table)
            return silent_list
        except Exception as ex:
            self._notif_bar_str.insertErrorNotification(ex.message)

    def archive_this_import_file(self, counter, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            self.importlogger.logger_sections()
            file_info = 'File instance ' + str(counter)+ ' : \n' + instance
            self.importlogger.log_action(file_info)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message)
            pass

    def log_table_entry(self, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            current_time = QDateTime()
            import_time = current_time.currentDateTime()
            log_entry = instance + ' '+ str(import_time.toPyDateTime())
            self.importlogger.log_action(log_entry)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message)
            pass

    def check_previous_import(self):
        """
        Ensure we are importing files once
        :return:
        """
        try:
            self.importlogger.add_log_info()
            for files in self.instance_list:
                current_dir = os.path.basename(files)
                exist = self.importlogger.check_file_exist(current_dir)
                if exist:
                    self.instance_list.remove(files)
            self.txt_count.setText(str(len(self.instance_list)))
            if self.record_count() != len(self.instance_list):
                msg = 'Some files have been already imported and therefore ' \
                   'not enumerated'
                self._notif_bar_str.insertErrorNotification(msg)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message)
            pass

    def available_records(self):
        """
        Let the user know how many records have been collected and are available
         for inport process
        :return:
        """
        self.txt_count.setText(unicode(self.record_count()))

    def record_count(self):
        """
        get the count of instance dir in the selected directory
        :return: integer
        """
        return len([name for name in os.listdir(self.path)
                    if os.path.isdir(os.path.join(self.path, name))
                    if name.startswith(self.profile_formater())])

    def profile_formater(self):
        """
        Format the profile name by removing underscore character
        :return:
        """
        if self.txt_filter.text() != '':
            filter_text = self.txt_filter.text()
            return filter_text
        else:
            return self.profile

    def update_files_with_custom_filter(self):
        """
        Get the new file count with the user custom filter text
        :return: file count
        """
        self.available_records()
        self.on_dir_path()
        self.populate_entities_widget()

    def projection_settings(self):
        """
        let user select the projections for the data
        :return:
        """
        project_select = ProjectionSelector(self)
        projection = project_select.loadAvailableSystems()
        self.txt_srid.setText(str(projection))

    def on_projection_select(self):
        """
        Get the selected projection and set it during data import
        :return:
        """
        vals = self.txt_srid.text().split(":")
        return vals[1]

    def on_directory_search(self):
        """
        Let the user choose the directory with instances
        :return:
        """
        home_path = 'home'
        if self.txt_directory.text() != '':
            home_path = self.txt_directory.text()

        dir_name = QFileDialog.getExistingDirectory(
                self, 'Open Directory', home_path, QFileDialog.ShowDirsOnly
                )
        if dir_name:
            self.txt_directory.setText(str(dir_name))
            self.current_profile_changed()
        self.change_check_state(self.chk_all.checkState())

    def feedback_message(self, msg):
        """
        Create a dialog box to capture and display errrors related to db
        while importing data
        :param: msg
        :type: string
        :return:Qdialog
        """
        msgbox = QMessageBox()
        msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No)
        msgbox.setWindowTitle("Data Import")
        msgbox.setText(msg)
        msgbox.exec_()
        msgbox.show()
        return msgbox

    def save_instance_data_to_db(self, entities):
        """
        Get the user selected entities and insert them into database
        params: selected entities
        rtype: list
        :return:Object
        :type: dbObject
        """
        cu_obj = ''
        import_status = False
        self.txt_feedback.clear()
        self.txt_feedback.append("Import started, please wait...\n")
        QCoreApplication.processEvents()
        self._notif_bar_str.clear()
        mobile_field_data = self.read_instance_data()
        self.has_foreign_keys_parent(entities)
        #print self.relations
        if len(self.parent_table_isselected()) > 0:
            if QMessageBox.information(self, QApplication.translate('GeoODKMobileSettings', " Import Warning"),
                                       QApplication.translate('GeoODKMobileSettings',
                                                              'Some of dependent tables (entities)'
                                                              'which may not be part of the selected tables '
                                                              'I.e: {} will be imported'
                                                                      .format(self.parent_table_isselected())),
                                       QMessageBox.Ok | QMessageBox.No) == QMessageBox.No:
                return
        try:
            counter = 0

            if len(mobile_field_data) > 0:
                self.pgbar.setRange(counter, len(self.instance_list))
                self.pgbar.setValue(0)
                self.importlogger.log_action("Import started ...\n")

                for instance_obj, instance_obj_data in mobile_field_data.iteritems():
                    self.importlogger.log_action("File {} ...\n".format(instance_obj))
                    parents_info = []
                    import_status = False
                    counter = counter + 1
                    self.parent_ids = {}

                    single_occuring, repeated_entities = self.uuid_extractor.attribute_data_from_nodelist(
                        instance_obj_data[0])

                    for entity, entity_data in single_occuring.iteritems():
                        import_status = False
                        if entity in self.relations.keys():
                            if entity in self.parent_ids:
                                continue
                            self.count_import_file_step(counter, entity)
                            log_timestamp = '=== parent table import  === : {0}'.format(entity)
                            cu_obj = entity
                            self.log_table_entry(log_timestamp)

                            entity_add = Save2DB(entity, entity_data)
                            entity_add.objects_from_supporting_doc(instance_obj)
                            ref_id = entity_add.save_parent_to_db()
                            import_status = True
                            self.parent_ids[entity] = [ref_id, entity]
                            #log_timestamp = ' --- import succeeded:    {0}' .format(str(import_status))
                            #self.log_table_entry(log_timestamp)


                            parents_info.append(entity)
                            single_occuring.pop(entity)

                        elif entity not in self.relations.keys():
                            import_status = False

                            for fk_table_name in self.relations.keys():
                                if fk_table_name not in self.parent_ids:
                                    in_relations = [_item for subitem in self.relations[fk_table_name]
                                                    for _item in subitem]
                                    if entity in in_relations:
                                        fk_table_data = single_occuring[fk_table_name]
                                        entity_add = Save2DB(fk_table_name, fk_table_data)
                                        ref_id = entity_add.save_parent_to_db()
                                        self.parent_ids[fk_table_name] = [ref_id, fk_table_name]
                                        continue

                            self.count_import_file_step(counter, entity)
                            log_timestamp = '=== standalone table import  === : {0}'.format(entity)
                            cu_obj = entity
                            self.log_table_entry(log_timestamp)
                            entity_add = Save2DB(entity, entity_data, self.parent_ids)
                            entity_add.objects_from_supporting_doc(instance_obj)
                            child_id = entity_add.save_to_db()
                            cu_obj = entity
                            import_status = True
                            parents_info.append(entity)
                            if entity in self.parent_ids:
                                continue
                            else:
                                self.parent_ids[entity] = [child_id, entity]
                            entity_add.cleanup()

                    if repeated_entities:
                        #self.log_table_entry(" ========== starting import of repeated tables ============")
                        import_status = False
                        for repeated_entity, entity_data in repeated_entities.iteritems():
                            """We are assuming that the number of repeat table cannot exceed 99"""
                            enum_index = repeated_entity[:2]
                            if enum_index.isdigit():
                                repeat_table = repeated_entity[2:]
                            else:
                                enum_index = repeated_entity[:1]
                                repeat_table = repeated_entity[1:]
                            log_timestamp = '          child table {0} >> : {1}' \
                                    .format(repeat_table, enum_index)
                            self.count_import_file_step(counter, repeat_table)
                            self.importlogger.log_action(log_timestamp)
                            if repeat_table in self.profile_entities_names(current_profile()):
                                entity_add = Save2DB(repeat_table, entity_data, self.parent_ids)
                                entity_add.objects_from_supporting_doc(instance_obj)
                                child_id = entity_add.save_to_db()
                                cu_obj = repeat_table
                                import_status = True
                                self.log_table_entry(" ------ import succeeded:   {0} ".format(import_status))
                                entity_add.cleanup()
                            else:
                                continue
                    if instance_obj_data[1]:
                        '''We treat social tenure entities separately because of foreign key references'''
                        entity_relation = EntityImporter(instance_obj)
                        single_str, multiple_str = self.uuid_extractor.attribute_data_from_nodelist(
                            instance_obj_data[1])
                        self.txt_feedback.append('----Creating social tenure relationship')
                        if len(single_str)>0:
                            entity_relation.process_social_tenure(single_str, self.parent_ids)

                        elif len(multiple_str)>1:
                            for repeated_entity, entity_data in multiple_str.iteritems():
                                """We are assuming that the number of repeat str cannot exceed 10"""
                                entity_relation.process_social_tenure(entity_data, self.parent_ids)

                        self.log_table_entry(" ----- saving social tenure relationship")
                        entity_add.cleanup()

                    self.txt_feedback.append('saving record "{0}" to database'.format(counter))
                    self.pgbar.setValue(counter)

                    QCoreApplication.processEvents()
                    self.log_instance(instance_obj)
                self.txt_feedback.append('Number of records successfully imported:  {}'
                                         .format(counter))

            else:
                self._notif_bar_str.insertErrorNotification("No available records to import")
                self.pgbar.setValue(0)
                return
        except SQLAlchemyError as ae:
            QCoreApplication.processEvents()
            QApplication.restoreOverrideCursor()
            self.feedback_message(unicode(ae.message))
            self.txt_feedback.append("current table {0}import failed...\n".format(cu_obj))
            self.txt_feedback.append(str(ae.message))
            self.log_table_entry(unicode(ae.message))
            return

    def count_import_file_step(self, count = None, table = None):
        """
        Tracking method to record the current import activity
        :param count: int
        :param table: string
        :return:
        """
        self.txt_feedback.append('      Table : {}'.format(table))

    def accept(self):
        """
        Execute the import dialog once the save button has been clicked
        :return:
        """
        self.buttonBox.setEnabled(False)
        QApplication.setOverrideCursor(Qt.WaitCursor)

        try:
            if self.lst_widget.count() < 1:
                msg = 'No mobile records found for the current profile'
                self._notif_bar_str.insertErrorNotification(msg)
                self.buttonBox.setEnabled(True)
                QApplication.restoreOverrideCursor()
                return
            entities = self.user_selected_entities()
            if len(entities) < 1:
                if QMessageBox.information(self,
                        QApplication.translate('MobileForms', 'Import Warning'),
                        QApplication.translate('MobileForms',
                        'You have not '
                        'selected any entity for import. All entities '
                        'will be imported'), QMessageBox.Ok |
                                            QMessageBox.No) == QMessageBox.Ok:
                    entities = self.instance_entities()
                else:
                    self.buttonBox.setEnabled(True)
                    return

            self.save_instance_data_to_db(entities)
            self.buttonBox.setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
            QApplication.restoreOverrideCursor()
        except Exception as ex:
            self.feedback_message(ex.message)
            self.log_table_entry(unicode(ex.message))
            self.buttonBox.setEnabled(True)
            QApplication.restoreOverrideCursor()
            return

    def log_instance(self, instance):
        instance_short_name = self.importlogger.log_data_name(instance)
        log_data = self.importlogger.read_log_data()
        log_data[instance_short_name] = self.importlogger.log_date()
        self.importlogger.write_log_data(log_data)

    def remove_imported_instances(self):
        count = 0
        del_list = []
        log_data = self.importlogger.read_log_data()
        if len(log_data) > 0:
            for instance in self.instance_list:
                instance_short_name = self.importlogger.log_data_name(instance)
                if instance_short_name in log_data:
                    del_list.append(instance)
                    count += 1

        for inst in del_list:
            self.instance_list.remove(inst)
        return count
Beispiel #45
0
class ColumnEditor(QDialog, Ui_ColumnEditor):
    """
    Dialog to add/edit entity columns
    """
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param kwargs: Keyword dictionary of the following parameters;
         column  - Column you editing, None if its a new column
         entity  - Entity you are adding the column to
         profile - Current profile
         in_db   - Boolean flag to indicate if a column has been created in 
                   the database
         auto_add- True to automatically add a new column to the entity, 
                   default is False.
        """

        self.form_parent = kwargs.get('parent', self)
        self.column = kwargs.get('column', None)
        self.entity = kwargs.get('entity', None)
        self.profile = kwargs.get('profile', None)
        self.in_db = kwargs.get('in_db', False)
        self.is_new = kwargs.get('is_new', True)
        self.auto_entity_add = kwargs.get('auto_add', False)

        QDialog.__init__(self, self.form_parent)

        self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set']

        self.EX_TYPE_INFO = [
            'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE',
            'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT',
            'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED'
        ]

        self.setupUi(self)
        self.dtypes = {}

        self.type_info = ''

        # dictionary to hold default attributes for each data type
        self.type_attribs = {}
        self.init_type_attribs()

        # dictionary to act as a work area for the form fields.
        self.form_fields = {}
        self.init_form_fields()

        self.fk_entities = []
        self.lookup_entities = []

        # Exclude column type info in the list
        self._exclude_col_type_info = []

        if self.is_new:
            self.prop_set = None  # why not False??
        else:
            self.prop_set = True

        # the current entity should not be part of the foreign key parent table,
        # add it to the exclusion list
        self.FK_EXCLUDE.append(self.entity.short_name)

        self.type_names = \
                [unicode(name) for name in BaseColumn.types_by_display_name().keys()]

        self.cboDataType.currentIndexChanged.connect(self.change_data_type)
        self.btnColProp.clicked.connect(self.data_type_property)
        self.edtColName.textChanged.connect(self.validate_text)

        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_controls()

    def exclude_column_types(self, type_info):
        """
        Exclude the column types with the given type_info.
        :param type_info: List of TYPE_INFO of columns to exclude.
        :type type_info: list
        """
        self._exclude_col_type_info = type_info

        # Block index change signal of combobox
        self.cboDataType.blockSignals(True)

        # Reload column data types
        self.populate_data_type_cbo()

        # Select column type if it had been specified
        if not self.column is None:
            text = self.column.display_name()
            self.cboDataType.setCurrentIndex(self.cboDataType.findText(text))

        # Re-enable signals
        self.cboDataType.blockSignals(False)

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(message)

    def _column_type_info(self, column):
        """
        Check if column has TYPE_INFO attribute
        :param column: Entity column object
        :return: Column type. Otherwise None
        :rtype: String or None
        """
        try:
            return column.TYPE_INFO
        except AttributeError:
            return None

    def init_controls(self):
        """
        Initialize GUI controls default state when the dialog window is opened.
        """
        self.populate_data_type_cbo()

        if not self.column is None:
            self.column_to_form(self.column)
            self.column_to_wa(self.column)

        self.edtColName.setFocus()

        self.edtColName.setEnabled(not self.in_db)

        self.cboDataType.setEnabled(not self.in_db)

        self.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(
            self.accept)
        self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(
            self.cancel)

        col_type = self._column_type_info(self.column)
        if not self.in_db and col_type == 'GEOMETRY':
            opts = self.type_attribs[col_type]
            self.cbMandt.setEnabled(opts['mandt']['enabled_state'])
            self.cbUnique.setEnabled(opts['unique']['enabled_state'])
            self.cbIndex.setEnabled(opts['index']['enabled_state'])
        else:
            self.cbMandt.setEnabled(not self.in_db)
            self.cbUnique.setEnabled(not self.in_db)
            self.cbIndex.setEnabled(not self.in_db)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return

        name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
        name_validator = QtGui.QRegExpValidator(name_regex)
        text_edit.setValidator(name_validator)
        QApplication.processEvents()
        last_character = text[-1:]
        locale = QSettings().value("locale/userLocale")[0:2]

        #if locale == 'en':
        state = name_validator.validate(text, text.index(last_character))[0]
        if state != QValidator.Acceptable:
            self.show_notification(
                u'"{}" is not allowed at this position.'.format(
                    last_character))
            text = text[:-1]

        # fix caps, _, and spaces
        if last_character.isupper():
            text = text.lower()
        if last_character == ' ':
            text = text.replace(' ', '_')
        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]
            text = text.replace(' ', '_').lower()

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def column_to_form(self, column):
        """
        Initializes form controls with Column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        text = column.display_name()
        self.cboDataType.setCurrentIndex(self.cboDataType.findText(text))

        self.edtColName.setText(column.name)
        self.edtColDesc.setText(column.description)
        self.txt_form_label.setText(column.label)
        self.edtUserTip.setText(column.user_tip)
        self.cbMandt.setChecked(column.mandatory)
        self.cbSearch.setCheckState(self.bool_to_check(column.searchable))
        self.cbUnique.setCheckState(self.bool_to_check(column.unique))
        self.cbIndex.setCheckState(self.bool_to_check(column.index))

        ti = self.current_type_info()
        ps = self.type_attribs[ti].get('prop_set', None)
        if ps is not None:
            self.type_attribs[ti]['prop_set'] = self.prop_set

    def column_to_wa(self, column):
        """
        Initialize 'work area' form_fields with column data.
        :param column: BaseColumn instance
        :type column: BaseColumn
        """
        if column is not None:
            self.form_fields['colname'] = column.name
            self.form_fields['value'] = None
            self.form_fields['mandt'] = column.mandatory
            self.form_fields['search'] = column.searchable
            self.form_fields['unique'] = column.unique
            self.form_fields['index'] = column.index

            if hasattr(column, 'minimum'):
                self.form_fields['minimum'] = column.minimum
                self.form_fields['maximum'] = column.maximum

            if hasattr(column, 'srid'):
                self.form_fields['srid'] = column.srid
                self.form_fields['geom_type'] = column.geom_type

            if hasattr(column, 'entity_relation'):
                self.form_fields['entity_relation'] = column.entity_relation

            if hasattr(column, 'association'):
                self.form_fields[
                    'first_parent'] = column.association.first_parent
                self.form_fields[
                    'second_parent'] = column.association.second_parent

            if hasattr(column, 'min_use_current_date'):
                self.form_fields[
                    'min_use_current_date'] = column.min_use_current_date
                self.form_fields[
                    'max_use_current_date'] = column.max_use_current_date

            if hasattr(column, 'min_use_current_datetime'):
                self.form_fields['min_use_current_datetime'] = \
                        column.min_use_current_datetime
                self.form_fields['max_use_current_datetime'] = \
                        column.max_use_current_datetime

            if hasattr(column, 'prefix_source'):
                self.form_fields['prefix_source'] = column.prefix_source
                self.form_fields['columns'] = column.columns
                self.form_fields[
                    'column_separators'] = column.column_separators
                self.form_fields['leading_zero'] = column.leading_zero
                self.form_fields['separator'] = column.separator
                self.form_fields['colname'] = column.name
                self.form_fields['enable_editing'] = column.enable_editing
                self.form_fields[
                    'disable_auto_increment'] = column.disable_auto_increment
                self.form_fields['hide_prefix'] = column.hide_prefix

            # Decimal properties
            if hasattr(column, 'precision'):
                self.form_fields['precision'] = column.precision
                self.form_fields['scale'] = column.scale

            # Expression column
            if hasattr(column, 'expression'):

                self.form_fields['expression'] = column.expression
                self.form_fields['output_data_type'] = column.output_data_type

    def bool_to_check(self, state):
        """
        Converts a boolean to a Qt checkstate.
        :param state: True/False
        :type state: boolean
        :rtype: Qt.CheckState
        """
        return Qt.Checked if state else Qt.Unchecked

    def init_form_fields(self):
        """
        Initializes work area 'form_fields' dictionary with default values.
        Used when creating a new column.
        """
        none = QApplication.translate('CodeProperty', 'None')
        self.form_fields['colname'] = ''
        self.form_fields['value'] = None
        self.form_fields['mandt'] = False
        self.form_fields['search'] = False
        self.form_fields['unique'] = False
        self.form_fields['index'] = False
        self.form_fields['minimum'] = self.type_attribs.get('minimum', 0)
        self.form_fields['maximum'] = self.type_attribs.get('maximum', 0)
        self.form_fields['srid'] = self.type_attribs.get('srid', "")
        self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0)
        self.form_fields['in_db'] = self.in_db
        self.form_fields['prefix_source'] = self.type_attribs.get(
            'prefix_source', none)
        self.form_fields['columns'] = self.type_attribs.get('columns', [])
        self.form_fields['column_separators'] = self.type_attribs.get(
            'column_separators', [])
        self.form_fields['leading_zero'] = self.type_attribs.get(
            'leading_zero', '')
        self.form_fields['separator'] = self.type_attribs.get('separator', '')
        self.form_fields['enable_editing'] = self.type_attribs.get(
            'enable_editing', '')
        self.form_fields['disable_auto_increment'] = self.type_attribs.get(
            'disable_auto_increment', '')
        self.form_fields['hide_prefix'] = self.type_attribs.get(
            'hide_prefix', '')
        self.form_fields['precision'] = self.type_attribs.get('precision', 18)
        self.form_fields['scale'] = self.type_attribs.get('scale', 6)


        self.form_fields['entity_relation'] = \
                self.type_attribs['FOREIGN_KEY'].get('entity_relation', None)

        self.form_fields['entity_relation'] = \
                self.type_attribs['LOOKUP'].get('entity_relation', None)

        self.form_fields['first_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None)

        self.form_fields['second_parent'] = \
                self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None)

        self.form_fields['min_use_current_date'] = \
                self.type_attribs['DATE'].get('min_use_current_date', None)

        self.form_fields['max_use_current_date'] = \
                self.type_attribs['DATE'].get('max_use_current_date', None)

        self.form_fields['min_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('min_use_current_datetime', None)

        self.form_fields['max_use_current_datetime'] = \
                self.type_attribs['DATETIME'].get('max_use_current_datetime', None)

        self.form_fields['expression'] = self.type_attribs.get(
            'expression', '')
        self.form_fields['output_data_type'] = self.type_attribs.get(
            'output_data_type', '')

    def init_type_attribs(self):
        """
        Initializes data type attributes. The attributes are used to
        set the form controls state when a particular data type is selected.
        mandt - enables/disables checkbox 'Mandatory'
        search - enables/disables checkbox 'Searchable'
        unique - enables/disables checkbox 'Unique'
        index - enables/disables checkbox 'Index'
        *property - function to execute when a data type is selected.
        """
        self.type_attribs['VARCHAR'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': True
            },
            'maximum': 30,
            'property': self.varchar_property
        }

        self.type_attribs['INT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': 0,
            'maximum': 0,
            'property': self.bigint_property
        }

        self.type_attribs['TEXT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
        }

        self.type_attribs['DOUBLE'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': True
            },
            'minimum': 0.0,
            'maximum': 0.0,
            'precision': 18,
            'scale': 6,
            'property': self.double_property
        }

        self.type_attribs['DATE'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': datetime.date.min,
            'maximum': datetime.date.max,
            'min_use_current_date': False,
            'max_use_current_date': False,
            'property': self.date_property
        }

        self.type_attribs['DATETIME'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'minimum': datetime.datetime.min,
            'maximum': datetime.datetime.max,
            'min_use_current_datetime': False,
            'max_use_current_datetime': False,
            'property': self.dtime_property
        }

        self.type_attribs['FOREIGN_KEY'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': None,
            'show_in_parent': True,
            'show_in_child': True,
            'property': self.fk_property,
            'prop_set': False
        }

        self.type_attribs['LOOKUP'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': {},
            'property': self.lookup_property,
            'prop_set': False
        }

        self.type_attribs['GEOMETRY'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': False
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'srid': "",
            'geom_type': 0,
            'property': self.geometry_property,
            'prop_set': False
        }

        self.type_attribs['BOOL'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': False
            },
            'search': {
                'check_state': False,
                'enabled_state': False
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            }
        }
        self.type_attribs['PERCENT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': False
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            }
        }

        self.type_attribs['ADMIN_SPATIAL_UNIT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'entity_relation': None
        }

        self.type_attribs['MULTIPLE_SELECT'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': False
            },
            'index': {
                'check_state': False,
                'enabled_state': False
            },
            'first_parent': None,
            'second_parent': self.entity,
            'property': self.multi_select_property,
            'prop_set': False
        }

        self.type_attribs['AUTO_GENERATED'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': True,
                'enabled_state': True
            },
            'unique': {
                'check_state': True,
                'enabled_state': True
            },
            'index': {
                'check_state': True,
                'enabled_state': True
            },
            'prefix_source': '',
            'columns': [],
            'column_separators': [],
            'leading_zero': '',
            'separator': '',
            'disable_auto_increment': False,
            'enable_editing': False,
            'property': self.code_property,
            'hide_prefix': False,
            'prop_set': True
        }

        self.type_attribs['EXPRESSION'] = {
            'mandt': {
                'check_state': False,
                'enabled_state': True
            },
            'search': {
                'check_state': False,
                'enabled_state': True
            },
            'unique': {
                'check_state': False,
                'enabled_state': True
            },
            'index': {
                'check_state': False,
                'enabled_state': True
            },
            'output_data_type': '',
            'expression': '',
            'property': self.expression_property,
            'prop_set': False
        }

    def data_type_property(self):
        """
        Executes the function assigned to the property attribute of 
        the current selected data type.
        """
        self.type_attribs[self.current_type_info()]['property']()

    def varchar_property(self):
        """
        Opens the property editor for the Varchar data type.
        If successful, set a minimum column in work area 'form fields'
        """
        editor = VarcharProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['maximum'] = editor.max_len()

    def bigint_property(self):
        """
        Opens a property editor for the BigInt data type.
        """
        editor = BigintProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()

    def double_property(self):
        """
        Opens a property editor for the Double data type.
        """
        editor = DoubleProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['precision'] = editor.precision
            self.form_fields['scale'] = editor.scale

    def date_property(self):
        """
        Opens a property editor for the Date data type.
        """
        editor = DateProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_date'] = \
                    editor.min_use_current_date
            self.form_fields['max_use_current_date'] = \
                    editor.max_use_current_date

    def dtime_property(self):
        """
        Opens a property editor for the DateTime data type.
        """
        editor = DTimeProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['minimum'] = editor.min_val()
            self.form_fields['maximum'] = editor.max_val()
            self.form_fields['min_use_current_datetime'] = \
                    editor.min_use_current_datetime
            self.form_fields['max_use_current_datetime'] = \
                    editor.max_use_current_datetime

    def geometry_property(self):
        """
        Opens a property editor for the Geometry data type.
        If successful, set the srid(projection), geom_type (LINE, POLYGON...)
        and prop_set which is boolean flag to verify that all the geometry
        properties are set. 
        Constraint - If 'prop_set' is False column cannot be saved.
        """
        editor = GeometryProperty(self, self.form_fields)
        result = editor.exec_()
        if result == 1:
            self.form_fields['srid'] = editor.coord_sys()
            self.form_fields['geom_type'] = editor.geom_type()
            self.property_set()

    def admin_spatial_unit_property(self):
        """
        Sets entity relation property used when creating column of type
        ADMIN_SPATIAL_UNIT
        """
        er_fields = {}
        er_fields['parent'] = self.entity
        er_fields['parent_column'] = None
        er_fields['display_columns'] = []
        er_fields['child'] = None
        er_fields['child_column'] = None
        self.form_fields['entity_relation'] = EntityRelation(
            self.profile, **er_fields)

    def fk_property(self):
        """
        Opens a property editor for the ForeignKey data type.
        """
        if len(self.edtColName.displayText()) == 0:
            self.show_message("Please enter column name!")
            return

        # filter list of lookup tables, don't show internal
        # tables in list of lookups
        fk_ent = [entity for entity in self.profile.entities.items() \
                if entity[1].TYPE_INFO not in self.EX_TYPE_INFO]

        fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \
                not in self.FK_EXCLUDE]

        relation = {}
        relation['form_fields'] = self.form_fields
        relation['fk_entities'] = fk_ent
        relation['profile'] = self.profile
        relation['entity'] = self.entity
        relation['column_name'] = unicode(self.edtColName.text())
        relation['show_in_parent'] = '1'
        relation['show_in_child'] = '1'
        editor = FKProperty(self, relation)
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            relation['show_in_parent'] = editor.show_in_parent()
            relation['show_in_child'] = editor.show_in_child()

            self.property_set()

    def lookup_property(self):
        """
        Opens a lookup type property editor
        """
        editor = LookupProperty(self, self.form_fields, profile=self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['entity_relation'] = editor.entity_relation()
            self.property_set()

    def multi_select_property(self):
        """
        Opens a multi select property editor
        """
        if len(self.edtColName.displayText()) == 0:
            self.show_message("Please enter column name!")
            return

        editor = MultiSelectProperty(self, self.form_fields, self.entity,
                                     self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['first_parent'] = editor.lookup()
            self.form_fields['second_parent'] = self.entity
            self.property_set()

    def code_property(self):
        """
        Opens the code data type property editor
        """
        editor = CodeProperty(self,
                              self.form_fields,
                              entity=self.entity,
                              profile=self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['prefix_source'] = editor.prefix_source()
            self.form_fields['columns'] = editor.columns()
            self.form_fields['leading_zero'] = editor.leading_zero()
            self.form_fields['separator'] = editor.separator()
            self.form_fields[
                'disable_auto_increment'] = editor.disable_auto_increment()
            self.form_fields['enable_editing'] = editor.enable_editing()
            self.form_fields['column_separators'] = editor.column_separators()
            self.form_fields['hide_prefix'] = editor.hide_prefix()

            self.property_set()

    def expression_property(self):
        """
        Opens the code data type property editor
        """
        layer = self.create_layer()

        editor = ExpressionProperty(layer, self.form_fields, self)
        result = editor.exec_()
        if result == 1:
            self.form_fields['expression'] = editor.expression_text()
            self.form_fields['output_data_type'] = editor.get_output_data_type(
            )
            self.property_set()

    def create_layer(self):
        srid = None
        column = ''
        if self.entity.has_geometry_column():
            geom_cols = [
                col.name for col in self.entity.columns.values()
                if col.TYPE_INFO == 'GEOMETRY'
            ]
            column = geom_cols[0]
            geom_col_obj = self.entity.columns[column]

            if geom_col_obj.srid >= 100000:
                srid = geom_col_obj.srid
        layer = vector_layer(self.entity.name,
                             geom_column=column,
                             proj_wkt=srid)
        return layer

    def create_column(self):
        """
        Creates a new BaseColumn.
        """
        column = None

        if self.type_info <> "":
            if self.type_info == 'ADMIN_SPATIAL_UNIT':
                self.admin_spatial_unit_property()
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity, **self.form_fields)
                return column

            if self.is_property_set(self.type_info):
                column = BaseColumn.registered_types[self.type_info] \
                        (self.form_fields['colname'], self.entity,
                                self.form_fields['geom_type'],
                                self.entity, **self.form_fields)
            else:
                self.show_message(self.tr('Please set column properties.'))
                return
        else:
            raise self.tr("No type to create.")

        return column

    def property_set(self):
        self.prop_set = True
        self.type_attribs[self.current_type_info()]['prop_set'] = True

    def is_property_set(self, ti):
        """
        Checks if column property is set by reading the value of
        attribute 'prop_set'
        :param ti: Type info to check for prop set
        :type ti: BaseColumn.TYPE_INFO
        :rtype: boolean
        """
        return self.type_attribs[ti].get('prop_set', True)

    def property_by_name(self, ti, name):
        try:
            return self.dtype_property(ti)['property'][name]
        except:
            return None

    def populate_data_type_cbo(self):
        """
        Fills the data type combobox widget with BaseColumn type names.
        """
        self.cboDataType.clear()

        for name, col in BaseColumn.types_by_display_name().iteritems():
            # Specify columns to exclude
            if col.TYPE_INFO not in self._exclude_col_type_info:
                self.cboDataType.addItem(name)

        if self.cboDataType.count() > 0:
            self.cboDataType.setCurrentIndex(0)

    def change_data_type(self, index):
        """
        Called by type combobox when you select a different data type.
        """
        text = self.cboDataType.itemText(index)
        col_cls = BaseColumn.types_by_display_name().get(text, None)
        if col_cls is None:
            return

        ti = col_cls.TYPE_INFO
        if ti not in self.type_attribs:
            msg = self.tr('Column type attributes could not be found.')
            self.notice_bar.clear()
            self.notice_bar.insertErrorNotification(msg)

            return

        self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property'))
        self.type_info = ti
        opts = self.type_attribs[ti]
        self.set_optionals(opts)
        self.set_min_max_defaults(ti)

    def set_optionals(self, opts):
        """
        Enable/disables form controls based on selected 
        column data type attributes
        param opts: Dictionary type properties of selected column
        type opts: dict
        """
        self.cbMandt.setEnabled(opts['mandt']['enabled_state'])
        self.cbSearch.setEnabled(opts['search']['enabled_state'])
        self.cbUnique.setEnabled(opts['unique']['enabled_state'])
        self.cbIndex.setEnabled(opts['index']['enabled_state'])

        self.cbMandt.setCheckState(
            self.bool_to_check(opts['mandt']['check_state']))
        self.cbSearch.setCheckState(
            self.bool_to_check(opts['search']['check_state']))
        self.cbUnique.setCheckState(
            self.bool_to_check(opts['unique']['check_state']))
        self.cbIndex.setCheckState(
            self.bool_to_check(opts['index']['check_state']))

    def set_min_max_defaults(self, type_info):
        """
        sets the work area 'form_fields' default values (minimum/maximum)
        from the column's type attribute dictionary
        :param type_info: BaseColumn.TYPE_INFO
        :type type_info: str
        """
        self.form_fields['minimum'] = \
                self.type_attribs[type_info].get('minimum', 0)

        self.form_fields['maximum'] = \
                self.type_attribs[type_info].get('maximum', 0)

    def current_type_info(self):
        """
        Returns a TYPE_INFO of a data type
        :rtype: str
        """
        text = self.cboDataType.itemText(self.cboDataType.currentIndex())
        try:
            return BaseColumn.types_by_display_name()[text].TYPE_INFO
        except:
            return ''

    def fill_work_area(self):
        """
        Sets work area 'form_fields' with form control values
        """
        self.form_fields['colname'] = unicode(self.edtColName.text())
        self.form_fields['description'] = unicode(self.edtColDesc.text())
        self.form_fields['label'] = unicode(self.txt_form_label.text())
        self.form_fields['index'] = self.cbIndex.isChecked()
        self.form_fields['mandatory'] = self.cbMandt.isChecked()
        self.form_fields['searchable'] = self.cbSearch.isChecked()
        self.form_fields['unique'] = self.cbUnique.isChecked()
        self.form_fields['user_tip'] = unicode(self.edtUserTip.text())

    def show_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM"))
        msg.setText(message)
        msg.exec_()

    def accept(self):
        col_name = unicode(self.edtColName.text()).strip()
        # column name is not empty
        if len(col_name) == 0 or col_name == '_':
            self.show_message(self.tr('Please enter a valid column name.'))
            return False

        # check for STDM reserved keywords
        if col_name in RESERVED_KEYWORDS:
            self.show_message(
                self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\
                "Please choose another column name.".format(col_name)) )
            return False

        new_column = self.make_column()

        if new_column is None:
            LOGGER.debug("Error creating column!")
            self.show_message('Unable to create column!')
            return False

        if self.column is None:  # new column
            if self.duplicate_check(col_name):
                self.show_message(
                    self.tr("Column with the same name already "
                            "exist in this entity!"))

                return False
            if self.auto_entity_add:
                self.entity.add_column(new_column)

            self.column = new_column
            self.done(1)
        else:  # editing a column
            self.column = new_column
            self.done(1)

    def cancel(self):
        self.done(0)

    def make_column(self):
        """
        Returns a newly created column
        :rtype: BaseColumn
        """
        self.fill_work_area()
        col = self.create_column()
        return col

    def duplicate_check(self, name):
        """
        Return True if we have a column in the current entity with same name
        as our new column
        :param col_name: column name
        :type col_name: str
        """
        # check if another column with the same name exist in the current entity
        if name in self.entity.columns:
            return True
        else:
            return False

    def rejectAct(self):
        self.done(0)
Beispiel #46
0
class ValueEditor(QDialog, Ui_LookupValue):
    """
    Form to add/edit values added to a lookup. Values are objects of type
    CodeValue
    """
    def __init__(self, parent, lookup, code_value=None):
        """
        :param parent: Owner of this dialog window
        :type parent: QWidget
        :param lookup: A value list object to add the value
        :type lookup: ValueList
        :param code_value: A value object to add to the lookup,
        if None this is a new value, else its an edit.
        :type code_value: CodeValue
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.lookup = lookup
        self.code_value = code_value
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()

    def init_gui(self):
        """
        initializes the form widgets
        """
        # Set character length constraints
        self.edtValue.setMaxLength(50)
        self.edtCode.setMaxLength(5)

        if self.code_value:
            if self.code_value.updated_value == '':
                self.edtValue.setText(self.code_value.value)
                self.edtCode.setText(self.code_value.code)
            else:
                self.edtValue.setText(self.code_value.updated_value)
                self.edtCode.setText(self.code_value.updated_code)

        self.edtCode.textChanged.connect(self.validate_text)
        self.edtValue.textChanged.connect(self.validate_text)

        self.edtValue.setFocus()

    def show_notification(self, message):
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(message)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()

        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return
        locale = QSettings().value("locale/userLocale")[0:2]

        if locale == 'en':
            name_regex = QtCore.QRegExp('^[ _0-9a-zA-Z][a-zA-Z0-9_/\\-()|.:,; ]*$')
            name_validator = QtGui.QRegExpValidator(name_regex)
            text_edit.setValidator(name_validator)
            QApplication.processEvents()
            last_character = text[-1:]
            state = name_validator.validate(text, text.index(last_character))[0]
            if state != QValidator.Acceptable:
                self.show_notification(u'"{}" is not allowed at this position.'.
                                       format(last_character)
                                       )
                text = text[:-1]

        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def add_value(self):
        """
        Adds a code value to a lookup object. Checks first if a previous value
        exist then removes it and then adds the new one.
        """
        value = unicode(self.edtValue.text().strip())
        code = unicode(self.edtCode.text().strip())
        
        # if its an edit, first remove the previous value
        if self.code_value:
            self.lookup.rename(self.code_value.value, value, code)
        else:
            self.lookup.add_code_value(CodeValue(code, value))
	    
    def accept(self):
        if self.edtValue.text() == '' or self.edtValue.text() == ' ':
                self.error_message(QApplication.translate(
                    "ValueEditor", "Please enter a valid lookup value.")
                )
                return

        self.add_value()
        
        self.done(1)

    def reject(self):
        self.done(0)
    
    def error_message(self, message):
        """
        Creates a message box and displays a message
        :param message: message to display
        :type message: str
        """
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(message)
        msg.exec_()  
Beispiel #47
0
class LookupEditor(QDialog, Ui_dlgLookup):
    """
    Form to add/edit lookup entities.
    """
    def __init__(self, parent, profile, lookup=None):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param profile: A profile to add/edit lookup
        :type profile: Profile
        :type inplace: Flag to check if lookup creation is initiated from the
                       'normal' lookup creation process -inplace = False,
                       this is the normal state. If 'inplace' = True, then
                       creation is initiated from the the lookup selection dialog
        :param lookup: Value list to create, if None this is a new value list
         else its an edit
        :type lookup: ValueList
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.profile = profile
        self.lookup = lookup
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()

    def init_gui(self):
        """
        Initializes form widgets
        """
        self.edtName.setFocus()
        if self.lookup:
            self.edtName.setText(self.lookup.short_name.replace('check_', ''))
        self.edtName.textChanged.connect(self.validate_text)

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        msg = self.tr(message)
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(msg)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return

        name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
        name_validator = QtGui.QRegExpValidator(name_regex)
        text_edit.setValidator(name_validator)
        QApplication.processEvents()
        last_character = text[-1:]
        state = name_validator.validate(text, text.index(last_character))[0]
        if state != QValidator.Acceptable:
            self.show_notification(
                '"{}" is not allowed at this position.'.format(last_character))
            text = text[:-1]
        else:
            # fix caps, underscores, and spaces
            if last_character.isupper():
                text = text.lower()
            if last_character == ' ':
                text = text.replace(' ', '_')
            if len(text) > 1:
                if text[0] == ' ' or text[0] == '_':
                    text = text[1:]
                text = text.replace(' ', '_').lower()

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def format_lookup_name(self, name):
        """
        Replace spaces with underscore in a name string
        :param name: Name to replace spaces 
        :type name: str
        :rtype: str
        """
        formatted_name = str(name).strip()
        formatted_name = formatted_name.replace(' ', "_")
        return formatted_name.lower()

    def create_lookup(self, name):
        """
        Creates a lookup entity and add it to a profile.
        If this is an edit, first the previous lookup is removed before
        adding a new one.
        :param name: Name of the new/edited lookup
        :type name: Unicode
        """
        name = self.format_lookup_name(name)
        new_lookup = self.profile.create_entity(name, value_list_factory)
        return new_lookup

    def accept(self):
        if self.edtName.text() == '' or self.edtName.text() == '_':
            self.error_message(
                QApplication.translate("LookupEditor",
                                       "Please enter a valid lookup name."))
            return

        if self.edtName.text() == 'check':
            self.error_message(
                QApplication.translate(
                    "LookupEditor", "'check' is used internally by STDM! "
                    "Select another name for the lookup"))
            return

        short_name = unicode(self.edtName.text())

        if self.lookup is None:  # new lookup
            if self.duplicate_check(short_name):
                self.show_message(
                    self.tr("Lookup with the same name already "
                            "exist in the current profile!"))
                return
            else:
                new_lookup = self.create_lookup(short_name)
                self.profile.add_entity(new_lookup)
                self.lookup = new_lookup
        else:
            self.edit_lookup(short_name)

        self.done(1)

    def duplicate_check(self, name):
        """
        Return True if we have an entity in the current profile with same 'name'
        :param name: entity short_name
        :type name: Unicode
        :rtype:boolean
        """
        return self.profile.entities.has_key(name)

    def edit_lookup(self, short_name):
        short_name = short_name.replace('check_', '')
        self.lookup.short_name = u'{0}_{1}'.format('check', short_name)

    def reject(self):
        self.done(0)

    def error_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(message)
        msg.exec_()
Beispiel #48
0
class ImageExportSettings(WIDGET, BASE):
    """A dialog for settings options for exporting an image."""
    def __init__(self, parent=None, **kwargs):
        super(ImageExportSettings, self).__init__(parent)
        self.setupUi(self)

        self.btn_path.setIcon(GuiUtils.get_icon('open_file.png'))

        self._image_tr = self.tr('Image')

        self.notif_bar = NotificationBar(self.vl_notification, 6000)

        # Connect signals
        self.btn_path.clicked.connect(self._on_choose_image_path)
        self.buttonBox.accepted.connect(self.on_accept)
        self.sb_resolution.valueChanged.connect(self._on_resolution_changed)

        # Set color button defaults
        self._default_color = Qt.white
        self.btn_color.setDefaultColor(self._default_color)
        self.btn_color.setColor(self._default_color)
        self.btn_color.setAllowOpacity(True)

        self.path = kwargs.get('image_path', '')
        self.resolution = kwargs.get('resolution', '96')
        self.background_color = kwargs.get('background', Qt.transparent)

        self._update_controls()

    def _update_controls(self):
        # Update input controls with export settings' values
        if self.background_color == Qt.transparent:
            self.rb_transparent.setChecked(True)
            self.btn_color.setVisible(False)

        else:
            self.rb_fill.setChecked(True)
            self.btn_color.setColor(self.background_color)

        self.sb_resolution.setValue(int(self.resolution))
        self.txt_path.setText(self.path)

        # Set image size just in case value does not change
        self._set_image_size()

    def _on_resolution_changed(self, value):
        # Slot raised when the resolution changes
        self._set_image_size()

    def _update_export_vars(self):
        # Update export variables based on user values.
        self.path = self.txt_path.text()
        self.resolution = self.sb_resolution.value()

        if self.rb_transparent.isChecked():
            self.background_color = Qt.transparent

        else:
            self.background_color = self.btn_color.color()

    def _set_image_size(self):
        # Set image size based on the resolution using A4 paper size
        res = self.sb_resolution.value()
        if res == 0:
            return

        # To mm
        res_mm = res / 25.4

        # A4 landscape size
        width = int(297 * res_mm)
        height = int(210 * res_mm)

        units = 'px'
        width_display = '{0} {1}'.format(width, units)
        height_display = '{0} {1}'.format(height, units)
        self.lbl_width.setText(width_display)
        self.lbl_height.setText(height_display)

    def _image_filters(self):
        # Return supported image formats for use in a QFileDialog filter
        formats = []

        for f in QImageWriter.supportedImageFormats():
            f_type = f.data().decode()
            filter_format = '{0} {1} (*.{2})'.format(f_type.upper(),
                                                     self._image_tr, f_type)
            formats.append(filter_format)

        return ';;'.join(formats)

    def _on_choose_image_path(self):
        # Slot raised to choose image path
        img_path = self.txt_path.text()
        title = self.tr('Specify image location')
        sel_image_path, _ = QFileDialog.getSaveFileName(
            self, title, img_path, self._image_filters())

        if sel_image_path:
            self.txt_path.setText(sel_image_path)

    def on_accept(self):
        """
        Slot raised to save the settings and close the dialog.
        """
        if not self.txt_path.text():
            msg = self.tr('Please specify the image path.')
            self.notif_bar.insertErrorNotification(msg)
            self.txt_path.setFocus()

            return

        self._update_export_vars()

        self.accept()
Beispiel #49
0
    def __init__(self, parent=None,selectMode=True, filter_data_source=''):
        QDialog.__init__(self,parent)
        self.setupUi(self)
        
        self.notifBar = NotificationBar(self.vlNotification)

        self._mode = selectMode

        #Filter templates by the specified table name
        self._filter_data_source = filter_data_source

        #Document templates in current profile
        self._profile_templates = []

        self._current_profile = current_profile()

        #Load current profile templates
        self._load_current_profile_templates()
        
        if selectMode:
            self.buttonBox.setVisible(True)
            self.manageButtonBox.setVisible(False)
            currHeight = self.size().height()
            self.resize(200,currHeight)
            
        else:
            self.buttonBox.setVisible(False)
            self.manageButtonBox.setVisible(True)
            self.setWindowTitle(
                QApplication.translate(
                    "TemplateDocumentSelector",
                    "Template Manager"
                )
            )
            
        #Configure manage buttons
        btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok)
        btnEdit.setText(QApplication.translate("TemplateDocumentSelector","Edit..."))
        btnEdit.setIcon(QIcon(":/plugins/stdm/images/icons/edit.png"))
        
        btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save)
        btnDelete.setText(QApplication.translate("TemplateDocumentSelector","Delete"))
        btnDelete.setIcon(QIcon(":/plugins/stdm/images/icons/delete.png"))
        
        #Connect signals
        self.buttonBox.accepted.connect(self.onAccept)
        btnEdit.clicked.connect(self.onEditTemplate)
        btnDelete.clicked.connect(self.onDeleteTemplate)
        
        #Get saved document templates then add to the model
        templates = documentTemplates()

        self._docItemModel = QStandardItemModel(parent)
        self._docItemModel.setColumnCount(2)

        #Append current profile templates to the model.
        for dt in self._profile_templates:

            if self._template_contains_filter_table(dt):
                doc_name_item = self._createDocNameItem(dt.name)
                file_path_item = QStandardItem(dt.path)
                self._docItemModel.appendRow([doc_name_item,file_path_item])

        self.lstDocs.setModel(self._docItemModel)
Beispiel #50
0
 def setNotificationLayout(self, layout):
     '''
     Set the vertical layout instance that will be used to display
     notification messages.
     '''
     self._notifBar = NotificationBar(layout)
Beispiel #51
0
class ProfileInstanceRecords(QDialog, FORM_CLASS):
    """
    class constructor
    The class handles all the instances that the user has collected
    and saved in the folder and saved in a computer. The class will
    construct the path to the folder and enumerate all available instances
    and return the count. It will also rename all the file based on instance
    unique GUUID for easier management and future updates.
    """
    def __init__(self, parent=None):
        """
        initailize class variables here
        """
        super(ProfileInstanceRecords, self).__init__(parent)
        self.setupUi(self)

        self.path = None
        self.instance_list = []
        self.relations = OrderedDict()
        self.parent_ids = {}
        self.importlogger = ImportLogger()
        self._notif_bar_str = NotificationBar(self.vlnotification)

        self.chk_all.setCheckState(Qt.Checked)
        self.entity_model = EntitiesModel()
        self.uuid_extractor = InstanceUUIDExtractor(self.path)
        self.btn_chang_dir.setIcon(
            QIcon(":/plugins/stdm/images/icons/open_file.png"))
        self.btn_refresh.setIcon(
            QIcon(":/plugins/stdm/images/icons/update.png"))

        self.chk_all.stateChanged.connect(self.change_check_state)
        #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed)
        self.btn_chang_dir.clicked.connect(self.on_directory_search)
        self.lst_widget.itemClicked.connect(self.user_selected_entities)
        self.btn_refresh.clicked.connect(self.update_files_with_custom_filter)

        self.buttonBox.button(QDialogButtonBox.Save).setText('Import')

        #self.load_config()
        self.init_file_path()
        self.current_profile_changed()
        self.change_check_state(self.chk_all.checkState())
        self.instance_dir()

    def load_config(self):
        """
        Load STDM configuration
        :return:
        """
        stdm_config = None
        if QFile.exists(HOME + "/stdm/configuration.stc"):
            stdm_config = QFile(CONFIG_FILE)
        ConfigurationFileSerializer(stdm_config)
        profiles = StdmConfiguration.instance().profiles
        return profiles

    def change_check_state(self, state):
        """
        Change the check state of items in a list widget
        """
        for i in range(self.lst_widget.count()):
            self.lst_widget.item(i).setCheckState(state)

    def profiles(self):
        """
        Return a list of all profiles
        :rtype: list
        """
        return self.load_config().values()

    def current_profile_changed(self):
        """
        Get the current profile so that it is the one selected at the combo box
        :return:
        """
        self.instance_list = []
        self.active_profile()
        self.init_file_path()
        self.available_records()
        self.on_dir_path()
        self.populate_entities_widget()

    def active_profile(self):
        """
        get the user selected profile
        :return:p
        """
        self.profile = current_profile().name
        return self.profile

    def instance_dir(self):
        """
        Create a path where imported instance will be kept
        :return:
        """
        self.inst_path = CONFIG_FILE + "_imported"
        if not os.access(self.inst_path, os.F_OK):
            os.makedirs(unicode(self.inst_path))
        else:
            return self.inst_path

    def imported_instance_path(self):
        """
        :return:
        """
        self.instance_dir()
        return self.inst_path

    def init_file_path(self):
        """
        Initialize GeoODK file path
        """
        self.path = self.geoODK_file_path(self.txt_directory.text())
        self.txt_directory.setText(self.path)

    def geoODK_file_path(self, path=''):
        """
        Check if geoODK file path has been configured, if not configure default
        and return it.
        :rtype: string
        """
        if not path.strip():
            path = self.make_path(GEOODK_FORM_HOME)
        return path

    def make_path(self, path):
        """
        Create and return a file path if is not available.
        :rtype: string
        """
        if not os.access(path, os.F_OK):
            os.makedirs(unicode(path))
        return path

    def xform_xpaths(self):
        """
        Return the full path to the default config path and filter geoodk
        instance that matches the current profile path
        :return: directories
        :rtype: list
        """
        return [
            os.path.join(self.path, name) for name in os.listdir(self.path)
            if os.path.isdir(os.path.join(self.path, name))
            if name.startswith(self.profile_formater())
        ]

    def on_dir_path(self):
        """
        Extract the specific folder information and rename the file
        :return:
        """
        self.uuid_extractor.new_list = []
        if self.record_count() > 0:
            directories = self.xform_xpaths()
            for directory in directories:
                self.extract_guuid_and_rename_file(directory)
        inst_count = len(self.instance_list)
        rm_count = self.remove_imported_instances()
        diff = inst_count - rm_count
        self.txt_count.setText(unicode(diff))

    def extract_guuid_and_rename_file(self, path):
        """
        Extract the unique Guuid and rename the file
        so that we can uniquely identify each file
        :return:
        """
        for f in os.listdir(path):
            if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'):
                file_instance = os.path.join(path, f)
                self.rename_file_to_UUID(file_instance)

    def read_instance_data(self):
        """Read all instance data once and store them in a dict
        :rtype: dict
        """
        mobile_data = OrderedDict()
        social_tenure_info = OrderedDict()
        for instance in self.instance_list:
            self.uuid_extractor.set_file_path(instance)

            field_data_nodes = self.uuid_extractor.document_entities_with_data(
                current_profile().name.replace(' ', '_'),
                self.user_selected_entities())
            str_data_nodes = self.uuid_extractor.document_entities_with_data(
                current_profile().name.replace(' ', '_'), ['social_tenure'])
            mobile_data[instance] = [field_data_nodes, str_data_nodes]

            self.uuid_extractor.close_document()
        return mobile_data

    def rename_file_to_UUID(self, file):
        """
        Extract the UUID from each folder and file
        :return:
        """
        self.uuid_extractor.set_file_path(file)
        self.uuid_extractor.on_file_passed()
        self.instance_list = self.uuid_extractor.new_list

    def move_imported_file(self, file):
        """
        Moves the imported files to avoid repetition
        :return:
        """
        instance_path = self.imported_instance_path()
        try:
            basename = os.path.basename(os.path.dirname(file))
            if not os.path.isdir(
                    os.path.join(self.imported_instance_path(), basename)):
                shutil.move(os.path.dirname(file), instance_path)
        except Exception as ex:
            return ex

    def populate_entities_widget(self):
        """
        Add entities in the instance file into a list view widget
        """
        self.lst_widget.clear()
        entities = self.instance_entities()
        if len(entities) > 0:
            for entity in entities:
                list_widget = QListWidgetItem(
                    current_profile().entity_by_name(entity).short_name,
                    self.lst_widget)
                list_widget.setCheckState(Qt.Checked)

    def user_selected_entities(self):
        """
        :rtype: list
        """
        entities = []
        count = self.lst_widget.count()
        if count > 0:
            for i in range(count):
                item = self.lst_widget.item(i)
                if item.checkState() == Qt.Checked:
                    entities.append(current_profile().entity(item.text()).name)
        return entities

    def instance_entities(self):
        """
        Enumerate the entities that are in the current profile
         and also that are captured in the form so that we are only importing relevant entities to database
        :return: entities
        """
        current_entities = []
        entity_collections = []
        instance_collections = self.instance_collection()
        if len(instance_collections) > 0:
            for entity_name in self.profile_entities_names(current_profile()):
                if current_profile().entity_by_name(entity_name) is not None:
                    current_entities.append(entity_name)
        return current_entities

    def instance_collection(self):
        """
        Enumerate all the instances found in the instance directory
        rtype: list
        """
        dirs = self.xform_xpaths()
        instance_collections = []
        if len(dirs) > 0:
            for dir_f in dirs:
                xml_files = [
                    dir_f.replace("\\", "/") + '/' + f
                    for f in os.listdir(dir_f) if f.endswith('.xml')
                ]
                if len(xml_files) > 0:
                    instance_collections.append(xml_files[0])
        return instance_collections

    def check_profile_with_custom_name(self):
        """
        Try extract mobile instance with custom filter name.
        Assumption is that there is a profile that bears that name
        :return:
        """
        mismatch_profile = 'Nothing found to import. \n' \
                           ' Ensure the current filter text or profile is correct'
        entity_attr = []
        if self.txt_filter.text() != '':
            for obj in self.profiles():
                if obj.name.startswith(self.txt_filter.text()):
                    if obj.name != current_profile().name:
                        self._notif_bar_str.insertErrorNotification(
                            mismatch_profile)
                        return
        return self.uuid_extractor.document_entities(self.profile)

    def profile_entities_names(self, profile):
        """
        Return names of all entities in a profile
        :rtype: list
        """
        entities_names = []
        for entity in profile.user_entities():
            entities_names.append(entity.name)
        return entities_names

    def has_foreign_keys_parent(self, select_entities):
        """
        Ensure we check that the table is not parent else
        import parent table first
        Revised in version 1.7. It explicitly assumes str is captured. before it was optional.
        :return:
        """
        has_relations = False
        str_tables = current_profile().social_tenure
        party_tbls = str_tables.parties
        sp_tbls = str_tables.spatial_units
        self.relations = OrderedDict()
        if len(self.instance_list) > 0:
            if self.uuid_extractor.has_str_captured_in_instance(
                    self.instance_list[0]):
                for party_tbl in party_tbls:
                    self.relations[party_tbl.name] = [
                        'social_tenure_relationship',
                        party_tbl.short_name.lower() + '_id'
                    ]
                for sp_tbl in sp_tbls:
                    self.relations[sp_tbl.name] = [
                        'social_tenure_relationship',
                        sp_tbl.short_name.lower() + '_id'
                    ]
        # print self.relations

        for table in select_entities:
            table_object = current_profile().entity_by_name(table)
            cols = table_object.columns.values()
            for col in cols:
                if col.TYPE_INFO == 'FOREIGN_KEY':
                    parent_object = table_object.columns[col.name]
                    if parent_object.parent:
                        if parent_object.parent.name in self.relations:
                            self.relations[parent_object.parent.name].append(
                                [table, col.name])
                        else:

                            self.relations[parent_object.parent.name] = [
                                table, col.name
                            ]
                            #self.relations[parent_object.parent.name].append([table, col.name])
                    has_relations = True
                else:
                    continue

            return has_relations

    def parent_table_isselected(self):
        """
        Take note that the user selected tables may or may not be imported
        based on parent child table relationship.
        Add those table silently so that we can show them to the user
        :return:
        """
        try:
            silent_list = []
            entities = self.user_selected_entities()
            if len(entities) > 0:
                for table in self.relations.keys():
                    if table not in entities:
                        silent_list.append(table)
            return silent_list
        except Exception as ex:
            self._notif_bar_str.insertErrorNotification(ex.message)

    def archive_this_import_file(self, counter, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            self.importlogger.logger_sections()
            file_info = 'File instance ' + str(counter) + ' : \n' + instance
            self.importlogger.log_action(file_info)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def log_table_entry(self, instance):
        """
        Ensure that only import are done once
        :return:
        """
        try:
            current_time = QDateTime()
            import_time = current_time.currentDateTime()
            log_entry = instance + ' ' + str(import_time.toPyDateTime())
            self.importlogger.log_action(log_entry)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def check_previous_import(self):
        """
        Ensure we are importing files once
        :return:
        """
        try:
            self.importlogger.add_log_info()
            for files in self.instance_list:
                current_dir = os.path.basename(files)
                exist = self.importlogger.check_file_exist(current_dir)
                if exist:
                    self.instance_list.remove(files)
            self.txt_count.setText(str(len(self.instance_list)))
            if self.record_count() != len(self.instance_list):
                msg = 'Some files have been already imported and therefore ' \
                   'not enumerated'
                self._notif_bar_str.insertErrorNotification(msg)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " +
                                                        io.message)
            pass

    def available_records(self):
        """
        Let the user know how many records have been collected and are available
         for inport process
        :return:
        """
        self.txt_count.setText(unicode(self.record_count()))

    def record_count(self):
        """
        get the count of instance dir in the selected directory
        :return: integer
        """
        return len([
            name for name in os.listdir(self.path)
            if os.path.isdir(os.path.join(self.path, name))
            if name.startswith(self.profile_formater())
        ])

    def profile_formater(self):
        """
        Format the profile name by removing underscore character
        :return:
        """
        if self.txt_filter.text() != '':
            filter_text = self.txt_filter.text()
            return filter_text
        else:
            return self.profile

    def update_files_with_custom_filter(self):
        """
        Get the new file count with the user custom filter text
        :return: file count
        """
        self.available_records()
        self.on_dir_path()
        self.populate_entities_widget()

    def projection_settings(self):
        """
        let user select the projections for the data
        :return:
        """
        project_select = ProjectionSelector(self)
        projection = project_select.loadAvailableSystems()
        self.txt_srid.setText(str(projection))

    def on_projection_select(self):
        """
        Get the selected projection and set it during data import
        :return:
        """
        vals = self.txt_srid.text().split(":")
        return vals[1]

    def on_directory_search(self):
        """
        Let the user choose the directory with instances
        :return:
        """
        home_path = 'home'
        if self.txt_directory.text() != '':
            home_path = self.txt_directory.text()

        dir_name = QFileDialog.getExistingDirectory(self, 'Open Directory',
                                                    home_path,
                                                    QFileDialog.ShowDirsOnly)
        if dir_name:
            self.txt_directory.setText(str(dir_name))
            self.current_profile_changed()
        self.change_check_state(self.chk_all.checkState())

    def feedback_message(self, msg):
        """
        Create a dialog box to capture and display errrors related to db
        while importing data
        :param: msg
        :type: string
        :return:Qdialog
        """
        msgbox = QMessageBox()
        msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No)
        msgbox.setWindowTitle("Data Import")
        msgbox.setText(msg)
        msgbox.exec_()
        msgbox.show()
        return msgbox

    def save_instance_data_to_db(self, entities):
        """
        Get the user selected entities and insert them into database
        params: selected entities
        rtype: list
        :return:Object
        :type: dbObject
        """
        cu_obj = ''
        import_status = False
        self.txt_feedback.clear()
        self.txt_feedback.append("Import started, please wait...\n")
        QCoreApplication.processEvents()
        self._notif_bar_str.clear()
        mobile_field_data = self.read_instance_data()
        self.has_foreign_keys_parent(entities)
        #print self.relations
        if len(self.parent_table_isselected()) > 0:
            if QMessageBox.information(
                    self,
                    QApplication.translate('GeoODKMobileSettings',
                                           " Import Warning"),
                    QApplication.translate(
                        'GeoODKMobileSettings',
                        'Some of dependent tables (entities)'
                        'which may not be part of the selected tables '
                        'I.e: {} will be imported'.format(
                            self.parent_table_isselected())),
                    QMessageBox.Ok | QMessageBox.No) == QMessageBox.No:
                return
        try:
            counter = 0

            if len(mobile_field_data) > 0:
                self.pgbar.setRange(counter, len(self.instance_list))
                self.pgbar.setValue(0)
                self.importlogger.log_action("Import started ...\n")

                for instance_obj, instance_obj_data in mobile_field_data.iteritems(
                ):
                    self.importlogger.log_action(
                        "File {} ...\n".format(instance_obj))
                    parents_info = []
                    import_status = False
                    counter = counter + 1
                    self.parent_ids = {}

                    single_occuring, repeated_entities = self.uuid_extractor.attribute_data_from_nodelist(
                        instance_obj_data[0])

                    for entity, entity_data in single_occuring.iteritems():
                        import_status = False
                        if entity in self.relations.keys():
                            if entity in self.parent_ids:
                                continue
                            self.count_import_file_step(counter, entity)
                            log_timestamp = '=== parent table import  === : {0}'.format(
                                entity)
                            cu_obj = entity
                            self.log_table_entry(log_timestamp)

                            entity_add = Save2DB(entity, entity_data)
                            entity_add.objects_from_supporting_doc(
                                instance_obj)
                            ref_id = entity_add.save_parent_to_db()
                            import_status = True
                            self.parent_ids[entity] = [ref_id, entity]
                            #log_timestamp = ' --- import succeeded:    {0}' .format(str(import_status))
                            #self.log_table_entry(log_timestamp)

                            parents_info.append(entity)
                            single_occuring.pop(entity)

                        elif entity not in self.relations.keys():
                            import_status = False

                            for fk_table_name in self.relations.keys():
                                if fk_table_name not in self.parent_ids:
                                    in_relations = [
                                        _item for subitem in
                                        self.relations[fk_table_name]
                                        for _item in subitem
                                    ]
                                    if entity in in_relations:
                                        fk_table_data = single_occuring[
                                            fk_table_name]
                                        entity_add = Save2DB(
                                            fk_table_name, fk_table_data)
                                        ref_id = entity_add.save_parent_to_db()
                                        self.parent_ids[fk_table_name] = [
                                            ref_id, fk_table_name
                                        ]
                                        continue

                            self.count_import_file_step(counter, entity)
                            log_timestamp = '=== standalone table import  === : {0}'.format(
                                entity)
                            cu_obj = entity
                            self.log_table_entry(log_timestamp)
                            entity_add = Save2DB(entity, entity_data,
                                                 self.parent_ids)
                            entity_add.objects_from_supporting_doc(
                                instance_obj)
                            child_id = entity_add.save_to_db()
                            cu_obj = entity
                            import_status = True
                            parents_info.append(entity)
                            if entity in self.parent_ids:
                                continue
                            else:
                                self.parent_ids[entity] = [child_id, entity]
                            entity_add.cleanup()

                    if repeated_entities:
                        #self.log_table_entry(" ========== starting import of repeated tables ============")
                        import_status = False
                        for repeated_entity, entity_data in repeated_entities.iteritems(
                        ):
                            """We are assuming that the number of repeat table cannot exceed 99"""
                            enum_index = repeated_entity[:2]
                            if enum_index.isdigit():
                                repeat_table = repeated_entity[2:]
                            else:
                                enum_index = repeated_entity[:1]
                                repeat_table = repeated_entity[1:]
                            log_timestamp = '          child table {0} >> : {1}' \
                                    .format(repeat_table, enum_index)
                            self.count_import_file_step(counter, repeat_table)
                            self.importlogger.log_action(log_timestamp)
                            if repeat_table in self.profile_entities_names(
                                    current_profile()):
                                entity_add = Save2DB(repeat_table, entity_data,
                                                     self.parent_ids)
                                entity_add.objects_from_supporting_doc(
                                    instance_obj)
                                child_id = entity_add.save_to_db()
                                cu_obj = repeat_table
                                import_status = True
                                self.log_table_entry(
                                    " ------ import succeeded:   {0} ".format(
                                        import_status))
                                entity_add.cleanup()
                            else:
                                continue

                    if instance_obj_data[1]:
                        '''We treat social tenure entities separately because of foreign key references'''
                        entity_relation = EntityImporter(instance_obj)
                        single_str, multiple_str = self.uuid_extractor.attribute_data_from_nodelist(
                            instance_obj_data[1])
                        self.txt_feedback.append(
                            '----Creating social tenure relationship')
                        if len(single_str) > 0:
                            entity_relation.process_social_tenure(
                                single_str, self.parent_ids)

                        elif len(multiple_str) > 1:
                            for repeated_entity, entity_data in multiple_str.iteritems(
                            ):
                                """We are assuming that the number of repeat str cannot exceed 10"""
                                entity_relation.process_social_tenure(
                                    entity_data, self.parent_ids)

                        self.log_table_entry(
                            " ----- saving social tenure relationship")
                        entity_add.cleanup()

                    self.txt_feedback.append(
                        'saving record "{0}" to database'.format(counter))
                    self.pgbar.setValue(counter)

                    QCoreApplication.processEvents()
                    self.log_instance(instance_obj)
                self.txt_feedback.append(
                    'Number of records successfully imported:  {}'.format(
                        counter))

            else:
                self._notif_bar_str.insertErrorNotification(
                    "No available records to import")
                self.pgbar.setValue(0)
                return
        except SQLAlchemyError as ae:
            QCoreApplication.processEvents()
            QApplication.restoreOverrideCursor()
            self.feedback_message(unicode(ae.message))
            self.txt_feedback.append(
                "current table {0}import failed...\n".format(cu_obj))
            self.txt_feedback.append(str(ae.message))
            self.log_table_entry(unicode(ae.message))
            return

    def count_import_file_step(self, count=None, table=None):
        """
        Tracking method to record the current import activity
        :param count: int
        :param table: string
        :return:
        """
        self.txt_feedback.append('      Table : {}'.format(table))

    def accept(self):
        """
        Execute the import dialog once the save button has been clicked
        :return:
        """
        self.buttonBox.setEnabled(False)
        QApplication.setOverrideCursor(Qt.WaitCursor)

        try:
            if self.lst_widget.count() < 1:
                msg = 'No mobile records found for the current profile'
                self._notif_bar_str.insertErrorNotification(msg)
                self.buttonBox.setEnabled(True)
                QApplication.restoreOverrideCursor()
                return
            entities = self.user_selected_entities()
            if len(entities) < 1:
                if QMessageBox.information(
                        self,
                        QApplication.translate('MobileForms',
                                               'Import Warning'),
                        QApplication.translate(
                            'MobileForms', 'You have not '
                            'selected any entity for import. All entities '
                            'will be imported'),
                        QMessageBox.Ok | QMessageBox.No) == QMessageBox.Ok:
                    entities = self.instance_entities()
                else:
                    self.buttonBox.setEnabled(True)
                    return

            self.save_instance_data_to_db(entities)
            self.buttonBox.setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
            QApplication.restoreOverrideCursor()
        except Exception as ex:
            self.feedback_message(ex.message)
            self.log_table_entry(unicode(ex.message))
            self.buttonBox.setEnabled(True)
            QApplication.restoreOverrideCursor()
            return

    def log_instance(self, instance):
        instance_short_name = self.importlogger.log_data_name(instance)
        log_data = self.importlogger.read_log_data()
        if len(log_data) > 0:
            log_data[instance_short_name] = self.importlogger.log_date()
            self.importlogger.write_log_data(log_data)

    def remove_imported_instances(self):
        count = 0
        del_list = []
        log_data = self.importlogger.read_log_data()
        if len(log_data) > 0:
            for instance in self.instance_list:
                instance_short_name = self.importlogger.log_data_name(instance)
                if instance_short_name in log_data:
                    del_list.append(instance)
                    count += 1

        for inst in del_list:
            self.instance_list.remove(inst)
        return count
Beispiel #52
0
    def __init__(self, **kwargs):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param kwargs: Keyword dictionary of the following parameters;
         column  - Column you editing, None if its a new column
         entity  - Entity you are adding the column to
         profile - Current profile
         in_db   - Boolean flag to indicate if a column has been created in 
                   the database
         auto_add- True to automatically add a new column to the entity, 
                   default is False.
        """
        
        self.form_parent = kwargs.get('parent', self)
        self.column  = kwargs.get('column', None)
        self.entity  = kwargs.get('entity', None)
        self.profile = kwargs.get('profile', None)
        self.in_db = kwargs.get('in_db', False)
        self.is_new = kwargs.get('is_new', True)
        self.auto_entity_add = kwargs.get('auto_add', False)

        QDialog.__init__(self, self.form_parent)

        self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set']

        self.EX_TYPE_INFO =  ['SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 
                'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT',
                'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED']

        self.setupUi(self)
        self.dtypes = {}

        self.type_info = ''
        
        # dictionary to hold default attributes for each data type
        self.type_attribs = {}
        self.init_type_attribs()

        # dictionary to act as a work area for the form fields.
        self.form_fields = {}
        self.init_form_fields()

        self.fk_entities = []
        self.lookup_entities = []

        # Exclude column type info in the list
        self._exclude_col_type_info = []

        if self.is_new:
            self.prop_set = None  # why not False??
        else:
            self.prop_set = True

        # the current entity should not be part of the foreign key parent table,
        # add it to the exclusion list
        self.FK_EXCLUDE.append(self.entity.short_name)

        self.type_names = \
                [unicode(name) for name in BaseColumn.types_by_display_name().keys()]

        self.cboDataType.currentIndexChanged.connect(self.change_data_type)
        self.btnColProp.clicked.connect(self.data_type_property)
        self.edtColName.textChanged.connect(self.validate_text)

        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_controls()
Beispiel #53
0
class LookupEditor(QDialog, Ui_dlgLookup):
    """
    Form to add/edit lookup entities.
    """
    def __init__(self, parent, profile, lookup=None):
        """
        :param parent: Owner of this dialog
        :type parent: QWidget
        :param profile: A profile to add/edit lookup
        :type profile: Profile
        :type inplace: Flag to check if lookup creation is initiated from the
                       'normal' lookup creation process -inplace = False,
                       this is the normal state. If 'inplace' = True, then
                       creation is initiated from the the lookup selection dialog
        :param lookup: Value list to create, if None this is a new value list
         else its an edit
        :type lookup: ValueList
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.profile = profile
        self.lookup = lookup
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()

    def init_gui(self):
        """
        Initializes form widgets
        """
        self.edtName.setFocus()
        if self.lookup:
            self.edtName.setText(
                    self.lookup.short_name.replace('check_',''))
            self.edtName.setEnabled(not self.lookup.entity_in_database)
            self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(not self.lookup.entity_in_database)
        self.edtName.textChanged.connect(self.validate_text)

    def show_notification(self, message):
        """
        Shows a warning notification bar message.
        :param message: The message of the notification.
        :type message: String
        """
        msg = self.tr(message)
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(msg)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        Spaces are replaced by _ and capital letters are replaced by small.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return
        locale = QSettings().value("locale/userLocale")[0:2]
        last_character = text[-1:]
        if locale == 'en':
            name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$')
            name_validator = QtGui.QRegExpValidator(name_regex)
            text_edit.setValidator(name_validator)
            QApplication.processEvents()

            state = name_validator.validate(text, text.index(last_character))[0]
            if state != QValidator.Acceptable:
                self.show_notification(u'"{}" is not allowed at this position.'.
                                       format(last_character)
                                       )
                text = text[:-1]

        # fix caps, underscores, and spaces
        if last_character.isupper():
            text = text.lower()
        if last_character == ' ':
            text = text.replace(' ', '_')

        if len(text) > 1:
            if text[0] == ' ' or text[0] == '_':
                text = text[1:]
            text = text.replace(' ', '_').lower()

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def format_lookup_name(self, name):
        """
        Replace spaces with underscore in a name string
        :param name: Name to replace spaces 
        :type name: str
        :rtype: str
        """
        formatted_name = str(name).strip()
        formatted_name = formatted_name.replace(' ', "_")
        return formatted_name.lower()
    
    def create_lookup(self, name):
        """
        Creates a lookup entity and add it to a profile.
        If this is an edit, first the previous lookup is removed before
        adding a new one.
        :param name: Name of the new/edited lookup
        :type name: Unicode
        """
        name = self.format_lookup_name(name)
        new_lookup = self.profile.create_entity(name, value_list_factory)
        return new_lookup
	    
    def accept(self):
        if self.edtName.text() == '' or self.edtName.text() == '_':
            self.error_message(
                QApplication.translate(
                    "LookupEditor","Please enter a valid lookup name."
                )
            )
            return

        if self.edtName.text() == 'check':
            self.error_message(QApplication.translate("LookupEditor",
                "'check' is used internally by STDM! "
                "Select another name for the lookup"))
            return

        short_name = unicode(self.edtName.text())

        if self.lookup is None:  # new lookup
            if self.duplicate_check(short_name):
                self.show_message(
                    self.tr(
                        "Lookup with the same name already "
                        "exist in the current profile!"
                    )
                )
                return
            else:
                new_lookup = self.create_lookup(short_name)
                self.profile.add_entity(new_lookup)
                self.lookup = new_lookup
        else:
            self.edit_lookup(short_name)

        self.done(1)


    def duplicate_check(self, name):
        """
        Return True if we have an entity in the current profile with same 'name'
        :param name: entity short_name
        :type name: Unicode
        :rtype:boolean
        """
        return self.profile.entities.has_key(name)

    def edit_lookup(self, short_name):
        short_name = short_name.replace('check_','')
        self.lookup.short_name = u'{0}_{1}'.format('check', short_name)

    def reject(self):
        self.done(0)
    
    def error_message(self, message):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(message)
        msg.exec_()
Beispiel #54
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 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. 
        '''
        self.saved_model = dbmodel

    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 = '{} {}'.format(field, msg)

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

        return error

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

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

        #Set minimum width
        self.setMinimumWidth(450)

        #Flag for mandatory columns
        self.has_mandatory = False
        self.reload_form = False
        self._entity = entity
        self.edit_model = model
        self.column_widgets = OrderedDict()
        self._parent = parent
        self.exclude_columns = exclude_columns
        self.entity_tab_widget = None
        self._disable_collections = False
        self.filter_val = None
        self.parent_entity = parent_entity
        self.child_models = OrderedDict()
        self.entity_scroll_area = None
        self.entity_editor_widgets = OrderedDict()
        #Set notification layout bar
        self.vlNotification = QVBoxLayout()
        self.vlNotification.setObjectName('vlNotification')
        self._notifBar = NotificationBar(self.vlNotification)

        # Set manage documents only if the entity supports documents
        if self._entity.supports_documents:
            self._manage_documents = manage_documents
        else:
            self._manage_documents = False

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

        self.collect_model = collect_model

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

        except AttributeError:
            self._parent._parent = None
        self._init_gui()
        self.resize(430, 400)
        self._get_entity_editor_widgets()

        # Set title
        editor_trans = self.tr('Editor')
        title = u'{0} {1}'.format(format_name(self._entity.short_name),
                                  editor_trans)
        self.setWindowTitle(title)

        if isinstance(parent._parent, EntityEditorDialog):
            self.parent_entity = parent.parent_entity
            self.set_parent_values()
            # make the size smaller to differentiate from parent and as it
            # only has few tabs.
            self.resize(390, 400)
Beispiel #56
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 #57
0
class ValueEditor(QDialog, Ui_LookupValue):
    """
    Form to add/edit values added to a lookup. Values are objects of type
    CodeValue
    """
    def __init__(self, parent, lookup, code_value=None):
        """
        :param parent: Owner of this dialog window
        :type parent: QWidget
        :param lookup: A value list object to add the value
        :type lookup: ValueList
        :param code_value: A value object to add to the lookup,
        if None this is a new value, else its an edit.
        :type code_value: CodeValue
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.lookup = lookup
        self.code_value = code_value
        self.notice_bar = NotificationBar(self.notif_bar)
        self.init_gui()

    def init_gui(self):
        """
        initializes the form widgets
        """
        if self.code_value:
            if self.code_value.updated_value == '':
                self.edtValue.setText(self.code_value.value)
                self.edtCode.setText(self.code_value.code)
            else:
                self.edtValue.setText(self.code_value.updated_value)
                self.edtCode.setText(self.code_value.updated_code)

        self.edtCode.textChanged.connect(self.validate_text)
        self.edtValue.textChanged.connect(self.validate_text)

        self.edtValue.setFocus()

    def show_notification(self, message):
        msg = self.tr(message)
        self.notice_bar.clear()
        self.notice_bar.insertErrorNotification(msg)

    def validate_text(self, text):
        """
        Validates and updates the entered text if necessary.
        :param text: The text entered
        :type text: String
        """
        text_edit = self.sender()
        cursor_position = text_edit.cursorPosition()
        text_edit.setValidator(None)
        if len(text) == 0:
            return

        name_regex = QtCore.QRegExp('^[ _0-9a-zA-Z][a-zA-Z0-9_/\\-()|.:,; ]*$')
        name_validator = QtGui.QRegExpValidator(name_regex)
        text_edit.setValidator(name_validator)
        QApplication.processEvents()
        last_character = text[-1:]
        state = name_validator.validate(text, text.index(last_character))[0]
        if state != QValidator.Acceptable:
            self.show_notification(
                '"{}" is not allowed at this position.'.format(last_character))
            text = text[:-1]
        else:
            if len(text) > 1:
                if text[0] == ' ' or text[0] == '_':
                    text = text[1:]

        self.blockSignals(True)
        text_edit.setText(text)
        text_edit.setCursorPosition(cursor_position)
        self.blockSignals(False)
        text_edit.setValidator(None)

    def add_value(self):
        """
        Adds a code value to a lookup object. Checks first if a previous value
        exist then removes it and then adds the new one.
        """
        value = unicode(self.edtValue.text().strip())
        code = unicode(self.edtCode.text().strip())

        # if its an edit, first remove the previous value
        if self.code_value:
            self.lookup.rename(self.code_value.value, value, code)
        else:
            self.lookup.add_code_value(CodeValue(code, value))

    def accept(self):
        if self.edtValue.text() == '' or self.edtValue.text() == ' ':
            self.error_message(
                QApplication.translate("ValueEditor",
                                       "Please enter a valid lookup value."))
            return

        self.add_value()

        self.done(1)

    def reject(self):
        self.done(0)

    def error_message(self, message):
        """
        Creates a message box and displays a message
        :param message: message to display
        :type message: str
        """
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setWindowTitle("STDM")
        msg.setText(message)
        msg.exec_()
Beispiel #58
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()