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