Beispiel #1
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(GuiUtils.get_icon("open_file.png"))
        self.btn_refresh.setIcon(GuiUtils.get_icon("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 profiles(self):
        """
        Return a list of all profiles
        :rtype: list
        """
        return list(self.load_config().values())

    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 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(str(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(str(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)
        self.importlogger.start_json_file()
        self.previous_import_instances()
        # diff = inst_count - rm_count
        self.txt_count.setText(str(len(self.instance_list)))

    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.normcase(os.path.join(path, f))
                if os.path.isfile(file_instance):
                    self.rename_file_to_UUID(file_instance)

    def read_instance_data(self) -> Dict[str, InstanceData]:
        """Read all instance data once and store them in a dict
        """
        mobile_data = OrderedDict()
        social_tenure_info = OrderedDict()
        self.uuid_extractor.unset_path()
        for instance_p in self.instance_list:
            if os.path.isfile(instance_p):
                isntance_dir, instance = os.path.split(instance_p)
            else:
                instance = instance_p
            self.uuid_extractor.set_file_path(instance)

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

            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.file_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 DummyException as ex:
            return str(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'
                    ]

        for table in select_entities:
            table_object = current_profile().entity_by_name(table)
            cols = list(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 DummyException as ex:
            self._notif_bar_str.insertErrorNotification(str(ex))

    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
            ImportLogger.log_action(file_info)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " + str(io))
            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())
            ImportLogger.log_action(log_entry)
        except IOError as io:
            self._notif_bar_str.insertErrorNotification(MSG + ": " + str(io))
            raise NameError(str(io))

    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 + ": " + str(io))
            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(str(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()
        self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)

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

        if not mobile_field_data:
            self.feedback_message('Not matching data in mobile files')
            return
        counter = 0
        try:
            self.pgbar.setRange(counter, len(self.instance_list))
            self.pgbar.setValue(0)
            ImportLogger.log_action("Import started ...\n")

            for instance_obj, instance_obj_data in mobile_field_data.items():

                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.field_data_nodes)

                single_occurring_keys = list(single_occuring.keys())
                for entity in single_occurring_keys:
                    entity_data = single_occuring[entity]
                    import_status = False
                    if entity in self.relations:
                        #if entity not in self.parent_ids.keys():
                        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,
                                             self.parent_ids)
                        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]
                        parents_info.append(entity)
                        single_occuring.pop(entity)

                    elif entity not in self.relations:
                        import_status = False
                        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 not in self.parent_ids.keys():
                            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.items(
                    ):
                        """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)
                        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()
                            self.parent_ids[repeat_table] = [
                                child_id, repeat_table
                            ]
                            cu_obj = repeat_table
                            import_status = True
                            self.log_table_entry(
                                " ------ import succeeded:   {0} ".format(
                                    import_status))
                            entity_add.cleanup()
                            QCoreApplication.processEvents()
                        else:
                            continue

                if instance_obj_data.str_data_nodes:
                    '''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.str_data_nodes)
                    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.items(
                        ):
                            """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)
                self.log_instance(instance_obj)
                QCoreApplication.processEvents()
            self.txt_feedback.append(
                'Number of records successfully imported:  {}'.format(counter))
        except DummyException as ex:
            self.feedback_message(str(ex))
            QCoreApplication.processEvents()
            QApplication.restoreOverrideCursor()
            self.buttonBox.setEnabled(True)
        except SQLAlchemyError as ae:
            QCoreApplication.processEvents()
            QApplication.restoreOverrideCursor()
            self.feedback_message(str(ae))
            self.txt_feedback.append(
                "current table {0}import failed...\n".format(cu_obj))
            self.txt_feedback.append(str(ae))
            self.log_table_entry(str(ae))
            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()
                    QApplication.restoreOverrideCursor()
                else:
                    self.buttonBox.setEnabled(True)
                    QApplication.restoreOverrideCursor()
                    return
            else:
                self.save_instance_data_to_db(entities)
                self.buttonBox.setEnabled(True)
                QApplication.restoreOverrideCursor()
        except DummyException as ex:
            QApplication.restoreOverrideCursor()
            self.feedback_message(str(ex))
            self.log_table_entry(str(ex))
            self.buttonBox.setEnabled(True)
            self.buttonBox.button(QDialogButtonBox.Save).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] = self.importlogger.log_date()
        self.importlogger.write_log_data(log_data)

    def previous_import_instances(self):
        count = 0
        del_list = []
        log_data = self.importlogger.read_log_data()
        if len(log_data) > 0:
            for instance in self.instance_list:
                # dir_path, file_name = os.path.split(instance)
                # if log_data.has_key(instance) or log_data.has_key(file_name):
                try:
                    if os.path.split(instance)[1] in log_data:
                        # del_list. append(instance)
                        self.instance_list.remove(instance)
                    else:
                        continue
                except DummyException:
                    continue
            # if len(del_list)>0:
            # [self.instance_list.remove(inst) for inst in del_list]

    def close(self):
        """
        when the user interrupts data import operations, we should close exit
        """
        self.instance_list = None
        QApplication.restoreOverrideCursor()
        QCoreApplication.processEvents()
        self.close()
