Beispiel #1
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()
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 #3
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)