class MapperMixin(object): ''' Mixin class for use in a dialog or widget, and manages attribute mapping. ''' def __init__(self, model, entity): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self.entity = entity self._attrMappers = [] self._attr_mapper_collection = {} self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None self.is_valid = False self.saved_model = None # Get document objects self.entity_model = entity_model(entity) self.entity_model_obj = self.entity_model() #Initialize notification bar if hasattr(self, "vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self, attributeName, control, isMandatory=False, pseudoname='', valueHandler=None, preloadfunc=None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName, control, self._model, pseudoname, isMandatory, valueHandler) self.addMapper(attrMapper, preloadfunc) def addMapper(self, attributeMapper, preloadfunc=None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) self._attr_mapper_collection[ attributeMapper.attributeName()] = attributeMapper def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def is_update_mode(self): """ :return: Returns True if the form is in UPDATE mode, otherwise False if in SAVE mode when creating a new record. :rtype: bool """ if self._mode == UPDATE: return True return False def attribute_mapper(self, attribute_name): """ Returns attribute mapper object corresponding to the the given attribute. :param attribute_name: Name of the attribute :type attribute_name: str :return: Attribute mapper :rtype: _AttributeMapper """ return self._attr_mapper_collection.get(attribute_name, None) def setSaveMode(self, mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self, stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self, message, mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate( "MappedDialog", "Would you like to save changes before closing?") msgResponse = QMessageBox.information( self, QApplication.translate("MappedDialog", "Save Changes"), msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) return isDirty, msgResponse def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self, dbmodel): ''' Executed once a record has been saved or updated. ''' self.saved_model = dbmodel self._post_save(self.saved_model) def _post_save(self, model): """ Enables sub-classes to incorporate additional logic after form data has been saved. """ pass def validate_all(self): """ Validate the entire form. :return: :rtype: """ errors = [] for attrMapper in self._attrMappers: error = self.validate(attrMapper) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) return errors def validate(self, attrMapper, update=False): """ Validate attribute. :param attrMapper: The attribute :type attrMapper: _AttributeMapper :param update: Whether the validation is on update or new entry :type update: Boolean :return: List of error messages or None :rtype: list or NoneType """ error = None field = attrMapper.pseudoName() column_name = attrMapper.attributeName() if column_name in self.entity.columns.keys(): column = self.entity.columns[column_name] else: return if column.unique: column_obj = getattr(self.entity_model, column_name, None) if not update: result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).first() else: id_obj = getattr(self.entity_model, 'id', None) result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).filter( id_obj != self.model().id).first() if result is not None: msg = QApplication.translate("MappedDialog", "field value should be unique.") error = u'{} {}'.format(field, msg) if column.mandatory: if attrMapper.valueHandler().value() == \ attrMapper.valueHandler().default(): # Notify user msg = QApplication.translate("MappedDialog", "is a required field.") error = u'{} {}'.format(field, msg) return error def _custom_validate(self): # Sub-classes can implement custom validation logic. return True def submit(self, collect_model=False, save_and_new=False): """ Slot for saving or updating the model. This will close the dialog on successful submission. :param collect_model: If set to True only returns the model without saving it to the database. :type collect_model: Boolean :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ if not self.preSaveUpdate(): return self.clearNotifications() self.is_valid = True # Validate mandatory fields have been entered by the user. errors = [] for attrMapper in self._attrMappers: if self._mode == 'SAVE': error = self.validate(attrMapper) else: # update mode error = self.validate(attrMapper, True) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) if len(errors) > 0 or not self._custom_validate(): self.is_valid = False if not self.is_valid: return # Bind model once all attributes are valid for attrMapper in self._attrMappers: control = attrMapper.valueHandler().control if isinstance(control, AutoGeneratedLineEdit): if control.column.prefix_source == control.column.columns_name: if attrMapper.valueHandler().value() is None: control.on_load_foreign_key_browser() attrMapper.set_model(self.model()) attrMapper.bindModel() if not collect_model: self._persistModel(save_and_new) def _persistModel(self, save_and_new): """ Saves the model to the database and shows a success message. :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ try: # Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() if not save_and_new: QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Saved"), QApplication.translate( "MappedDialog", "New record has been successfully saved.")) else: self._model.update() QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Updated"), QApplication.translate( "MappedDialog", "Record has been successfully updated.")) STDMDb.instance().session.flush() for attrMapper in self._attrMappers: control = attrMapper.valueHandler().control if isinstance(control, ExpressionLineEdit): value = control.on_expression_triggered() print attrMapper._attrName, value setattr(self.model(), attrMapper._attrName, value) self._model.update() # STDMDb.instance().session.flush() except Exception as ex: QMessageBox.critical( self, QApplication.translate("MappedDialog", "Data Operation Error"), QApplication.translate( "MappedDialog", u'The data could not be saved due to ' u'the error: \n{}'.format(ex.args[0]))) self.is_valid = False # Close the dialog if isinstance(self, QDialog) and self.is_valid: self.postSaveUpdate(self._model) if not save_and_new: self.accept() def clear(self): """ Clears the form values. """ for attrMapper in self._attrMappers: attrMapper.valueHandler().clear()
class TenureCustomAttributesEditor(QDialog, Ui_EntityAttributesEditor): """ Dialog for editing an entity's attributes. """ def __init__(self, profile, tenure_custom_entities, parent=None, editable=True, exclude_columns=None): """ Class constructor. :param profile: Profile object. :type profile: Profile :param tenure_custom_entities: Collection of tenure types and corresponding custom attribute entities. :type tenure_custom_entities: OrderedDict :param proxy_name: Name of the entity which will be used to create a proxy table.The attributes of the entity can then be copied to the target entity which shares the same name as the proxy entity. :type proxy_name: str :param parent: Parent object. :type parent: QWidget :param editable: True if the attributes can be edited, otherwise False. :type editable: bool :param exclude_columns: List of column names to exclude. :type exclude_columns: list """ super(TenureCustomAttributesEditor, self).__init__(parent) self.setupUi(self) self._notifBar = NotificationBar(self.vlNotification) self._profile = profile self._exclude_names = exclude_columns if self._exclude_names is None: self._exclude_names = [] # Attributes for each tenure type # Tenure type: list of attributes self._tenure_custom_entities = tenure_custom_entities # Populate attributes minus excluded columns self._tenure_custom_attrs = {} for tt, custom_ent in self._tenure_custom_entities.iteritems(): attrs = custom_ent.columns.values() self._tenure_custom_attrs[tt] = [ a for a in attrs if a.name not in self._exclude_names ] # Column types to omit self._exc_cols = ['GEOMETRY', 'FOREIGN_KEY'] # Connect signals self.btnAddColumn.clicked.connect(self.on_new_column) self.btnEditColumn.clicked.connect(self.on_edit_column) self.btnDeleteColumn.clicked.connect(self.on_delete_column) self._editable = editable #if not editable: #self._disable_editing() # Update excluded columns self._update_excluded_columns() # Load tenure types self._load_tenure_types() # Connect tenure type signal self.cbo_tenure_type.currentIndexChanged.connect( self._on_tenure_type_changed) # Load attributes for current tenure type if self.cbo_tenure_type.count() > 0: c_tenure_type = self.cbo_tenure_type.currentText() self._tenure_custom_attrs_entity(c_tenure_type) @property def custom_tenure_attributes(self): """ :return: Returns a collection containing the attributes for each entity corresponding to the tenure types. Key is tenure type lookup name, value is a list of tenure attributes. :rtype: dict(str, list) """ return self._tenure_custom_attrs def exclude_column_names(self, names): """ Set the list of columns to be excluded from the view only, not from the collection. :param names: List containing column names that will be excluded from the view. If there are already existing columns in the view which are also in the list of excluded columns then they will be removed from the view but reference will remain in the collection. :type names: list """ self._exclude_names = names def _load_tenure_types(self): # Load tenure types t_types = self._tenure_custom_attrs.keys() if len(t_types) > 0: self.cbo_tenure_type.clear() for t_name in t_types: self.cbo_tenure_type.addItem(t_name) def _on_tenure_type_changed(self, idx): # Slot raised when the index of the tenure type combo changes if idx == -1: return t_type = self.cbo_tenure_type.itemText(idx) if t_type: self._tenure_custom_attrs_entity(t_type) def _tenure_custom_attrs_entity(self, tenure_type): # Loads the custom attributes entity. Creates if it does not exist. c_ent_attrs = self._tenure_custom_attrs.get(tenure_type, None) if c_ent_attrs is None: QMessageBox.critical( self, self.tr('Custom Tenure Attributes'), self.tr('Attributes entity is not available.')) return self.load_attributes(c_ent_attrs) def _update_excluded_columns(self): # Remove excluded columns. for n in self._exclude_names: self.tb_view.remove_item(n) def _get_current_entity(self): # Returns the custom attributes entity corresponding to the # currently selected tenure type. tenure_type = self.cbo_tenure_type.currentText() return self._tenure_custom_entities.get(tenure_type, None) def _get_attribute(self, tenure_type, name): # Get attribute by name and index otherwise None. attr, idx = None, -1 attrs = self._tenure_custom_attrs.get(tenure_type, None) if attrs is None: return attr, idx for i, a in enumerate(attrs): if a.name == name: attr = a idx = i break return attr, idx #def _disable_editing(self): # Disable editing #self.btnAddColumn.setEnabled(False) #self.btnEditColumn.setEnabled(False) #self.btnDeleteColumn.setEnabled(False) #self.tb_view.setEnabled(False) #ok_btn = self.buttonBox.button(QDialogButtonBox.Ok) #ok_btn.setEnabled(False) def _column_editor_params(self): # Constructor params for column editor params = {} params['parent'] = self params['entity'] = self._get_current_entity() params['profile'] = self._profile return params def _on_column_added(self, column): # Slot raised when a new column has been added to the entity. if self._validate_excluded_column(column.name): self.add_column(column) def _get_tenure_type_attrs(self, tenure_type): # Returns a list of attributes matching to the given tenure type. # Creates an empty list if None is found. if not tenure_type in self._tenure_custom_attrs: self._tenure_custom_attrs[tenure_type] = [] return self._tenure_custom_attrs[tenure_type] def add_column(self, column): """ Adds a new column to the view. :param tenure_type: Name of tenure type lookup. :type tenure_type: str :param column: Column object. :type column: BaseColumn """ tenure_type = self.cbo_tenure_type.currentText() attrs = self._get_tenure_type_attrs(tenure_type) # Check if the column is already in the list attr, idx = self._get_attribute(tenure_type, column.name) if idx == -1: attrs.append(column) self.tb_view.add_item(column) def edit_column(self, original_name, column): """ Updates the edited column in the view. :param original_name: Original name of the column. :type original_name: str :param column: Column object. :type column: BaseColumn :return: Returns True if the operation succeeded, otherwise False if the column does not exist. :rtype: bool """ tenure_type = self.cbo_tenure_type.currentText() col, idx = self._get_attribute(tenure_type, original_name) if idx == -1: return False attrs = self._get_tenure_type_attrs(tenure_type) col = attrs.pop(idx) attrs.insert(idx, column) self.tb_view.update_item(original_name, column) def on_new_column(self): """ Slot for showing New column dialog. """ editor_params = self._column_editor_params() editor_params['is_new'] = True editor = ColumnEditor(**editor_params) editor.exclude_column_types(self._exc_cols) editor_title = self.tr('Create New Column') editor.setWindowTitle(editor_title) if editor.exec_() == QDialog.Accepted: # Do nothing since we are connecting to the signal self.add_column(editor.column) def selected_column(self): """ :return: Returns the selected column object in the view otherwise None if there is no row selected. :rtype: BaseColumn """ tenure_type = self.cbo_tenure_type.currentText() sel_col_name = self.tb_view.selected_column() if not sel_col_name: return None col, idx = self._get_attribute(tenure_type, sel_col_name) if idx == -1: return None return col def on_edit_column(self): """ Slot for showing a dialog for editing the selected column. """ self._notifBar.clear() sel_col = self.selected_column() if sel_col is None: msg = self.tr('Please select a column to edit.') self._notifBar.insertWarningNotification(msg) return original_name = sel_col.name editor_params = self._column_editor_params() editor_params['column'] = sel_col editor_params['is_new'] = False editor_params['in_db'] = not self._editable editor = ColumnEditor(**editor_params) editor.exclude_column_types(self._exc_cols) editor_title = self.tr('Edit Column') editor.setWindowTitle(editor_title) if editor.exec_() == QDialog.Accepted: if self._validate_excluded_column(editor.column.name): self.edit_column(original_name, editor.column) def _validate_excluded_column(self, name): # Check name against list of excluded names if name in self._exclude_names: msg = self.tr( 'column has been defined as an excluded column, it will not ' 'be added.') msg = '\'{0}\' {1}'.format(name, msg) QMessageBox.critical(self, self.tr('Excluded column'), msg) return False return True def on_delete_column(self): """ Slot for deleting the selected column. """ sel_col = self.selected_column() if sel_col is None: self._notifBar.clear() msg = self.tr('Please select a column to delete.') self._notifBar.insertWarningNotification(msg) return msg = self.tr( 'Are you sure you want to permanently delete the attribute?') result = QMessageBox.warning(self, self.tr('Delete Attribute'), msg, QMessageBox.Yes | QMessageBox.No) if result == QMessageBox.Yes: self.delete_column(sel_col.name) def delete_column(self, name): """ Removes the column with the given name from the view. :param name: Column name. :type name: str :return: Return True if the column was successfully deleted, otherwise False if the column does not exist. :rtype: bool """ tenure_type = self.cbo_tenure_type.currentText() attr, idx = self._get_attribute(tenure_type, name) if idx == -1: return False attrs = self._get_tenure_type_attrs(tenure_type) col = attrs.pop(idx) return self.tb_view.remove_item(attr.name) def load_attributes_from_entity(self, entity): """ Loads the view with the attributes of the specified entity. Any previous attributes will be removed. :param entity: Entity whose attributes will be loaded. :type entity: Entity """ self.load_attributes(entity.columns) def load_attributes(self, attributes): """ Loads the collection of attibutes to the view. Any previous attributes will be removed. The collection should not be empty. :param attributes: Collection of attributes to be loaded to te view. :type attributes: OrderedDict """ # Clear view self.tb_view.clear_view() if len(attributes) > 0: for a in attributes: self.add_column(a) # Refresh excluded columns self._update_excluded_columns()
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()
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
class MapperMixin(object): ''' Mixin class for use in a dialog or widget, and manages attribute mapping. ''' def __init__(self, model): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self._attrMappers = [] self._attr_mapper_collection = {} self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None self.is_valid = False #Initialize notification bar if hasattr(self, "vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self, attributeName, control, isMandatory=False, pseudoname='', valueHandler=None, preloadfunc=None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName, control, self._model, pseudoname, isMandatory, valueHandler) self.addMapper(attrMapper, preloadfunc) def addMapper(self, attributeMapper, preloadfunc=None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) self._attr_mapper_collection[ attributeMapper.attributeName()] = attributeMapper def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def attribute_mapper(self, attribute_name): """ Returns attribute mapper object corresponding to the the given attribute. :param attribute_name: Name of the attribute :type attribute_name: str :return: Attribute mapper :rtype: _AttributeMapper """ return self._attr_mapper_collection.get(attribute_name, None) def setSaveMode(self, mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self, stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self, message, mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate( "MappedDialog", "Would you like to save changes before closing?") msgResponse = QMessageBox.information( self, QApplication.translate("MappedDialog", "Save Changes"), msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) return isDirty, msgResponse def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: # We need to ignore the event so that validation and # saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self, dbmodel): ''' Executed once a record has been saved or updated. ''' pass def submit(self, collect_model=False, save_and_new=False): """ Slot for saving or updating the model. This will close the dialog on successful submission. :param collect_model: If set to True only returns the model without saving it to the database. :type collect_model: Boolean :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ if not self.preSaveUpdate(): return self.clearNotifications() self.is_valid = True # Validate mandatory fields have been entered by the user. for attrMapper in self._attrMappers: if attrMapper.isMandatory() and \ attrMapper.valueHandler().supportsMandatory(): if attrMapper.valueHandler().value() == \ attrMapper.valueHandler().default(): #Notify user msg = QApplication.translate("MappedDialog", "'%s' is a required field.")\ %unicode(attrMapper.pseudoName()) self._notifBar.insertWarningNotification(msg) self.is_valid = False if not self.is_valid: return # Bind model once all attributes are valid for attrMapper in self._attrMappers: attrMapper.set_model(self.model()) attrMapper.bindModel() if not collect_model: self._persistModel(save_and_new) def _persistModel(self, save_and_new): """ Saves the model to the database and shows a success message. :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ try: # Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() if not save_and_new: QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Saved"), QApplication.translate( "MappedDialog", "New record has been successfully saved.")) else: self._model.update() QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Updated"), QApplication.translate( "MappedDialog", "Record has been successfully updated.")) except Exception as ex: QMessageBox.critical( self, QApplication.translate("MappedDialog", "Data Operation Error"), QApplication.translate( "MappedDialog", u'The data could not be saved due to ' u'the error: \n{}'.format(ex.args[0]))) self.is_valid = False # Close the dialog if isinstance(self, QDialog) and self.is_valid: self.postSaveUpdate(self._model) if not save_and_new: self.accept() def clear(self): """ Clears the form values. """ for attrMapper in self._attrMappers: attrMapper.valueHandler().clear()
class RelatedTableDialog(QDialog, Ui_RelatedTableTranslatorDialog, TranslatorDialogBase): """ Dialog for defining configuration settings for the RelatedTableTranslator class implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col) self._notif_bar = NotificationBar(self.vl_notification) self._set_source_table_headers() #Set UI values self.txt_table_name.setText(dest_table) self.txt_column_name.setText(dest_col) #Load STDM tables exluding views self._load_tables() #Connect signals self.cbo_source_tables.currentIndexChanged.connect( self._on_source_table_changed) def _load_tables(self): """ Loads both textual and spatial tables into the user list. """ self.cbo_source_tables.addItem("") self.cbo_source_tables.addItems(self.db_tables()) def value_translator(self): rel_tab_translator = RelatedTableTranslator() rel_tab_translator.set_referencing_table(self.txt_table_name.text()) rel_tab_translator.set_referencing_column(self.txt_column_name.text()) rel_tab_translator.set_referenced_table( self.cbo_source_tables.currentText()) rel_tab_translator.set_output_reference_column( self.cbo_output_column.currentText()) rel_tab_translator.set_input_referenced_columns(self.column_pairings()) return rel_tab_translator def _set_source_table_headers(self): labels = [ QApplication.translate("RelatedTableDialog", "Source Table"), QApplication.translate("RelatedTableDialog", "Referenced Table") ] self.tb_source_trans_cols.set_header_labels(labels) def _on_source_table_changed(self, index): source_table = self.cbo_source_tables.currentText() self.cbo_output_column.clear() if source_table: ref_table_cols = table_column_names(source_table) self.tb_source_trans_cols.set_combo_selection( [self._source_cols, ref_table_cols]) #self.cbo_output_column.addItem("") self.cbo_output_column.addItems(ref_table_cols) else: self.tb_source_trans_cols.clear_view() def column_pairings(self): """ :return: Source and reference table column matchings. :rtype: dict """ return self.tb_source_trans_cols.column_pairings() def validate(self): """ :return: Check user entries. :rtype: bool """ if self.cbo_source_tables.currentText() == "": msg = QApplication.translate( "RelatedTableDialog", "Please select " "the reference table name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if self.cbo_output_column.currentText() == "": msg = QApplication.translate( "RelatedTableDialog", "Please select " "the output column name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if len(self.column_pairings()) == 0: msg = QApplication.translate( "RelatedTableDialog", "Please specify " "at least one column pairing.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False return True def accept(self): """ Validate before accepting user input. """ if self.validate(): super(RelatedTableDialog, self).accept()
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()
class LookupValueSelector(WIDGET, BASE): """ A dialog that enables to select a value and code from a lookup. .. versionadded:: 1.5 """ def __init__(self, parent, lookup_entity_name, profile=None): """ Initializes LookupValueSelector. :param parent: The parent of the dialog. :type parent: QWidget :param lookup_entity_name: The lookup entity name :type lookup_entity_name: String :param profile: The current profile object :type profile: Object """ QDialog.__init__(self, parent, Qt.WindowTitleHint | Qt.WindowCloseButtonHint) self.setupUi(self) self.value_and_code = None if profile is None: self._profile = current_profile() else: self._profile = profile self.lookup_entity = self._profile.entity_by_name('{}_{}'.format( self._profile.prefix, lookup_entity_name)) self.notice = NotificationBar(self.notice_bar) self._view_model = QStandardItemModel() self.value_list_box.setModel(self._view_model) header_item = QStandardItem(lookup_entity_name) self._view_model.setHorizontalHeaderItem(0, header_item) self.populate_value_list_view() self.selected_code = None self.selected_value_code = None self.value_list_box.clicked.connect(self.validate_selected_code) def populate_value_list_view(self): """ Populates the lookup values and codes. """ self.value_and_code = self.lookup_entity.values for value, code in self.value_and_code.items(): u_value = str(value) code_value = self.lookup_entity.values[u_value] value_code = QStandardItem('{} ({})'.format( code_value.value, code.code)) value_code.setData(code.code) self._view_model.appendRow(value_code) def validate_selected_code(self): """ Validate the selected code for the presence of Code or not. """ self.notice.clear() self.selected_code_value() if self.selected_code == '': notice = QApplication.tr(self, 'The selected value has no code.') self.notice.insertWarningNotification(notice) def selected_code_value(self): """ Get the selected lookup value. """ index = self.value_list_box.currentIndex() item = self._view_model.itemFromIndex(index) self.selected_code = item.data() self.selected_value_code = item.text() def accept(self): """ Overridden QDialog accept method. """ self.selected_code_value() self.done(1) def reject(self): """ Overridden QDialog accept method. """ self.selected_code = None self.selected_value_code = None self.done(0)
class DirDocumentTypeSelector(QDialog): """ Dialog for selecting supporting documents from a given directory. Default filter searches for PDF files only. """ def __init__(self, dir, doc_types, parent=None, filters=None): super(DirDocumentTypeSelector, self).__init__(parent) self.setWindowTitle( self.tr('Documents in Folder') ) self._filters = filters # Use PDF as default filter if not self._filters: self._filters = ['*.pdf'] self._init_ui() self._dir = QDir(dir) self._dir.setNameFilters(self._filters) self._doc_types = doc_types self._attr_model = QStandardItemModel(self) self._sel_doc_types = OrderedDict() # Notification bar self._notif_bar = NotificationBar(self.vl_notif) self.resize(320, 350) # Load documents self.load_document_types() @property def selected_document_types(self): """ :return: Returns a dictionary of the document types and the corresponding file paths as selected by the user. :rtype: dict """ return self._sel_doc_types def _init_ui(self): # Draw UI widgets layout = QVBoxLayout() # Add layout for notification bar self.vl_notif = QVBoxLayout() layout.addLayout(self.vl_notif) self.lbl_info = QLabel() self.lbl_info.setObjectName('lbl_info') self.lbl_info.setText(self.tr( 'The selected document types have been found in the directory, ' 'check/uncheck to specify which ones to upload.' )) self.lbl_info.setWordWrap(True) layout.addWidget(self.lbl_info) self.lst_docs = QListView() layout.addWidget(self.lst_docs) self.lbl_warning = QLabel() self.lbl_warning.setTextFormat(Qt.RichText) self.lbl_warning.setText(self.tr( '<html><head/><body><p><span style=" font-style:italic;">' '* Previously uploaded documents will be replaced.</span></p>' '</body></html>' )) self.lbl_warning.setWordWrap(True) layout.addWidget(self.lbl_warning) self.btn_box = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) layout.addWidget(self.btn_box) self.setLayout(layout) # Connect signals self.btn_box.accepted.connect( self.set_selected_document_types ) self.btn_box.rejected.connect( self.reject ) def set_selected_document_types(self): """ Sets the collections of accepted document types and their corresponding file paths and accepts the dialog. """ self._sel_doc_types = OrderedDict() for i in range(self._attr_model.rowCount()): doc_type_item = self._attr_model.item(i, 0) if doc_type_item.checkState() == Qt.Checked: path_item = self._attr_model.item(i, 1) self._sel_doc_types[doc_type_item.text()] = path_item.text() if len(self._sel_doc_types) == 0: self._notif_bar.clear() msg = self.tr('No matching documents found or selected.') self._notif_bar.insertWarningNotification(msg) return self.accept() def load_document_types(self): """ Load all document types to the list view and enable/check the items for those types that have been found. """ self._attr_model.clear() self._attr_model.setColumnCount(2) file_infos = self._dir.entryInfoList( QDir.Readable | QDir.Files, QDir.Name ) # Index file info based on name idx_file_infos = {fi.completeBaseName().lower(): fi for fi in file_infos} for d in self._doc_types: doc_type_item = QStandardItem(d) doc_type_item.setCheckable(True) path_item = QStandardItem() item_enabled = False check_state = Qt.Unchecked dl = d.lower() if dl in idx_file_infos: item_enabled = True check_state = Qt.Checked path = idx_file_infos[dl].filePath() path_item.setText(path) doc_type_item.setToolTip(path) doc_type_item.setEnabled(item_enabled) doc_type_item.setCheckState(check_state) self._attr_model.appendRow([doc_type_item, path_item]) self.lst_docs.setModel(self._attr_model)
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 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)
class RelatedTableDialog(QDialog, Ui_RelatedTableTranslatorDialog, TranslatorDialogBase): """ Dialog for defining configuration settings for the RelatedTableTranslator class implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col) self._notif_bar = NotificationBar(self.vl_notification) self._set_source_table_headers() #Set UI values self.txt_table_name.setText(dest_table) self.txt_column_name.setText(dest_col) #Load STDM tables exluding views self._load_tables() #Connect signals self.cbo_source_tables.currentIndexChanged.connect(self._on_source_table_changed) def _load_tables(self): """ Loads both textual and spatial tables into the user list. """ self.cbo_source_tables.addItem("") self.cbo_source_tables.addItems(self.db_tables()) def value_translator(self): rel_tab_translator = RelatedTableTranslator() rel_tab_translator.set_referencing_table(self.txt_table_name.text()) rel_tab_translator.set_referencing_column(self.txt_column_name.text()) rel_tab_translator.set_referenced_table(self.cbo_source_tables.currentText()) rel_tab_translator.set_output_reference_column(self.cbo_output_column.currentText()) rel_tab_translator.set_input_referenced_columns(self.column_pairings()) return rel_tab_translator def _set_source_table_headers(self): labels = [QApplication.translate("RelatedTableDialog","Source Table"), QApplication.translate("RelatedTableDialog", "Referenced Table")] self.tb_source_trans_cols.set_header_labels(labels) def _on_source_table_changed(self, index): source_table = self.cbo_source_tables.currentText() self.cbo_output_column.clear() if source_table: ref_table_cols = table_column_names(source_table) self.tb_source_trans_cols.set_combo_selection([self._source_cols, ref_table_cols]) #self.cbo_output_column.addItem("") self.cbo_output_column.addItems(ref_table_cols) else: self.tb_source_trans_cols.clear_view() def column_pairings(self): """ :return: Source and reference table column matchings. :rtype: dict """ return self.tb_source_trans_cols.column_pairings() def validate(self): """ :return: Check user entries. :rtype: bool """ if self.cbo_source_tables.currentText() == "": msg = QApplication.translate("RelatedTableDialog", "Please select " "the reference table name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if self.cbo_output_column.currentText() == "": msg = QApplication.translate("RelatedTableDialog", "Please select " "the output column name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if len(self.column_pairings()) == 0: msg = QApplication.translate("RelatedTableDialog", "Please specify " "at least one column pairing.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False return True def accept(self): """ Validate before accepting user input. """ if self.validate(): super(RelatedTableDialog, self).accept()
class MapperMixin(object): ''' Mixin class for use in a dialog or widget, and does the heavy lifting when it comes to managing attribute mapping. ''' def __init__(self,model): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self._attrMappers = [] self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None #Initialize notification bar if hasattr(self,"vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self,attributeName,control,isMandatory = False,pseudoname = "",valueHandler = None,preloadfunc = None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName,control,self._model,pseudoname,isMandatory,valueHandler) self.addMapper(attrMapper,preloadfunc) def addMapper(self,attributeMapper,preloadfunc = None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def setSaveMode(self,mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self,stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self,layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self,message,mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate("MappedDialog","Would you like to save changes before closing?") msgResponse = QMessageBox.information(self, QApplication.translate("MappedDialog","Save Changes"), msg, QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) return isDirty,msgResponse def closeEvent(self,event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' isDirty,userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: #We need to ignore the event so that validation and saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty,userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self,dbmodel): ''' Executed once a record has been saved or updated. ''' pass def submit(self): ''' Slot for saving or updating the model. This will close the dialog on successful submission. ''' if not self.preSaveUpdate(): return self.clearNotifications() isValid= True #Validate mandatory fields have been entered by the user. for attrMapper in self._attrMappers: if attrMapper.isMandatory() and attrMapper.valueHandler().supportsMandatory(): if attrMapper.valueHandler().value() == attrMapper.valueHandler().default(): #Notify user msg = "{0} is a required field.".format(attrMapper.pseudoName()) self._notifBar.insertWarningNotification(msg) isValid = False else: attrMapper.bindModel() else: attrMapper.bindModel() if not isValid: return self._persistModel() def _persistModel(self): #Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() QMessageBox.information(self, QApplication.translate("MappedDialog","Record Saved"), \ QApplication.translate("MappedDialog","New record has been successfully saved")) else: self._model.update() QMessageBox.information(self, QApplication.translate("MappedDialog","Record Updated"), \ QApplication.translate("MappedDialog","Record has been successfully updated")) #Close the dialog if isinstance(self, QDialog): self.postSaveUpdate(self._model) self.accept()
class LookupValueSelector(QDialog, Ui_LookupValueSelector): """ A dialog that enables to select a value and code from a lookup. .. versionadded:: 1.5 """ def __init__(self, parent, lookup_entity_name, profile=None): """ """ QDialog.__init__(self, parent, Qt.WindowTitleHint|Qt.WindowCloseButtonHint) self.setupUi(self) self.value_and_code = None if profile is None: self._profile = current_profile() else: self._profile = profile self.lookup_entity = self._profile.entity_by_name( '{}_{}'.format(self._profile.prefix, lookup_entity_name) ) self.notice = NotificationBar(self.notice_bar) self._view_model = QStandardItemModel() self.value_list_box.setModel(self._view_model) header_item = QStandardItem(lookup_entity_name) self._view_model.setHorizontalHeaderItem(0, header_item) self.populate_value_list_view() self.selected_code = None self.selected_value_code = None self.value_list_box.clicked.connect(self.validate_selected_code) def populate_value_list_view(self): self.value_and_code = self.lookup_entity.values for value, code in self.value_and_code.iteritems(): value_code = QStandardItem('{} ({})'.format(value, code.code)) value_code.setData(code.code) self._view_model.appendRow(value_code) def validate_selected_code(self): self.notice.clear() self.selected_code_value() if self.selected_code == '': notice = QApplication.tr(self, 'The selected value has no code.') self.notice.insertWarningNotification(notice) def selected_code_value(self): index = self.value_list_box.currentIndex() item = self._view_model.itemFromIndex(index) self.selected_code = item.data() self.selected_value_code = item.text() def accept(self): self.selected_code_value() self.done(1) def reject(self): self.selected_code = None self.selected_value_code = None self.done(0)
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()
class LookupValueSelector(QDialog, Ui_LookupValueSelector): """ A dialog that enables to select a value and code from a lookup. .. versionadded:: 1.5 """ def __init__(self, parent, lookup_entity_name, profile=None): """ Initializes LookupValueSelector. :param parent: The parent of the dialog. :type parent: QWidget :param lookup_entity_name: The lookup entity name :type lookup_entity_name: String :param profile: The current profile object :type profile: Object """ QDialog.__init__(self, parent, Qt.WindowTitleHint | Qt.WindowCloseButtonHint) self.setupUi(self) self.value_and_code = None if profile is None: self._profile = current_profile() else: self._profile = profile self.lookup_entity = self._profile.entity_by_name( '{}_{}'.format(self._profile.prefix, lookup_entity_name) ) self.notice = NotificationBar(self.notice_bar) self._view_model = QStandardItemModel() self.value_list_box.setModel(self._view_model) header_item = QStandardItem(lookup_entity_name) self._view_model.setHorizontalHeaderItem(0, header_item) self.populate_value_list_view() self.selected_code = None self.selected_value_code = None self.value_list_box.clicked.connect(self.validate_selected_code) def populate_value_list_view(self): """ Populates the lookup values and codes. """ self.value_and_code = self.lookup_entity.values for value, code in self.value_and_code.iteritems(): u_value = unicode(value) code_value = self.lookup_entity.values[u_value] value_code = QStandardItem('{} ({})'.format( code_value.value, code.code ) ) value_code.setData(code.code) self._view_model.appendRow(value_code) def validate_selected_code(self): """ Validate the selected code for the presence of Code or not. """ self.notice.clear() self.selected_code_value() if self.selected_code == '': notice = QApplication.tr(self, 'The selected value has no code.') self.notice.insertWarningNotification(notice) def selected_code_value(self): """ Get the selected lookup value. """ index = self.value_list_box.currentIndex() item = self._view_model.itemFromIndex(index) self.selected_code = item.data() self.selected_value_code = item.text() def accept(self): """ Overridden QDialog accept method. """ self.selected_code_value() self.done(1) def reject(self): """ Overridden QDialog accept method. """ self.selected_code = None self.selected_value_code = None self.done(0)
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()