Beispiel #2
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 #3
0
class LookupDialog(QDialog, Ui_LookupTranslatorDialog, TranslatorDialogBase):
    """
    Dialog for defining configuration settings for the lookup translation
    implementation.
    """
    def __init__(self, parent, source_cols, dest_table, dest_col, src_col):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col,
                                      src_col)

        self._notif_bar = NotificationBar(self.vl_notification)

        # Populate controls
        self._load_lookup_tables()

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

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

            return

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

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

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

        if idx == -1:
            return

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

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

        if not table_name:
            return

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

            return

        lk_values = lk_ent.lookups()

        self.cbo_default.addItem('')

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

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

        return lookup_translator

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

            return False

        return True

    def accept(self):
        """
        Validate before accepting user input.
        """
        if self.validate():
            super(LookupDialog, self).accept()
Beispiel #4
0
class EntityEditor(QDialog, Ui_dlgEntity):
    """
    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.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 = u'{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
        """
        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 = 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:
            self.show_notification(
                '"{}" is not allowed at this position.'.format(last_character))
            text = text[:-1]
        else:
            # 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 = unicode(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.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
        #self.profile.remove_entity(self.entity.short_name)
        #self.entity = self._create_entity(short_name)
        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.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 self.profile.entities.has_key(name)

    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 = unicode(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 = u'{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 #5
0
class loginDlg(WIDGET, BASE):
    '''
    This class handles user authentication for accessing STDM resources
    '''
    def __init__(self, parent=None, test_connect_mode=False):
        QDialog.__init__(self, parent)
        self.setupUi(self)

        QgsGui.enableAutoGeometryRestore(self)

        self.btn_db_settings.setIcon(
            GuiUtils.get_icon('db_server_settings.png'))

        # If dialog is used in the context of testing database connections
        self._test_connect_mode = test_connect_mode

        # gui initialization
        self.initGui()

        # class properties
        self.user = None
        self.dbConn = None

    def initGui(self):
        '''
        Initialize GUI
        '''
        # Change the name of the OK button to Login
        btnLogin = self.btnBox.button(QDialogButtonBox.Ok)

        if self._test_connect_mode:
            btnLogin.setText(QApplication.translate("LoginDialog", "Test"))
            self.setWindowTitle(
                QApplication.translate("LoginDialog", "STDM Database "
                                       "Connection"))
            self.btn_db_settings.setVisible(False)

        else:
            btnLogin.setText(QApplication.translate("LoginDialog", "Login"))
            self.btn_db_settings.setVisible(True)

        # Connect slots
        self.btn_db_settings.clicked.connect(self.settingsDialog)
        self.btnBox.accepted.connect(self.acceptdlg)

        # Configure notification bar
        self.notifBar = NotificationBar(self.vlNotification)

        if self._test_connect_mode:
            self.txtUserName.setFocus()

        else:
            self.txtUserName.setText('postgres')
            self.txtPassword.setFocus()

    def test_connect_mode(self):
        return self._test_connect_mode

    def set_database_connection(self, db_connection):
        """
        Set database connection object. User object will be overwritten
        as it will be derived from the username and password controls.
        :param db_connection: Database connection object
        :type db_connection: DatabaseConnection
        """
        self.dbConn = db_connection

    def validateInput(self):
        '''
        Assert whether required fields have been entered
        '''
        self.notifBar.clear()

        if self.txtUserName.text() == "":
            self.notifBar.insertErrorNotification(
                QApplication.translate("loginDlg",
                                       "UserName field cannot be empty"))
            self.txtUserName.setFocus()

            return False

        if self.txtPassword.text() == "":
            self.notifBar.insertErrorNotification(
                QApplication.translate("loginDlg",
                                       "Password field cannot be empty"))
            self.txtPassword.setFocus()

            return False

        else:
            return True

    def _set_user(self):
        '''
        Create the user object based on the user input
        '''
        username = self.txtUserName.text()
        password = self.txtPassword.text()
        self.user = User(username, password)

    def settingsDialog(self):
        '''
        In case the user clicks reset button to change the settings
        '''
        setting_data = self.reg_setting()
        dbDlg = dbconnDlg(self)

        if 'Database' in setting_data.keys():
            dbDlg.txtDatabase.setText(str(setting_data['Database']))
        if 'Host' in setting_data.keys():
            dbDlg.txtHost.setText(str(setting_data['Host']))
        if 'Port' in setting_data.keys():
            dbDlg.txtPort.setText(str(setting_data['Port']))
        dbDlg.exec_()

    def reg_setting(self):
        connSettings = ['Host', 'Database', 'Port']
        set_conn = RegistryConfig()
        settings = set_conn.read(connSettings)

        return settings

    def acceptdlg(self):
        '''
        On user clicking the login button
        '''
        isValid = self.validateInput()

        if isValid:
            # Set user object
            self._set_user()

            # Get mode and corresponding database connection object
            if not self._test_connect_mode:
                # Get DB connection
                dbconfig = DatabaseConfig()
                dbconn = dbconfig.read()
                if dbconn is None:
                    msg = QApplication.translate(
                        "loginDlg", "The STDM database "
                        "connection has not "
                        "been configured in "
                        "your system.\nWould "
                        "you like to configure "
                        "it now?")
                    response = QMessageBox.warning(
                        self,
                        QApplication.translate("LoginDialog",
                                               "Database Connection"), msg,
                        QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
                    if response == QMessageBox.Yes:
                        dbDlg = dbconnDlg(self)
                        if dbDlg.exec_() == QDialog.Accepted:
                            # set the partial database connection properties
                            dbconn = dbDlg.dbconn

            else:
                dbconn = self.dbConn

            # Whatever the outcome of the database settings definition process
            if dbconn is None:
                return

            dbconn.User = self.user

            # Test connection
            success, msg = dbconn.validateConnection()

            if success:
                self.dbConn = dbconn
                self.accept()

            else:
                QMessageBox.critical(
                    self,
                    QApplication.translate("LoginDialog",
                                           "Authentication Failed"), msg)
                self.txtPassword.setFocus()
                self.txtPassword.selectAll()
Beispiel #6
0
class ValueEditor(WIDGET, BASE):
    """
    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") or 'en-US')[0:2]

        if locale == 'en':
            name_regex = QRegExp('^[ _0-9a-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:
                self.show_notification('"{}" 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 = str(self.edtValue.text().strip())
        code = str(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_()
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 #8
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 #9
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 #10
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 #11
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 #12
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 #13
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 #14
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 #15
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 #16
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 #17
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 #18
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 #19
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 #20
0
class OptionsDialog(QDialog, Ui_DlgOptions):
    """
    Dialog for editing STDM settings.
    """
    def __init__(self, iface):
        QDialog.__init__(self, iface.mainWindow())
        self.setupUi(self)
        self.iface = iface

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

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

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

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

        # Load profiles
        self.load_profiles()

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

        # Load current database connection properties
        self._load_db_conn_properties()

        # Load existing PostgreSQL connections
        self._load_qgis_pg_connections()

        # Load directory paths
        self._load_directory_paths()

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

        # Load holders configuration file path
        self._load_holders_configuration_file()

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

        # Shortcut dialog
        self._check_shortcuts_settings()

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

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

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

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

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

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

        self.txt_holders_config.setText(holders_config)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            # Restore current connection in registry
            self._load_db_conn_properties()

        else:
            self.cbo_pg_connections.setEnabled(True)

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

        profile_path = self.cbo_pg_connections.itemData(index)

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

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

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

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

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

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

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

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

        self.notif_bar.clear()

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

            status = False

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

            status = False

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

            status = False

        return status

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

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

        return db_conn

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

        if not status:
            return

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

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

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

        self.notif_bar.clear()

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

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

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

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

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

        if not profile_name:
            self.notif_bar.clear()

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

            return False

        save_current_profile(profile_name)

        return True

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

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

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

        return True

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

        status = True

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

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

        return status

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

        set_holders_config_path(holders_config_file)

        return True

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

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

        return True

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

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

            return False

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

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

        return True

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

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

            return False

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

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

        return True

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

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

            # Highlight textbox control
            text_box.setStyleSheet(INVALIDATESTYLESHEET)

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

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

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

            return False

        return True

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

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

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

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

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

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

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

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

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

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

        self.apply_debug_logging()

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

        return True

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

        self.accept()
Beispiel #21
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 #22
0
class OptionsDialog(QDialog, Ui_DlgOptions):
    """
    Dialog for editing STDM settings.
    """
    def __init__(self, iface):
        QDialog.__init__(self, iface.mainWindow())
        self.setupUi(self)
        self.iface = iface

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

        #Connect signals
        self._apply_btn.clicked.connect(self.apply_settings)
        self.buttonBox.accepted.connect(self.on_accept)
        self.chk_pg_connections.toggled.connect(self._on_use_pg_connections)
        self.cbo_pg_connections.currentIndexChanged.connect(
            self._on_pg_profile_changed)
        self.btn_db_conn_clear.clicked.connect(self.clear_properties)
        self.btn_test_db_connection.clicked.connect(self._on_test_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()

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

        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 #23
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 #24
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 #25
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 #26
0
class ViewSTRWidget(WIDGET, BASE):
    """
    Search and browse the social tenure relationship
    of all participating entities.
    """
    def __init__(self, plugin):
        QMainWindow.__init__(self, plugin.iface.mainWindow())
        self.setupUi(self)

        self.btnSearch.setIcon(GuiUtils.get_icon('search.png'))
        self.btnClearSearch.setIcon(GuiUtils.get_icon('reset.png'))

        self._plugin = plugin

        self.search_done = False
        # self.tbPropertyPreview.set_iface(self._plugin.iface)
        QTimer.singleShot(
            100, lambda: self.tbPropertyPreview.set_iface(self._plugin.iface))

        self.curr_profile = current_profile()

        self.spatial_units = self.curr_profile.social_tenure.spatial_units
        # Center me
        self.move(QDesktopWidget().availableGeometry().center() -
                  self.frameGeometry().center())
        self.sp_unit_manager = SpatialUnitManagerDockWidget(
            self._plugin.iface, self._plugin)
        self.geom_cols = []
        for spatial_unit in self.spatial_units:
            each_geom_col = self.sp_unit_manager.geom_columns(spatial_unit)
            self.geom_cols.extend(each_geom_col)

        # Configure notification bar
        self._notif_search_config = NotificationBar(self.vl_notification)

        # set whether currently logged in user has
        # permissions to edit existing STR records
        self._can_edit = self._plugin.STRCntGroup.canUpdate()
        self._can_delete = self._plugin.STRCntGroup.canDelete()
        self._can_create = self._plugin.STRCntGroup.canCreate()
        # Variable used to store a reference to the
        # currently selected social tenure relationship
        # when displaying documents in the supporting documents tab window.
        # This ensures that there are no duplicates
        # when the same item is selected over and over again.

        self._strID = None
        self.removed_docs = None
        # Used to store the root hash of the currently selected node.
        self._curr_rootnode_hash = ""

        self.str_model, self.str_doc_model = entity_model(
            self.curr_profile.social_tenure, False, True)

        self._source_doc_manager = SourceDocumentManager(
            self.curr_profile.social_tenure.supporting_doc, self.str_doc_model,
            self)

        self._source_doc_manager.documentRemoved.connect(
            self.onSourceDocumentRemoved)

        self._source_doc_manager.setEditPermissions(False)

        self.initGui()
        self.add_spatial_unit_layer()
        self.details_tree_view = DetailsTreeView(iface, self._plugin, self)
        # else:
        #     self.details_tree_view = self._plugin.details_tree_view
        self.details_tree_view.activate_feature_details(True)
        self.details_tree_view.add_tree_view()
        self.details_tree_view.model.clear()

        count = pg_table_count(self.curr_profile.social_tenure.name)
        self.setWindowTitle(
            self.tr('{}{}'.format(self.windowTitle(),
                                  '- ' + str(count) + ' rows')))

        self.active_spu_id = -1

        self.toolBox.setStyleSheet('''
            QToolBox::tab {
                background: qlineargradient(
                    x1: 0, y1: 0, x2: 0, y2: 1,
                    stop: 0 #EDEDED, stop: 0.4 #EDEDED,
                    stop: 0.5 #EDEDED, stop: 1.0 #D3D3D3
                );
                border-radius: 2px;
                border-style: outset;
                border-width: 2px;
                height: 100px;
                border-color: #C3C3C3;
            }

            QToolBox::tab:selected {
                font: italic;
            }
            ''')

        self.details_tree_view.view.setStyleSheet('''
            QTreeView:!active {
                selection-background-color: #72a6d9;
            }
            ''')

    def add_tool_buttons(self):
        """
        Add toolbar buttons of add, edit and delete buttons.
        :return: None
        :rtype: NoneType
        """
        tool_buttons = QToolBar()
        tool_buttons.setObjectName('form_toolbar')
        tool_buttons.setIconSize(QSize(16, 16))

        self.addSTR = QAction(GuiUtils.get_icon('add.png'),
                              QApplication.translate('ViewSTRWidget', 'Add'),
                              self)

        self.editSTR = QAction(GuiUtils.get_icon('edit.png'),
                               QApplication.translate('ViewSTRWidget', 'Edit'),
                               self)

        self.deleteSTR = QAction(
            GuiUtils.get_icon('remove.png'),
            QApplication.translate('ViewSTRWidget', 'Remove'), self)

        tool_buttons.addAction(self.addSTR)
        tool_buttons.addAction(self.editSTR)
        tool_buttons.addAction(self.deleteSTR)

        self.toolbarVBox.addWidget(tool_buttons)

    def initGui(self):
        """
        Initialize widget
        """
        self.tb_actions.setVisible(False)
        self._load_entity_configurations()

        self.add_tool_buttons()

        # Connect signals
        self.tbSTREntity.currentChanged.connect(self.entityTabIndexChanged)
        self.btnSearch.clicked.connect(self.searchEntityRelations)
        self.btnClearSearch.clicked.connect(self.clearSearch)
        # self.tvSTRResults.expanded.connect(self.onTreeViewItemExpanded)

        # Set the results treeview to accept requests for context menus
        # self.tvSTRResults.setContextMenuPolicy(Qt.CustomContextMenu)
        # self.tvSTRResults.customContextMenuRequested.connect(
        #     self.onResultsContextMenuRequested
        # )

        if not self._can_create:
            self.addSTR.hide()

        if not self._can_edit:
            self.editSTR.hide()
        else:
            self.editSTR.setDisabled(True)
        if not self._can_delete:
            self.deleteSTR.hide()
        else:
            self.deleteSTR.setDisabled(True)

        self.addSTR.triggered.connect(self.load_new_str_editor)

        self.deleteSTR.triggered.connect(self.delete_str)

        self.editSTR.triggered.connect(self.load_edit_str_editor)

        # Load async for the current widget
        self.entityTabIndexChanged(0)

    def init_progress_dialog(self):
        """
        Initializes the progress dialog.
        """
        self.progress = QProgressBar(self)
        self.progress.resize(self.width(), 10)
        self.progress.setTextVisible(False)

    def add_spatial_unit_layer(self):
        """
        Add the spatial unit layer into the map canvas for later use.
        """
        # Used for startup of view STR, just add the first geom layer.
        if len(self.geom_cols) > 0:
            for spatial_unit in self.spatial_units:
                layer_name_item = self.sp_unit_manager.geom_col_layer_name(
                    spatial_unit.name, self.geom_cols[0])
                self.sp_unit_manager.add_layer_by_name(layer_name_item)

    def _check_permissions(self):
        """
        Enable/disable actions based on the
        permissions defined in the content
        group.
        """
        if self._can_edit:
            self.tb_actions.addAction(self._new_str_action)
        else:
            self.tb_actions.removeAction(self._new_str_action)

        if len(self.tb_actions.actions()) == 0:
            self.tb_actions.setVisible(False)

        else:
            self.tb_actions.setVisible(True)

    def _load_entity_configurations(self):
        """
        Specify the entity configurations.
        """
        try:
            self.parties = self.curr_profile.social_tenure.parties
            tb_str_entities = self.parties + self.spatial_units

            for i, t in enumerate(tb_str_entities):
                QApplication.processEvents()
                entity_cfg = self._entity_config_from_profile(
                    str(t.name), t.short_name)

                if not entity_cfg is None:
                    entity_widget = self.add_entity_config(entity_cfg)

                    # entity_widget.setNodeFormatter(
                    #     EntityNodeFormatter(
                    #         entity_cfg, self.tvSTRResults, self
                    #     )
                    # )

        except DummyException as pe:
            self._notif_search_config.clear()
            self._notif_search_config.insertErrorNotification(str(pe))

    def _entity_config_from_profile(self, table_name, short_name):
        """
        Creates an EntityConfig object from the table name.
        :param table_name: Name of the database table.
        :type table_name: str
        :return: Entity configuration object.
        :rtype: EntityConfig
        """
        table_display_name = format_name(short_name)

        entity = self.curr_profile.entity_by_name(table_name)
        model = entity_model(entity)

        if model is not None:
            # Entity configuration
            entity_cfg = EntityConfiguration()
            entity_cfg.Title = table_display_name
            entity_cfg.STRModel = model
            entity_cfg.data_source_name = table_name
            for col, factory in self._get_widget_factory(entity):
                entity_cfg.LookupFormatters[col.name] = factory

            # Load filter and display columns
            # using only those which are of
            # numeric/varchar type
            searchable_columns = entity_searchable_columns(entity)
            display_columns = entity_display_columns(entity)
            for c in searchable_columns:
                if c != 'id':
                    entity_cfg.filterColumns[c] = format_name(c)
            for c in display_columns:
                if c != 'id':
                    entity_cfg.displayColumns[c] = format_name(c)
            return entity_cfg
        else:
            return None

    def _get_widget_factory(self, entity):
        """
        Get widget factory for specific column type
        :param entity: Current column entity object
        :type entity: Entity
        :return c: Column object corresponding to the widget factory
        :rtype c: BaseColumn
        :return col_factory: Widget factory corresponding to the column type
        :rtype col_factory: ColumnWidgetRegistry
        """
        for c in entity.columns.values():
            col_factory = ColumnWidgetRegistry.factory(c.TYPE_INFO)
            if col_factory is not None:
                yield c, col_factory(c)

    def add_entity_config(self, config):
        """
        Set an entity configuration option and
        add it to the 'Search Entity' tab.
        """
        entityWidg = STRViewEntityWidget(config)
        entityWidg.asyncStarted.connect(self._progressStart)
        entityWidg.asyncFinished.connect(self._progressFinish)

        tabIndex = self.tbSTREntity.addTab(entityWidg, config.Title)

        return entityWidg

    def entityTabIndexChanged(self, index):
        """
        Raised when the tab index of the entity search tab widget changes.
        """
        # Get the current widget in the tab container
        entityWidget = self.tbSTREntity.currentWidget()

        if isinstance(entityWidget, EntitySearchItem):
            entityWidget.loadAsync()

    def searchEntityRelations(self):
        """
        Slot that searches for matching items for
        the specified entity and corresponding STR entities.
        """

        entityWidget = self.tbSTREntity.currentWidget()

        entity_name = entityWidget.config.data_source_name

        self._reset_controls()

        if isinstance(entityWidget, EntitySearchItem):
            valid, msg = entityWidget.validate()

            if not valid:
                self._notif_search_config.clear()
                self._notif_search_config.insertErrorNotification(msg)

                return

            results, searchWord = entityWidget.executeSearch()

            # Show error message
            if len(results) == 0:
                noResultsMsg = QApplication.translate(
                    'ViewSTR', 'No results found for "{}"'.format(searchWord))
                self._notif_search_config.clear()
                self._notif_search_config.insertErrorNotification(noResultsMsg)

                return

            party_names = [
                e.name for e in self.curr_profile.social_tenure.parties
            ]
            entity = self.curr_profile.entity_by_name(entity_name)

            result_ids = [r.id for r in results]

            if entity_name in party_names:

                self.active_spu_id = self.details_tree_view.search_party(
                    entity, result_ids)
            else:
                self.details_tree_view.search_spatial_unit(entity, result_ids)

            # self.tbPropertyPreview._iface.activeLayer().selectByExpression("id={}".format(self.active_spu_id))
            # self.details_tree_view._selected_features = self.tbPropertyPreview._iface.activeLayer().selectedFeatures()
            # self._load_root_node(entity_name, formattedNode)

    def clearSearch(self):
        """
        Clear search input parameters (for current widget) and results.
        """
        entityWidget = self.tbSTREntity.currentWidget()
        if isinstance(entityWidget, EntitySearchItem):
            entityWidget.reset()
        self._reset_controls()

    def _reset_controls(self):
        # Clear tree view
        self._resetTreeView()

        # Clear document listings
        self._deleteSourceDocTabs()

        # Remove spatial unit memory layer
        self.tbPropertyPreview.remove_layer()

    def on_select_results(self):
        """
        Slot which is raised when the selection
        is changed in the tree view
        selection model.
        """
        if len(self.details_tree_view.view.selectedIndexes()) < 1:
            self.disable_buttons()
            return
        self.search_done = True

        index = self.details_tree_view.view.selectedIndexes()[0]
        item = self.details_tree_view.model.itemFromIndex(index)

        QApplication.processEvents()

        # STR node - edit social tenure relationship
        if item.text() == self.details_tree_view.str_text:
            entity = self.curr_profile.social_tenure
            str_model = self.details_tree_view.str_models[item.data()]

            self.details_tree_view.selected_model = str_model
            self.details_tree_view.selected_item = SelectedItem(item)

            documents = self.details_tree_view._supporting_doc_models(
                entity.name, str_model)

            self._load_source_documents(documents)
            # if there is supporting document,
            # expand supporting document tab
            if len(documents) > 0:
                self.toolBox.setCurrentIndex(1)
            self.disable_buttons(False)

        # party node - edit party
        elif item.data() in self.details_tree_view.spatial_unit_items.keys():
            self.toolBox.setCurrentIndex(0)
            entity = self.details_tree_view.spatial_unit_items[item.data()]

            model = self.details_tree_view.feature_model(entity, item.data())
            self.draw_spatial_unit(entity.name, model)
            self.disable_buttons()

            canvas = iface.mapCanvas()
            if canvas:
                canvas.zoomToFullExtent()

        else:
            self.disable_buttons()

    def disable_buttons(self, status=True):
        if self._can_edit:
            self.deleteSTR.setDisabled(status)
        if self._can_delete:
            self.editSTR.setDisabled(status)

    def str_party_column_obj(self, record):
        """
        Gets the current party column name in STR
        table by finding party column with value
        other than None.
        :param record: The STR record or result.
        :type record: Dictionary
        :return: The party column name with value.
        :rtype: String
        """
        for party in self.parties:
            party_name = party.short_name.lower()
            party_id = '{}_id'.format(party_name)
            if party_id not in record.__dict__:
                return None
            if record.__dict__[party_id] != None:
                party_id_obj = getattr(self.str_model, party_id)
                return party_id_obj

    def load_edit_str_editor(self):
        self.details_tree_view.edit_selected_node(self.details_tree_view)
        self.btnSearch.click()
        self.disable_buttons()

    def load_new_str_editor(self):
        try:
            # Check type of node and perform corresponding action
            add_str = STREditor()
            add_str.exec_()

        except DummyException as ex:
            QMessageBox.critical(
                self._plugin.iface.mainWindow(),
                QApplication.translate("STDMPlugin", "Loading Error"), str(ex))

    def delete_str(self):
        self.details_tree_view.delete_selected_item()
        self.btnSearch.click()
        self.disable_buttons()

    def onSourceDocumentRemoved(self, container_id, doc_uuid, removed_doc):
        """
        Slot raised when a source document is removed from the container.
        If there are no documents in the specified container then remove
        the tab.
        """
        curr_container = self.tbSupportingDocs.currentWidget()
        curr_doc_widget = curr_container.findChildren(DocumentWidget)

        for doc in curr_doc_widget:
            if doc.fileUUID == doc_uuid:
                doc.deleteLater()
        self.removed_docs = removed_doc

    def draw_spatial_unit(self, entity_name, model):
        """
        Render the geometry of the given spatial unit in the spatial view.
        :param row_id: Sqlalchemy object representing a feature.
        """
        entity = self.curr_profile.entity_by_name(entity_name)

        self.tbPropertyPreview.draw_spatial_unit(entity, model)

    def showEvent(self, event):
        """
        (Re)load map layers in the viewer and main canvas.
        :param event: Window event
        :type event: QShowEvent
        """
        self.setEnabled(True)
        if QTimer is not None:
            QTimer.singleShot(200, self.init_mirror_map)

        return QMainWindow.showEvent(self, event)

    def init_mirror_map(self):
        self._notify_no_base_layers()
        # Add spatial unit layer if it doesn't exist
        self.tbPropertyPreview.refresh_canvas_layers()
        self.tbPropertyPreview.load_web_map()

    def _notify_no_base_layers(self):
        """
        Checks if there are any base layers that will be used when
        visualizing the spatial units. If there are no base layers
        then insert warning message.
        """
        self._notif_search_config.clear()

        num_layers = len(QgsProject.instance().mapLayers())
        if num_layers == 0:
            msg = QApplication.translate(
                "ViewSTR", "No basemap layers are loaded in the "
                "current project. Basemap layers "
                "enhance the visualization of spatial units.")
            self._notif_search_config.insertWarningNotification(msg)

    def _deleteSourceDocTabs(self):
        """
        Removes all source document tabs and deletes their references.
        """
        tabCount = self.tbSupportingDocs.count()

        while tabCount != 0:
            srcDocWidget = self.tbSupportingDocs.widget(tabCount - 1)
            self.tbSupportingDocs.removeTab(tabCount - 1)
            del srcDocWidget
            tabCount -= 1

        self._strID = None
        self._source_doc_manager.reset()

    def _resetTreeView(self):
        """
        Clears the results tree view.
        """
        # Reset tree view
        strModel = self.details_tree_view.view.model()
        resultsSelModel = self.details_tree_view.view.selectionModel()

        if strModel:
            strModel.clear()

        if resultsSelModel:
            if self.search_done:
                resultsSelModel.selectionChanged.disconnect(
                    self.on_select_results)
            resultsSelModel.selectionChanged.connect(self.on_select_results)

    def _load_source_documents(self, source_docs):
        """
        Load source documents into document listing widget.
        """
        # Configure progress dialog
        progress_msg = QApplication.translate(
            "ViewSTR", "Loading supporting documents...")

        progress_dialog = QProgressDialog(self)
        if len(source_docs) > 0:
            progress_dialog.setWindowTitle(progress_msg)
            progress_dialog.setRange(0, len(source_docs))
            progress_dialog.setWindowModality(Qt.WindowModal)
            progress_dialog.setFixedWidth(380)
            progress_dialog.show()
            progress_dialog.setValue(0)
        self._notif_search_config.clear()

        self.tbSupportingDocs.clear()
        self._source_doc_manager.reset()

        if len(source_docs) < 1:
            empty_msg = QApplication.translate(
                'ViewSTR', 'No supporting document is uploaded '
                'for this social tenure relationship.')
            self._notif_search_config.clear()
            self._notif_search_config.insertWarningNotification(empty_msg)

        for i, (doc_type_id, doc_obj) in enumerate(source_docs.items()):

            # add tabs, and container and widget for each tab
            tab_title = self._source_doc_manager.doc_type_mapping[doc_type_id]

            tab_widget = QWidget()
            tab_widget.setObjectName(tab_title)

            cont_layout = QVBoxLayout(tab_widget)
            cont_layout.setObjectName('widget_layout_' + tab_title)

            scrollArea = QScrollArea(tab_widget)
            scrollArea.setFrameShape(QFrame.NoFrame)

            scrollArea_contents = QWidget()
            scrollArea_contents.setObjectName('tab_scroll_area_' + tab_title)

            tab_layout = QVBoxLayout(scrollArea_contents)
            tab_layout.setObjectName('layout_' + tab_title)

            scrollArea.setWidgetResizable(True)

            scrollArea.setWidget(scrollArea_contents)
            cont_layout.addWidget(scrollArea)

            self._source_doc_manager.registerContainer(tab_layout, doc_type_id)

            for doc in doc_obj:

                try:
                    # add doc widgets
                    self._source_doc_manager.insertDocFromModel(
                        doc, doc_type_id)
                except DummyException as ex:
                    LOGGER.debug(str(ex))

            self.tbSupportingDocs.addTab(tab_widget, tab_title)
            progress_dialog.setValue(i + 1)

    # def _on_node_reference_changed(self, rootHash):
    #     """
    #     Method for resetting document listing and map preview
    #     if another root node and its children
    #     are selected then the documents are reset as
    #     well as the map preview control.
    #     """
    #     if rootHash != self._curr_rootnode_hash:
    #         self._deleteSourceDocTabs()
    #         self._curr_rootnode_hash = rootHash

    def _progressStart(self):
        """
        Load progress dialog window.
        For items whose durations is unknown,
        'isindefinite' = True by default.
        If 'isindefinite' is False, then
        'rangeitems' has to be specified.
        """
        pass

    def _progressFinish(self):
        """
        Hide progress dialog window.
        """
        pass

    def _edit_permissions(self):
        """
        Returns True/False whether the current logged in user
        has permissions to create new social tenure relationships.
        If true, then the system assumes that
        they can also edit STR records.
        """
        canEdit = False
        userName = globals.APP_DBCONN.User.UserName
        authorizer = Authorizer(userName)
        newSTRCode = "9576A88D-C434-40A6-A318-F830216CA15A"

        # Get the name of the content from the code
        cnt = Content()
        createSTRCnt = cnt.queryObject().filter(
            Content.code == newSTRCode).first()
        if createSTRCnt:
            name = createSTRCnt.name
            canEdit = authorizer.CheckAccess(name)

        return canEdit
Beispiel #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 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 #29
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_()
class MultipleEnumerationDialog(WIDGET, BASE, TranslatorDialogBase):
    """
    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()
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 list(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(str(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:
        """
        from stdm.ui.geoodk_mobile_upload import FormUploader
        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 DummyException 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 #32
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 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)

        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['leading_zero'] = column.leading_zero
                self.form_fields['separator'] = column.separator

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

    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['leading_zero'] = self.type_attribs.get(
            'leading_zero', '')
        self.form_fields['separator'] = self.type_attribs.get('separator', '')

        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)

    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,
            '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['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': False
            },
            'index': {
                'check_state': True,
                'enabled_state': False
            },
            'prefix_source': '',
            'leading_zero': '',
            'separator': '',
            'property': self.code_property,
            'prop_set': True
        }

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

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

        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 code_property(self):
        """
        Opens the code data type property editor
        """
        editor = CodeProperty(self, self.form_fields, profile=self.profile)
        result = editor.exec_()
        if result == 1:
            self.form_fields['prefix_source'] = editor.prefix_source()
            self.form_fields['leading_zero'] = editor.leading_zero()
            self.form_fields['separator'] = editor.separator()
            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)

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