def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__( self, source_cols, dest_table, dest_col, src_col ) self._notif_bar = NotificationBar(self.vlNotification) #Assert if the entity supports documents self._assert_entity_supports_documents() #Set the source document directory self.source_document_directory = None #Document type name self._document_type_name = self._dest_col #Document type ID self._document_type_id = None #Set document type ID self._set_document_type_id() #Connect slots self.btn_source_doc_folder.clicked.connect( self._load_source_document_directory_selector )
def __init__(self, parent=None): """Class Constructor.""" super(GeoODKConverter, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-aut o-connect self.connect_action = pyqtSignal(str) self.setupUi(self) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.set_entity_model_view(self.entity_model) self.stdm_config = None self.parent = parent self.load_profiles() self.check_state_on() self.check_geoODK_path_exist() self.chk_all.stateChanged.connect(self.check_state_on) self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder) #self.btn_upload.clicked.connect(self.upload_generated_form) self._notif_bar_str = NotificationBar(self.vlnotification)
def __init__(self, parent=None): """ initailize class variables here """ super(ProfileInstanceRecords, self).__init__(parent) self.setupUi(self) self.path = None self.instance_list = [] self.relations = OrderedDict() self.parent_ids = {} self.importlogger = ImportLogger() self._notif_bar_str = NotificationBar(self.vlnotification) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.uuid_extractor = InstanceUUIDExtractor(self.path) self.btn_chang_dir.setIcon(QIcon(":/plugins/stdm/images/icons/open_file.png")) self.btn_refresh.setIcon(QIcon(":/plugins/stdm/images/icons/update.png")) self.chk_all.stateChanged.connect(self.change_check_state) #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed) self.btn_chang_dir.clicked.connect(self.on_directory_search) self.lst_widget.itemClicked.connect(self.user_selected_entities) self.btn_refresh.clicked.connect(self.update_files_with_custom_filter) self.buttonBox.button(QDialogButtonBox.Save).setText('Import') #self.load_config() self.init_file_path() self.current_profile_changed() self.change_check_state(self.chk_all.checkState()) self.instance_dir()
def __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 __init__(self, parent): QDialog.__init__(self, parent) self.profile_name = '' self.desc = '' self.setupUi(self) self.init_controls() self.notice_bar = NotificationBar(self.notif_bar)
def __init__(self, parent, lookup, code_value=None): """ :param parent: Owner of this dialog window :type parent: QWidget :param lookup: A value list object to add the value :type lookup: ValueList :param code_value: A value object to add to the lookup, if None this is a new value, else its an edit. :type code_value: CodeValue """ QDialog.__init__(self, parent) self.setupUi(self) self.lookup = lookup self.code_value = code_value self.notice_bar = NotificationBar(self.notif_bar) self.init_gui()
def __init__(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 __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)
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 __init__(self, iface): QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface self.notif_bar = NotificationBar(self.vlNotification, 6000) self._apply_btn = self.buttonBox.button(QDialogButtonBox.Apply) self._reg_config = RegistryConfig() self._db_config = DatabaseConfig() version = version_from_metadata() upgrade_label_text = self.label_9.text().replace('1.4', version) self.label_9.setText(upgrade_label_text) #Connect signals self._apply_btn.clicked.connect(self.apply_settings) self.buttonBox.accepted.connect(self.on_accept) self.chk_pg_connections.toggled.connect(self._on_use_pg_connections) self.cbo_pg_connections.currentIndexChanged.connect( self._on_pg_profile_changed) self.btn_db_conn_clear.clicked.connect(self.clear_properties) self.btn_test_db_connection.clicked.connect(self._on_test_connection) self.btn_supporting_docs.clicked.connect( self._on_choose_supporting_docs_path ) self.btn_template_folder.clicked.connect( self._on_choose_doc_designer_template_path ) self.btn_composer_out_folder.clicked.connect( self._on_choose_doc_generator_output_path ) self.upgradeButton.toggled.connect( self.manage_upgrade ) self._config = StdmConfiguration.instance() self._default_style_sheet = self.txtRepoLocation.styleSheet() self.manage_upgrade() self.init_gui()
def __init__(self, parent, profile, lookup=None): """ :param parent: Owner of this dialog :type parent: QWidget :param profile: A profile to add/edit lookup :type profile: Profile :type inplace: Flag to check if lookup creation is initiated from the 'normal' lookup creation process -inplace = False, this is the normal state. If 'inplace' = True, then creation is initiated from the the lookup selection dialog :param lookup: Value list to create, if None this is a new value list else its an edit :type lookup: ValueList """ QDialog.__init__(self, parent) self.setupUi(self) self.profile = profile self.lookup = lookup self.notice_bar = NotificationBar(self.notif_bar) self.init_gui()
def __init__(self, parent=None, **kwargs): super(ImageExportSettings, self).__init__(parent) self.setupUi(self) self._image_tr = self.tr('Image') self.notif_bar = NotificationBar(self.vl_notification, 6000) #Connect signals self.btn_path.clicked.connect(self._on_choose_image_path) self.buttonBox.accepted.connect(self.on_accept) self.sb_resolution.valueChanged.connect(self._on_resolution_changed) #Set color button defaults self._default_color = Qt.white self.btn_color.setDefaultColor(self._default_color) self.btn_color.setColor(self._default_color) self.btn_color.setAllowAlpha(True) self.path = kwargs.get('image_path', '') self.resolution = kwargs.get('resolution', '96') self.background_color = kwargs.get('background', Qt.transparent) self._update_controls()
class TenureCustomAttributesEditor(WIDGET, BASE): """ Dialog for editing an entity's attributes. """ def __init__(self, profile, tenure_custom_entities, parent=None, editable=True, exclude_columns=None): """ Class constructor. :param profile: Profile object. :type profile: Profile :param tenure_custom_entities: Collection of tenure types and corresponding custom attribute entities. :type tenure_custom_entities: OrderedDict :param proxy_name: Name of the entity which will be used to create a proxy table.The attributes of the entity can then be copied to the target entity which shares the same name as the proxy entity. :type proxy_name: str :param parent: Parent object. :type parent: QWidget :param editable: True if the attributes can be edited, otherwise False. :type editable: bool :param exclude_columns: List of column names to exclude. :type exclude_columns: list """ super(TenureCustomAttributesEditor, self).__init__(parent) self.setupUi(self) self.btnAddColumn.setIcon(GuiUtils.get_icon('add.png')) self.btnEditColumn.setIcon(GuiUtils.get_icon('edit.png')) self.btnDeleteColumn.setIcon(GuiUtils.get_icon('delete.png')) self._notifBar = NotificationBar(self.vlNotification) self._profile = profile self._exclude_names = exclude_columns if self._exclude_names is None: self._exclude_names = [] # Attributes for each tenure type # Tenure type: list of attributes self._tenure_custom_entities = tenure_custom_entities # Populate attributes minus excluded columns self._tenure_custom_attrs = {} for tt, custom_ent in list(self._tenure_custom_entities.items()): attrs = list(custom_ent.columns.values()) self._tenure_custom_attrs[tt] = [ a for a in attrs if a.name not in self._exclude_names ] # Column types to omit self._exc_cols = ['GEOMETRY', 'FOREIGN_KEY'] # Connect signals self.btnAddColumn.clicked.connect(self.on_new_column) self.btnEditColumn.clicked.connect(self.on_edit_column) self.btnDeleteColumn.clicked.connect(self.on_delete_column) self._editable = editable # if not editable: # self._disable_editing() # Update excluded columns self._update_excluded_columns() # Load tenure types self._load_tenure_types() # Connect tenure type signal self.cbo_tenure_type.currentIndexChanged.connect( self._on_tenure_type_changed) # Load attributes for current tenure type if self.cbo_tenure_type.count() > 0: c_tenure_type = self.cbo_tenure_type.currentText() self._tenure_custom_attrs_entity(c_tenure_type) @property def custom_tenure_attributes(self): """ :return: Returns a collection containing the attributes for each entity corresponding to the tenure types. Key is tenure type lookup name, value is a list of tenure attributes. :rtype: dict(str, list) """ return self._tenure_custom_attrs def exclude_column_names(self, names): """ Set the list of columns to be excluded from the view only, not from the collection. :param names: List containing column names that will be excluded from the view. If there are already existing columns in the view which are also in the list of excluded columns then they will be removed from the view but reference will remain in the collection. :type names: list """ self._exclude_names = names def _load_tenure_types(self): # Load tenure types t_types = list(self._tenure_custom_attrs.keys()) if len(t_types) > 0: self.cbo_tenure_type.clear() for t_name in t_types: self.cbo_tenure_type.addItem(t_name) def _on_tenure_type_changed(self, idx): # Slot raised when the index of the tenure type combo changes if idx == -1: return t_type = self.cbo_tenure_type.itemText(idx) if t_type: self._tenure_custom_attrs_entity(t_type) def _tenure_custom_attrs_entity(self, tenure_type): # Loads the custom attributes entity. Creates if it does not exist. c_ent_attrs = self._tenure_custom_attrs.get(tenure_type, None) if c_ent_attrs is None: QMessageBox.critical( self, self.tr('Custom Tenure Attributes'), self.tr('Attributes entity is not available.')) return self.load_attributes(c_ent_attrs) def _update_excluded_columns(self): # Remove excluded columns. for n in self._exclude_names: self.tb_view.remove_item(n) def _get_current_entity(self): # Returns the custom attributes entity corresponding to the # currently selected tenure type. tenure_type = self.cbo_tenure_type.currentText() return self._tenure_custom_entities.get(tenure_type, None) def _get_attribute(self, tenure_type, name): # Get attribute by name and index otherwise None. attr, idx = None, -1 attrs = self._tenure_custom_attrs.get(tenure_type, None) if attrs is None: return attr, idx for i, a in enumerate(attrs): if a.name == name: attr = a idx = i break return attr, idx def _column_editor_params(self): # Constructor params for column editor params = {} params['parent'] = self params['entity'] = self._get_current_entity() params['profile'] = self._profile return params def _on_column_added(self, column): # Slot raised when a new column has been added to the entity. if self._validate_excluded_column(column.name): self.add_column(column) def _get_tenure_type_attrs(self, tenure_type): # Returns a list of attributes matching to the given tenure type. # Creates an empty list if None is found. if not tenure_type in self._tenure_custom_attrs: self._tenure_custom_attrs[tenure_type] = [] return self._tenure_custom_attrs[tenure_type] def add_column(self, column): """ Adds a new column to the view. :param tenure_type: Name of tenure type lookup. :type tenure_type: str :param column: Column object. :type column: BaseColumn """ tenure_type = self.cbo_tenure_type.currentText() attrs = self._get_tenure_type_attrs(tenure_type) # Check if the column is already in the list attr, idx = self._get_attribute(tenure_type, column.name) if idx == -1: attrs.append(column) self.tb_view.add_item(column) def edit_column(self, original_name, column): """ Updates the edited column in the view. :param original_name: Original name of the column. :type original_name: str :param column: Column object. :type column: BaseColumn :return: Returns True if the operation succeeded, otherwise False if the column does not exist. :rtype: bool """ tenure_type = self.cbo_tenure_type.currentText() col, idx = self._get_attribute(tenure_type, original_name) if idx == -1: return False attrs = self._get_tenure_type_attrs(tenure_type) col = attrs.pop(idx) attrs.insert(idx, column) self.tb_view.update_item(original_name, column) def on_new_column(self): """ Slot for showing New column dialog. """ editor_params = self._column_editor_params() editor_params['is_new'] = True editor = ColumnEditor(**editor_params) editor.exclude_column_types(self._exc_cols) editor_title = self.tr('Create New Column') editor.setWindowTitle(editor_title) if editor.exec_() == QDialog.Accepted: # Do nothing since we are connecting to the signal self.add_column(editor.column) def selected_column(self): """ :return: Returns the selected column object in the view otherwise None if there is no row selected. :rtype: BaseColumn """ tenure_type = self.cbo_tenure_type.currentText() sel_col_name = self.tb_view.selected_column() if not sel_col_name: return None col, idx = self._get_attribute(tenure_type, sel_col_name) if idx == -1: return None return col def on_edit_column(self): """ Slot for showing a dialog for editing the selected column. """ self._notifBar.clear() sel_col = self.selected_column() if sel_col is None: msg = self.tr('Please select a column to edit.') self._notifBar.insertWarningNotification(msg) return original_name = sel_col.name editor_params = self._column_editor_params() editor_params['column'] = sel_col editor_params['is_new'] = False editor_params['in_db'] = not self._editable editor = ColumnEditor(**editor_params) editor.exclude_column_types(self._exc_cols) editor_title = self.tr('Edit Column') editor.setWindowTitle(editor_title) if editor.exec_() == QDialog.Accepted: if self._validate_excluded_column(editor.column.name): self.edit_column(original_name, editor.column) def _validate_excluded_column(self, name): # Check name against list of excluded names if name in self._exclude_names: msg = self.tr( 'column has been defined as an excluded column, it will not ' 'be added.') msg = '\'{0}\' {1}'.format(name, msg) QMessageBox.critical(self, self.tr('Excluded column'), msg) return False return True def on_delete_column(self): """ Slot for deleting the selected column. """ sel_col = self.selected_column() if sel_col is None: self._notifBar.clear() msg = self.tr('Please select a column to delete.') self._notifBar.insertWarningNotification(msg) return msg = self.tr( 'Are you sure you want to permanently delete the attribute?') result = QMessageBox.warning(self, self.tr('Delete Attribute'), msg, QMessageBox.Yes | QMessageBox.No) if result == QMessageBox.Yes: self.delete_column(sel_col.name) def delete_column(self, name): """ Removes the column with the given name from the view. :param name: Column name. :type name: str :return: Return True if the column was successfully deleted, otherwise False if the column does not exist. :rtype: bool """ tenure_type = self.cbo_tenure_type.currentText() attr, idx = self._get_attribute(tenure_type, name) if idx == -1: return False attrs = self._get_tenure_type_attrs(tenure_type) col = attrs.pop(idx) col.entity.remove_column(col.name) return self.tb_view.remove_item(attr.name) def load_attributes_from_entity(self, entity): """ Loads the view with the attributes of the specified entity. Any previous attributes will be removed. :param entity: Entity whose attributes will be loaded. :type entity: Entity """ self.load_attributes(entity.columns) def load_attributes(self, attributes): """ Loads the collection of attibutes to the view. Any previous attributes will be removed. The collection should not be empty. :param attributes: Collection of attributes to be loaded to te view. :type attributes: OrderedDict """ # Clear view self.tb_view.clear_view() if len(attributes) > 0: for a in attributes: self.add_column(a) # Refresh excluded columns self._update_excluded_columns()
class GeoODKConverter(QDialog, FORM_CLASS): def __init__(self, parent=None): """Class Constructor.""" super(GeoODKConverter, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-aut o-connect self.connect_action = pyqtSignal(str) self.setupUi(self) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.set_entity_model_view(self.entity_model) self.stdm_config = None self.parent = parent self.load_profiles() self.check_state_on() self.check_geoODK_path_exist() self.chk_all.stateChanged.connect(self.check_state_on) self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder) #self.btn_upload.clicked.connect(self.upload_generated_form) self._notif_bar_str = NotificationBar(self.vlnotification) def onShowOutputFolder(self): output_path = FORM_HOME # windows if sys.platform.startswith('win32'): os.startfile(output_path) # *nix systems if sys.platform.startswith('linux'): subprocess.Popen(['xdg-open', output_path]) # macOS if sys.platform.startswith('darwin'): subprocess.Popen(['open', output_path]) def check_state_on(self): """ Ensure all the items in the list are checked :return: """ if self.entity_model.rowCount() > 0: for row in range(self.entity_model.rowCount()): item = self.entity_model.item(row) if self.chk_all.isChecked(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def load_profiles(self): """ Read and load profiles from StdmConfiguration instance """ self.populate_view_models(current_profile()) def profiles(self): """ Get all profiles :return: """ return self.load_config().values() def populate_view_models(self, profile): for entity in profile.entities.values(): if entity.action == DbItem.DROP: continue if hasattr(entity, 'user_editable') and entity.TYPE_INFO <> 'VALUE_LIST': if entity.user_editable == False: continue if entity.TYPE_INFO not in [ 'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'ASSOCIATION_ENTITY', 'AUTO_GENERATE_CODE' ]: if entity.TYPE_INFO == 'VALUE_LIST': pass else: self.entity_model.add_entity(entity) self.set_model_items_selectable() def set_entity_model_view(self, entity_model): """ Set our list view to the default model :return: """ self.trentities.setModel(entity_model) def set_model_items_selectable(self): """ Ensure that the entities are checkable :return: """ if self.entity_model.rowCount() > 0: for row in range(self.entity_model.rowCount()): index = self.entity_model.index(row, 0) item_index = self.entity_model.itemFromIndex(index) item_index.setCheckable(True) def selected_entities_from_Model(self): """ Get selected entities for conversion to Xform from the user selection :return: """ entity_list = [] if self.entity_model.rowCount() > 0: for row in range(self.entity_model.rowCount()): item = self.entity_model.item(row) if item.isCheckable() and item.checkState() == Qt.Checked: entity_list.append(item.text()) return entity_list def check_geoODK_path_exist(self): """ Check if the geoodk paths are there in the directory Otherwise create them :return: """ if not os.access(FORM_HOME, os.F_OK): os.makedirs(unicode(FORM_HOME)) def upload_generated_form(self): """ Upload the generated Xform file to mobile phone. This eliminates the process of copying the file manually to the mobile device :return: """ form_uploader = FormUploader(self) form_uploader.exec_() def generate_mobile_form(self, selected_entities): """ Generate mobile form based on the selected entities. :return: """ try: self._notif_bar_str.clear() if len(selected_entities) == 0: self._notif_bar_str.insertErrorNotification( 'No entity selected. Please select at least one entity...') return if len(selected_entities) > 0: geoodk_writer = GeoodkWriter(selected_entities, self.str_supported) geoodk_writer.write_data_to_xform() msg = 'File saved ' \ 'in: {}' self._notif_bar_str.insertInformationNotification( msg.format(FORM_HOME)) except Exception as ex: self._notif_bar_str.insertErrorNotification( ex.message + ': Unable to generate Mobile Form') return def accept(self): """ Generate mobile forms based on user selected entities. Check if str is enabled, then ensure str tables are enabled. :return: """ user_entities = self.selected_entities_from_Model() self.str_supported = False if self.ck_social_tenure.isChecked(): self.str_supported = True str_definition = current_profile().social_tenure str_definition_party = str_definition.parties[0].short_name str_definition_spatial = str_definition.spatial_units[0].short_name if str_definition_party not in user_entities or str_definition_spatial not in user_entities: self._notif_bar_str.insertErrorNotification( 'One of the entities required to define str is not selected. Form not saved' ) return #else: #self.generate_mobile_form(user_entities) #else: self.generate_mobile_form(user_entities)
def setNotificationLayout(self,layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout)
class ComposerTableDataSourceEditor(WIDGET, BASE): def __init__(self, composer_wrapper, frame_item, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self._composer_wrapper = composer_wrapper if isinstance(frame_item, QgsComposerFrame): self._composer_table_item = frame_item.multiFrame() else: self._composer_table_item = frame_item self._notif_bar = NotificationBar(self.vl_notification) # Load fields if the data source has been specified ds_name = self._composer_wrapper.selectedDataSource() self.ref_table.load_data_source_fields(ds_name) # Load source tables self.ref_table.load_link_tables() # Connect signals self._composer_wrapper.dataSourceSelected.connect(self.ref_table.on_data_source_changed) # self.ref_table.cbo_ref_table.currentIndexChanged[str].connect(self.set_table_vector_layer) def composer_item(self): return self._composer_table_item def set_table_vector_layer(self, table_name): """ Creates a vector layer and appends it to the composer table item. :param table_name: Name of the linked table containing tabular information. :type table_name: str """ self._notif_bar.clear() if not table_name: return v_layer = vector_layer(table_name) if v_layer is None: msg = QApplication.translate("ComposerTableDataSourceEditor", "A vector layer could not be created from the table.") self._notif_bar.insertErrorNotification(msg) return if not v_layer.isValid(): msg = QApplication.translate("ComposerTableDataSourceEditor", "Invalid vector layer, the table will not be added.") self._notif_bar.insertErrorNotification(msg) return # No need to add the layer in the legend QgsMapLayerRegistry.instance().addMapLayer(v_layer, False) if len(self.composer_item().columns()) > 0: self._composer_table_item.setVectorLayer(v_layer) # _composer_table_item is QgsComposerAttributeTable self._composer_table_item.update() def configuration(self): from stdm.stdm.composer import TableConfiguration linked_table_props = self.ref_table.properties() table_config = TableConfiguration() table_config.set_linked_table(linked_table_props.linked_table) table_config.set_source_field(linked_table_props.source_field) table_config.set_linked_column(linked_table_props.linked_field) return table_config def set_configuration(self, configuration): # Load referenced table editor with item configuration settings. table_props = LinkedTableProps(linked_table=configuration.linked_table(), source_field=configuration.source_field(), linked_field=configuration.linked_field()) self.ref_table.set_properties(table_props)
class ProfileEditor(QDialog, Ui_Profile): def __init__(self, parent): QDialog.__init__(self, parent) self.profile_name = '' self.desc = '' self.setupUi(self) self.init_controls() self.notice_bar = NotificationBar(self.notif_bar) def init_controls(self): self.edtProfile.clear() self.edtDesc.clear() self.edtProfile.setFocus() self.edtProfile.textChanged.connect(self.validate_text) def format_name(self, txt): ''''remove any trailing spaces in the name and replace them underscore''' formatted_name = txt.strip().replace(' ', "_") return formatted_name def add_profile(self): self.profile_name = self.format_name(unicode(self.edtProfile.text())) self.desc = unicode(self.edtDesc.text()) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ msg = self.trUtf8(message) self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return locale = QSettings().value("locale/userLocale")[0:2] if locale == 'en': name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: msg = u'\'{0}\' is not allowed at this position.'.format( last_character ) self.show_notification(msg) text = text[:-1] # remove space and underscore at the beginning of the text if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def accept(self): '''listen to user action on the dialog''' if self.edtProfile.text() == '' or self.edtProfile.text() == ' ': self.error_info_message( QApplication.translate( "ProfileEditor", "Please enter a valid Profile name." ) ) return self.add_profile() self.done(1) def reject(self): self.done(0) def error_info_message(self, Message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(Message) msg.exec_()
class TemplateDocumentSelector(QDialog,Ui_frmDocumentSelector): """ Dialog for selecting a document template from the saved list. """ def __init__(self, parent=None,selectMode=True, filter_data_source=''): QDialog.__init__(self,parent) self.setupUi(self) self.notifBar = NotificationBar(self.vlNotification) self._mode = selectMode #Filter templates by the specified table name self._filter_data_source = filter_data_source #Document templates in current profile self._profile_templates = [] self._current_profile = current_profile() #Load current profile templates self._load_current_profile_templates() if selectMode: self.buttonBox.setVisible(True) self.manageButtonBox.setVisible(False) currHeight = self.size().height() self.resize(200,currHeight) else: self.buttonBox.setVisible(False) self.manageButtonBox.setVisible(True) self.setWindowTitle( QApplication.translate( "TemplateDocumentSelector", "Template Manager" ) ) #Configure manage buttons btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok) btnEdit.setText(QApplication.translate("TemplateDocumentSelector","Edit...")) btnEdit.setIcon(QIcon(":/plugins/stdm/images/icons/edit.png")) btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save) btnDelete.setText(QApplication.translate("TemplateDocumentSelector","Delete")) btnDelete.setIcon(QIcon(":/plugins/stdm/images/icons/delete.png")) #Connect signals self.buttonBox.accepted.connect(self.onAccept) btnEdit.clicked.connect(self.onEditTemplate) btnDelete.clicked.connect(self.onDeleteTemplate) #Get saved document templates then add to the model templates = documentTemplates() self._docItemModel = QStandardItemModel(parent) self._docItemModel.setColumnCount(2) #Append current profile templates to the model. for dt in self._profile_templates: if self._template_contains_filter_table(dt): doc_name_item = self._createDocNameItem(dt.name) file_path_item = QStandardItem(dt.path) self._docItemModel.appendRow([doc_name_item,file_path_item]) self.lstDocs.setModel(self._docItemModel) def _load_current_profile_templates(self): # Loads only those templates that refer to tables in the current # profile. if self._current_profile is None: return #Get saved document templates then add to the model templates = documentTemplates() profile_tables = self._current_profile.table_names() #Get templates for the current profile for name, path in templates.iteritems(): doc_temp = _DocumentTemplate.build_from_path(name, path) if doc_temp.data_source is None: continue #Assert data source is in the current profile if doc_temp.data_source.referenced_table_name in profile_tables: self._add_doc_temp(doc_temp) #self._profile_templates.append(doc_temp) if doc_temp.data_source._dataSourceName in user_non_profile_views(): self._add_doc_temp(doc_temp) #self._profile_templates.append(doc_temp) def _add_doc_temp(self, doc_temp): found = False for template in self._profile_templates: if template.name == doc_temp.name: found = True break if not found: self._profile_templates.append(doc_temp) def _template_contains_filter_table(self, document_template): #Returns true if the template refers to the filter data source #If no filter data source defined then always return True if document_template.data_source._dataSourceName in user_non_profile_views(): return True if not self._filter_data_source: return True referenced_table = document_template.referenced_table_name if referenced_table == self._filter_data_source: return True return False @property def mode(self): return self._mode @property def filter_data_source(self): return self._filter_data_source def _createDocNameItem(self,docName): """ Create a template document standard item. """ #Set icon icon = QIcon() icon.addPixmap( QPixmap( ":/plugins/stdm/images/icons/document.png" ), QIcon.Normal, QIcon.Off ) dnItem = QStandardItem(icon,docName) return dnItem def onEditTemplate(self): """ Slot raised to edit document template. """ self.notifBar.clear() if self.documentMapping() == None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document template to edit")) return templateName,filePath = self.documentMapping() docName,ok = QInputDialog.getText(self, \ QApplication.translate("TemplateDocumentSelector","Edit Template"), \ QApplication.translate("TemplateDocumentSelector","Please enter the new template name below"), \ text = templateName) if ok and docName == "": self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Template name cannot be empty")) return elif docName == templateName: return elif ok and docName != "": result,newTemplatePath = self._editTemplate(filePath, docName) if result: #Update view mIndices = self._selectedMappings() docNameItem = self._docItemModel.itemFromIndex(mIndices[0]) filePathItem = self._docItemModel.itemFromIndex(mIndices[1]) docNameItem.setText(docName) filePathItem.setText(newTemplatePath) self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \ "'{0}' template has been successfully updated".format(docName))) else: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Error: '{0}' template could not be updated".format(templateName))) def onDeleteTemplate(self): """ Slot raised to delete document template. """ self.notifBar.clear() if self.documentMapping() == None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document template to delete")) return templateName,filePath = self.documentMapping() result = QMessageBox.warning(self, QApplication.translate("TemplateDocumentSelector", \ "Confirm delete"), QApplication.translate("TemplateDocumentSelector", \ "Are you sure you want to delete '{0}' template?" \ "This action cannot be undone.\nClick Yes to proceed " \ "or No to cancel.".format(templateName)), QMessageBox.Yes|QMessageBox.No) if result == QMessageBox.No: return status = self._deleteDocument(filePath) if status: #Remove item from list using model index row number selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0) row = selectedDocNameIndices[0].row() self._docItemModel.removeRow(row) self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \ "'{0}' template has been successfully removed".format(templateName))) else: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Error: '{0}' template could not be removed".format(templateName))) def onAccept(self): """ Slot raised to close the dialog only when a selection has been made by the user. """ self.notifBar.clear() if self.documentMapping() == None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document")) return self.accept() def _selectedMappings(self): """ Returns the model indices for the selected row. """ selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0) selectedFilePathIndices = self.lstDocs.selectionModel().selectedRows(1) if len(selectedDocNameIndices) == 0: return None docNameIndex = selectedDocNameIndices[0] filePathIndex = selectedFilePathIndices[0] return (docNameIndex,filePathIndex) def documentMapping(self): """ Returns a tuple containing the selected document name and the corresponding file name. """ mIndices = self._selectedMappings() if mIndices == None: return None docNameItem = self._docItemModel.itemFromIndex(mIndices[0]) filePathItem = self._docItemModel.itemFromIndex(mIndices[1]) return (docNameItem.text(),filePathItem.text()) def _editTemplate(self,templatePath,newName): """ Updates the template document to use the new name. """ templateFile = QFile(templatePath) if not templateFile.open(QIODevice.ReadOnly): QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Open Operation Error"), \ "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector","Cannot read template file."), \ templateFile.errorString() )) return (False,"") templateDoc = QDomDocument() if templateDoc.setContent(templateFile): composerElement = templateDoc.documentElement() titleAttr = composerElement.attributeNode("_title") if not titleAttr.isNull(): titleAttr.setValue(newName) #Try remove file status = templateFile.remove() if not status: return (False,"") #Create new file newTemplatePath = self._composerTemplatesPath() + "/" + newName + ".sdt" newTemplateFile = QFile(newTemplatePath) if not newTemplateFile.open(QIODevice.WriteOnly): QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Save Operation Error"), \ "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector","Could not save template file."), \ newTemplateFile.errorString() )) return (False,"") if newTemplateFile.write(templateDoc.toByteArray()) == -1: QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector","Save Error"), \ QApplication.translate("TemplateDocumentSelector","Could not save template file.")) return (False,"") newTemplateFile.close() return (True,newTemplatePath) def _deleteDocument(self,templatePath): """ Delete the document template from the file system. """ docFile = QFile(templatePath) return docFile.remove() def _composerTemplatesPath(self): """ Reads the path of composer templates in the registry. """ regConfig = RegistryConfig() keyName = "ComposerTemplates" valueCollection = regConfig.read([keyName]) if len(valueCollection) == 0: return None else: return valueCollection[keyName]
class DocumentGeneratorDialog(WIDGET, BASE): """ Dialog that enables a user to generate documents by using configuration information for different entities. """ def __init__(self, iface, access_templates, parent=None, plugin=None): QDialog.__init__(self, parent) self.setupUi(self) self.btnSelectTemplate.setIcon(GuiUtils.get_icon('document.png')) self._iface = iface self.plugin = plugin self._docTemplatePath = "" self._outputFilePath = "" self.curr_profile = current_profile() self.last_data_source = None self._config_mapping = OrderedDict() self._notif_bar = NotificationBar(self.vlNotification) self._doc_generator = DocumentGenerator(self._iface, self) self._data_source = "" self.access_templates = access_templates enable_drag_sort(self.lstDocNaming) # Configure generate button generateBtn = self.buttonBox.button(QDialogButtonBox.Ok) if not generateBtn is None: generateBtn.setText( QApplication.translate("DocumentGeneratorDialog", "Generate")) # Load supported image types supportedImageTypes = QImageWriter.supportedImageFormats() for imageType in supportedImageTypes: imageTypeStr = imageType.data().decode() self.cboImageType.addItem(imageTypeStr) self._init_progress_dialog() # Connect signals self.btnSelectTemplate.clicked.connect(self.onSelectTemplate) self.buttonBox.accepted.connect(self.onGenerate) self.chkUseOutputFolder.stateChanged.connect( self.onToggledOutputFolder) self.rbExpImage.toggled.connect(self.onToggleExportImage) self.tabWidget.currentChanged.connect(self.on_tab_index_changed) self.chk_template_datasource.stateChanged.connect( self.on_use_template_datasource) self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder) def _init_progress_dialog(self): """ Initializes the progress dialog. """ self.progress = QProgressBar(self) self.progress.resize(self.width(), 10) self.progress.setTextVisible(False) def add_entity_configuration(self, **kwargs): ent_config = EntityConfig(**kwargs) self.add_entity_config(ent_config) def add_entity_config(self, ent_config, progress_value=0): QApplication.processEvents() if not self._config_mapping.get(ent_config.title(), ""): fk_mapper = self._create_fk_mapper(ent_config) self.tabWidget.addTab(fk_mapper, ent_config.title()) self._config_mapping[ent_config.title()] = ent_config # Force list of column names to be loaded if self.tabWidget.currentIndex() != 0: self.tabWidget.setCurrentIndex(0) else: self.on_tab_index_changed(0) self.progress.setValue(progress_value) def on_tab_index_changed(self, index): if index == -1: return config = self.config(index) if not config is None: # Set data source name self._data_source = config.data_source() def on_use_template_datasource(self, state): if state == Qt.Checked: self.tabWidget.setEnabled(False) self.chkUseOutputFolder.setEnabled(False) self.chkUseOutputFolder.setChecked(False) elif state == Qt.Unchecked: self.tabWidget.setEnabled(True) self.chkUseOutputFolder.setEnabled(True) self.chkUseOutputFolder.setChecked(False) self.on_tab_index_changed(self.tabWidget.currentIndex()) def onShowOutputFolder(self): reg_config = RegistryConfig() path = reg_config.read([COMPOSER_OUTPUT]) output_path = path.get(COMPOSER_OUTPUT, '') # windows if sys.platform.startswith('win32'): os.startfile(output_path) # *nix systems if sys.platform.startswith('linux'): subprocess.Popen(['xdg-open', output_path]) # macOS if sys.platform.startswith('darwin'): subprocess.Popen(['open', output_path]) def notification_bar(self): """ :return: Returns an instance of the notification bar. :rtype: NotificationBar """ return self._notif_bar def config(self, index): """ Returns the configuration for the current mapper in the tab widget. """ tab_key = self.tabWidget.tabText(index) return self._config_mapping.get(tab_key, None) def current_config(self): """ Returns the configuration corresponding to the current widget in the tab. """ return self.config(self.tabWidget.currentIndex()) def _load_model_columns(self, config): """ Load model columns into the view for specifying file output name. Only those columns of display type variants will be used. """ model_attr_mapping = OrderedDict() for c in config.data_source_columns(): model_attr_mapping[c] = format_name(c) self.lstDocNaming.load_mapping(model_attr_mapping) def _load_data_source_columns(self, entity): """ Load the columns of a data source for use in the file naming. """ table_cols = entity_display_columns(entity, True) attr_mapping = OrderedDict() for c, header in table_cols.items(): attr_mapping[c] = header self.lstDocNaming.load_mapping(attr_mapping) def _create_fk_mapper(self, config): fk_mapper = ForeignKeyMapper(config.ds_entity, self.tabWidget, self._notif_bar, enable_list=True, can_filter=True, plugin=self.plugin) fk_mapper.setDatabaseModel(config.model()) fk_mapper.setSupportsList(True) fk_mapper.setDeleteonRemove(False) fk_mapper.setNotificationBar(self._notif_bar) return fk_mapper def onSelectTemplate(self): """ Slot raised to load the template selector dialog. """ current_config = self.current_config() if current_config is None: msg = QApplication.translate( 'DocumentGeneratorDialog', 'An error occured while trying to determine the data source ' 'for the current entity.\nPlease check your current profile ' 'settings.') QMessageBox.critical( self, QApplication.translate('DocumentGeneratorDialog', 'Template Selector'), msg) return # Set the template selector to only load those templates that # reference the current data source. filter_table = current_config.data_source() templateSelector = TemplateDocumentSelector( self, filter_data_source=filter_table, access_templates=self.access_templates) if templateSelector.exec_() == QDialog.Accepted: docName, docPath = templateSelector.documentMapping() self.lblTemplateName.setText(docName) self._docTemplatePath = docPath if filter_table != self.last_data_source: # Load template data source fields self._load_template_datasource_fields() def _load_template_datasource_fields(self): # If using template data source template_doc, err_msg = self._doc_generator.template_document( self._docTemplatePath) if template_doc is None: QMessageBox.critical( self, "Error Generating documents", QApplication.translate( "DocumentGeneratorDialog", "Error Generating documents - %s" % (err_msg))) return composer_ds, err_msg = self._doc_generator.composer_data_source( template_doc) if composer_ds is None: QMessageBox.critical( self, "Error Generating documents", QApplication.translate( "DocumentGeneratorDialog", "Error Generating documents - %s" % (err_msg))) return # Load data source columns self._data_source = self.current_config().data_source() self.ds_entity = self.curr_profile.entity_by_name(self._data_source) self._load_data_source_columns(self.ds_entity) def onToggledOutputFolder(self, state): """ Slot raised to enable/disable the generated output documents to be written to the plugin composer output folder using the specified naming convention. """ if state == Qt.Checked: self.gbDocNaming.setEnabled(True) elif state == Qt.Unchecked: self.gbDocNaming.setEnabled(False) def reset(self, success_status=False): """ Clears/resets the dialog from user-defined options. """ self._docTemplatePath = "" self._data_source = "" self.lblTemplateName.setText("") # reset form only if generation is successful if success_status: fk_table_view = self.tabWidget.currentWidget(). \ findChild(QTableView) while fk_table_view.model().rowCount() > 0: fk_table_view.model().rowCount(0) fk_table_view.model().removeRow(0) if self.tabWidget.count() > 0 and \ not self.chk_template_datasource.isChecked(): self.on_tab_index_changed(0) if self.cboImageType.count() > 0: self.cboImageType.setCurrentIndex(0) def onToggleExportImage(self, state): """ Slot raised to enable/disable the image formats combobox. """ if state: self.cboImageType.setEnabled(True) else: self.cboImageType.setEnabled(False) def onGenerate(self): """ Slot raised to initiate the certificate generation process. """ self._notif_bar.clear() success_status = True config = self.current_config() self.last_data_source = config.data_source() if config is None: self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \ "The entity configuration could not be extracted.")) return # Get selected records and validate records = self.tabWidget.currentWidget().entities() if self.chk_template_datasource.isChecked(): records = self._dummy_template_records() if len(records) == 0: self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \ "Please load at least one entity record")) return if not self._docTemplatePath: self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \ "Please select a document template to use")) return documentNamingAttrs = self.lstDocNaming.selectedMappings() if self.chkUseOutputFolder.checkState() == Qt.Checked and len( documentNamingAttrs) == 0: self._notif_bar.insertErrorNotification(QApplication.translate("DocumentGeneratorDialog", \ "Please select at least one field for naming the output document")) return # Set output file properties if self.rbExpImage.isChecked(): outputMode = DocumentGenerator.Image fileExtension = self.cboImageType.currentText() saveAsText = "Image File" else: outputMode = DocumentGenerator.PDF fileExtension = "pdf" saveAsText = "PDF File" # Show save file dialog if not using output folder if self.chkUseOutputFolder.checkState() == Qt.Unchecked: docDir = source_document_location() if self._outputFilePath: fileInfo = QFileInfo(self._outputFilePath) docDir = fileInfo.dir().path() self._outputFilePath, _ = QFileDialog.getSaveFileName( self, QApplication.translate("DocumentGeneratorDialog", "Save Document"), docDir, "{0} (*.{1})".format( QApplication.translate("DocumentGeneratorDialog", saveAsText), fileExtension)) if not self._outputFilePath: self._notif_bar.insertErrorNotification( QApplication.translate( "DocumentGeneratorDialog", "Process aborted. No output file was specified.")) return # Include extension in file name self._outputFilePath = self._outputFilePath # + "." + fileExtension # else: # Multiple files to be generated. # pass self._doc_generator.set_link_field(config.link_field()) self._doc_generator.clear_attr_value_formatters() if not self.chk_template_datasource.isChecked(): # Apply cell formatters for naming output files self._doc_generator.set_attr_value_formatters(config.formatters()) entity_field_name = "id" # Iterate through the selected records progressDlg = QProgressDialog(self) progressDlg.setMaximum(len(records)) try: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) for i, record in enumerate(records): progressDlg.setValue(i) if progressDlg.wasCanceled(): success_status = False break # User-defined location if self.chkUseOutputFolder.checkState() == Qt.Unchecked: status, msg = self._doc_generator.run( self._docTemplatePath, entity_field_name, record.id, outputMode, data_source=self.ds_entity.name, filePath=self._outputFilePath) self._doc_generator.clear_temporary_layers() # Output folder location using custom naming else: status, msg = self._doc_generator.run( self._docTemplatePath, entity_field_name, record.id, outputMode, dataFields=documentNamingAttrs, fileExtension=fileExtension, data_source=self.ds_entity.name) self._doc_generator.clear_temporary_layers() if not status: result = QMessageBox.warning( self, QApplication.translate("DocumentGeneratorDialog", "Document Generate Error"), msg, QMessageBox.Ignore | QMessageBox.Abort) if result == QMessageBox.Abort: progressDlg.close() success_status = False # Restore cursor QApplication.restoreOverrideCursor() return # If its the last record and user has selected to ignore if i + 1 == len(records): progressDlg.close() success_status = False # Restore cursor QApplication.restoreOverrideCursor() return else: progressDlg.setValue(len(records)) QApplication.restoreOverrideCursor() QMessageBox.information( self, QApplication.translate("DocumentGeneratorDialog", "Document Generation Complete"), QApplication.translate( "DocumentGeneratorDialog", "Document generation has successfully completed.")) except DummyException as ex: LOGGER.debug(str(ex)) err_msg = sys.exc_info()[1] QApplication.restoreOverrideCursor() QMessageBox.critical( self, "STDM", QApplication.translate( "DocumentGeneratorDialog", "Error Generating documents - %s" % (err_msg))) success_status = False # Reset UI self.reset(success_status) def _dummy_template_records(self): """ This is applied when records from a template data source are to be used to generate the documents where no related entity will be used to filter matching records in the data source. The iteration of the data source records will be done internally within the DocumentGenerator class. """ class _DummyRecord: id = 1 return [_DummyRecord()] def showEvent(self, event): """ Notifies if there are not entity configuration objects defined. :param event: Window event :type event: QShowEvent """ QTimer.singleShot(500, self.check_entity_config) return QDialog.showEvent(self, event) def check_entity_config(self): if len(self._config_mapping) == 0: self._notif_bar.clear() msg = QApplication.translate( "DocumentGeneratorDialog", "Table " "configurations do not exist or have not been configured properly" ) self._notif_bar.insertErrorNotification(msg)
class EntityEditor(WIDGET, BASE): """ Dialog to add and edit entities """ def __init__(self, **kwargs): """ :param parent: Owner of this dialog :param profile : current profile :param entity : current entity :param in_db : Boolean flag to check if entity exist in database """ self.form_parent = kwargs.get('parent', self) self.profile = kwargs.get('profile', None) self.entity = kwargs.get('entity', None) self.in_db = kwargs.get('in_db', False) QDialog.__init__(self, self.form_parent) self.setupUi(self) self.notice_bar = NotificationBar(self.notif_bar) self.init_gui_controls() def init_gui_controls(self): self.edtTable.setFocus() self.setTabOrder(self.edtTable, self.edtDesc) if self.entity: self.edtTable.setText(self.entity.short_name) self.edtDesc.setText(self.entity.description) self.txt_display_name.setText(self.entity.label) self.cbSupportDoc.setCheckState( self.bool_to_check(self.entity.supports_documents)) if self.entity.supports_documents and self.supporting_document_exists( ): self.cbSupportDoc.setEnabled(False) self.edtTable.textChanged.connect(self.validate_text) self.edtTable.setEnabled(not self.in_db) def supporting_document_exists(self): sd_name = '{0}_{1}_{2}'.format(self.profile.prefix, self.entity.short_name.lower(), 'supporting_document') return pg_table_exists(sd_name) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ self.notice_bar.clear() self.notice_bar.insertErrorNotification(message) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return locale = (QSettings().value("locale/userLocale") or 'en-US')[0:2] name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] msg = QApplication.translate('EntityEditor', 'is not allowed at this position.') if state != QValidator.Acceptable: self.show_notification('"{}" {}'.format(last_character, msg)) text = text[:-1] # remove space and underscore at the beginning of the text if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def bool_to_check(self, state): """ Returns a check state given a boolean value :param state : Boolean value :type state: Boolean """ if state: return Qt.Checked else: return Qt.Unchecked def accept(self): if self.edtTable.text() == '' or self.edtTable.text() == ' ': self.show_message(self.tr("Please enter a valid entity name.")) return sn = str(self.edtTable.text().strip()) short_name = sn[0].upper() + sn[1:] if self.entity is None: # New entity if self.duplicate_check(short_name): self.show_message( self.tr("Entity with the same name already " "exist in the current profile!")) return else: self.add_entity(short_name) self.done(0) else: self.edit_entity(short_name) self.done(1) def add_entity(self, short_name): """ Creates and adds a new entity to a profile :param entity_name: name of the new entity :type entity_name: str """ self.entity = self._create_entity(short_name) self.profile.add_entity(self.entity) # return True def _create_entity(self, short_name): entity = self.profile.create_entity( short_name, entity_factory, supports_documents=self.support_doc()) entity.description = self.edtDesc.text() entity.label = self.txt_display_name.text() entity.column_added.connect(self.form_parent.add_column_item) entity.column_removed.connect(self.form_parent.delete_column_item) return entity def edit_entity(self, short_name): # remove old entity old_short_name = self.entity.short_name if old_short_name != short_name: status = self.profile.rename(old_short_name, short_name) self.entity.short_name = short_name self.entity.description = self.edtDesc.text() self.entity.label = self.txt_display_name.text() self.entity.supports_documents = self.support_doc() def duplicate_check(self, name): """ Return True if we have an entity in the current profile with same 'name' :param name: entity short_name :type name: str """ return name in self.profile.entities def support_doc(self): """ Return boolean value representing the check state of supporting document checkbox """ values = [False, None, True] cs = values[self.cbSupportDoc.checkState()] return cs def format_internal_name(self, short_name): """ Returns a table name used internally by the entity :param short_name: Entity name entered by user :type short_name: str :rtype: str """ name = str(short_name).strip() name = name.replace(' ', "_") name = name.lower() # Ensure prefix is not duplicated in the names prfx = self.profile.prefix prefix_idx = name.find(prfx, 0, len(prfx)) # If there is no prefix then append if prefix_idx == -1: name = '{0}_{1}'.format(self.profile.prefix, name) return name def reject(self): self.done(0) def show_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(message) msg.exec_()
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 ProfileEditor(QDialog, Ui_Profile): def __init__(self, parent): QDialog.__init__(self, parent) self.profile_name = '' self.desc = '' self.setupUi(self) self.init_controls() self.notice_bar = NotificationBar(self.notif_bar) def init_controls(self): self.edtProfile.clear() self.edtDesc.clear() self.edtProfile.setFocus() self.edtProfile.textChanged.connect(self.validate_text) def format_name(self, txt): ''''remove any trailing spaces in the name and replace them underscore''' formatted_name = txt.strip().replace(' ', "_") return formatted_name def add_profile(self): self.profile_name = self.format_name(unicode(self.edtProfile.text())) self.desc = unicode(self.edtDesc.text()) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ msg = self.trUtf8(message) self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return locale = QSettings().value("locale/userLocale")[0:2] if locale == 'en': name_regex = QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: msg = u'\'{0}\' is not allowed at this position.'.format( last_character) self.show_notification(msg) text = text[:-1] # remove space and underscore at the beginning of the text if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def accept(self): '''listen to user action on the dialog''' if self.edtProfile.text() == '' or self.edtProfile.text() == ' ': self.error_info_message( QApplication.translate("ProfileEditor", "Please enter a valid Profile name.")) return self.add_profile() self.done(1) def reject(self): self.done(0) def error_info_message(self, Message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(Message) msg.exec_()
class ProfileInstanceRecords(QDialog, FORM_CLASS): """ class constructor The class handles all the instances that the user has collected and saved in the folder and saved in a computer. The class will construct the path to the folder and enumerate all available instances and return the count. It will also rename all the file based on instance unique GUUID for easier management and future updates. """ def __init__(self, parent=None): """ initailize class variables here """ super(ProfileInstanceRecords, self).__init__(parent) self.setupUi(self) self.path = None self.instance_list = [] self.relations = {} self.parent_ids = {} self.importlogger = ImportLogger() self._notif_bar_str = NotificationBar(self.vlnotification) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.uuid_extractor = InstanceUUIDExtractor(self.path) self.btn_chang_dir.setIcon( QIcon(":/plugins/stdm/images/icons/open_file.png")) self.btn_refresh.setIcon( QIcon(":/plugins/stdm/images/icons/update.png")) self.btn_srid.setIcon(QIcon(":/plugins/stdm/images/icons/edit24.png")) self.chk_all.stateChanged.connect(self.check_state_on) #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed) self.btn_chang_dir.clicked.connect(self.on_directory_search) self.lst_widget.itemClicked.connect(self.user_selected_entities) self.btn_srid.clicked.connect(self.projection_settings) self.btn_refresh.clicked.connect(self.update_files_with_custom_filter) #self.load_config() self.on_filepath() self.current_profile_changed() self.check_state_on() self.instance_dir() def load_config(self): """ Load STDM configuration :return: """ stdm_config = None if QFile.exists(HOME + "/stdm/configuration.stc"): stdm_config = QFile(CONFIG_FILE) ConfigurationFileSerializer(stdm_config) profiles = StdmConfiguration.instance().profiles return profiles def check_state_on(self): """ Ensure all the items in the list are checked :return: """ count = self.lst_widget.count() if count > 0: for i in range(count): item = self.lst_widget.item(i) if self.chk_all.isChecked(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def profiles(self): """ Get all profiles :return: """ return self.load_config().values() def current_profile_changed(self): """ Get the current profile so that it is the one selected at the combo box :return: """ self.instance_list = [] self.active_profile() self.on_filepath() self.available_records() self.on_dir_path() self.profile_instance_entities() def active_profile(self): """ get the user selected profile :return:p """ self.profile = current_profile().name return self.profile def instance_dir(self): """ Create a path where imported instance will be kept :return: """ self.inst_path = self.path + "imported_instance" if not os.access(self.inst_path, os.F_OK): os.makedirs(unicode(self.inst_path)) else: return self.inst_path def instance_path(self): """ :return: """ self.instance_dir() return self.inst_path def on_filepath(self): """ Access the file directory with geoodk files by constructing the full path :return: path :rtype: string """ if self.txt_directory.text() != '': self.path = self.txt_directory.text() else: self.path = GEOODK_FORM_HOME if not os.access(self.path, os.F_OK): os.makedirs(unicode(self.path)) self.txt_directory.setText(self.path) return self.path def xform_xpaths(self): """ Return the full path to the default config path and filter geoodk instance that matches the current profile path :return: directories :rtype: list """ dirs = [] return [ os.path.join(self.path, name) for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater()) ] def on_dir_path(self): """ Extract the specific folder information and rename the file :return: """ self.uuid_extractor.new_list = [] if self.record_count() > 0: directories = self.xform_xpaths() for directory in directories: self.extract_guuid_and_rename_file(directory) def extract_guuid_and_rename_file(self, path): """ Extract teh unique Guuid and rename the file so that we can uniquely identify each file :return: """ for f in os.listdir(path): if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'): file_instance = os.path.join(path, f) self.rename_file_to_UUID(file_instance) def rename_file_to_UUID(self, file): """ Extract the UUID from each folder and file :return: """ self.uuid_extractor.set_file_path(file) self.uuid_extractor.on_file_passed() self.instance_list = self.uuid_extractor.new_list def move_imported_file(self, file): """ Moves the imported files to avoid repetition :return: """ instance_path = self.instance_path() try: basename = os.path.basename(os.path.dirname(file)) if not os.path.isdir(os.path.join(self.instance_path(), basename)): shutil.move(os.path.dirname(file), instance_path) else: pass except Exception as ex: return ex def profile_instance_entities(self): """ Add the user entities that are in the form to be imported into database into a list view widget :return: model """ self.lst_widget.clear() entity_list = self.instance_entities() if entity_list is not None and len(entity_list) > 0: for entity in entity_list: list_widget = QListWidgetItem( current_profile().entity_by_name(entity).short_name, self.lst_widget) list_widget.setCheckState(Qt.Checked) else: return def user_selected_entities(self): """ :return: """ user_list = [] count = self.lst_widget.count() if count > 0: for i in range(count): item = self.lst_widget.item(i) if item.checkState() == Qt.Checked: user_list.append(current_profile().entity( item.text()).name) return user_list else: return None def instance_entities(self): """ Enumerate the entities that are in the current profile and also part of the form so that we are only importing relevant entities to database :return: entities """ dirs = self.xform_xpaths() current_etities = [] if len(dirs) > 0: dir_f = dirs[0] instance_file = [ f for f in os.listdir(dir_f) if f.endswith('.xml') ] if len(instance_file) > 0: self.uuid_extractor.set_file_path( os.path.join(dir_f, instance_file[0])) entity_list = self.check_profile_with_custom_name() for entity_name in entity_list: if current_profile().entity_by_name( entity_name) is not None: current_etities.append(entity_name) if len(current_etities) > 0: return current_etities def check_profile_with_custom_name(self): """ Try extract mobile instance with custom filter name. Assumption is that there is a profile that bears that name :return: """ mismatch_profile = 'Please set current profile based on the data to be imported' entity_attr = [] if self.txt_filter.text() != '': for obj in self.profiles(): if obj.name.startswith(self.txt_filter.text()): if obj.name != current_profile().name: self._notif_bar_str.insertErrorNotification( mismatch_profile) return return self.uuid_extractor.document_entities(self.profile) def entity_attribute_to_database(self, entity_info): """ Get the user selected entities and insert tehm into database params: selected entities rtype: list :return:Object :type: dbObject """ cu_obj = '' import_status = False self.txt_feedback.clear() self._notif_bar_str.clear() has_relations = self.has_foreign_keys_parent(entity_info) if len(self.parent_table_isselected()) > 0: if QMessageBox.information( self, QApplication.translate('GeoODKMobileSettings', " Import Warning"), QApplication.translate( 'GeoODKMobileSettings', 'Some of dependent tables (entities)' 'which may not be part of the selected tables ' 'I.e: {} will be imported'.format( self.parent_table_isselected())), QMessageBox.Ok | QMessageBox.No) == QMessageBox.No: return try: parents_info = [] counter = 0 if len(self.instance_list) > 0: self.pgbar.setRange(counter, len(self.instance_list)) self.pgbar.setValue(0) for instance in self.instance_list: import_status = False counter = counter + 1 self.parent_ids = {} entity_importer = EntityImporter(instance) group_identifier = entity_importer.instance_group_id() #set the geometry coordinate system entity_importer.geomsetter(self.on_projection_select()) self.archive_this_import_file(counter, instance) if has_relations: #Import parents table first for parent_table in self.relations.keys(): cu_obj = parent_table if parent_table in self.instance_entities(): ref_id, import_status = entity_importer.process_parent_entity_import( parent_table) if group_identifier: self.parent_ids[parent_table] = [ ref_id, group_identifier ] else: self.parent_ids[parent_table] = [ ref_id, parent_table ] log_timestamp = '{0} -- parent table import succeeded: {1}'\ .format(parent_table, str(import_status)) self.log_table_entry(log_timestamp) parents_info.append(parent_table) if parent_table[1] in entity_info: entity_info.remove(parent_table) for table in entity_info: cu_obj = table if table not in parents_info: table_id, status = entity_importer.process_import_to_db( table, self.parent_ids) if table in self.parent_ids: continue else: self.parent_ids[table] = [ table_id, group_identifier ] self.log_table_entry( " -- {0} import succeeded: ".format(cu_obj) + str(status)) self.txt_feedback.append( 'saving record "{0}" to database'.format(counter)) if self.uuid_extractor.has_str_captured_in_instance(): if self.parent_ids is not None: entity_importer.process_social_tenure( self.parent_ids) self.log_table_entry( " -- saving social tenure relationship") self.pgbar.setValue(counter) self.txt_feedback.append( 'Number of record successfully imported: {}'.format( counter)) else: self._notif_bar_str.insertErrorNotification( "No user selected entities to import") self.pgbar.setValue(0) return except Exception as ex: self.log_table_entry( unicode(ex.message) + '-- {0} import succeeded: '.format(cu_obj) + unicode(import_status)) self.feedback_message(unicode(ex.message)) return def has_foreign_keys_parent(self, select_entities): """ Ensure we check that the table is not parent else import parent table first :return: """ self.relations = {} str_tables = current_profile().social_tenure party_tbl = str_tables.parties[0].name sp_tbl = str_tables.spatial_units[0].name has_relations = False for table in select_entities: table_object = current_profile().entity_by_name(table) cols = table_object.columns.values() for col in cols: if col.TYPE_INFO == 'FOREIGN_KEY': parent_object = table_object.columns[col.name] if parent_object.parent: self.relations[parent_object.parent.name] = [ table, col.name ] has_relations = True else: self.feedback_message( 'unable to read foreign key properties for "{0}"'. format(parent_object.name)) return has_str_defined = self.uuid_extractor.has_str_captured_in_instance() if has_str_defined: if party_tbl not in self.relations.keys(): self.relations[party_tbl] = [ 'social_tenure_relationship', str_tables.parties[0].short_name.lower() + '_id' ] if sp_tbl not in self.relations.keys(): self.relations[sp_tbl] = [ 'social_tenure_relationship', str_tables.spatial_units[0].short_name.lower() + '_id' ] else: return has_relations def parent_table_isselected(self): """ Take note that the user selected tables may or may not be imported based on parent child table relationship :return: """ try: silent_list = [] if self.user_selected_entities() > 0: for table in self.relations.keys(): if table not in self.user_selected_entities(): silent_list.append(table[1]) return silent_list except Exception as ex: self._notif_bar_str.insertErrorNotification(ex.message) def archive_this_import_file(self, counter, instance): """ Ensure that only import are done once :return: """ try: self.importlogger.logger_sections() file_info = 'File instance ' + str(counter) + ' : \n' + instance self.importlogger.onlogger_action(file_info) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def log_table_entry(self, instance): """ Ensure that only import are done once :return: """ try: current_time = QDateTime() import_time = current_time.currentDateTime() log_entry = instance + ' ' + str(import_time.toPyDateTime()) self.importlogger.onlogger_action(log_entry) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def check_previous_import(self): """ Ensure we are importing files once :return: """ try: self.importlogger.add_log_info() for files in self.instance_list: current_dir = os.path.basename(files) exist = self.importlogger.check_file_exist(current_dir) if exist: self.instance_list.remove(files) self.txt_count.setText(str(len(self.instance_list))) if self.record_count() != len(self.instance_list): msg = 'Some files have been already imported and therefore ' \ 'not enumerated' self._notif_bar_str.insertErrorNotification(msg) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def available_records(self): """ Let the user know how many records have been collected and are available for inport process :return: """ self.txt_count.setText(unicode(self.record_count())) def record_count(self): """ get the count of instance dir in the selected directory :return: integer """ return len([ name for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater()) ]) def profile_formater(self): """ Format the profile name by removing underscore character :return: """ if self.txt_filter.text() != '': filter_text = self.txt_filter.text() return filter_text else: return self.profile def update_files_with_custom_filter(self): """ Get the new file count with the user custom filter text :return: file count """ self.available_records() self.on_dir_path() self.profile_instance_entities() def projection_settings(self): """ let user select the projections for the data :return: """ project_select = ProjectionSelector(self) projection = project_select.loadAvailableSystems() self.txt_srid.setText(str(projection)) def on_projection_select(self): """ Get the selected projection and set it during data import :return: """ vals = self.txt_srid.text().split(":") return vals[1] def on_directory_search(self): """ Let the user choose the directory with instances :return: """ home_path = 'home' if self.txt_directory.text() != '': home_path = self.txt_directory.text() dir_name = QFileDialog.getExistingDirectory(self, 'Open Directory', home_path, QFileDialog.ShowDirsOnly) if dir_name: self.txt_directory.setText(str(dir_name)) self.current_profile_changed() self.check_state_on() def feedback_message(self, msg): """ Create a dialog box to capture and display errrors related to db while importing data :param: msg :type: string :return:Qdialog """ msgbox = QMessageBox() msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No) msgbox.setWindowTitle("Data Import") msgbox.setText(msg) msgbox.exec_() msgbox.show() return msgbox def accept(self): """ Execute the import dialog once the save button has been clicked :return: """ # if self.tab_widget.currentIndex() == 1: # host = self.txt_host.text() # passwd = self.txt_pass.text() # db = self.cbo_dbname.currentText() # user = self.txt_username.text() # port = self.txt_port.text() # json_extractor = JSONEXTRACTOR(user,passwd,host,port,db, self.profile) # json_conn = json_extractor.create_orphan_connection() # self.importlogger.onlogger_action(json_conn) # self.txt_svlog.append(json_conn) #else: self.buttonBox.setEnabled(False) try: if self.lst_widget.count() < 1: msg = 'No mobile records could be found for the current profile' self._notif_bar_str.insertErrorNotification(msg) self.buttonBox.setEnabled(True) return entities = self.user_selected_entities() if len(entities) < 1: if QMessageBox.information( self, QApplication.translate('MobileForms', 'Import Warning'), QApplication.translate( 'MobileForms', 'You have not ' 'selected any entity for import. All entities ' 'will be imported'), QMessageBox.Ok | QMessageBox.No) == QMessageBox.Ok: entities = self.instance_entities() else: return self.entity_attribute_to_database(entities) self.buttonBox.setEnabled(True) except Exception as ex: self._notif_bar_str.insertErrorNotification(ex.message) self.feedback_message(str(ex.message)) self.buttonBox.setEnabled(True)
class ColumnEditor(QDialog, Ui_ColumnEditor): """ Dialog to add/edit entity columns """ def __init__(self, **kwargs): """ :param parent: Owner of this dialog :type parent: QWidget :param kwargs: Keyword dictionary of the following parameters; column - Column you editing, None if its a new column entity - Entity you are adding the column to profile - Current profile in_db - Boolean flag to indicate if a column has been created in the database """ self.form_parent = kwargs.get('parent', self) self.column = kwargs.get('column', None) self.entity = kwargs.get('entity', None) self.profile = kwargs.get('profile', None) self.in_db = kwargs.get('in_db', False) self.is_new = kwargs.get('is_new', True) QDialog.__init__(self, self.form_parent) self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set'] self.EX_TYPE_INFO = [ 'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'VALUE_LIST', 'ASSOCIATION_ENTITY' ] self.setupUi(self) self.dtypes = {} self.type_info = '' # dictionary to hold default attributes for each data type self.type_attribs = {} self.init_type_attribs() # dictionary to act as a work area for the form fields. self.form_fields = {} self.init_form_fields() self.fk_entities = [] self.lookup_entities = [] if self.is_new: self.prop_set = None else: self.prop_set = True # the current entity should not be part of the foreign key parent table, # add it to the exclusion list self.FK_EXCLUDE.append(self.entity.short_name) self.type_names = \ [unicode(name) for name in BaseColumn.types_by_display_name().keys()] self.cboDataType.currentIndexChanged.connect(self.change_data_type) self.btnColProp.clicked.connect(self.data_type_property) self.init_controls() self.notice_bar = NotificationBar(self.notif_bar) self.show_notification() def show_notification(self): msg = self.tr('Column names should be in lower case with no spaces.') self.notice_bar.clear() self.notice_bar.insertNotification(msg, INFORMATION) def init_controls(self): """ Initialize GUI controls default state when the dialog window is opened. """ self.popuplate_data_type_cbo() name_regex = QtCore.QRegExp('^[a-z][a-z0-9_]*$') name_validator = QtGui.QRegExpValidator(name_regex) self.edtColName.setValidator(name_validator) #if self.column: if not self.column is None: self.column_to_form(self.column) self.column_to_wa(self.column) self.edtColName.setFocus() self.edtColName.setEnabled(not self.in_db) self.cboDataType.setEnabled(not self.in_db) self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect( self.cancel) def column_to_form(self, column): """ Initializes form controls with Column data. :param column: BaseColumn instance :type column: BaseColumn """ text = column.display_name() self.cboDataType.setCurrentIndex(self.cboDataType.findText(text)) self.edtColName.setText(column.name) self.edtColDesc.setText(column.description) self.edtUserTip.setText(column.user_tip) self.cbMandt.setChecked(column.mandatory) self.cbSearch.setCheckState(self.bool_to_check(column.searchable)) self.cbUnique.setCheckState(self.bool_to_check(column.unique)) self.cbIndex.setCheckState(self.bool_to_check(column.index)) ti = self.current_type_info() ps = self.type_attribs[ti].get('prop_set', None) if ps is not None: self.type_attribs[ti]['prop_set'] = self.prop_set def column_to_wa(self, column): """ Initialize 'work area' form_fields with column data. :param column: BaseColumn instance :type column: BaseColumn """ if column is not None: self.form_fields['colname'] = column.name self.form_fields['value'] = None self.form_fields['mandt'] = column.mandatory self.form_fields['search'] = column.searchable self.form_fields['unique'] = column.unique self.form_fields['index'] = column.index if hasattr(column, 'minimum'): self.form_fields['minimum'] = column.minimum self.form_fields['maximum'] = column.maximum if hasattr(column, 'srid'): self.form_fields['srid'] = column.srid self.form_fields['geom_type'] = column.geom_type if hasattr(column, 'entity_relation'): self.form_fields['entity_relation'] = column.entity_relation if hasattr(column, 'association'): self.form_fields[ 'first_parent'] = column.association.first_parent self.form_fields[ 'second_parent'] = column.association.second_parent if hasattr(column, 'min_use_current_date'): self.form_fields[ 'min_use_current_date'] = column.min_use_current_date self.form_fields[ 'max_use_current_date'] = column.max_use_current_date if hasattr(column, 'min_use_current_datetime'): self.form_fields['min_use_current_datetime'] = \ column.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ column.max_use_current_datetime def bool_to_check(self, state): """ Converts a boolean to a Qt checkstate. :param state: True/False :type state: boolean :rtype: Qt.CheckState """ if state: return Qt.Checked else: return Qt.Unchecked def init_form_fields(self): """ Initializes work area 'form_fields' dictionary with default values. Used when creating a new column. """ self.form_fields['colname'] = '' self.form_fields['value'] = None self.form_fields['mandt'] = False self.form_fields['search'] = False self.form_fields['unique'] = False self.form_fields['index'] = False self.form_fields['minimum'] = self.type_attribs.get('minimum', 0) self.form_fields['maximum'] = self.type_attribs.get('maximum', 0) self.form_fields['srid'] = self.type_attribs.get('srid', "") self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0) self.form_fields['in_db'] = self.in_db self.form_fields['entity_relation'] = \ self.type_attribs['FOREIGN_KEY'].get('entity_relation', None) self.form_fields['entity_relation'] = \ self.type_attribs['LOOKUP'].get('entity_relation', None) self.form_fields['first_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None) self.form_fields['second_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None) self.form_fields['min_use_current_date'] = \ self.type_attribs['DATE'].get('min_use_current_date', None) self.form_fields['max_use_current_date'] = \ self.type_attribs['DATE'].get('max_use_current_date', None) self.form_fields['min_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('min_use_current_datetime', None) self.form_fields['max_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('max_use_current_datetime', None) def init_type_attribs(self): """ Initializes data type attributes. The attributes are used to set the form controls state when a particular data type is selected. mandt - enables/disables checkbox 'Mandatory' search - enables/disables checkbox 'Searchable' unique - enables/disables checkbox 'Unique' index - enables/disables checkbox 'Index' *property - function to execute when a data type is selected. """ self.type_attribs['VARCHAR'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': True }, 'maximum': 30, 'property': self.varchar_property } self.type_attribs['INT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': 0, 'maximum': 0, 'property': self.bigint_property } self.type_attribs['TEXT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, } self.type_attribs['DOUBLE'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': True }, 'minimum': 0.0, 'maximum': 0.0, 'property': self.double_property } self.type_attribs['DATE'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': datetime.date.min, 'maximum': datetime.date.max, 'min_use_current_date': False, 'max_use_current_date': False, 'property': self.date_property } self.type_attribs['DATETIME'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': datetime.datetime.min, 'maximum': datetime.datetime.max, 'min_use_current_datetime': False, 'max_use_current_datetime': False, 'property': self.dtime_property } self.type_attribs['FOREIGN_KEY'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': None, 'property': self.fk_property, 'prop_set': False } self.type_attribs['LOOKUP'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': {}, 'property': self.lookup_property, 'prop_set': False } self.type_attribs['GEOMETRY'] = { 'mandt': { 'check_state': False, 'enabled_state': False }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': True, 'enabled_state': False }, 'index': { 'check_state': True, 'enabled_state': False }, 'srid': "", 'geom_type': 0, 'property': self.geometry_property, 'prop_set': False } self.type_attribs['BOOL'] = { 'mandt': { 'check_state': False, 'enabled_state': False }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False } } self.type_attribs['ADMIN_SPATIAL_UNIT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': None } self.type_attribs['MULTIPLE_SELECT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'first_parent': None, 'second_parent': self.entity, 'property': self.multi_select_property, 'prop_set': False } def data_type_property(self): """ Executes the function assigned to the property attribute of the current selected data type. """ self.type_attribs[self.current_type_info()]['property']() def varchar_property(self): """ Opens the property editor for the Varchar data type. If successfull, set a minimum column in work area 'form fields' """ editor = VarcharProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['maximum'] = editor.max_len() def bigint_property(self): """ Opens a property editor for the BigInt data type. """ editor = BigintProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() def double_property(self): """ Opens a property editor for the Double data type. """ editor = DoubleProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() def date_property(self): """ Opens a property editor for the Date data type. """ editor = DateProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_date'] = \ editor.min_use_current_date self.form_fields['max_use_current_date'] = \ editor.max_use_current_date def dtime_property(self): """ Opens a property editor for the DateTime data type. """ editor = DTimeProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_datetime'] = \ editor.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ editor.max_use_current_datetime def geometry_property(self): """ Opens a property editor for the Geometry data type. If successfull, set the srid(projection), geom_type (LINE, POLYGON...) and prop_set which is boolean flag to verify that all the geometry properties are set. Constraint - If 'prop_set' is False column cannot be saved. """ editor = GeometryProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['srid'] = editor.coord_sys() self.form_fields['geom_type'] = editor.geom_type() self.property_set() def admin_spatial_unit_property(self): """ Sets entity relation property used when creating column of type ADMIN_SPATIAL_UNIT """ er_fields = {} er_fields['parent'] = self.entity er_fields['parent_column'] = None er_fields['display_columns'] = [] er_fields['child'] = None er_fields['child_column'] = None self.form_fields['entity_relation'] = EntityRelation( self.profile, **er_fields) def fk_property(self): """ Opens a property editor for the ForeignKey data type. """ if len(self.edtColName.displayText()) == 0: self.show_message("Please enter column name!") return # filter list of lookup tables, don't show internal # tables in list of lookups fk_ent = [entity for entity in self.profile.entities.items() \ if entity[1].TYPE_INFO not in self.EX_TYPE_INFO] fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \ not in self.FK_EXCLUDE] relation = {} relation['form_fields'] = self.form_fields relation['fk_entities'] = fk_ent relation['profile'] = self.profile relation['entity'] = self.entity relation['column_name'] = unicode(self.edtColName.text()) editor = FKProperty(self, relation) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() self.property_set() def lookup_property(self): """ Opens a lookup type property editor """ editor = LookupProperty(self, self.form_fields, profile=self.profile) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() self.property_set() def multi_select_property(self): """ Opens a multi select property editor """ if len(self.edtColName.displayText()) == 0: self.show_message("Please enter column name!") return editor = MultiSelectProperty(self, self.form_fields, self.entity, self.profile) result = editor.exec_() if result == 1: self.form_fields['first_parent'] = editor.lookup() self.form_fields['second_parent'] = self.entity self.property_set() def create_column(self): """ Creates a new BaseColumn. """ column = None if self.type_info <> "": if self.type_info == 'ADMIN_SPATIAL_UNIT': self.admin_spatial_unit_property() column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, **self.form_fields) return column if self.is_property_set(self.type_info): column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, self.form_fields['geom_type'], self.entity, **self.form_fields) else: self.show_message(self.tr('Please set column properties.')) return else: raise self.tr("No type to create!") return column def property_set(self): self.prop_set = True self.type_attribs[self.current_type_info()]['prop_set'] = True def is_property_set(self, ti): """ Checks if column property is set by reading the value of attribute 'prop_set' :param ti: Type info to check for prop set :type ti: BaseColumn.TYPE_INFO :rtype: boolean """ return self.type_attribs[ti].get('prop_set', True) #if self.prop_set is None: #return self.type_attribs[ti].get('prop_set', True) #else: #return self.prop_set def property_by_name(self, ti, name): try: return self.dtype_property(ti)['property'][name] except: return None def popuplate_data_type_cbo(self): """ Fills the data type combobox widget with BaseColumn type names """ self.cboDataType.clear() self.cboDataType.insertItems(0, BaseColumn.types_by_display_name().keys()) self.cboDataType.setCurrentIndex(0) def change_data_type(self, index): """ Called by type combobox when you select a different data type. """ #ti = self.current_type_info() #if ti=='': #return text = self.cboDataType.itemText(index) ti = BaseColumn.types_by_display_name()[text].TYPE_INFO self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property')) self.type_info = ti opts = self.type_attribs[ti] self.set_optionals(opts) self.set_min_max_defaults(ti) #self.column_to_form(self.column, text) #self.column_to_wa(self.column) def set_optionals(self, opts): """ Enable/disables form controls based on selected column data type attributes param opts: Dictionary type properties of selected column type opts: dict """ self.cbMandt.setEnabled(opts['mandt']['enabled_state']) self.cbSearch.setEnabled(opts['search']['enabled_state']) self.cbUnique.setEnabled(opts['unique']['enabled_state']) self.cbIndex.setEnabled(opts['index']['enabled_state']) self.cbMandt.setCheckState( self.bool_to_check(opts['mandt']['check_state'])) self.cbSearch.setCheckState( self.bool_to_check(opts['search']['check_state'])) self.cbUnique.setCheckState( self.bool_to_check(opts['unique']['check_state'])) self.cbIndex.setCheckState( self.bool_to_check(opts['index']['check_state'])) def set_min_max_defaults(self, type_info): """ sets the work area 'form_fields' default values (minimum/maximum) from the column's type attribute dictionary :param type_info: BaseColumn.TYPE_INFO :type type_info: str """ self.form_fields['minimum'] = \ self.type_attribs[type_info].get('minimum', 0) self.form_fields['maximum'] = \ self.type_attribs[type_info].get('maximum', 0) def current_type_info(self): """ Returns a TYPE_INFO of a data type :rtype: str """ text = self.cboDataType.itemText(self.cboDataType.currentIndex()) try: return BaseColumn.types_by_display_name()[text].TYPE_INFO except: return '' def fill_work_area(self): """ Sets work area 'form_fields' with form control values """ self.form_fields['colname'] = unicode(self.edtColName.text()) self.form_fields['description'] = unicode(self.edtColDesc.text()) self.form_fields['index'] = self.cbIndex.isChecked() self.form_fields['mandatory'] = self.cbMandt.isChecked() self.form_fields['searchable'] = self.cbSearch.isChecked() self.form_fields['unique'] = self.cbUnique.isChecked() self.form_fields['user_tip'] = unicode(self.edtUserTip.text()) def show_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM")) msg.setText(message) msg.exec_() def accept(self): col_name = unicode(self.edtColName.text()).strip() # column name is not empty if len(col_name) == 0: self.show_message(self.tr('Please enter the column name!')) return False # check for STDM reserved keywords if col_name in RESERVED_KEYWORDS: self.show_message(self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\ "Please choose another column name.".format(col_name)) ) return False new_column = self.make_column() if new_column is None: LOGGER.debug("Error creating column!") self.show_message('Unable to create column!') return False if self.column is None: # new column if self.duplicate_check(col_name): self.show_message( self.tr("Column with the same name already " "exist in this entity!")) return False self.entity.add_column(new_column) self.done(1) else: # editing a column self.column = new_column self.done(1) def cancel(self): self.done(0) def make_column(self): """ Returns a newly created column :rtype: BaseColumn """ self.fill_work_area() col = self.create_column() return col def duplicate_check(self, name): """ Return True if we have a column in the current entity with same name as our new column :param col_name: column name :type col_name: str """ # check if another column with the same name exist in the current entity if self.entity.columns.has_key(name): return True else: return False def rejectAct(self): self.done(0)
class ColumnEditor(QDialog, Ui_ColumnEditor): """ Dialog to add/edit entity columns """ def __init__(self, **kwargs): """ :param parent: Owner of this dialog :type parent: QWidget :param kwargs: Keyword dictionary of the following parameters; column - Column you editing, None if its a new column entity - Entity you are adding the column to profile - Current profile in_db - Boolean flag to indicate if a column has been created in the database auto_add- True to automatically add a new column to the entity, default is False. """ self.form_parent = kwargs.get('parent', self) self.column = kwargs.get('column', None) self.entity = kwargs.get('entity', None) self.profile = kwargs.get('profile', None) self.in_db = kwargs.get('in_db', False) self.is_new = kwargs.get('is_new', True) self.auto_entity_add = kwargs.get('auto_add', False) QDialog.__init__(self, self.form_parent) self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set'] self.EX_TYPE_INFO = ['SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED'] self.setupUi(self) self.dtypes = {} self.type_info = '' # dictionary to hold default attributes for each data type self.type_attribs = {} self.init_type_attribs() # dictionary to act as a work area for the form fields. self.form_fields = {} self.init_form_fields() self.fk_entities = [] self.lookup_entities = [] # Exclude column type info in the list self._exclude_col_type_info = [] if self.is_new: self.prop_set = None # why not False?? else: self.prop_set = True # the current entity should not be part of the foreign key parent table, # add it to the exclusion list self.FK_EXCLUDE.append(self.entity.short_name) self.type_names = \ [unicode(name) for name in BaseColumn.types_by_display_name().keys()] self.cboDataType.currentIndexChanged.connect(self.change_data_type) self.btnColProp.clicked.connect(self.data_type_property) self.edtColName.textChanged.connect(self.validate_text) self.notice_bar = NotificationBar(self.notif_bar) self.init_controls() def exclude_column_types(self, type_info): """ Exclude the column types with the given type_info. :param type_info: List of TYPE_INFO of columns to exclude. :type type_info: list """ self._exclude_col_type_info = type_info # Block index change signal of combobox self.cboDataType.blockSignals(True) # Reload column data types self.populate_data_type_cbo() # Select column type if it had been specified if not self.column is None: text = self.column.display_name() self.cboDataType.setCurrentIndex(self.cboDataType.findText(text)) # Re-enable signals self.cboDataType.blockSignals(False) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ self.notice_bar.clear() self.notice_bar.insertErrorNotification(message) def _column_type_info(self, column): """ Check if column has TYPE_INFO attribute :param column: Entity column object :return: Column type. Otherwise None :rtype: String or None """ try: return column.TYPE_INFO except AttributeError: return None def init_controls(self): """ Initialize GUI controls default state when the dialog window is opened. """ self.populate_data_type_cbo() if not self.column is None: self.column_to_form(self.column) self.column_to_wa(self.column) self.edtColName.setFocus() self.edtColName.setEnabled(not self.in_db) self.cboDataType.setEnabled(not self.in_db) self.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect(self.accept) self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.cancel) col_type = self._column_type_info(self.column) if not self.in_db and col_type == 'GEOMETRY': opts = self.type_attribs[col_type] self.cbMandt.setEnabled(opts['mandt']['enabled_state']) self.cbUnique.setEnabled(opts['unique']['enabled_state']) self.cbIndex.setEnabled(opts['index']['enabled_state']) else: self.cbMandt.setEnabled(not self.in_db) self.cbUnique.setEnabled(not self.in_db) self.cbIndex.setEnabled(not self.in_db) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] locale = QSettings().value("locale/userLocale")[0:2] #if locale == 'en': state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification(u'"{}" is not allowed at this position.'. format(last_character) ) text = text[:-1] # fix caps, _, and spaces if last_character.isupper(): text = text.lower() if last_character == ' ': text = text.replace(' ', '_') if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] text = text.replace(' ', '_').lower() self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def column_to_form(self, column): """ Initializes form controls with Column data. :param column: BaseColumn instance :type column: BaseColumn """ text = column.display_name() self.cboDataType.setCurrentIndex(self.cboDataType.findText(text)) self.edtColName.setText(column.name) self.edtColDesc.setText(column.description) self.txt_form_label.setText(column.label) self.edtUserTip.setText(column.user_tip) self.cbMandt.setChecked(column.mandatory) self.cbSearch.setCheckState(self.bool_to_check(column.searchable)) self.cbUnique.setCheckState(self.bool_to_check(column.unique)) self.cbIndex.setCheckState(self.bool_to_check(column.index)) ti = self.current_type_info() ps = self.type_attribs[ti].get('prop_set', None) if ps is not None: self.type_attribs[ti]['prop_set']= self.prop_set def column_to_wa(self, column): """ Initialize 'work area' form_fields with column data. :param column: BaseColumn instance :type column: BaseColumn """ if column is not None: self.form_fields['colname'] = column.name self.form_fields['value'] = None self.form_fields['mandt'] = column.mandatory self.form_fields['search'] = column.searchable self.form_fields['unique'] = column.unique self.form_fields['index'] = column.index if hasattr(column, 'minimum'): self.form_fields['minimum'] = column.minimum self.form_fields['maximum'] = column.maximum if hasattr(column, 'srid'): self.form_fields['srid'] = column.srid self.form_fields['geom_type'] = column.geom_type if hasattr(column, 'entity_relation'): self.form_fields['entity_relation'] = column.entity_relation if hasattr(column, 'association'): self.form_fields['first_parent'] = column.association.first_parent self.form_fields['second_parent'] = column.association.second_parent if hasattr(column, 'min_use_current_date'): self.form_fields['min_use_current_date'] = column.min_use_current_date self.form_fields['max_use_current_date'] = column.max_use_current_date if hasattr(column, 'min_use_current_datetime'): self.form_fields['min_use_current_datetime'] = \ column.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ column.max_use_current_datetime if hasattr(column, 'prefix_source'): self.form_fields['prefix_source'] = column.prefix_source self.form_fields['columns'] = column.columns self.form_fields['column_separators'] = column.column_separators self.form_fields['leading_zero'] = column.leading_zero self.form_fields['separator'] = column.separator self.form_fields['colname'] = column.name self.form_fields['enable_editing'] = column.enable_editing self.form_fields['disable_auto_increment'] = column.disable_auto_increment self.form_fields['hide_prefix'] = column.hide_prefix # Decimal properties if hasattr(column, 'precision'): self.form_fields['precision'] = column.precision self.form_fields['scale'] = column.scale # Expression column if hasattr(column, 'expression'): self.form_fields['expression'] = column.expression self.form_fields['output_data_type'] = column.output_data_type def bool_to_check(self, state): """ Converts a boolean to a Qt checkstate. :param state: True/False :type state: boolean :rtype: Qt.CheckState """ return Qt.Checked if state else Qt.Unchecked def init_form_fields(self): """ Initializes work area 'form_fields' dictionary with default values. Used when creating a new column. """ none = QApplication.translate('CodeProperty', 'None') self.form_fields['colname'] = '' self.form_fields['value'] = None self.form_fields['mandt'] = False self.form_fields['search'] = False self.form_fields['unique'] = False self.form_fields['index'] = False self.form_fields['minimum'] = self.type_attribs.get('minimum', 0) self.form_fields['maximum'] = self.type_attribs.get('maximum', 0) self.form_fields['srid'] = self.type_attribs.get('srid', "") self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0) self.form_fields['in_db'] = self.in_db self.form_fields['prefix_source'] = self.type_attribs.get( 'prefix_source', none ) self.form_fields['columns'] = self.type_attribs.get( 'columns', [] ) self.form_fields['column_separators'] = self.type_attribs.get( 'column_separators', [] ) self.form_fields['leading_zero'] = self.type_attribs.get( 'leading_zero', '' ) self.form_fields['separator'] = self.type_attribs.get( 'separator', '' ) self.form_fields['enable_editing'] = self.type_attribs.get( 'enable_editing', '' ) self.form_fields['disable_auto_increment'] = self.type_attribs.get( 'disable_auto_increment', '' ) self.form_fields['hide_prefix'] = self.type_attribs.get( 'hide_prefix', '' ) self.form_fields['precision'] = self.type_attribs.get( 'precision', 18 ) self.form_fields['scale'] = self.type_attribs.get( 'scale', 6 ) self.form_fields['entity_relation'] = \ self.type_attribs['FOREIGN_KEY'].get('entity_relation', None) self.form_fields['entity_relation'] = \ self.type_attribs['LOOKUP'].get('entity_relation', None) self.form_fields['first_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None) self.form_fields['second_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None) self.form_fields['min_use_current_date'] = \ self.type_attribs['DATE'].get('min_use_current_date', None) self.form_fields['max_use_current_date'] = \ self.type_attribs['DATE'].get('max_use_current_date', None) self.form_fields['min_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('min_use_current_datetime', None) self.form_fields['max_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('max_use_current_datetime', None) self.form_fields['expression'] = self.type_attribs.get( 'expression', '' ) self.form_fields['output_data_type'] = self.type_attribs.get( 'output_data_type', '' ) def init_type_attribs(self): """ Initializes data type attributes. The attributes are used to set the form controls state when a particular data type is selected. mandt - enables/disables checkbox 'Mandatory' search - enables/disables checkbox 'Searchable' unique - enables/disables checkbox 'Unique' index - enables/disables checkbox 'Index' *property - function to execute when a data type is selected. """ self.type_attribs['VARCHAR'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':True, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':True}, 'index':{'check_state':False, 'enabled_state':True}, 'maximum':30,'property': self.varchar_property } self.type_attribs['INT'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':True, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':True}, 'index':{'check_state':False, 'enabled_state':False}, 'minimum':0, 'maximum':0, 'property':self.bigint_property } self.type_attribs['TEXT'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':False, 'enabled_state':False}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, } self.type_attribs['DOUBLE' ] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':True, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':True}, 'index':{'check_state':False, 'enabled_state':True}, 'minimum':0.0, 'maximum':0.0, 'precision': 18, 'scale': 6, 'property':self.double_property } self.type_attribs['DATE'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':False, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'minimum':datetime.date.min, 'maximum':datetime.date.max, 'min_use_current_date':False, 'max_use_current_date':False, 'property':self.date_property } self.type_attribs['DATETIME'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':False, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'minimum':datetime.datetime.min, 'maximum':datetime.datetime.max, 'min_use_current_datetime':False, 'max_use_current_datetime':False, 'property':self.dtime_property } self.type_attribs['FOREIGN_KEY'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':False, 'enabled_state':False}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'entity_relation':None, 'show_in_parent': True, 'show_in_child': True, 'property':self.fk_property, 'prop_set':False } self.type_attribs['LOOKUP'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':True, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'entity_relation':{}, 'property':self.lookup_property, 'prop_set':False } self.type_attribs['GEOMETRY'] = { 'mandt':{'check_state':False, 'enabled_state':False}, 'search':{'check_state':False, 'enabled_state':False}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'srid':"", 'geom_type':0, 'property':self.geometry_property, 'prop_set':False } self.type_attribs['BOOL'] = { 'mandt':{'check_state':False, 'enabled_state':False}, 'search':{'check_state':False, 'enabled_state':False}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False} } self.type_attribs['PERCENT'] = { 'mandt': {'check_state': False, 'enabled_state': False}, 'search': {'check_state': False, 'enabled_state': True}, 'unique': {'check_state': False, 'enabled_state': False}, 'index': {'check_state': False, 'enabled_state': False} } self.type_attribs['ADMIN_SPATIAL_UNIT'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':True, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'entity_relation':None} self.type_attribs['MULTIPLE_SELECT'] = { 'mandt':{'check_state':False, 'enabled_state':True}, 'search':{'check_state':False, 'enabled_state':True}, 'unique':{'check_state':False, 'enabled_state':False}, 'index':{'check_state':False, 'enabled_state':False}, 'first_parent':None, 'second_parent':self.entity, 'property':self.multi_select_property, 'prop_set':False } self.type_attribs['AUTO_GENERATED'] = { 'mandt': {'check_state': False, 'enabled_state': True}, 'search': {'check_state': True, 'enabled_state': True}, 'unique': {'check_state': True, 'enabled_state': True}, 'index': {'check_state': True, 'enabled_state': True}, 'prefix_source': '', 'columns':[], 'column_separators':[], 'leading_zero': '', 'separator':'', 'disable_auto_increment': False, 'enable_editing': False, 'property': self.code_property, 'hide_prefix': False, 'prop_set': True} self.type_attribs['EXPRESSION'] = { 'mandt': {'check_state': False, 'enabled_state': True}, 'search': {'check_state': False, 'enabled_state': True}, 'unique': {'check_state': False, 'enabled_state': True}, 'index': {'check_state': False, 'enabled_state': True}, 'output_data_type': '', 'expression':'', 'property': self.expression_property, 'prop_set': False} def data_type_property(self): """ Executes the function assigned to the property attribute of the current selected data type. """ self.type_attribs[self.current_type_info()]['property']() def varchar_property(self): """ Opens the property editor for the Varchar data type. If successful, set a minimum column in work area 'form fields' """ editor = VarcharProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['maximum'] = editor.max_len() def bigint_property(self): """ Opens a property editor for the BigInt data type. """ editor = BigintProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() def double_property(self): """ Opens a property editor for the Double data type. """ editor = DoubleProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['precision'] = editor.precision self.form_fields['scale'] = editor.scale def date_property(self): """ Opens a property editor for the Date data type. """ editor = DateProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_date'] = \ editor.min_use_current_date self.form_fields['max_use_current_date'] = \ editor.max_use_current_date def dtime_property(self): """ Opens a property editor for the DateTime data type. """ editor = DTimeProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_datetime'] = \ editor.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ editor.max_use_current_datetime def geometry_property(self): """ Opens a property editor for the Geometry data type. If successful, set the srid(projection), geom_type (LINE, POLYGON...) and prop_set which is boolean flag to verify that all the geometry properties are set. Constraint - If 'prop_set' is False column cannot be saved. """ editor = GeometryProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['srid'] = editor.coord_sys() self.form_fields['geom_type'] = editor.geom_type() self.property_set() def admin_spatial_unit_property(self): """ Sets entity relation property used when creating column of type ADMIN_SPATIAL_UNIT """ er_fields = {} er_fields['parent'] = self.entity er_fields['parent_column'] = None er_fields['display_columns'] = [] er_fields['child'] = None er_fields['child_column'] = None self.form_fields['entity_relation'] = EntityRelation(self.profile, **er_fields) def fk_property(self): """ Opens a property editor for the ForeignKey data type. """ if len(self.edtColName.displayText())==0: self.show_message("Please enter column name!") return # filter list of lookup tables, don't show internal # tables in list of lookups fk_ent = [entity for entity in self.profile.entities.items() \ if entity[1].TYPE_INFO not in self.EX_TYPE_INFO] fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \ not in self.FK_EXCLUDE] relation = {} relation['form_fields'] = self.form_fields relation['fk_entities'] = fk_ent relation['profile'] = self.profile relation['entity'] = self.entity relation['column_name'] = unicode(self.edtColName.text()) relation['show_in_parent'] = '1' relation['show_in_child'] = '1' editor = FKProperty(self, relation) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() relation['show_in_parent'] = editor.show_in_parent() relation['show_in_child'] = editor.show_in_child() self.property_set() def lookup_property(self): """ Opens a lookup type property editor """ editor = LookupProperty(self, self.form_fields, profile=self.profile) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() self.property_set() def multi_select_property(self): """ Opens a multi select property editor """ if len(self.edtColName.displayText())==0: self.show_message("Please enter column name!") return editor = MultiSelectProperty(self, self.form_fields, self.entity, self.profile) result = editor.exec_() if result == 1: self.form_fields['first_parent'] = editor.lookup() self.form_fields['second_parent'] = self.entity self.property_set() def code_property(self): """ Opens the code data type property editor """ editor = CodeProperty(self, self.form_fields, entity=self.entity, profile=self.profile) result = editor.exec_() if result == 1: self.form_fields['prefix_source'] = editor.prefix_source() self.form_fields['columns'] = editor.columns() self.form_fields['leading_zero'] = editor.leading_zero() self.form_fields['separator'] = editor.separator() self.form_fields['disable_auto_increment'] = editor.disable_auto_increment() self.form_fields['enable_editing'] = editor.enable_editing() self.form_fields['column_separators'] = editor.column_separators() self.form_fields['hide_prefix'] = editor.hide_prefix() self.property_set() def expression_property(self): """ Opens the code data type property editor """ layer = self.create_layer() editor = ExpressionProperty(layer, self.form_fields, self) result = editor.exec_() if result == 1: self.form_fields['expression'] = editor.expression_text() self.form_fields['output_data_type'] = editor.get_output_data_type() self.property_set() def create_layer(self): srid = None column = '' if self.entity.has_geometry_column(): geom_cols = [col.name for col in self.entity.columns.values() if col.TYPE_INFO == 'GEOMETRY'] column = geom_cols[0] geom_col_obj = self.entity.columns[column] if geom_col_obj.srid >= 100000: srid = geom_col_obj.srid layer = vector_layer(self.entity.name, geom_column=column, proj_wkt=srid) return layer def create_column(self): """ Creates a new BaseColumn. """ column = None if self.type_info <> "": if self.type_info == 'ADMIN_SPATIAL_UNIT': self.admin_spatial_unit_property() column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, **self.form_fields) return column if self.is_property_set(self.type_info): column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, self.form_fields['geom_type'], self.entity, **self.form_fields) else: self.show_message(self.tr('Please set column properties.')) return else: raise self.tr("No type to create.") return column def property_set(self): self.prop_set = True self.type_attribs[self.current_type_info()]['prop_set'] = True def is_property_set(self, ti): """ Checks if column property is set by reading the value of attribute 'prop_set' :param ti: Type info to check for prop set :type ti: BaseColumn.TYPE_INFO :rtype: boolean """ return self.type_attribs[ti].get('prop_set', True) def property_by_name(self, ti, name): try: return self.dtype_property(ti)['property'][name] except: return None def populate_data_type_cbo(self): """ Fills the data type combobox widget with BaseColumn type names. """ self.cboDataType.clear() for name, col in BaseColumn.types_by_display_name().iteritems(): # Specify columns to exclude if col.TYPE_INFO not in self._exclude_col_type_info: self.cboDataType.addItem(name) if self.cboDataType.count() > 0: self.cboDataType.setCurrentIndex(0) def change_data_type(self, index): """ Called by type combobox when you select a different data type. """ text = self.cboDataType.itemText(index) col_cls = BaseColumn.types_by_display_name().get(text, None) if col_cls is None: return ti = col_cls.TYPE_INFO if ti not in self.type_attribs: msg = self.tr('Column type attributes could not be found.') self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) return self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property')) self.type_info = ti opts = self.type_attribs[ti] self.set_optionals(opts) self.set_min_max_defaults(ti) def set_optionals(self, opts): """ Enable/disables form controls based on selected column data type attributes param opts: Dictionary type properties of selected column type opts: dict """ self.cbMandt.setEnabled(opts['mandt']['enabled_state']) self.cbSearch.setEnabled(opts['search']['enabled_state']) self.cbUnique.setEnabled(opts['unique']['enabled_state']) self.cbIndex.setEnabled(opts['index']['enabled_state']) self.cbMandt.setCheckState(self.bool_to_check(opts['mandt']['check_state'])) self.cbSearch.setCheckState(self.bool_to_check(opts['search']['check_state'])) self.cbUnique.setCheckState(self.bool_to_check(opts['unique']['check_state'])) self.cbIndex.setCheckState(self.bool_to_check(opts['index']['check_state'])) def set_min_max_defaults(self, type_info): """ sets the work area 'form_fields' default values (minimum/maximum) from the column's type attribute dictionary :param type_info: BaseColumn.TYPE_INFO :type type_info: str """ self.form_fields['minimum'] = \ self.type_attribs[type_info].get('minimum', 0) self.form_fields['maximum'] = \ self.type_attribs[type_info].get('maximum', 0) def current_type_info(self): """ Returns a TYPE_INFO of a data type :rtype: str """ text = self.cboDataType.itemText(self.cboDataType.currentIndex()) try: return BaseColumn.types_by_display_name()[text].TYPE_INFO except: return '' def fill_work_area(self): """ Sets work area 'form_fields' with form control values """ self.form_fields['colname'] = unicode(self.edtColName.text()) self.form_fields['description'] = unicode(self.edtColDesc.text()) self.form_fields['label'] = unicode(self.txt_form_label.text()) self.form_fields['index'] = self.cbIndex.isChecked() self.form_fields['mandatory'] = self.cbMandt.isChecked() self.form_fields['searchable'] = self.cbSearch.isChecked() self.form_fields['unique'] = self.cbUnique.isChecked() self.form_fields['user_tip'] = unicode(self.edtUserTip.text()) def show_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM")) msg.setText(message) msg.exec_() def accept(self): col_name = unicode(self.edtColName.text()).strip() # column name is not empty if len(col_name)==0 or col_name == '_': self.show_message(self.tr('Please enter a valid column name.')) return False # check for STDM reserved keywords if col_name in RESERVED_KEYWORDS: self.show_message( self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\ "Please choose another column name.".format(col_name)) ) return False new_column = self.make_column() if new_column is None: LOGGER.debug("Error creating column!") self.show_message('Unable to create column!') return False if self.column is None: # new column if self.duplicate_check(col_name): self.show_message(self.tr("Column with the same name already " "exist in this entity!")) return False if self.auto_entity_add: self.entity.add_column(new_column) self.column = new_column self.done(1) else: # editing a column self.column = new_column self.done(1) def cancel(self): self.done(0) def make_column(self): """ Returns a newly created column :rtype: BaseColumn """ self.fill_work_area() col = self.create_column() return col def duplicate_check(self, name): """ Return True if we have a column in the current entity with same name as our new column :param col_name: column name :type col_name: str """ # check if another column with the same name exist in the current entity if name in self.entity.columns: return True else: return False def rejectAct(self): self.done(0)
class GeoODKConverter(QDialog, FORM_CLASS): def __init__(self, parent=None): """Class Constructor.""" super(GeoODKConverter, self).__init__(parent) # Set up the user interface from Designer. # After setupUI you can access any designer object by doing # self.<objectname>, and you can use autoconnect slots - see # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html # #widgets-and-dialogs-with-aut o-connect self.connect_action = pyqtSignal(str) self.setupUi(self) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.set_entity_model_view(self.entity_model) self.stdm_config = None self.parent = parent self.load_profiles() self.check_state_on() self.check_geoODK_path_exist() self.chk_all.stateChanged.connect(self.check_state_on) self.btnShowOutputFolder.clicked.connect(self.onShowOutputFolder) #self.btn_upload.clicked.connect(self.upload_generated_form) self._notif_bar_str = NotificationBar(self.vlnotification) def onShowOutputFolder(self): output_path = FORM_HOME # windows if sys.platform.startswith('win32'): os.startfile(output_path) # *nix systems if sys.platform.startswith('linux'): subprocess.Popen(['xdg-open', output_path]) # macOS if sys.platform.startswith('darwin'): subprocess.Popen(['open', output_path]) def check_state_on(self): """ Ensure all the items in the list are checked :return: """ if self.entity_model.rowCount() > 0: for row in range(self.entity_model.rowCount()): item = self.entity_model.item(row) if self.chk_all.isChecked(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def load_profiles(self): """ Read and load profiles from StdmConfiguration instance """ self.populate_view_models(current_profile()) def profiles(self): """ Get all profiles :return: """ return self.load_config().values() def populate_view_models(self, profile): for entity in profile.entities.values(): if entity.action == DbItem.DROP: continue if hasattr(entity, 'user_editable') and entity.TYPE_INFO <> 'VALUE_LIST': if entity.user_editable == False: continue if entity.TYPE_INFO not in ['SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'ASSOCIATION_ENTITY', 'AUTO_GENERATE_CODE']: if entity.TYPE_INFO == 'VALUE_LIST': pass else: self.entity_model.add_entity(entity) self.set_model_items_selectable() def set_entity_model_view(self, entity_model): """ Set our list view to the default model :return: """ self.trentities.setModel(entity_model) def set_model_items_selectable(self): """ Ensure that the entities are checkable :return: """ if self.entity_model.rowCount() >0: for row in range(self.entity_model.rowCount()): index = self.entity_model.index(row,0) item_index = self.entity_model.itemFromIndex(index) item_index.setCheckable(True) def selected_entities_from_Model(self): """ Get selected entities for conversion to Xform from the user selection :return: """ entity_list =[] if self.entity_model.rowCount() > 0: for row in range(self.entity_model.rowCount()): item = self.entity_model.item(row) if item.isCheckable() and item.checkState() == Qt.Checked: entity_list.append(item.text()) return entity_list def check_geoODK_path_exist(self): """ Check if the geoodk paths are there in the directory Otherwise create them :return: """ if not os.access(FORM_HOME, os.F_OK): os.makedirs(unicode(FORM_HOME)) def upload_generated_form(self): """ Upload the generated Xform file to mobile phone. This eliminates the process of copying the file manually to the mobile device :return: """ form_uploader = FormUploader(self) form_uploader.exec_() def generate_mobile_form(self, selected_entities): """ Generate mobile form based on the selected entities. :return: """ #try: self._notif_bar_str.clear() if len(selected_entities) == 0: self._notif_bar_str.insertErrorNotification( 'No entity selected. Please select at least one entity...' ) return if len(selected_entities) > 0: geoodk_writer = GeoodkWriter(selected_entities, self.str_supported) geoodk_writer.write_data_to_xform() msg = 'File saved in: {}' self._notif_bar_str.insertInformationNotification(msg.format(FORM_HOME)) #except Exception as ex: # self._notif_bar_str.insertErrorNotification(ex.message + # ': Unable to generate Mobile Form') # return def accept(self): """ Generate mobile forms based on user selected entities. Check if str is enabled, then ensure str tables are enabled. :return: """ user_entities = self.selected_entities_from_Model() self.str_supported = False if self.ck_social_tenure.isChecked(): self.str_supported = True str_definition = current_profile().social_tenure str_definition_party = str_definition.parties[0].short_name str_definition_spatial = str_definition.spatial_units[0].short_name if str_definition_party not in user_entities or str_definition_spatial not in user_entities: self._notif_bar_str.insertErrorNotification( 'One of the entities required to define str is not selected. Form not saved' ) return #else: #self.generate_mobile_form(user_entities) #else: self.generate_mobile_form(user_entities)
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 RelatedTableDialog(TranslatorDialogBase, WIDGET, BASE): """ Dialog for defining configuration settings for the RelatedTableTranslator class implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col) self._notif_bar = NotificationBar(self.vl_notification) self._set_source_table_headers() # Set UI values self.txt_table_name.setText(dest_table) self.txt_column_name.setText(dest_col) # Load STDM tables exluding views self._load_tables() # Connect signals self.cbo_source_tables.currentIndexChanged.connect( self._on_source_table_changed) def _load_tables(self): """ Loads both textual and spatial tables into the user list. """ self.cbo_source_tables.addItem("") self.cbo_source_tables.addItems(self.db_tables()) def value_translator(self): rel_tab_translator = RelatedTableTranslator() rel_tab_translator.set_referencing_table(self.txt_table_name.text()) rel_tab_translator.set_referencing_column(self.txt_column_name.text()) rel_tab_translator.set_referenced_table( self.cbo_source_tables.currentText()) rel_tab_translator.set_output_reference_column( self.cbo_output_column.currentText()) rel_tab_translator.set_input_referenced_columns(self.column_pairings()) return rel_tab_translator def _set_source_table_headers(self): labels = [ QApplication.translate("RelatedTableDialog", "Source Table"), QApplication.translate("RelatedTableDialog", "Referenced Table") ] self.tb_source_trans_cols.set_header_labels(labels) def _on_source_table_changed(self, index): source_table = self.cbo_source_tables.currentText() self.cbo_output_column.clear() if source_table: ref_table_cols = table_column_names(source_table) self.tb_source_trans_cols.set_combo_selection( [self._source_cols, ref_table_cols]) # self.cbo_output_column.addItem("") self.cbo_output_column.addItems(ref_table_cols) else: self.tb_source_trans_cols.clear_view() def column_pairings(self): """ :return: Source and reference table column matchings. :rtype: dict """ return self.tb_source_trans_cols.column_pairings() def validate(self): """ :return: Check user entries. :rtype: bool """ if self.cbo_source_tables.currentText() == "": msg = QApplication.translate( "RelatedTableDialog", "Please select " "the reference table name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if self.cbo_output_column.currentText() == "": msg = QApplication.translate( "RelatedTableDialog", "Please select " "the output column name.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False if len(self.column_pairings()) == 0: msg = QApplication.translate( "RelatedTableDialog", "Please specify " "at least one column pairing.") self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False return True def accept(self): """ Validate before accepting user input. """ if self.validate(): super(RelatedTableDialog, self).accept()
def __init__(self, **kwargs): """ :param parent: Owner of this dialog :type parent: QWidget :param kwargs: Keyword dictionary of the following parameters; column - Column you editing, None if its a new column entity - Entity you are adding the column to profile - Current profile in_db - Boolean flag to indicate if a column has been created in the database auto_add- True to automatically add a new column to the entity, default is False. """ self.form_parent = kwargs.get('parent', self) self.column = kwargs.get('column', None) self.entity = kwargs.get('entity', None) self.profile = kwargs.get('profile', None) self.in_db = kwargs.get('in_db', False) self.is_new = kwargs.get('is_new', True) self.auto_entity_add = kwargs.get('auto_add', False) QDialog.__init__(self, self.form_parent) self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set'] self.EX_TYPE_INFO = [ 'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED' ] self.setupUi(self) self.dtypes = {} self.type_info = '' # dictionary to hold default attributes for each data type self.type_attribs = {} self.init_type_attribs() # dictionary to act as a work area for the form fields. self.form_fields = {} self.init_form_fields() self.fk_entities = [] self.lookup_entities = [] # Exclude column type info in the list self._exclude_col_type_info = [] if self.is_new: self.prop_set = None # why not False?? else: self.prop_set = True # the current entity should not be part of the foreign key parent table, # add it to the exclusion list self.FK_EXCLUDE.append(self.entity.short_name) self.type_names = \ [unicode(name) for name in BaseColumn.types_by_display_name().keys()] self.cboDataType.currentIndexChanged.connect(self.change_data_type) self.btnColProp.clicked.connect(self.data_type_property) self.edtColName.textChanged.connect(self.validate_text) self.notice_bar = NotificationBar(self.notif_bar) self.init_controls()
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 ImageExportSettings(QDialog, Ui_ImageExportSettings): """A dialog for settings options for exporting an image.""" def __init__(self, parent=None, **kwargs): super(ImageExportSettings, self).__init__(parent) self.setupUi(self) self._image_tr = self.tr('Image') self.notif_bar = NotificationBar(self.vl_notification, 6000) #Connect signals self.btn_path.clicked.connect(self._on_choose_image_path) self.buttonBox.accepted.connect(self.on_accept) self.sb_resolution.valueChanged.connect(self._on_resolution_changed) #Set color button defaults self._default_color = Qt.white self.btn_color.setDefaultColor(self._default_color) self.btn_color.setColor(self._default_color) self.btn_color.setAllowAlpha(True) self.path = kwargs.get('image_path', '') self.resolution = kwargs.get('resolution', '96') self.background_color = kwargs.get('background', Qt.transparent) self._update_controls() def _update_controls(self): #Update input controls with export settings' values if self.background_color == Qt.transparent: self.rb_transparent.setChecked(True) self.btn_color.setVisible(False) else: self.rb_fill.setChecked(True) self.btn_color.setColor(self.background_color) self.sb_resolution.setValue(int(self.resolution)) self.txt_path.setText(self.path) #Set image size just in case value does not change self._set_image_size() def _on_resolution_changed(self, value): #Slot raised when the resolution changes self._set_image_size() def _update_export_vars(self): #Update export variables based on user values. self.path = self.txt_path.text() self.resolution = self.sb_resolution.value() if self.rb_transparent.isChecked(): self.background_color = Qt.transparent else: self.background_color = self.btn_color.color() def _set_image_size(self): #Set image size based on the resolution using A4 paper size res = self.sb_resolution.value() if res == 0: return #To mm res_mm = res / 25.4 #A4 landscape size width = int(297 * res_mm) height = int(210 * res_mm) units = 'px' width_display = u'{0} {1}'.format(width, units) height_display = u'{0} {1}'.format(height, units) self.lbl_width.setText(width_display) self.lbl_height.setText(height_display) def _image_filters(self): #Return supported image formats for use in a QFileDialog filter formats = [] for f in QImageWriter.supportedImageFormats(): f_type = f.data() filter_format = u'{0} {1} (*.{2})'.format( f_type.upper(), self._image_tr, f_type ) formats.append(filter_format) return ';;'.join(formats) def _on_choose_image_path(self): #Slot raised to choose image path img_path = self.txt_path.text() title = self.tr('Specify image location') sel_image_path = QFileDialog.getSaveFileName( self, title, img_path, self._image_filters() ) if sel_image_path: self.txt_path.setText(sel_image_path) def on_accept(self): """ Slot raised to save the settings and close the dialog. """ if not self.txt_path.text(): msg = self.tr('Please specify the image path.') self.notif_bar.insertErrorNotification(msg) self.txt_path.setFocus() return self._update_export_vars() self.accept()
class TemplateDocumentSelector(WIDGET, BASE): """ Dialog for selecting a document template from the saved list. """ def __init__(self, parent=None, selectMode=True, filter_data_source='', access_templates=None): QDialog.__init__(self, parent) self.setupUi(self) self.notifBar = NotificationBar(self.vlNotification) self._mode = selectMode # Filter templates by the specified table name self._filter_data_source = filter_data_source # Document templates in current profile self._profile_templates = [] self._current_profile = current_profile() # Load current profile templates self._load_current_profile_templates() self.access_templates = access_templates or [] if selectMode: self.buttonBox.setVisible(True) self.manageButtonBox.setVisible(False) currHeight = self.size().height() self.resize(200, currHeight) else: self.buttonBox.setVisible(False) self.manageButtonBox.setVisible(True) self.setWindowTitle( QApplication.translate("TemplateDocumentSelector", "Template Manager")) # Configure manage buttons btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok) btnEdit.setText( QApplication.translate("TemplateDocumentSelector", "Edit...")) btnEdit.setIcon(GuiUtils.get_icon("edit.png")) btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save) btnDelete.setText( QApplication.translate("TemplateDocumentSelector", "Delete")) btnDelete.setIcon(GuiUtils.get_icon("delete.png")) # Connect signals self.buttonBox.accepted.connect(self.onAccept) btnEdit.clicked.connect(self.onEditTemplate) btnDelete.clicked.connect(self.onDeleteTemplate) # Get saved document templates then add to the model templates = documentTemplates() self._docItemModel = QStandardItemModel(parent) self._docItemModel.setColumnCount(2) # Append current profile templates to the model. for dt in self._profile_templates: if self._template_contains_filter_table( dt): # and dt.name in self.access_templates: doc_name_item = self._createDocNameItem(dt.name) file_path_item = QStandardItem(dt.path) self._docItemModel.appendRow([doc_name_item, file_path_item]) self.lstDocs.setModel(self._docItemModel) def _load_current_profile_templates(self): # Loads only those templates that refer to tables in the current # profile. if self._current_profile is None: return # Get saved document templates then add to the model templates = documentTemplates() profile_tables = self._current_profile.table_names() # Get templates for the current profile for name, path in templates.items(): doc_temp = DocumentTemplate.build_from_path(name, path) if doc_temp.data_source is None: continue # Assert data source is in the current profile if doc_temp.data_source.referenced_table_name in profile_tables or \ doc_temp.data_source.name() in user_non_profile_views(): self._add_doc_temp(doc_temp) # self._profile_templates.append(doc_temp) def _add_doc_temp(self, doc_temp): found = False for template in self._profile_templates: if template.name == doc_temp.name: found = True break if not found: self._profile_templates.append(doc_temp) def _template_contains_filter_table(self, document_template): # Returns true if the template refers to the filter data source # If no filter data source defined then always return True if document_template.data_source._dataSourceName in user_non_profile_views( ): return True if not self._filter_data_source: return True referenced_table = document_template.referenced_table_name if referenced_table == self._filter_data_source: return True return False @property def mode(self): return self._mode @property def filter_data_source(self): return self._filter_data_source def _createDocNameItem(self, docName): """ Create a template document standard item. """ # Set icon icon = QIcon() icon.addPixmap(GuiUtils.get_icon_pixmap("document.png"), QIcon.Normal, QIcon.Off) dnItem = QStandardItem(icon, docName) return dnItem def onEditTemplate(self): """ Slot raised to edit document template. """ self.notifBar.clear() if self.documentMapping() is None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document template to edit")) return templateName, filePath = self.documentMapping() docName, ok = QInputDialog.getText(self, \ QApplication.translate("TemplateDocumentSelector", "Edit Template"), \ QApplication.translate("TemplateDocumentSelector", "Please enter the new template name below"), \ text=templateName) if ok and docName == "": self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Template name cannot be empty")) return elif docName == templateName: return elif ok and docName != "": result, newTemplatePath = self._editTemplate(filePath, docName) if result: # Update view mIndices = self._selectedMappings() docNameItem = self._docItemModel.itemFromIndex(mIndices[0]) filePathItem = self._docItemModel.itemFromIndex(mIndices[1]) docNameItem.setText(docName) filePathItem.setText(newTemplatePath) self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \ "'{0}' template has been successfully updated".format( docName))) else: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Error: '{0}' template could not be updated".format( templateName))) def onDeleteTemplate(self): """ Slot raised to delete document template. """ self.notifBar.clear() if self.documentMapping() == None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document template to delete")) return templateName, filePath = self.documentMapping() result = QMessageBox.warning(self, QApplication.translate("TemplateDocumentSelector", \ "Confirm delete"), QApplication.translate("TemplateDocumentSelector", \ "Are you sure you want to delete '{0}' template?" \ "This action cannot be undone.\nClick Yes to proceed " \ "or No to cancel.".format(templateName)), QMessageBox.Yes | QMessageBox.No) if result == QMessageBox.No: return status = self._deleteDocument(filePath) if status: # Remove item from list using model index row number selectedDocNameIndices = self.lstDocs.selectionModel( ).selectedRows(0) row = selectedDocNameIndices[0].row() self._docItemModel.removeRow(row) self.notifBar.insertSuccessNotification(QApplication.translate("TemplateDocumentSelector", \ "'{0}' template has been successfully removed".format( templateName))) else: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Error: '{0}' template could not be removed".format( templateName))) def onAccept(self): """ Slot raised to close the dialog only when a selection has been made by the user. """ self.notifBar.clear() if self.documentMapping() == None: self.notifBar.insertErrorNotification(QApplication.translate("TemplateDocumentSelector", \ "Please select a document")) return self.accept() def _selectedMappings(self): """ Returns the model indices for the selected row. """ selectedDocNameIndices = self.lstDocs.selectionModel().selectedRows(0) selectedFilePathIndices = self.lstDocs.selectionModel().selectedRows(1) if len(selectedDocNameIndices) == 0: return None docNameIndex = selectedDocNameIndices[0] filePathIndex = selectedFilePathIndices[0] return (docNameIndex, filePathIndex) def documentMapping(self): """ Returns a tuple containing the selected document name and the corresponding file name. """ mIndices = self._selectedMappings() if mIndices == None: return None docNameItem = self._docItemModel.itemFromIndex(mIndices[0]) filePathItem = self._docItemModel.itemFromIndex(mIndices[1]) return (docNameItem.text(), filePathItem.text()) def _editTemplate(self, templatePath, newName): """ Updates the template document to use the new name. """ templateFile = QFile(templatePath) if not templateFile.open(QIODevice.ReadOnly): QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Open Operation Error"), \ "{0}\n{1}".format( QApplication.translate("TemplateDocumentSelector", "Cannot read template file."), \ templateFile.errorString() )) return (False, "") templateDoc = QDomDocument() if templateDoc.setContent(templateFile): composerElement = templateDoc.documentElement() titleAttr = composerElement.attributeNode("_title") if not titleAttr.isNull(): titleAttr.setValue(newName) # Try remove file status = templateFile.remove() if not status: return (False, "") # Create new file newTemplatePath = self._composerTemplatesPath( ) + "/" + newName + ".sdt" newTemplateFile = QFile(newTemplatePath) if not newTemplateFile.open(QIODevice.WriteOnly): QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Operation Error"), \ "{0}\n{1}".format(QApplication.translate("TemplateDocumentSelector", "Could not save template file."), \ newTemplateFile.errorString() )) return (False, "") if newTemplateFile.write(templateDoc.toByteArray()) == -1: QMessageBox.critical(self, QApplication.translate("TemplateDocumentSelector", "Save Error"), \ QApplication.translate("TemplateDocumentSelector", "Could not save template file.")) return (False, "") newTemplateFile.close() return (True, newTemplatePath) def _deleteDocument(self, templatePath): """ Delete the document template from the file system. """ docFile = QFile(templatePath) return docFile.remove() def _composerTemplatesPath(self): """ Reads the path of composer templates in the registry. """ regConfig = RegistryConfig() keyName = "ComposerTemplates" valueCollection = regConfig.read([keyName]) if len(valueCollection) == 0: return None else: return valueCollection[keyName]
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)
def __init__(self, parent=None, selectMode=True, filter_data_source='', access_templates=None): QDialog.__init__(self, parent) self.setupUi(self) self.notifBar = NotificationBar(self.vlNotification) self._mode = selectMode # Filter templates by the specified table name self._filter_data_source = filter_data_source # Document templates in current profile self._profile_templates = [] self._current_profile = current_profile() # Load current profile templates self._load_current_profile_templates() self.access_templates = access_templates or [] if selectMode: self.buttonBox.setVisible(True) self.manageButtonBox.setVisible(False) currHeight = self.size().height() self.resize(200, currHeight) else: self.buttonBox.setVisible(False) self.manageButtonBox.setVisible(True) self.setWindowTitle( QApplication.translate("TemplateDocumentSelector", "Template Manager")) # Configure manage buttons btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok) btnEdit.setText( QApplication.translate("TemplateDocumentSelector", "Edit...")) btnEdit.setIcon(GuiUtils.get_icon("edit.png")) btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save) btnDelete.setText( QApplication.translate("TemplateDocumentSelector", "Delete")) btnDelete.setIcon(GuiUtils.get_icon("delete.png")) # Connect signals self.buttonBox.accepted.connect(self.onAccept) btnEdit.clicked.connect(self.onEditTemplate) btnDelete.clicked.connect(self.onDeleteTemplate) # Get saved document templates then add to the model templates = documentTemplates() self._docItemModel = QStandardItemModel(parent) self._docItemModel.setColumnCount(2) # Append current profile templates to the model. for dt in self._profile_templates: if self._template_contains_filter_table( dt): # and dt.name in self.access_templates: doc_name_item = self._createDocNameItem(dt.name) file_path_item = QStandardItem(dt.path) self._docItemModel.appendRow([doc_name_item, file_path_item]) self.lstDocs.setModel(self._docItemModel)
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()
def __init__(self, entity, model=None, parent=None, manage_documents=True, collect_model=False, parent_entity=None, exclude_columns=[], plugin=None): """ Class constructor. :param entity: Entity object corresponding to a table object. :type entity: Entity :param model: Data object for loading data into the form widgets. If the model is set, then the editor dialog is assumed to be in edit mode. :type model: object :param parent: Parent widget that the form belongs to. :type parent: QWidget :param manage_documents: True if the dialog should provide controls for managing supporting documents. Only applicable if the entity allows for supporting documents to be attached. :type manage_documents: bool :param collect_model: If set to True only returns the filled form model without saving it to the database. :type collect_model: Boolean :param parent_entity: The parent entity of the editor :type parent_entity: Object :param exclude_columns: List of columns to be excluded if in a list. :type exclude_columns: List :return: If collect_model, returns SQLAlchemy Model """ QDialog.__init__(self, parent) self.entity_table_model = {} self.collection_suffix = self.tr('Collection') #Set minimum width self.setMinimumWidth(450) self.plugin = plugin #Flag for mandatory columns self.has_mandatory = False self.reload_form = False self._entity = entity self.edit_model = model self.column_widgets = OrderedDict() self._parent = parent self.exclude_columns = exclude_columns self.entity_tab_widget = None self._disable_collections = False self.filter_val = None self.parent_entity = parent_entity self.child_models = OrderedDict() self.entity_scroll_area = None self.entity_editor_widgets = OrderedDict() # Set notification layout bar self.vlNotification = QVBoxLayout() self.vlNotification.setObjectName('vlNotification') self._notifBar = NotificationBar(self.vlNotification) self.do_not_check_dirty = False # Set manage documents only if the entity supports documents if self._entity.supports_documents: self._manage_documents = manage_documents else: self._manage_documents = False # Setup entity model self._ent_document_model = None if self._entity.supports_documents: self.ent_model, self._ent_document_model = entity_model( self._entity, with_supporting_document=True) else: self.ent_model = entity_model(self._entity) if not model is None: self.ent_model = model MapperMixin.__init__(self, self.ent_model, entity) self.collect_model = collect_model self.register_column_widgets() if not isinstance(parent, QTabWidget): if not isinstance(parent, QMainWindow): self.current_user = parent.entity_browser.current_user try: if isinstance(parent._parent, EntityEditorDialog): # hide collections form child editor self._disable_collections = True except AttributeError: self._parent._parent = None # Set title editor_trans = self.tr('Editor') if self._entity.label is not None: if self._entity.label != '': title_str = self._entity.label else: title_str = format_name(self._entity.short_name) else: title_str = format_name(self._entity.short_name) self.title = u'{0} {1}'.format(title_str, editor_trans) self.setWindowTitle(self.title) self._init_gui() self.adjustSize() self._get_entity_editor_widgets() if isinstance(parent._parent, EntityEditorDialog): self.parent_entity = parent.parent_entity self.set_parent_values() # make the size smaller to differentiate from parent and as it # only has few tabs. self.adjustSize() self.attribute_mappers = self._attr_mapper_collection # Exception title for editor extension exceptions self._ext_exc_msg = self.tr( 'An error has occured while executing Python code in the editor ' 'extension:') # Register custom editor extension if specified self._editor_ext = entity_dlg_extension(self) if not self._editor_ext is None: self._editor_ext.post_init() # Initialize CascadingFieldContext objects self._editor_ext.connect_cf_contexts()
class OptionsDialog(QDialog, Ui_DlgOptions): """ Dialog for editing STDM settings. """ def __init__(self, iface): QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface self.notif_bar = NotificationBar(self.vlNotification, 6000) self._apply_btn = self.buttonBox.button(QDialogButtonBox.Apply) self._reg_config = RegistryConfig() self._db_config = DatabaseConfig() version = version_from_metadata() upgrade_label_text = self.label_9.text().replace('1.4', version) self.label_9.setText(upgrade_label_text) #Connect signals self._apply_btn.clicked.connect(self.apply_settings) self.buttonBox.accepted.connect(self.on_accept) self.chk_pg_connections.toggled.connect(self._on_use_pg_connections) self.cbo_pg_connections.currentIndexChanged.connect( self._on_pg_profile_changed) self.btn_db_conn_clear.clicked.connect(self.clear_properties) self.btn_test_db_connection.clicked.connect(self._on_test_connection) self.btn_supporting_docs.clicked.connect( self._on_choose_supporting_docs_path ) self.btn_template_folder.clicked.connect( self._on_choose_doc_designer_template_path ) self.btn_composer_out_folder.clicked.connect( self._on_choose_doc_generator_output_path ) self.upgradeButton.toggled.connect( self.manage_upgrade ) self._config = StdmConfiguration.instance() self._default_style_sheet = self.txtRepoLocation.styleSheet() self.manage_upgrade() self.init_gui() def init_gui(self): #Set integer validator for the port number int_validator = QIntValidator(1024, 49151) self.txtPort.setValidator(int_validator) #Load profiles self.load_profiles() #Set current profile in the combobox curr_profile = current_profile() if not curr_profile is None: setComboCurrentIndexWithText(self.cbo_profiles, curr_profile.name) #Load current database connection properties self._load_db_conn_properties() #Load existing PostgreSQL connections self._load_qgis_pg_connections() #Load directory paths self._load_directory_paths() self.edtEntityRecords.setMaximum(MAX_LIMIT) self.edtEntityRecords.setValue(get_entity_browser_record_limit()) # Debug logging lvl = debug_logging() if lvl: self.chk_logging.setCheckState(Qt.Checked) else: self.chk_logging.setCheckState(Qt.Unchecked) def load_profiles(self): """ Load existing profiles into the combobox. """ profile_names = self._config.profiles.keys() self.cbo_profiles.clear() self.cbo_profiles.addItem('') self.cbo_profiles.addItems(profile_names) def _load_db_conn_properties(self): #Load database connection properties from the registry. db_conn = self._db_config.read() if not db_conn is None: self.txtHost.setText(db_conn.Host) self.txtPort.setText(db_conn.Port) self.txtDatabase.setText(db_conn.Database) def _load_qgis_pg_connections(self): """ Load QGIS postgres connections. """ self.cbo_pg_connections.addItem('') profiles = pg_profile_names() for profile in profiles: self.cbo_pg_connections.addItem(profile[0], profile[1]) def _load_directory_paths(self): #Load paths to various directory settings. comp_out_path = composer_output_path() comp_temp_path = composer_template_path() source_doc_path = source_documents_path() if not source_doc_path is None: self.txtRepoLocation.setText(source_doc_path) if not comp_out_path is None: self.txt_output_dir.setText(comp_out_path) if not comp_temp_path is None: self.txt_template_dir.setText(comp_temp_path) def _on_use_pg_connections(self, state): #Slot raised when to (not) use existing pg connections if not state: self.cbo_pg_connections.setCurrentIndex(0) self.cbo_pg_connections.setEnabled(False) #Restore current connection in registry self._load_db_conn_properties() else: self.cbo_pg_connections.setEnabled(True) def _on_pg_profile_changed(self, index): """ Slot raised when the index of the pg profile changes. If the selection is valid then the system will attempt to extract the database connection properties of the selected profile stored in the registry. """ if index == 0: return profile_path = self.cbo_pg_connections.itemData(index) q_config = QGISRegistryConfig(profile_path) db_items = q_config.read(['Database', 'Host', 'Port']) if len(db_items) > 0: self.txtDatabase.setText(db_items['Database']) self.txtHost.setText(db_items['Host']) self.txtPort.setText(db_items['Port']) def clear_properties(self): """ Clears the host, database name and port number values from the respective controls. """ self.txtDatabase.clear() self.txtHost.clear() self.txtPort.clear() def _on_choose_supporting_docs_path(self): #Slot raised to select directory for supporting documents. self._set_selected_directory(self.txtRepoLocation, self.tr( 'Supporting Documents Directory') ) def _on_choose_doc_designer_template_path(self): #Slot raised to select directory for document designer templates. self._set_selected_directory(self.txt_template_dir, self.tr( 'Document Designer Templates Directory') ) def _on_choose_doc_generator_output_path(self): #Slot raised to select directory for doc generator outputs. self._set_selected_directory(self.txt_output_dir, self.tr( 'Document Generator Output Directory') ) def _set_selected_directory(self, txt_box, title): def_path= txt_box.text() sel_doc_path = QFileDialog.getExistingDirectory(self, title, def_path) if sel_doc_path: normalized_path = QDir.fromNativeSeparators(sel_doc_path) txt_box.clear() txt_box.setText(normalized_path) def _validate_db_props(self): #Test if all properties have been specified status = True self.notif_bar.clear() if not self.txtHost.text(): msg = self.tr('Please specify the database host address.') self.notif_bar.insertErrorNotification(msg) status = False if not self.txtPort.text(): msg = self.tr('Please specify the port number.') self.notif_bar.insertErrorNotification(msg) status = False if not self.txtDatabase.text(): msg = self.tr('Please specify the database name.') self.notif_bar.insertErrorNotification(msg) status = False return status def _database_connection(self): #Creates a databaase connection object from the specified args host = self.txtHost.text() port = self.txtPort.text() database = self.txtDatabase.text() #Create database connection object db_conn = DatabaseConnection(host, port, database) return db_conn def _on_test_connection(self): """ Slot raised to test database connection. """ status = self._validate_db_props() if not status: return login_dlg = loginDlg(self, True) db_conn = self._database_connection() login_dlg.set_database_connection(db_conn) res = login_dlg.exec_() if res == QDialog.Accepted: msg = self.tr(u"Connection to '{0}' database was " "successful.".format(db_conn.Database)) QMessageBox.information(self, self.tr('Database Connection'), msg) def set_current_profile(self): """ Saves the given profile name as the current profile. """ profile_name = self.cbo_profiles.currentText() if not profile_name: self.notif_bar.clear() msg = self.tr('Profile name is empty, current profile will not ' 'be set.') self.notif_bar.insertErrorNotification(msg) return False save_current_profile(profile_name) return True def save_database_properties(self): """ Saves the specified database connection properties to the registry. :return: True if the connection properties were successfully saved. :rtype: bool """ if not self._validate_db_props(): return False #Create a database object and write it to the registry db_conn = self._database_connection() self._db_config.write(db_conn) return True def set_supporting_documents_path(self): """ Set the directory of supporting documents. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txtRepoLocation.text() if not path: msg = self.tr('Please set the supporting documents directory.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txtRepoLocation): return False #Commit to registry self._reg_config.write({NETWORK_DOC_RESOURCE: path}) return True def set_document_templates_path(self): """ Set the directory of document designer templates. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txt_template_dir.text() if not path: msg = self.tr('Please set the document designer templates ' 'directory.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txt_template_dir): return False #Commit to registry self._reg_config.write({COMPOSER_TEMPLATE: path}) return True def set_document_output_path(self): """ Set the directory of document generator outputs. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txt_output_dir.text() if not path: msg = self.tr('Please set the document generator output directory' '.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txt_output_dir): return False #Commit to registry self._reg_config.write({COMPOSER_OUTPUT: path}) return True def _check_path_exists(self, path, text_box): #Validates if the specified folder exists dir = QDir() if not dir.exists(path): msg = self.tr(u"'{0}' directory does not exist.".format(path)) self.notif_bar.insertErrorNotification(msg) #Highlight textbox control text_box.setStyleSheet(INVALIDATESTYLESHEET) timer = QTimer(self) #Sync interval with that of the notification bar timer.setInterval(self.notif_bar.interval) timer.setSingleShot(True) #Remove previous connected slots (if any) receivers = timer.receivers(SIGNAL('timeout()')) if receivers > 0: self._timer.timeout.disconnect() timer.start() timer.timeout.connect(lambda:self._restore_stylesheet( text_box) ) return False return True def _restore_stylesheet(self, textbox): # Slot raised to restore the original stylesheet of the textbox control textbox.setStyleSheet(self._default_style_sheet) # Get reference to timer and delete sender = self.sender() if not sender is None: sender.deleteLater() def apply_debug_logging(self): # Save debug logging logger = logging.getLogger('stdm') if self.chk_logging.checkState() == Qt.Checked: logger.setLevel(logging.DEBUG) set_debug_logging(True) else: logger.setLevel(logging.ERROR) set_debug_logging(False) def apply_settings(self): """ Save settings. :return: True if the settings were successfully applied, otherwise False. :rtype: bool """ #Set current profile if not self.set_current_profile(): return False #Set db connection properties if not self.save_database_properties(): return False #Set supporting documents directory if not self.set_supporting_documents_path(): return False #Set document designer templates path if not self.set_document_templates_path(): return False #Set document generator output path if not self.set_document_output_path(): return False self.apply_debug_logging() # Set Entity browser record limit save_entity_browser_record_limit(self.edtEntityRecords.value()) msg = self.tr('Settings successfully saved.') self.notif_bar.insertSuccessNotification(msg) return True def on_accept(self): """ Slot raised to save the settings of the current widget and close the widget. """ if not self.apply_settings(): return self.accept() def manage_upgrade(self): """ A slot raised when the upgrade button is clicked. It disables or enables the upgrade button based on the ConfigUpdated registry value. """ self.config_updated_dic = self._reg_config.read( [CONFIG_UPDATED] ) # if config file exists, check if registry key exists if len(self.config_updated_dic) > 0: config_updated_val = self.config_updated_dic[ CONFIG_UPDATED ] # If failed to upgrade, enable the upgrade button if config_updated_val == '0' or config_updated_val == '-1': self.upgradeButton.setEnabled(True) # disable the button if any other value. else: self.upgradeButton.setEnabled(False) else: self.upgradeButton.setEnabled(False)
class MultipleEnumerationDialog(TranslatorDialogBase, WIDGET, BASE): """ Dialog for defining configuration settings for the MultipleEnumerationTranslator class implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col) self._notif_bar = NotificationBar(self.vl_notification) # Container of column names for an enumeration table self._enum_col_names = [] self._load_separators() # Init user selection to the corresponding UI controls self.txt_source_col.setText(self._src_col) def _load_separators(self): separators = [] comma_sep = (QApplication.translate("MultipleEnumerationDialog", "Comma (,)"), ",") separators.append(comma_sep) colon_sep = (QApplication.translate("MultipleEnumerationDialog", "Colon (:)"), ":") separators.append(colon_sep) semi_colon_sep = (QApplication.translate("MultipleEnumerationDialog", "Semi-colon (;)"), ";") separators.append(semi_colon_sep) asterisk_sep = (QApplication.translate("MultipleEnumerationDialog", "Asterisk (*)"), "*") separators.append(asterisk_sep) self.cbo_separator.addItem("") for sep in separators: self.cbo_separator.addItem(sep[0], sep[1]) def validate(self): """ :return: Check user entries. :rtype: bool """ if not self.txt_source_col.text(): msg = QApplication.translate("MultipleEnumerationDialog", "Source column does not exist.") self._notif_bar.clear() self._notif_bar.insertErrorNotification(msg) return False if not self.cbo_separator.currentText(): msg = QApplication.translate( "MultipleEnumerationDialog", "Please specify a separator for the multiple select data.") self._notif_bar.clear() self._notif_bar.insertErrorNotification(msg) return False return True def separator(self): sep_idx = self.cbo_separator.currentIndex() if sep_idx == 0: return "" else: return self.cbo_separator.itemData(sep_idx) def column_pairings(self): """ Format of dict - source column name: matching enumeration table column. Primary enum column is the first item in the dictionary. """ col_pairs = OrderedDict() col_pairs[self.txt_source_col.text()] = self._dest_col return col_pairs def value_translator(self): enum_translator = MultipleEnumerationTranslator() enum_translator.set_referencing_table(self._dest_table) enum_translator.set_referencing_column(self._dest_col) enum_translator.set_input_referenced_columns(self.column_pairings()) enum_translator.set_separator(self.separator()) return enum_translator def accept(self): """ Validate before accepting user input. """ if self.validate(): super(MultipleEnumerationDialog, self).accept()
class SourceDocumentTranslatorDialog(QDialog, Ui_SourceDocumentTranslatorDialog, TranslatorDialogBase): """ Dialog for defining configuration settings for the SourceDocumentTranslator implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__( self, source_cols, dest_table, dest_col, src_col ) self._notif_bar = NotificationBar(self.vlNotification) #Assert if the entity supports documents self._assert_entity_supports_documents() #Set the source document directory self.source_document_directory = None #Document type name self._document_type_name = self._dest_col #Document type ID self._document_type_id = None #Set document type ID self._set_document_type_id() #Connect slots self.btn_source_doc_folder.clicked.connect( self._load_source_document_directory_selector ) @property def document_type_id(self): """ :return: Returns source document type id. Otherwise None if not set. :rtype: int """ return self._document_type_id @property def document_type(self): """ :return: Returns the name of the selected document type. :rtype: str """ return self._document_type_name @property def documents_directory(self): """ :return: Returns the specified root directory for supporting documents. Otherwise None if not specified by the user. :rtype: str """ return self.txtRootFolder.text() def _assert_entity_supports_documents(self): #Check if the entity supports documents and close automatically if not. entity = self.entity() if entity is None: msg = self.tr('Invalid table object') raise RuntimeError(msg) if not entity.supports_documents: msg = self.tr('The selected destination table does not support ' 'documents.\nHence, this translator is not ' 'applicable.') raise RuntimeError(msg) def _set_document_type_id(self): #Load document id based on the name entity = self.entity() if entity is None: return vl_cls = entity_model( entity.supporting_doc.document_type_entity, entity_only=True ) vl_obj = vl_cls() res = vl_obj.queryObject().all() for r in res: if r.value == self._dest_col: self._document_type_id = r.id break if not entity.supports_documents: msg = self.tr('The selected column does not correspond to a ' 'document type.\nHence, this translator is not ' 'applicable.') title = self.tr('Invalid Document Type') QMessageBox.critical(self, title, msg) #Close dialog self.reject() def _load_source_document_directory_selector(self): #Load file dialog for selecting source documents directory title = self.tr('Select Source Document Directory') def_path = self.txtRootFolder.text() #Use the last set source document directory if not def_path: def_path = last_document_path() sel_doc_path = QFileDialog.getExistingDirectory(self, title, def_path) if sel_doc_path: normalized_path = QDir.fromNativeSeparators(sel_doc_path) self.txtRootFolder.clear() self.txtRootFolder.setText(normalized_path) def value_translator(self): source_doc_translator = SourceDocumentTranslator() source_doc_translator.set_referencing_table(self._dest_table) source_doc_translator.set_referencing_column(self._dest_col) #Just use the source column for getting the relative image path # and name source_doc_translator.add_source_reference_column( self._src_col, self._dest_col ) source_doc_translator.entity = self.entity() source_doc_translator.document_type_id = self._document_type_id source_doc_translator.document_type = self._document_type_name source_doc_translator.source_directory = self.documents_directory return source_doc_translator def validate(self): """ :return: Return True if the source document directory exists, otherwise False. :rtype: bool """ source_doc_path = self.txtRootFolder.text() #Clear previous notifications self._notif_bar.clear() if not source_doc_path: msg = self.tr( 'Please set the root directory of source documents.' ) self._notif_bar.insertErrorNotification(msg) return False dir = QDir() if not dir.exists(source_doc_path): msg = self.tr(u"'{0}' directory does not exist.".format( source_doc_path)) self._notif_bar.insertErrorNotification(msg) return False return True def accept(self): """ Validate before accepting user input. """ if self.validate(): super(SourceDocumentTranslatorDialog, self).accept()
class LookupDialog(QDialog, Ui_LookupTranslatorDialog, TranslatorDialogBase): """ Dialog for defining configuration settings for the lookup translation implementation. """ def __init__(self, parent, source_cols, dest_table, dest_col, src_col): QDialog.__init__(self, parent) self.setupUi(self) TranslatorDialogBase.__init__(self, source_cols, dest_table, dest_col, src_col) self._notif_bar = NotificationBar(self.vl_notification) # Populate controls self._load_lookup_tables() # Connect signals self.cbo_lookup.currentIndexChanged.connect( self._on_lookup_table_name_changed) def _load_lookup_tables(self): # Load lookup table names c_profile = self._current_profile if c_profile is None: msg = QApplication.translate( 'LookupDialog', 'Current profile could not be determined.') self._notif_bar.clear() self._notif_bar.insertErrorNotification(msg) return lookups = [e.name for e in c_profile.value_lists()] self.cbo_lookup.clear() self.cbo_lookup.addItem('') self.cbo_lookup.addItems(lookups) def _on_lookup_table_name_changed(self, idx): # Slot raised when the lookup table name changes. self.cbo_default.clear() if idx == -1: return t_name = self.cbo_lookup.currentText() self.load_lookup_values(t_name) def load_lookup_values(self, table_name): """ Load the default value combobox with values from the specified lookup table name. :param table_name: Lookup table name. :type table_name: str """ self.cbo_default.clear() if not table_name: return # Get lookup entity lk_ent = self._current_profile.entity_by_name(table_name) if lk_ent is None: msg = QApplication.translate('LookupDialog', 'Lookup values could not be loaded.') self._notif_bar.clear() self._notif_bar.insertErrorNotification(msg) return lk_values = lk_ent.lookups() self.cbo_default.addItem('') for lk_value in lk_values: vt = unicode(lk_value) text_value = lk_ent.values[vt] self.cbo_default.addItem(text_value.value) def value_translator(self): """ :return: Returns the lookup value translator object. :rtype: LookupValueTranslator """ lookup_translator = LookupValueTranslator() lookup_translator.set_referencing_table(self._dest_table) lookup_translator.set_referencing_column(self._dest_col) lookup_translator.set_referenced_table(self.cbo_lookup.currentText()) lookup_translator.add_source_reference_column(self._src_col, self._dest_col) lookup_translator.default_value = self.cbo_default.currentText() return lookup_translator def validate(self): """ Check user configuration and validate if they are correct. :return: Returns True if user configuration is correct, otherwise False. :rtype: bool """ if not self.cbo_lookup.currentText(): msg = QApplication.translate( 'LookupDialog', 'Please select the referenced lookup table.') self._notif_bar.clear() self._notif_bar.insertWarningNotification(msg) return False return True def accept(self): """ Validate before accepting user input. """ if self.validate(): super(LookupDialog, self).accept()
class OptionsDialog(QDialog, Ui_DlgOptions): """ Dialog for editing STDM settings. """ def __init__(self, iface): QDialog.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface self.notif_bar = NotificationBar(self.vlNotification, 6000) self._apply_btn = self.buttonBox.button(QDialogButtonBox.Apply) self._reg_config = RegistryConfig() self._db_config = DatabaseConfig() version = version_from_metadata() upgrade_label_text = self.label_9.text().replace('1.4', version) self.label_9.setText(upgrade_label_text) #Connect signals self._apply_btn.clicked.connect(self.apply_settings) self.buttonBox.accepted.connect(self.on_accept) self.chk_pg_connections.toggled.connect(self._on_use_pg_connections) self.cbo_pg_connections.currentIndexChanged.connect( self._on_pg_profile_changed) self.btn_db_conn_clear.clicked.connect(self.clear_properties) self.btn_test_db_connection.clicked.connect(self._on_test_connection) self.btn_supporting_docs.clicked.connect( self._on_choose_supporting_docs_path) self.btn_template_folder.clicked.connect( self._on_choose_doc_designer_template_path) self.btn_composer_out_folder.clicked.connect( self._on_choose_doc_generator_output_path) self.upgradeButton.toggled.connect(self.manage_upgrade) self._config = StdmConfiguration.instance() self._default_style_sheet = self.txtRepoLocation.styleSheet() self.manage_upgrade() self.init_gui() def init_gui(self): #Set integer validator for the port number int_validator = QIntValidator(1024, 49151) self.txtPort.setValidator(int_validator) #Load profiles self.load_profiles() #Set current profile in the combobox curr_profile = current_profile() if not curr_profile is None: setComboCurrentIndexWithText(self.cbo_profiles, curr_profile.name) #Load current database connection properties self._load_db_conn_properties() #Load existing PostgreSQL connections self._load_qgis_pg_connections() #Load directory paths self._load_directory_paths() self.edtEntityRecords.setMaximum(MAX_LIMIT) self.edtEntityRecords.setValue(get_entity_browser_record_limit()) # Debug logging lvl = debug_logging() if lvl: self.chk_logging.setCheckState(Qt.Checked) else: self.chk_logging.setCheckState(Qt.Unchecked) def load_profiles(self): """ Load existing profiles into the combobox. """ profile_names = self._config.profiles.keys() self.cbo_profiles.clear() self.cbo_profiles.addItem('') self.cbo_profiles.addItems(profile_names) def _load_db_conn_properties(self): #Load database connection properties from the registry. db_conn = self._db_config.read() if not db_conn is None: self.txtHost.setText(db_conn.Host) self.txtPort.setText(db_conn.Port) self.txtDatabase.setText(db_conn.Database) def _load_qgis_pg_connections(self): """ Load QGIS postgres connections. """ self.cbo_pg_connections.addItem('') profiles = pg_profile_names() for profile in profiles: self.cbo_pg_connections.addItem(profile[0], profile[1]) def _load_directory_paths(self): #Load paths to various directory settings. comp_out_path = composer_output_path() comp_temp_path = composer_template_path() source_doc_path = source_documents_path() if not source_doc_path is None: self.txtRepoLocation.setText(source_doc_path) if not comp_out_path is None: self.txt_output_dir.setText(comp_out_path) if not comp_temp_path is None: self.txt_template_dir.setText(comp_temp_path) def _on_use_pg_connections(self, state): #Slot raised when to (not) use existing pg connections if not state: self.cbo_pg_connections.setCurrentIndex(0) self.cbo_pg_connections.setEnabled(False) #Restore current connection in registry self._load_db_conn_properties() else: self.cbo_pg_connections.setEnabled(True) def _on_pg_profile_changed(self, index): """ Slot raised when the index of the pg profile changes. If the selection is valid then the system will attempt to extract the database connection properties of the selected profile stored in the registry. """ if index == 0: return profile_path = self.cbo_pg_connections.itemData(index) q_config = QGISRegistryConfig(profile_path) db_items = q_config.read(['Database', 'Host', 'Port']) if len(db_items) > 0: self.txtDatabase.setText(db_items['Database']) self.txtHost.setText(db_items['Host']) self.txtPort.setText(db_items['Port']) def clear_properties(self): """ Clears the host, database name and port number values from the respective controls. """ self.txtDatabase.clear() self.txtHost.clear() self.txtPort.clear() def _on_choose_supporting_docs_path(self): #Slot raised to select directory for supporting documents. self._set_selected_directory(self.txtRepoLocation, self.tr('Supporting Documents Directory')) def _on_choose_doc_designer_template_path(self): #Slot raised to select directory for document designer templates. self._set_selected_directory( self.txt_template_dir, self.tr('Document Designer Templates Directory')) def _on_choose_doc_generator_output_path(self): #Slot raised to select directory for doc generator outputs. self._set_selected_directory( self.txt_output_dir, self.tr('Document Generator Output Directory')) def _set_selected_directory(self, txt_box, title): def_path = txt_box.text() sel_doc_path = QFileDialog.getExistingDirectory(self, title, def_path) if sel_doc_path: normalized_path = QDir.fromNativeSeparators(sel_doc_path) txt_box.clear() txt_box.setText(normalized_path) def _validate_db_props(self): #Test if all properties have been specified status = True self.notif_bar.clear() if not self.txtHost.text(): msg = self.tr('Please specify the database host address.') self.notif_bar.insertErrorNotification(msg) status = False if not self.txtPort.text(): msg = self.tr('Please specify the port number.') self.notif_bar.insertErrorNotification(msg) status = False if not self.txtDatabase.text(): msg = self.tr('Please specify the database name.') self.notif_bar.insertErrorNotification(msg) status = False return status def _database_connection(self): #Creates a databaase connection object from the specified args host = self.txtHost.text() port = self.txtPort.text() database = self.txtDatabase.text() #Create database connection object db_conn = DatabaseConnection(host, port, database) return db_conn def _on_test_connection(self): """ Slot raised to test database connection. """ status = self._validate_db_props() if not status: return login_dlg = loginDlg(self, True) db_conn = self._database_connection() login_dlg.set_database_connection(db_conn) res = login_dlg.exec_() if res == QDialog.Accepted: msg = self.tr(u"Connection to '{0}' database was " "successful.".format(db_conn.Database)) QMessageBox.information(self, self.tr('Database Connection'), msg) def set_current_profile(self): """ Saves the given profile name as the current profile. """ profile_name = self.cbo_profiles.currentText() if not profile_name: self.notif_bar.clear() msg = self.tr('Profile name is empty, current profile will not ' 'be set.') self.notif_bar.insertErrorNotification(msg) return False save_current_profile(profile_name) return True def save_database_properties(self): """ Saves the specified database connection properties to the registry. :return: True if the connection properties were successfully saved. :rtype: bool """ if not self._validate_db_props(): return False #Create a database object and write it to the registry db_conn = self._database_connection() self._db_config.write(db_conn) return True def set_supporting_documents_path(self): """ Set the directory of supporting documents. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txtRepoLocation.text() if not path: msg = self.tr('Please set the supporting documents directory.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txtRepoLocation): return False #Commit to registry self._reg_config.write({NETWORK_DOC_RESOURCE: path}) return True def set_document_templates_path(self): """ Set the directory of document designer templates. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txt_template_dir.text() if not path: msg = self.tr('Please set the document designer templates ' 'directory.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txt_template_dir): return False #Commit to registry self._reg_config.write({COMPOSER_TEMPLATE: path}) return True def set_document_output_path(self): """ Set the directory of document generator outputs. :return: True if the directory was set in the registry, otherwise False. :rtype: bool """ path = self.txt_output_dir.text() if not path: msg = self.tr('Please set the document generator output directory' '.') self.notif_bar.insertErrorNotification(msg) return False #Validate path if not self._check_path_exists(path, self.txt_output_dir): return False #Commit to registry self._reg_config.write({COMPOSER_OUTPUT: path}) return True def _check_path_exists(self, path, text_box): #Validates if the specified folder exists dir = QDir() if not dir.exists(path): msg = self.tr(u"'{0}' directory does not exist.".format(path)) self.notif_bar.insertErrorNotification(msg) #Highlight textbox control text_box.setStyleSheet(INVALIDATESTYLESHEET) timer = QTimer(self) #Sync interval with that of the notification bar timer.setInterval(self.notif_bar.interval) timer.setSingleShot(True) #Remove previous connected slots (if any) receivers = timer.receivers(SIGNAL('timeout()')) if receivers > 0: self._timer.timeout.disconnect() timer.start() timer.timeout.connect(lambda: self._restore_stylesheet(text_box)) return False return True def _restore_stylesheet(self, textbox): # Slot raised to restore the original stylesheet of the textbox control textbox.setStyleSheet(self._default_style_sheet) # Get reference to timer and delete sender = self.sender() if not sender is None: sender.deleteLater() def apply_debug_logging(self): # Save debug logging logger = logging.getLogger('stdm') if self.chk_logging.checkState() == Qt.Checked: logger.setLevel(logging.DEBUG) set_debug_logging(True) else: logger.setLevel(logging.ERROR) set_debug_logging(False) def apply_settings(self): """ Save settings. :return: True if the settings were successfully applied, otherwise False. :rtype: bool """ #Set current profile if not self.set_current_profile(): return False #Set db connection properties if not self.save_database_properties(): return False #Set supporting documents directory if not self.set_supporting_documents_path(): return False #Set document designer templates path if not self.set_document_templates_path(): return False #Set document generator output path if not self.set_document_output_path(): return False self.apply_debug_logging() # Set Entity browser record limit save_entity_browser_record_limit(self.edtEntityRecords.value()) msg = self.tr('Settings successfully saved.') self.notif_bar.insertSuccessNotification(msg) return True def on_accept(self): """ Slot raised to save the settings of the current widget and close the widget. """ if not self.apply_settings(): return self.accept() def manage_upgrade(self): """ A slot raised when the upgrade button is clicked. It disables or enables the upgrade button based on the ConfigUpdated registry value. """ self.config_updated_dic = self._reg_config.read([CONFIG_UPDATED]) # if config file exists, check if registry key exists if len(self.config_updated_dic) > 0: config_updated_val = self.config_updated_dic[CONFIG_UPDATED] # If failed to upgrade, enable the upgrade button if config_updated_val == '0' or config_updated_val == '-1': self.upgradeButton.setEnabled(True) # disable the button if any other value. else: self.upgradeButton.setEnabled(False) else: self.upgradeButton.setEnabled(False)
class 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)
def __init__(self, profile, tenure_custom_entities, parent=None, editable=True, exclude_columns=None): """ Class constructor. :param profile: Profile object. :type profile: Profile :param tenure_custom_entities: Collection of tenure types and corresponding custom attribute entities. :type tenure_custom_entities: OrderedDict :param proxy_name: Name of the entity which will be used to create a proxy table.The attributes of the entity can then be copied to the target entity which shares the same name as the proxy entity. :type proxy_name: str :param parent: Parent object. :type parent: QWidget :param editable: True if the attributes can be edited, otherwise False. :type editable: bool :param exclude_columns: List of column names to exclude. :type exclude_columns: list """ super(TenureCustomAttributesEditor, self).__init__(parent) self.setupUi(self) self.btnAddColumn.setIcon(GuiUtils.get_icon('add.png')) self.btnEditColumn.setIcon(GuiUtils.get_icon('edit.png')) self.btnDeleteColumn.setIcon(GuiUtils.get_icon('delete.png')) self._notifBar = NotificationBar(self.vlNotification) self._profile = profile self._exclude_names = exclude_columns if self._exclude_names is None: self._exclude_names = [] # Attributes for each tenure type # Tenure type: list of attributes self._tenure_custom_entities = tenure_custom_entities # Populate attributes minus excluded columns self._tenure_custom_attrs = {} for tt, custom_ent in list(self._tenure_custom_entities.items()): attrs = list(custom_ent.columns.values()) self._tenure_custom_attrs[tt] = [ a for a in attrs if a.name not in self._exclude_names ] # Column types to omit self._exc_cols = ['GEOMETRY', 'FOREIGN_KEY'] # Connect signals self.btnAddColumn.clicked.connect(self.on_new_column) self.btnEditColumn.clicked.connect(self.on_edit_column) self.btnDeleteColumn.clicked.connect(self.on_delete_column) self._editable = editable # if not editable: # self._disable_editing() # Update excluded columns self._update_excluded_columns() # Load tenure types self._load_tenure_types() # Connect tenure type signal self.cbo_tenure_type.currentIndexChanged.connect( self._on_tenure_type_changed) # Load attributes for current tenure type if self.cbo_tenure_type.count() > 0: c_tenure_type = self.cbo_tenure_type.currentText() self._tenure_custom_attrs_entity(c_tenure_type)
class ProfileInstanceRecords(QDialog, FORM_CLASS): """ class constructor The class handles all the instances that the user has collected and saved in the folder and saved in a computer. The class will construct the path to the folder and enumerate all available instances and return the count. It will also rename all the file based on instance unique GUUID for easier management and future updates. """ def __init__(self, parent=None): """ initailize class variables here """ super(ProfileInstanceRecords, self).__init__(parent) self.setupUi(self) self.path = None self.instance_list = [] self.relations = OrderedDict() self.parent_ids = {} self.importlogger = ImportLogger() self._notif_bar_str = NotificationBar(self.vlnotification) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.uuid_extractor = InstanceUUIDExtractor(self.path) self.btn_chang_dir.setIcon(QIcon(":/plugins/stdm/images/icons/open_file.png")) self.btn_refresh.setIcon(QIcon(":/plugins/stdm/images/icons/update.png")) self.chk_all.stateChanged.connect(self.change_check_state) #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed) self.btn_chang_dir.clicked.connect(self.on_directory_search) self.lst_widget.itemClicked.connect(self.user_selected_entities) self.btn_refresh.clicked.connect(self.update_files_with_custom_filter) self.buttonBox.button(QDialogButtonBox.Save).setText('Import') #self.load_config() self.init_file_path() self.current_profile_changed() self.change_check_state(self.chk_all.checkState()) self.instance_dir() def load_config(self): """ Load STDM configuration :return: """ stdm_config = None if QFile.exists(HOME+"/stdm/configuration.stc"): stdm_config = QFile(CONFIG_FILE) ConfigurationFileSerializer(stdm_config) profiles = StdmConfiguration.instance().profiles return profiles def change_check_state(self, state): """ Change the check state of items in a list widget """ for i in range(self.lst_widget.count()): self.lst_widget.item(i).setCheckState(state) def profiles(self): """ Return a list of all profiles :rtype: list """ return self.load_config().values() def current_profile_changed(self): """ Get the current profile so that it is the one selected at the combo box :return: """ self.instance_list = [] self.active_profile() self.init_file_path() self.available_records() self.on_dir_path() self.populate_entities_widget() def active_profile(self): """ get the user selected profile :return:p """ self.profile = current_profile().name return self.profile def instance_dir(self): """ Create a path where imported instance will be kept :return: """ self.inst_path = CONFIG_FILE+"_imported" if not os.access(self.inst_path, os.F_OK): os.makedirs(unicode(self.inst_path)) else: return self.inst_path def imported_instance_path(self): """ :return: """ self.instance_dir() return self.inst_path def init_file_path(self): """ Initialize GeoODK file path """ self.path = self.geoODK_file_path(self.txt_directory.text()) self.txt_directory.setText(self.path) def geoODK_file_path(self, path=''): """ Check if geoODK file path has been configured, if not configure default and return it. :rtype: string """ if not path.strip(): path = self.make_path(GEOODK_FORM_HOME) return path def make_path(self, path): """ Create and return a file path if is not available. :rtype: string """ if not os.access(path, os.F_OK): os.makedirs(unicode(path)) return path def xform_xpaths(self): """ Return the full path to the default config path and filter geoodk instance that matches the current profile path :return: directories :rtype: list """ return [os.path.join(self.path, name) for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater())] def on_dir_path(self): """ Extract the specific folder information and rename the file :return: """ self.uuid_extractor.new_list = [] if self.record_count() > 0: directories = self.xform_xpaths() for directory in directories: self.extract_guuid_and_rename_file(directory) inst_count = len(self.instance_list) rm_count = self.remove_imported_instances() diff = inst_count - rm_count self.txt_count.setText(unicode(diff)) def extract_guuid_and_rename_file(self, path): """ Extract the unique Guuid and rename the file so that we can uniquely identify each file :return: """ for f in os.listdir(path): if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'): file_instance = os.path.join(path, f) self.rename_file_to_UUID(file_instance) def read_instance_data(self): """Read all instance data once and store them in a dict :rtype: dict """ mobile_data = OrderedDict() social_tenure_info = OrderedDict() for instance in self.instance_list: self.uuid_extractor.set_file_path(instance) field_data_nodes = self.uuid_extractor.document_entities_with_data(current_profile().name.replace(' ', '_'), self.user_selected_entities()) str_data_nodes = self.uuid_extractor.document_entities_with_data(current_profile().name.replace(' ', '_'), ['social_tenure']) mobile_data[instance] = [field_data_nodes, str_data_nodes] self.uuid_extractor.close_document() return mobile_data def rename_file_to_UUID(self, file): """ Extract the UUID from each folder and file :return: """ self.uuid_extractor.set_file_path(file) self.uuid_extractor.on_file_passed() self.instance_list = self.uuid_extractor.new_list def move_imported_file(self, file): """ Moves the imported files to avoid repetition :return: """ instance_path = self.imported_instance_path() try: basename = os.path.basename(os.path.dirname(file)) if not os.path.isdir(os.path.join(self.imported_instance_path(), basename)): shutil.move(os.path.dirname(file), instance_path) except Exception as ex: return ex def populate_entities_widget(self): """ Add entities in the instance file into a list view widget """ self.lst_widget.clear() entities = self.instance_entities() if len(entities) > 0: for entity in entities: list_widget = QListWidgetItem( current_profile().entity_by_name(entity).short_name, self.lst_widget) list_widget.setCheckState(Qt.Checked) def user_selected_entities(self): """ :rtype: list """ entities= [] count = self.lst_widget.count() if count > 0: for i in range(count): item = self.lst_widget.item(i) if item.checkState() == Qt.Checked: entities.append(current_profile().entity(item.text()).name) return entities def instance_entities(self): """ Enumerate the entities that are in the current profile and also that are captured in the form so that we are only importing relevant entities to database :return: entities """ current_entities = [] entity_collections = [] instance_collections = self.instance_collection() if len(instance_collections) > 0: for entity_name in self.profile_entities_names(current_profile()): if current_profile().entity_by_name(entity_name) is not None: current_entities.append(entity_name) return current_entities def instance_collection(self): """ Enumerate all the instances found in the instance directory rtype: list """ dirs = self.xform_xpaths() instance_collections = [] if len(dirs) > 0: for dir_f in dirs: xml_files = [dir_f.replace("\\", "/")+'/'+f for f in os.listdir(dir_f) if f.endswith('.xml')] if len(xml_files)>0: instance_collections.append(xml_files[0]) return instance_collections def check_profile_with_custom_name(self): """ Try extract mobile instance with custom filter name. Assumption is that there is a profile that bears that name :return: """ mismatch_profile = 'Nothing found to import. \n' \ ' Ensure the current filter text or profile is correct' entity_attr = [] if self.txt_filter.text()!= '': for obj in self.profiles(): if obj.name.startswith(self.txt_filter.text()): if obj.name != current_profile().name: self._notif_bar_str.insertErrorNotification(mismatch_profile) return return self.uuid_extractor.document_entities(self.profile) def profile_entities_names(self, profile): """ Return names of all entities in a profile :rtype: list """ entities_names = [] for entity in profile.user_entities(): entities_names.append(entity.name) return entities_names def has_foreign_keys_parent(self, select_entities): """ Ensure we check that the table is not parent else import parent table first Revised in version 1.7. It explicitly assumes str is captured. before it was optional. :return: """ has_relations = False str_tables = current_profile().social_tenure party_tbls = str_tables.parties sp_tbls = str_tables.spatial_units self.relations = OrderedDict() if len(self.instance_list) > 0: if self.uuid_extractor.has_str_captured_in_instance(self.instance_list[0]): for party_tbl in party_tbls: self.relations[party_tbl.name] = ['social_tenure_relationship', party_tbl.short_name.lower() + '_id'] for sp_tbl in sp_tbls: self.relations[sp_tbl.name] = ['social_tenure_relationship', sp_tbl.short_name.lower() + '_id'] # print self.relations for table in select_entities: table_object = current_profile().entity_by_name(table) cols = table_object.columns.values() for col in cols: if col.TYPE_INFO == 'FOREIGN_KEY': parent_object = table_object.columns[col.name] if parent_object.parent: if parent_object.parent.name in self.relations: self.relations[parent_object.parent.name].append([table, col.name]) else: self.relations[parent_object.parent.name] = [table, col.name] #self.relations[parent_object.parent.name].append([table, col.name]) has_relations = True else: continue return has_relations def parent_table_isselected(self): """ Take note that the user selected tables may or may not be imported based on parent child table relationship. Add those table silently so that we can show them to the user :return: """ try: silent_list = [] entities = self.user_selected_entities() if len(entities) > 0: for table in self.relations.keys(): if table not in entities: silent_list.append(table) return silent_list except Exception as ex: self._notif_bar_str.insertErrorNotification(ex.message) def archive_this_import_file(self, counter, instance): """ Ensure that only import are done once :return: """ try: self.importlogger.logger_sections() file_info = 'File instance ' + str(counter)+ ' : \n' + instance self.importlogger.log_action(file_info) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message) pass def log_table_entry(self, instance): """ Ensure that only import are done once :return: """ try: current_time = QDateTime() import_time = current_time.currentDateTime() log_entry = instance + ' '+ str(import_time.toPyDateTime()) self.importlogger.log_action(log_entry) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message) pass def check_previous_import(self): """ Ensure we are importing files once :return: """ try: self.importlogger.add_log_info() for files in self.instance_list: current_dir = os.path.basename(files) exist = self.importlogger.check_file_exist(current_dir) if exist: self.instance_list.remove(files) self.txt_count.setText(str(len(self.instance_list))) if self.record_count() != len(self.instance_list): msg = 'Some files have been already imported and therefore ' \ 'not enumerated' self._notif_bar_str.insertErrorNotification(msg) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": "+io.message) pass def available_records(self): """ Let the user know how many records have been collected and are available for inport process :return: """ self.txt_count.setText(unicode(self.record_count())) def record_count(self): """ get the count of instance dir in the selected directory :return: integer """ return len([name for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater())]) def profile_formater(self): """ Format the profile name by removing underscore character :return: """ if self.txt_filter.text() != '': filter_text = self.txt_filter.text() return filter_text else: return self.profile def update_files_with_custom_filter(self): """ Get the new file count with the user custom filter text :return: file count """ self.available_records() self.on_dir_path() self.populate_entities_widget() def projection_settings(self): """ let user select the projections for the data :return: """ project_select = ProjectionSelector(self) projection = project_select.loadAvailableSystems() self.txt_srid.setText(str(projection)) def on_projection_select(self): """ Get the selected projection and set it during data import :return: """ vals = self.txt_srid.text().split(":") return vals[1] def on_directory_search(self): """ Let the user choose the directory with instances :return: """ home_path = 'home' if self.txt_directory.text() != '': home_path = self.txt_directory.text() dir_name = QFileDialog.getExistingDirectory( self, 'Open Directory', home_path, QFileDialog.ShowDirsOnly ) if dir_name: self.txt_directory.setText(str(dir_name)) self.current_profile_changed() self.change_check_state(self.chk_all.checkState()) def feedback_message(self, msg): """ Create a dialog box to capture and display errrors related to db while importing data :param: msg :type: string :return:Qdialog """ msgbox = QMessageBox() msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No) msgbox.setWindowTitle("Data Import") msgbox.setText(msg) msgbox.exec_() msgbox.show() return msgbox def save_instance_data_to_db(self, entities): """ Get the user selected entities and insert them into database params: selected entities rtype: list :return:Object :type: dbObject """ cu_obj = '' import_status = False self.txt_feedback.clear() self.txt_feedback.append("Import started, please wait...\n") QCoreApplication.processEvents() self._notif_bar_str.clear() mobile_field_data = self.read_instance_data() self.has_foreign_keys_parent(entities) #print self.relations if len(self.parent_table_isselected()) > 0: if QMessageBox.information(self, QApplication.translate('GeoODKMobileSettings', " Import Warning"), QApplication.translate('GeoODKMobileSettings', 'Some of dependent tables (entities)' 'which may not be part of the selected tables ' 'I.e: {} will be imported' .format(self.parent_table_isselected())), QMessageBox.Ok | QMessageBox.No) == QMessageBox.No: return try: counter = 0 if len(mobile_field_data) > 0: self.pgbar.setRange(counter, len(self.instance_list)) self.pgbar.setValue(0) self.importlogger.log_action("Import started ...\n") for instance_obj, instance_obj_data in mobile_field_data.iteritems(): self.importlogger.log_action("File {} ...\n".format(instance_obj)) parents_info = [] import_status = False counter = counter + 1 self.parent_ids = {} single_occuring, repeated_entities = self.uuid_extractor.attribute_data_from_nodelist( instance_obj_data[0]) for entity, entity_data in single_occuring.iteritems(): import_status = False if entity in self.relations.keys(): if entity in self.parent_ids: continue self.count_import_file_step(counter, entity) log_timestamp = '=== parent table import === : {0}'.format(entity) cu_obj = entity self.log_table_entry(log_timestamp) entity_add = Save2DB(entity, entity_data) entity_add.objects_from_supporting_doc(instance_obj) ref_id = entity_add.save_parent_to_db() import_status = True self.parent_ids[entity] = [ref_id, entity] #log_timestamp = ' --- import succeeded: {0}' .format(str(import_status)) #self.log_table_entry(log_timestamp) parents_info.append(entity) single_occuring.pop(entity) elif entity not in self.relations.keys(): import_status = False for fk_table_name in self.relations.keys(): if fk_table_name not in self.parent_ids: in_relations = [_item for subitem in self.relations[fk_table_name] for _item in subitem] if entity in in_relations: fk_table_data = single_occuring[fk_table_name] entity_add = Save2DB(fk_table_name, fk_table_data) ref_id = entity_add.save_parent_to_db() self.parent_ids[fk_table_name] = [ref_id, fk_table_name] continue self.count_import_file_step(counter, entity) log_timestamp = '=== standalone table import === : {0}'.format(entity) cu_obj = entity self.log_table_entry(log_timestamp) entity_add = Save2DB(entity, entity_data, self.parent_ids) entity_add.objects_from_supporting_doc(instance_obj) child_id = entity_add.save_to_db() cu_obj = entity import_status = True parents_info.append(entity) if entity in self.parent_ids: continue else: self.parent_ids[entity] = [child_id, entity] entity_add.cleanup() if repeated_entities: #self.log_table_entry(" ========== starting import of repeated tables ============") import_status = False for repeated_entity, entity_data in repeated_entities.iteritems(): """We are assuming that the number of repeat table cannot exceed 99""" enum_index = repeated_entity[:2] if enum_index.isdigit(): repeat_table = repeated_entity[2:] else: enum_index = repeated_entity[:1] repeat_table = repeated_entity[1:] log_timestamp = ' child table {0} >> : {1}' \ .format(repeat_table, enum_index) self.count_import_file_step(counter, repeat_table) self.importlogger.log_action(log_timestamp) if repeat_table in self.profile_entities_names(current_profile()): entity_add = Save2DB(repeat_table, entity_data, self.parent_ids) entity_add.objects_from_supporting_doc(instance_obj) child_id = entity_add.save_to_db() cu_obj = repeat_table import_status = True self.log_table_entry(" ------ import succeeded: {0} ".format(import_status)) entity_add.cleanup() else: continue if instance_obj_data[1]: '''We treat social tenure entities separately because of foreign key references''' entity_relation = EntityImporter(instance_obj) single_str, multiple_str = self.uuid_extractor.attribute_data_from_nodelist( instance_obj_data[1]) self.txt_feedback.append('----Creating social tenure relationship') if len(single_str)>0: entity_relation.process_social_tenure(single_str, self.parent_ids) elif len(multiple_str)>1: for repeated_entity, entity_data in multiple_str.iteritems(): """We are assuming that the number of repeat str cannot exceed 10""" entity_relation.process_social_tenure(entity_data, self.parent_ids) self.log_table_entry(" ----- saving social tenure relationship") entity_add.cleanup() self.txt_feedback.append('saving record "{0}" to database'.format(counter)) self.pgbar.setValue(counter) QCoreApplication.processEvents() self.log_instance(instance_obj) self.txt_feedback.append('Number of records successfully imported: {}' .format(counter)) else: self._notif_bar_str.insertErrorNotification("No available records to import") self.pgbar.setValue(0) return except SQLAlchemyError as ae: QCoreApplication.processEvents() QApplication.restoreOverrideCursor() self.feedback_message(unicode(ae.message)) self.txt_feedback.append("current table {0}import failed...\n".format(cu_obj)) self.txt_feedback.append(str(ae.message)) self.log_table_entry(unicode(ae.message)) return def count_import_file_step(self, count = None, table = None): """ Tracking method to record the current import activity :param count: int :param table: string :return: """ self.txt_feedback.append(' Table : {}'.format(table)) def accept(self): """ Execute the import dialog once the save button has been clicked :return: """ self.buttonBox.setEnabled(False) QApplication.setOverrideCursor(Qt.WaitCursor) try: if self.lst_widget.count() < 1: msg = 'No mobile records found for the current profile' self._notif_bar_str.insertErrorNotification(msg) self.buttonBox.setEnabled(True) QApplication.restoreOverrideCursor() return entities = self.user_selected_entities() if len(entities) < 1: if QMessageBox.information(self, QApplication.translate('MobileForms', 'Import Warning'), QApplication.translate('MobileForms', 'You have not ' 'selected any entity for import. All entities ' 'will be imported'), QMessageBox.Ok | QMessageBox.No) == QMessageBox.Ok: entities = self.instance_entities() else: self.buttonBox.setEnabled(True) return self.save_instance_data_to_db(entities) self.buttonBox.setEnabled(True) self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) QApplication.restoreOverrideCursor() except Exception as ex: self.feedback_message(ex.message) self.log_table_entry(unicode(ex.message)) self.buttonBox.setEnabled(True) QApplication.restoreOverrideCursor() return def log_instance(self, instance): instance_short_name = self.importlogger.log_data_name(instance) log_data = self.importlogger.read_log_data() log_data[instance_short_name] = self.importlogger.log_date() self.importlogger.write_log_data(log_data) def remove_imported_instances(self): count = 0 del_list = [] log_data = self.importlogger.read_log_data() if len(log_data) > 0: for instance in self.instance_list: instance_short_name = self.importlogger.log_data_name(instance) if instance_short_name in log_data: del_list.append(instance) count += 1 for inst in del_list: self.instance_list.remove(inst) return count
class ColumnEditor(QDialog, Ui_ColumnEditor): """ Dialog to add/edit entity columns """ def __init__(self, **kwargs): """ :param parent: Owner of this dialog :type parent: QWidget :param kwargs: Keyword dictionary of the following parameters; column - Column you editing, None if its a new column entity - Entity you are adding the column to profile - Current profile in_db - Boolean flag to indicate if a column has been created in the database auto_add- True to automatically add a new column to the entity, default is False. """ self.form_parent = kwargs.get('parent', self) self.column = kwargs.get('column', None) self.entity = kwargs.get('entity', None) self.profile = kwargs.get('profile', None) self.in_db = kwargs.get('in_db', False) self.is_new = kwargs.get('is_new', True) self.auto_entity_add = kwargs.get('auto_add', False) QDialog.__init__(self, self.form_parent) self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set'] self.EX_TYPE_INFO = [ 'SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED' ] self.setupUi(self) self.dtypes = {} self.type_info = '' # dictionary to hold default attributes for each data type self.type_attribs = {} self.init_type_attribs() # dictionary to act as a work area for the form fields. self.form_fields = {} self.init_form_fields() self.fk_entities = [] self.lookup_entities = [] # Exclude column type info in the list self._exclude_col_type_info = [] if self.is_new: self.prop_set = None # why not False?? else: self.prop_set = True # the current entity should not be part of the foreign key parent table, # add it to the exclusion list self.FK_EXCLUDE.append(self.entity.short_name) self.type_names = \ [unicode(name) for name in BaseColumn.types_by_display_name().keys()] self.cboDataType.currentIndexChanged.connect(self.change_data_type) self.btnColProp.clicked.connect(self.data_type_property) self.edtColName.textChanged.connect(self.validate_text) self.notice_bar = NotificationBar(self.notif_bar) self.init_controls() def exclude_column_types(self, type_info): """ Exclude the column types with the given type_info. :param type_info: List of TYPE_INFO of columns to exclude. :type type_info: list """ self._exclude_col_type_info = type_info # Block index change signal of combobox self.cboDataType.blockSignals(True) # Reload column data types self.populate_data_type_cbo() # Select column type if it had been specified if not self.column is None: text = self.column.display_name() self.cboDataType.setCurrentIndex(self.cboDataType.findText(text)) # Re-enable signals self.cboDataType.blockSignals(False) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ self.notice_bar.clear() self.notice_bar.insertErrorNotification(message) def _column_type_info(self, column): """ Check if column has TYPE_INFO attribute :param column: Entity column object :return: Column type. Otherwise None :rtype: String or None """ try: return column.TYPE_INFO except AttributeError: return None def init_controls(self): """ Initialize GUI controls default state when the dialog window is opened. """ self.populate_data_type_cbo() if not self.column is None: self.column_to_form(self.column) self.column_to_wa(self.column) self.edtColName.setFocus() self.edtColName.setEnabled(not self.in_db) self.cboDataType.setEnabled(not self.in_db) self.buttonBox.button(QtGui.QDialogButtonBox.Ok).clicked.connect( self.accept) self.buttonBox.button(QtGui.QDialogButtonBox.Cancel).clicked.connect( self.cancel) col_type = self._column_type_info(self.column) if not self.in_db and col_type == 'GEOMETRY': opts = self.type_attribs[col_type] self.cbMandt.setEnabled(opts['mandt']['enabled_state']) self.cbUnique.setEnabled(opts['unique']['enabled_state']) self.cbIndex.setEnabled(opts['index']['enabled_state']) else: self.cbMandt.setEnabled(not self.in_db) self.cbUnique.setEnabled(not self.in_db) self.cbIndex.setEnabled(not self.in_db) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] locale = QSettings().value("locale/userLocale")[0:2] #if locale == 'en': state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification( u'"{}" is not allowed at this position.'.format( last_character)) text = text[:-1] # fix caps, _, and spaces if last_character.isupper(): text = text.lower() if last_character == ' ': text = text.replace(' ', '_') if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] text = text.replace(' ', '_').lower() self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def column_to_form(self, column): """ Initializes form controls with Column data. :param column: BaseColumn instance :type column: BaseColumn """ text = column.display_name() self.cboDataType.setCurrentIndex(self.cboDataType.findText(text)) self.edtColName.setText(column.name) self.edtColDesc.setText(column.description) self.txt_form_label.setText(column.label) self.edtUserTip.setText(column.user_tip) self.cbMandt.setChecked(column.mandatory) self.cbSearch.setCheckState(self.bool_to_check(column.searchable)) self.cbUnique.setCheckState(self.bool_to_check(column.unique)) self.cbIndex.setCheckState(self.bool_to_check(column.index)) ti = self.current_type_info() ps = self.type_attribs[ti].get('prop_set', None) if ps is not None: self.type_attribs[ti]['prop_set'] = self.prop_set def column_to_wa(self, column): """ Initialize 'work area' form_fields with column data. :param column: BaseColumn instance :type column: BaseColumn """ if column is not None: self.form_fields['colname'] = column.name self.form_fields['value'] = None self.form_fields['mandt'] = column.mandatory self.form_fields['search'] = column.searchable self.form_fields['unique'] = column.unique self.form_fields['index'] = column.index if hasattr(column, 'minimum'): self.form_fields['minimum'] = column.minimum self.form_fields['maximum'] = column.maximum if hasattr(column, 'srid'): self.form_fields['srid'] = column.srid self.form_fields['geom_type'] = column.geom_type if hasattr(column, 'entity_relation'): self.form_fields['entity_relation'] = column.entity_relation if hasattr(column, 'association'): self.form_fields[ 'first_parent'] = column.association.first_parent self.form_fields[ 'second_parent'] = column.association.second_parent if hasattr(column, 'min_use_current_date'): self.form_fields[ 'min_use_current_date'] = column.min_use_current_date self.form_fields[ 'max_use_current_date'] = column.max_use_current_date if hasattr(column, 'min_use_current_datetime'): self.form_fields['min_use_current_datetime'] = \ column.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ column.max_use_current_datetime if hasattr(column, 'prefix_source'): self.form_fields['prefix_source'] = column.prefix_source self.form_fields['columns'] = column.columns self.form_fields[ 'column_separators'] = column.column_separators self.form_fields['leading_zero'] = column.leading_zero self.form_fields['separator'] = column.separator self.form_fields['colname'] = column.name self.form_fields['enable_editing'] = column.enable_editing self.form_fields[ 'disable_auto_increment'] = column.disable_auto_increment self.form_fields['hide_prefix'] = column.hide_prefix # Decimal properties if hasattr(column, 'precision'): self.form_fields['precision'] = column.precision self.form_fields['scale'] = column.scale # Expression column if hasattr(column, 'expression'): self.form_fields['expression'] = column.expression self.form_fields['output_data_type'] = column.output_data_type def bool_to_check(self, state): """ Converts a boolean to a Qt checkstate. :param state: True/False :type state: boolean :rtype: Qt.CheckState """ return Qt.Checked if state else Qt.Unchecked def init_form_fields(self): """ Initializes work area 'form_fields' dictionary with default values. Used when creating a new column. """ none = QApplication.translate('CodeProperty', 'None') self.form_fields['colname'] = '' self.form_fields['value'] = None self.form_fields['mandt'] = False self.form_fields['search'] = False self.form_fields['unique'] = False self.form_fields['index'] = False self.form_fields['minimum'] = self.type_attribs.get('minimum', 0) self.form_fields['maximum'] = self.type_attribs.get('maximum', 0) self.form_fields['srid'] = self.type_attribs.get('srid', "") self.form_fields['geom_type'] = self.type_attribs.get('geom_type', 0) self.form_fields['in_db'] = self.in_db self.form_fields['prefix_source'] = self.type_attribs.get( 'prefix_source', none) self.form_fields['columns'] = self.type_attribs.get('columns', []) self.form_fields['column_separators'] = self.type_attribs.get( 'column_separators', []) self.form_fields['leading_zero'] = self.type_attribs.get( 'leading_zero', '') self.form_fields['separator'] = self.type_attribs.get('separator', '') self.form_fields['enable_editing'] = self.type_attribs.get( 'enable_editing', '') self.form_fields['disable_auto_increment'] = self.type_attribs.get( 'disable_auto_increment', '') self.form_fields['hide_prefix'] = self.type_attribs.get( 'hide_prefix', '') self.form_fields['precision'] = self.type_attribs.get('precision', 18) self.form_fields['scale'] = self.type_attribs.get('scale', 6) self.form_fields['entity_relation'] = \ self.type_attribs['FOREIGN_KEY'].get('entity_relation', None) self.form_fields['entity_relation'] = \ self.type_attribs['LOOKUP'].get('entity_relation', None) self.form_fields['first_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('first_parent', None) self.form_fields['second_parent'] = \ self.type_attribs['MULTIPLE_SELECT'].get('second_parent', None) self.form_fields['min_use_current_date'] = \ self.type_attribs['DATE'].get('min_use_current_date', None) self.form_fields['max_use_current_date'] = \ self.type_attribs['DATE'].get('max_use_current_date', None) self.form_fields['min_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('min_use_current_datetime', None) self.form_fields['max_use_current_datetime'] = \ self.type_attribs['DATETIME'].get('max_use_current_datetime', None) self.form_fields['expression'] = self.type_attribs.get( 'expression', '') self.form_fields['output_data_type'] = self.type_attribs.get( 'output_data_type', '') def init_type_attribs(self): """ Initializes data type attributes. The attributes are used to set the form controls state when a particular data type is selected. mandt - enables/disables checkbox 'Mandatory' search - enables/disables checkbox 'Searchable' unique - enables/disables checkbox 'Unique' index - enables/disables checkbox 'Index' *property - function to execute when a data type is selected. """ self.type_attribs['VARCHAR'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': True }, 'maximum': 30, 'property': self.varchar_property } self.type_attribs['INT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': 0, 'maximum': 0, 'property': self.bigint_property } self.type_attribs['TEXT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, } self.type_attribs['DOUBLE'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': True }, 'minimum': 0.0, 'maximum': 0.0, 'precision': 18, 'scale': 6, 'property': self.double_property } self.type_attribs['DATE'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': datetime.date.min, 'maximum': datetime.date.max, 'min_use_current_date': False, 'max_use_current_date': False, 'property': self.date_property } self.type_attribs['DATETIME'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'minimum': datetime.datetime.min, 'maximum': datetime.datetime.max, 'min_use_current_datetime': False, 'max_use_current_datetime': False, 'property': self.dtime_property } self.type_attribs['FOREIGN_KEY'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': None, 'show_in_parent': True, 'show_in_child': True, 'property': self.fk_property, 'prop_set': False } self.type_attribs['LOOKUP'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': {}, 'property': self.lookup_property, 'prop_set': False } self.type_attribs['GEOMETRY'] = { 'mandt': { 'check_state': False, 'enabled_state': False }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'srid': "", 'geom_type': 0, 'property': self.geometry_property, 'prop_set': False } self.type_attribs['BOOL'] = { 'mandt': { 'check_state': False, 'enabled_state': False }, 'search': { 'check_state': False, 'enabled_state': False }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False } } self.type_attribs['PERCENT'] = { 'mandt': { 'check_state': False, 'enabled_state': False }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False } } self.type_attribs['ADMIN_SPATIAL_UNIT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'entity_relation': None } self.type_attribs['MULTIPLE_SELECT'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': False }, 'index': { 'check_state': False, 'enabled_state': False }, 'first_parent': None, 'second_parent': self.entity, 'property': self.multi_select_property, 'prop_set': False } self.type_attribs['AUTO_GENERATED'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': True, 'enabled_state': True }, 'unique': { 'check_state': True, 'enabled_state': True }, 'index': { 'check_state': True, 'enabled_state': True }, 'prefix_source': '', 'columns': [], 'column_separators': [], 'leading_zero': '', 'separator': '', 'disable_auto_increment': False, 'enable_editing': False, 'property': self.code_property, 'hide_prefix': False, 'prop_set': True } self.type_attribs['EXPRESSION'] = { 'mandt': { 'check_state': False, 'enabled_state': True }, 'search': { 'check_state': False, 'enabled_state': True }, 'unique': { 'check_state': False, 'enabled_state': True }, 'index': { 'check_state': False, 'enabled_state': True }, 'output_data_type': '', 'expression': '', 'property': self.expression_property, 'prop_set': False } def data_type_property(self): """ Executes the function assigned to the property attribute of the current selected data type. """ self.type_attribs[self.current_type_info()]['property']() def varchar_property(self): """ Opens the property editor for the Varchar data type. If successful, set a minimum column in work area 'form fields' """ editor = VarcharProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['maximum'] = editor.max_len() def bigint_property(self): """ Opens a property editor for the BigInt data type. """ editor = BigintProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() def double_property(self): """ Opens a property editor for the Double data type. """ editor = DoubleProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['precision'] = editor.precision self.form_fields['scale'] = editor.scale def date_property(self): """ Opens a property editor for the Date data type. """ editor = DateProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_date'] = \ editor.min_use_current_date self.form_fields['max_use_current_date'] = \ editor.max_use_current_date def dtime_property(self): """ Opens a property editor for the DateTime data type. """ editor = DTimeProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['minimum'] = editor.min_val() self.form_fields['maximum'] = editor.max_val() self.form_fields['min_use_current_datetime'] = \ editor.min_use_current_datetime self.form_fields['max_use_current_datetime'] = \ editor.max_use_current_datetime def geometry_property(self): """ Opens a property editor for the Geometry data type. If successful, set the srid(projection), geom_type (LINE, POLYGON...) and prop_set which is boolean flag to verify that all the geometry properties are set. Constraint - If 'prop_set' is False column cannot be saved. """ editor = GeometryProperty(self, self.form_fields) result = editor.exec_() if result == 1: self.form_fields['srid'] = editor.coord_sys() self.form_fields['geom_type'] = editor.geom_type() self.property_set() def admin_spatial_unit_property(self): """ Sets entity relation property used when creating column of type ADMIN_SPATIAL_UNIT """ er_fields = {} er_fields['parent'] = self.entity er_fields['parent_column'] = None er_fields['display_columns'] = [] er_fields['child'] = None er_fields['child_column'] = None self.form_fields['entity_relation'] = EntityRelation( self.profile, **er_fields) def fk_property(self): """ Opens a property editor for the ForeignKey data type. """ if len(self.edtColName.displayText()) == 0: self.show_message("Please enter column name!") return # filter list of lookup tables, don't show internal # tables in list of lookups fk_ent = [entity for entity in self.profile.entities.items() \ if entity[1].TYPE_INFO not in self.EX_TYPE_INFO] fk_ent = [entity for entity in fk_ent if unicode(entity[0]) \ not in self.FK_EXCLUDE] relation = {} relation['form_fields'] = self.form_fields relation['fk_entities'] = fk_ent relation['profile'] = self.profile relation['entity'] = self.entity relation['column_name'] = unicode(self.edtColName.text()) relation['show_in_parent'] = '1' relation['show_in_child'] = '1' editor = FKProperty(self, relation) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() relation['show_in_parent'] = editor.show_in_parent() relation['show_in_child'] = editor.show_in_child() self.property_set() def lookup_property(self): """ Opens a lookup type property editor """ editor = LookupProperty(self, self.form_fields, profile=self.profile) result = editor.exec_() if result == 1: self.form_fields['entity_relation'] = editor.entity_relation() self.property_set() def multi_select_property(self): """ Opens a multi select property editor """ if len(self.edtColName.displayText()) == 0: self.show_message("Please enter column name!") return editor = MultiSelectProperty(self, self.form_fields, self.entity, self.profile) result = editor.exec_() if result == 1: self.form_fields['first_parent'] = editor.lookup() self.form_fields['second_parent'] = self.entity self.property_set() def code_property(self): """ Opens the code data type property editor """ editor = CodeProperty(self, self.form_fields, entity=self.entity, profile=self.profile) result = editor.exec_() if result == 1: self.form_fields['prefix_source'] = editor.prefix_source() self.form_fields['columns'] = editor.columns() self.form_fields['leading_zero'] = editor.leading_zero() self.form_fields['separator'] = editor.separator() self.form_fields[ 'disable_auto_increment'] = editor.disable_auto_increment() self.form_fields['enable_editing'] = editor.enable_editing() self.form_fields['column_separators'] = editor.column_separators() self.form_fields['hide_prefix'] = editor.hide_prefix() self.property_set() def expression_property(self): """ Opens the code data type property editor """ layer = self.create_layer() editor = ExpressionProperty(layer, self.form_fields, self) result = editor.exec_() if result == 1: self.form_fields['expression'] = editor.expression_text() self.form_fields['output_data_type'] = editor.get_output_data_type( ) self.property_set() def create_layer(self): srid = None column = '' if self.entity.has_geometry_column(): geom_cols = [ col.name for col in self.entity.columns.values() if col.TYPE_INFO == 'GEOMETRY' ] column = geom_cols[0] geom_col_obj = self.entity.columns[column] if geom_col_obj.srid >= 100000: srid = geom_col_obj.srid layer = vector_layer(self.entity.name, geom_column=column, proj_wkt=srid) return layer def create_column(self): """ Creates a new BaseColumn. """ column = None if self.type_info <> "": if self.type_info == 'ADMIN_SPATIAL_UNIT': self.admin_spatial_unit_property() column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, **self.form_fields) return column if self.is_property_set(self.type_info): column = BaseColumn.registered_types[self.type_info] \ (self.form_fields['colname'], self.entity, self.form_fields['geom_type'], self.entity, **self.form_fields) else: self.show_message(self.tr('Please set column properties.')) return else: raise self.tr("No type to create.") return column def property_set(self): self.prop_set = True self.type_attribs[self.current_type_info()]['prop_set'] = True def is_property_set(self, ti): """ Checks if column property is set by reading the value of attribute 'prop_set' :param ti: Type info to check for prop set :type ti: BaseColumn.TYPE_INFO :rtype: boolean """ return self.type_attribs[ti].get('prop_set', True) def property_by_name(self, ti, name): try: return self.dtype_property(ti)['property'][name] except: return None def populate_data_type_cbo(self): """ Fills the data type combobox widget with BaseColumn type names. """ self.cboDataType.clear() for name, col in BaseColumn.types_by_display_name().iteritems(): # Specify columns to exclude if col.TYPE_INFO not in self._exclude_col_type_info: self.cboDataType.addItem(name) if self.cboDataType.count() > 0: self.cboDataType.setCurrentIndex(0) def change_data_type(self, index): """ Called by type combobox when you select a different data type. """ text = self.cboDataType.itemText(index) col_cls = BaseColumn.types_by_display_name().get(text, None) if col_cls is None: return ti = col_cls.TYPE_INFO if ti not in self.type_attribs: msg = self.tr('Column type attributes could not be found.') self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) return self.btnColProp.setEnabled(self.type_attribs[ti].has_key('property')) self.type_info = ti opts = self.type_attribs[ti] self.set_optionals(opts) self.set_min_max_defaults(ti) def set_optionals(self, opts): """ Enable/disables form controls based on selected column data type attributes param opts: Dictionary type properties of selected column type opts: dict """ self.cbMandt.setEnabled(opts['mandt']['enabled_state']) self.cbSearch.setEnabled(opts['search']['enabled_state']) self.cbUnique.setEnabled(opts['unique']['enabled_state']) self.cbIndex.setEnabled(opts['index']['enabled_state']) self.cbMandt.setCheckState( self.bool_to_check(opts['mandt']['check_state'])) self.cbSearch.setCheckState( self.bool_to_check(opts['search']['check_state'])) self.cbUnique.setCheckState( self.bool_to_check(opts['unique']['check_state'])) self.cbIndex.setCheckState( self.bool_to_check(opts['index']['check_state'])) def set_min_max_defaults(self, type_info): """ sets the work area 'form_fields' default values (minimum/maximum) from the column's type attribute dictionary :param type_info: BaseColumn.TYPE_INFO :type type_info: str """ self.form_fields['minimum'] = \ self.type_attribs[type_info].get('minimum', 0) self.form_fields['maximum'] = \ self.type_attribs[type_info].get('maximum', 0) def current_type_info(self): """ Returns a TYPE_INFO of a data type :rtype: str """ text = self.cboDataType.itemText(self.cboDataType.currentIndex()) try: return BaseColumn.types_by_display_name()[text].TYPE_INFO except: return '' def fill_work_area(self): """ Sets work area 'form_fields' with form control values """ self.form_fields['colname'] = unicode(self.edtColName.text()) self.form_fields['description'] = unicode(self.edtColDesc.text()) self.form_fields['label'] = unicode(self.txt_form_label.text()) self.form_fields['index'] = self.cbIndex.isChecked() self.form_fields['mandatory'] = self.cbMandt.isChecked() self.form_fields['searchable'] = self.cbSearch.isChecked() self.form_fields['unique'] = self.cbUnique.isChecked() self.form_fields['user_tip'] = unicode(self.edtUserTip.text()) def show_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle(QApplication.translate("AttributeEditor", "STDM")) msg.setText(message) msg.exec_() def accept(self): col_name = unicode(self.edtColName.text()).strip() # column name is not empty if len(col_name) == 0 or col_name == '_': self.show_message(self.tr('Please enter a valid column name.')) return False # check for STDM reserved keywords if col_name in RESERVED_KEYWORDS: self.show_message( self.tr(u"'{0}' is a reserved keyword used internally by STDM.\n"\ "Please choose another column name.".format(col_name)) ) return False new_column = self.make_column() if new_column is None: LOGGER.debug("Error creating column!") self.show_message('Unable to create column!') return False if self.column is None: # new column if self.duplicate_check(col_name): self.show_message( self.tr("Column with the same name already " "exist in this entity!")) return False if self.auto_entity_add: self.entity.add_column(new_column) self.column = new_column self.done(1) else: # editing a column self.column = new_column self.done(1) def cancel(self): self.done(0) def make_column(self): """ Returns a newly created column :rtype: BaseColumn """ self.fill_work_area() col = self.create_column() return col def duplicate_check(self, name): """ Return True if we have a column in the current entity with same name as our new column :param col_name: column name :type col_name: str """ # check if another column with the same name exist in the current entity if name in self.entity.columns: return True else: return False def rejectAct(self): self.done(0)
class ValueEditor(QDialog, Ui_LookupValue): """ Form to add/edit values added to a lookup. Values are objects of type CodeValue """ def __init__(self, parent, lookup, code_value=None): """ :param parent: Owner of this dialog window :type parent: QWidget :param lookup: A value list object to add the value :type lookup: ValueList :param code_value: A value object to add to the lookup, if None this is a new value, else its an edit. :type code_value: CodeValue """ QDialog.__init__(self, parent) self.setupUi(self) self.lookup = lookup self.code_value = code_value self.notice_bar = NotificationBar(self.notif_bar) self.init_gui() def init_gui(self): """ initializes the form widgets """ # Set character length constraints self.edtValue.setMaxLength(50) self.edtCode.setMaxLength(5) if self.code_value: if self.code_value.updated_value == '': self.edtValue.setText(self.code_value.value) self.edtCode.setText(self.code_value.code) else: self.edtValue.setText(self.code_value.updated_value) self.edtCode.setText(self.code_value.updated_code) self.edtCode.textChanged.connect(self.validate_text) self.edtValue.textChanged.connect(self.validate_text) self.edtValue.setFocus() def show_notification(self, message): self.notice_bar.clear() self.notice_bar.insertErrorNotification(message) def validate_text(self, text): """ Validates and updates the entered text if necessary. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return locale = QSettings().value("locale/userLocale")[0:2] if locale == 'en': name_regex = QtCore.QRegExp('^[ _0-9a-zA-Z][a-zA-Z0-9_/\\-()|.:,; ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification(u'"{}" is not allowed at this position.'. format(last_character) ) text = text[:-1] if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def add_value(self): """ Adds a code value to a lookup object. Checks first if a previous value exist then removes it and then adds the new one. """ value = unicode(self.edtValue.text().strip()) code = unicode(self.edtCode.text().strip()) # if its an edit, first remove the previous value if self.code_value: self.lookup.rename(self.code_value.value, value, code) else: self.lookup.add_code_value(CodeValue(code, value)) def accept(self): if self.edtValue.text() == '' or self.edtValue.text() == ' ': self.error_message(QApplication.translate( "ValueEditor", "Please enter a valid lookup value.") ) return self.add_value() self.done(1) def reject(self): self.done(0) def error_message(self, message): """ Creates a message box and displays a message :param message: message to display :type message: str """ msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(message) msg.exec_()
class LookupEditor(QDialog, Ui_dlgLookup): """ Form to add/edit lookup entities. """ def __init__(self, parent, profile, lookup=None): """ :param parent: Owner of this dialog :type parent: QWidget :param profile: A profile to add/edit lookup :type profile: Profile :type inplace: Flag to check if lookup creation is initiated from the 'normal' lookup creation process -inplace = False, this is the normal state. If 'inplace' = True, then creation is initiated from the the lookup selection dialog :param lookup: Value list to create, if None this is a new value list else its an edit :type lookup: ValueList """ QDialog.__init__(self, parent) self.setupUi(self) self.profile = profile self.lookup = lookup self.notice_bar = NotificationBar(self.notif_bar) self.init_gui() def init_gui(self): """ Initializes form widgets """ self.edtName.setFocus() if self.lookup: self.edtName.setText(self.lookup.short_name.replace('check_', '')) self.edtName.textChanged.connect(self.validate_text) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ msg = self.tr(message) self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification( '"{}" is not allowed at this position.'.format(last_character)) text = text[:-1] else: # fix caps, underscores, and spaces if last_character.isupper(): text = text.lower() if last_character == ' ': text = text.replace(' ', '_') if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] text = text.replace(' ', '_').lower() self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def format_lookup_name(self, name): """ Replace spaces with underscore in a name string :param name: Name to replace spaces :type name: str :rtype: str """ formatted_name = str(name).strip() formatted_name = formatted_name.replace(' ', "_") return formatted_name.lower() def create_lookup(self, name): """ Creates a lookup entity and add it to a profile. If this is an edit, first the previous lookup is removed before adding a new one. :param name: Name of the new/edited lookup :type name: Unicode """ name = self.format_lookup_name(name) new_lookup = self.profile.create_entity(name, value_list_factory) return new_lookup def accept(self): if self.edtName.text() == '' or self.edtName.text() == '_': self.error_message( QApplication.translate("LookupEditor", "Please enter a valid lookup name.")) return if self.edtName.text() == 'check': self.error_message( QApplication.translate( "LookupEditor", "'check' is used internally by STDM! " "Select another name for the lookup")) return short_name = unicode(self.edtName.text()) if self.lookup is None: # new lookup if self.duplicate_check(short_name): self.show_message( self.tr("Lookup with the same name already " "exist in the current profile!")) return else: new_lookup = self.create_lookup(short_name) self.profile.add_entity(new_lookup) self.lookup = new_lookup else: self.edit_lookup(short_name) self.done(1) def duplicate_check(self, name): """ Return True if we have an entity in the current profile with same 'name' :param name: entity short_name :type name: Unicode :rtype:boolean """ return self.profile.entities.has_key(name) def edit_lookup(self, short_name): short_name = short_name.replace('check_', '') self.lookup.short_name = u'{0}_{1}'.format('check', short_name) def reject(self): self.done(0) def error_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(message) msg.exec_()
class ImageExportSettings(WIDGET, BASE): """A dialog for settings options for exporting an image.""" def __init__(self, parent=None, **kwargs): super(ImageExportSettings, self).__init__(parent) self.setupUi(self) self.btn_path.setIcon(GuiUtils.get_icon('open_file.png')) self._image_tr = self.tr('Image') self.notif_bar = NotificationBar(self.vl_notification, 6000) # Connect signals self.btn_path.clicked.connect(self._on_choose_image_path) self.buttonBox.accepted.connect(self.on_accept) self.sb_resolution.valueChanged.connect(self._on_resolution_changed) # Set color button defaults self._default_color = Qt.white self.btn_color.setDefaultColor(self._default_color) self.btn_color.setColor(self._default_color) self.btn_color.setAllowOpacity(True) self.path = kwargs.get('image_path', '') self.resolution = kwargs.get('resolution', '96') self.background_color = kwargs.get('background', Qt.transparent) self._update_controls() def _update_controls(self): # Update input controls with export settings' values if self.background_color == Qt.transparent: self.rb_transparent.setChecked(True) self.btn_color.setVisible(False) else: self.rb_fill.setChecked(True) self.btn_color.setColor(self.background_color) self.sb_resolution.setValue(int(self.resolution)) self.txt_path.setText(self.path) # Set image size just in case value does not change self._set_image_size() def _on_resolution_changed(self, value): # Slot raised when the resolution changes self._set_image_size() def _update_export_vars(self): # Update export variables based on user values. self.path = self.txt_path.text() self.resolution = self.sb_resolution.value() if self.rb_transparent.isChecked(): self.background_color = Qt.transparent else: self.background_color = self.btn_color.color() def _set_image_size(self): # Set image size based on the resolution using A4 paper size res = self.sb_resolution.value() if res == 0: return # To mm res_mm = res / 25.4 # A4 landscape size width = int(297 * res_mm) height = int(210 * res_mm) units = 'px' width_display = '{0} {1}'.format(width, units) height_display = '{0} {1}'.format(height, units) self.lbl_width.setText(width_display) self.lbl_height.setText(height_display) def _image_filters(self): # Return supported image formats for use in a QFileDialog filter formats = [] for f in QImageWriter.supportedImageFormats(): f_type = f.data().decode() filter_format = '{0} {1} (*.{2})'.format(f_type.upper(), self._image_tr, f_type) formats.append(filter_format) return ';;'.join(formats) def _on_choose_image_path(self): # Slot raised to choose image path img_path = self.txt_path.text() title = self.tr('Specify image location') sel_image_path, _ = QFileDialog.getSaveFileName( self, title, img_path, self._image_filters()) if sel_image_path: self.txt_path.setText(sel_image_path) def on_accept(self): """ Slot raised to save the settings and close the dialog. """ if not self.txt_path.text(): msg = self.tr('Please specify the image path.') self.notif_bar.insertErrorNotification(msg) self.txt_path.setFocus() return self._update_export_vars() self.accept()
def __init__(self, parent=None,selectMode=True, filter_data_source=''): QDialog.__init__(self,parent) self.setupUi(self) self.notifBar = NotificationBar(self.vlNotification) self._mode = selectMode #Filter templates by the specified table name self._filter_data_source = filter_data_source #Document templates in current profile self._profile_templates = [] self._current_profile = current_profile() #Load current profile templates self._load_current_profile_templates() if selectMode: self.buttonBox.setVisible(True) self.manageButtonBox.setVisible(False) currHeight = self.size().height() self.resize(200,currHeight) else: self.buttonBox.setVisible(False) self.manageButtonBox.setVisible(True) self.setWindowTitle( QApplication.translate( "TemplateDocumentSelector", "Template Manager" ) ) #Configure manage buttons btnEdit = self.manageButtonBox.button(QDialogButtonBox.Ok) btnEdit.setText(QApplication.translate("TemplateDocumentSelector","Edit...")) btnEdit.setIcon(QIcon(":/plugins/stdm/images/icons/edit.png")) btnDelete = self.manageButtonBox.button(QDialogButtonBox.Save) btnDelete.setText(QApplication.translate("TemplateDocumentSelector","Delete")) btnDelete.setIcon(QIcon(":/plugins/stdm/images/icons/delete.png")) #Connect signals self.buttonBox.accepted.connect(self.onAccept) btnEdit.clicked.connect(self.onEditTemplate) btnDelete.clicked.connect(self.onDeleteTemplate) #Get saved document templates then add to the model templates = documentTemplates() self._docItemModel = QStandardItemModel(parent) self._docItemModel.setColumnCount(2) #Append current profile templates to the model. for dt in self._profile_templates: if self._template_contains_filter_table(dt): doc_name_item = self._createDocNameItem(dt.name) file_path_item = QStandardItem(dt.path) self._docItemModel.appendRow([doc_name_item,file_path_item]) self.lstDocs.setModel(self._docItemModel)
def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout)
class ProfileInstanceRecords(QDialog, FORM_CLASS): """ class constructor The class handles all the instances that the user has collected and saved in the folder and saved in a computer. The class will construct the path to the folder and enumerate all available instances and return the count. It will also rename all the file based on instance unique GUUID for easier management and future updates. """ def __init__(self, parent=None): """ initailize class variables here """ super(ProfileInstanceRecords, self).__init__(parent) self.setupUi(self) self.path = None self.instance_list = [] self.relations = OrderedDict() self.parent_ids = {} self.importlogger = ImportLogger() self._notif_bar_str = NotificationBar(self.vlnotification) self.chk_all.setCheckState(Qt.Checked) self.entity_model = EntitiesModel() self.uuid_extractor = InstanceUUIDExtractor(self.path) self.btn_chang_dir.setIcon( QIcon(":/plugins/stdm/images/icons/open_file.png")) self.btn_refresh.setIcon( QIcon(":/plugins/stdm/images/icons/update.png")) self.chk_all.stateChanged.connect(self.change_check_state) #self.cbo_profile.currentIndexChanged.connect(self.current_profile_changed) self.btn_chang_dir.clicked.connect(self.on_directory_search) self.lst_widget.itemClicked.connect(self.user_selected_entities) self.btn_refresh.clicked.connect(self.update_files_with_custom_filter) self.buttonBox.button(QDialogButtonBox.Save).setText('Import') #self.load_config() self.init_file_path() self.current_profile_changed() self.change_check_state(self.chk_all.checkState()) self.instance_dir() def load_config(self): """ Load STDM configuration :return: """ stdm_config = None if QFile.exists(HOME + "/stdm/configuration.stc"): stdm_config = QFile(CONFIG_FILE) ConfigurationFileSerializer(stdm_config) profiles = StdmConfiguration.instance().profiles return profiles def change_check_state(self, state): """ Change the check state of items in a list widget """ for i in range(self.lst_widget.count()): self.lst_widget.item(i).setCheckState(state) def profiles(self): """ Return a list of all profiles :rtype: list """ return self.load_config().values() def current_profile_changed(self): """ Get the current profile so that it is the one selected at the combo box :return: """ self.instance_list = [] self.active_profile() self.init_file_path() self.available_records() self.on_dir_path() self.populate_entities_widget() def active_profile(self): """ get the user selected profile :return:p """ self.profile = current_profile().name return self.profile def instance_dir(self): """ Create a path where imported instance will be kept :return: """ self.inst_path = CONFIG_FILE + "_imported" if not os.access(self.inst_path, os.F_OK): os.makedirs(unicode(self.inst_path)) else: return self.inst_path def imported_instance_path(self): """ :return: """ self.instance_dir() return self.inst_path def init_file_path(self): """ Initialize GeoODK file path """ self.path = self.geoODK_file_path(self.txt_directory.text()) self.txt_directory.setText(self.path) def geoODK_file_path(self, path=''): """ Check if geoODK file path has been configured, if not configure default and return it. :rtype: string """ if not path.strip(): path = self.make_path(GEOODK_FORM_HOME) return path def make_path(self, path): """ Create and return a file path if is not available. :rtype: string """ if not os.access(path, os.F_OK): os.makedirs(unicode(path)) return path def xform_xpaths(self): """ Return the full path to the default config path and filter geoodk instance that matches the current profile path :return: directories :rtype: list """ return [ os.path.join(self.path, name) for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater()) ] def on_dir_path(self): """ Extract the specific folder information and rename the file :return: """ self.uuid_extractor.new_list = [] if self.record_count() > 0: directories = self.xform_xpaths() for directory in directories: self.extract_guuid_and_rename_file(directory) inst_count = len(self.instance_list) rm_count = self.remove_imported_instances() diff = inst_count - rm_count self.txt_count.setText(unicode(diff)) def extract_guuid_and_rename_file(self, path): """ Extract the unique Guuid and rename the file so that we can uniquely identify each file :return: """ for f in os.listdir(path): if os.path.isfile(os.path.join(path, f)) and f.endswith('.xml'): file_instance = os.path.join(path, f) self.rename_file_to_UUID(file_instance) def read_instance_data(self): """Read all instance data once and store them in a dict :rtype: dict """ mobile_data = OrderedDict() social_tenure_info = OrderedDict() for instance in self.instance_list: self.uuid_extractor.set_file_path(instance) field_data_nodes = self.uuid_extractor.document_entities_with_data( current_profile().name.replace(' ', '_'), self.user_selected_entities()) str_data_nodes = self.uuid_extractor.document_entities_with_data( current_profile().name.replace(' ', '_'), ['social_tenure']) mobile_data[instance] = [field_data_nodes, str_data_nodes] self.uuid_extractor.close_document() return mobile_data def rename_file_to_UUID(self, file): """ Extract the UUID from each folder and file :return: """ self.uuid_extractor.set_file_path(file) self.uuid_extractor.on_file_passed() self.instance_list = self.uuid_extractor.new_list def move_imported_file(self, file): """ Moves the imported files to avoid repetition :return: """ instance_path = self.imported_instance_path() try: basename = os.path.basename(os.path.dirname(file)) if not os.path.isdir( os.path.join(self.imported_instance_path(), basename)): shutil.move(os.path.dirname(file), instance_path) except Exception as ex: return ex def populate_entities_widget(self): """ Add entities in the instance file into a list view widget """ self.lst_widget.clear() entities = self.instance_entities() if len(entities) > 0: for entity in entities: list_widget = QListWidgetItem( current_profile().entity_by_name(entity).short_name, self.lst_widget) list_widget.setCheckState(Qt.Checked) def user_selected_entities(self): """ :rtype: list """ entities = [] count = self.lst_widget.count() if count > 0: for i in range(count): item = self.lst_widget.item(i) if item.checkState() == Qt.Checked: entities.append(current_profile().entity(item.text()).name) return entities def instance_entities(self): """ Enumerate the entities that are in the current profile and also that are captured in the form so that we are only importing relevant entities to database :return: entities """ current_entities = [] entity_collections = [] instance_collections = self.instance_collection() if len(instance_collections) > 0: for entity_name in self.profile_entities_names(current_profile()): if current_profile().entity_by_name(entity_name) is not None: current_entities.append(entity_name) return current_entities def instance_collection(self): """ Enumerate all the instances found in the instance directory rtype: list """ dirs = self.xform_xpaths() instance_collections = [] if len(dirs) > 0: for dir_f in dirs: xml_files = [ dir_f.replace("\\", "/") + '/' + f for f in os.listdir(dir_f) if f.endswith('.xml') ] if len(xml_files) > 0: instance_collections.append(xml_files[0]) return instance_collections def check_profile_with_custom_name(self): """ Try extract mobile instance with custom filter name. Assumption is that there is a profile that bears that name :return: """ mismatch_profile = 'Nothing found to import. \n' \ ' Ensure the current filter text or profile is correct' entity_attr = [] if self.txt_filter.text() != '': for obj in self.profiles(): if obj.name.startswith(self.txt_filter.text()): if obj.name != current_profile().name: self._notif_bar_str.insertErrorNotification( mismatch_profile) return return self.uuid_extractor.document_entities(self.profile) def profile_entities_names(self, profile): """ Return names of all entities in a profile :rtype: list """ entities_names = [] for entity in profile.user_entities(): entities_names.append(entity.name) return entities_names def has_foreign_keys_parent(self, select_entities): """ Ensure we check that the table is not parent else import parent table first Revised in version 1.7. It explicitly assumes str is captured. before it was optional. :return: """ has_relations = False str_tables = current_profile().social_tenure party_tbls = str_tables.parties sp_tbls = str_tables.spatial_units self.relations = OrderedDict() if len(self.instance_list) > 0: if self.uuid_extractor.has_str_captured_in_instance( self.instance_list[0]): for party_tbl in party_tbls: self.relations[party_tbl.name] = [ 'social_tenure_relationship', party_tbl.short_name.lower() + '_id' ] for sp_tbl in sp_tbls: self.relations[sp_tbl.name] = [ 'social_tenure_relationship', sp_tbl.short_name.lower() + '_id' ] # print self.relations for table in select_entities: table_object = current_profile().entity_by_name(table) cols = table_object.columns.values() for col in cols: if col.TYPE_INFO == 'FOREIGN_KEY': parent_object = table_object.columns[col.name] if parent_object.parent: if parent_object.parent.name in self.relations: self.relations[parent_object.parent.name].append( [table, col.name]) else: self.relations[parent_object.parent.name] = [ table, col.name ] #self.relations[parent_object.parent.name].append([table, col.name]) has_relations = True else: continue return has_relations def parent_table_isselected(self): """ Take note that the user selected tables may or may not be imported based on parent child table relationship. Add those table silently so that we can show them to the user :return: """ try: silent_list = [] entities = self.user_selected_entities() if len(entities) > 0: for table in self.relations.keys(): if table not in entities: silent_list.append(table) return silent_list except Exception as ex: self._notif_bar_str.insertErrorNotification(ex.message) def archive_this_import_file(self, counter, instance): """ Ensure that only import are done once :return: """ try: self.importlogger.logger_sections() file_info = 'File instance ' + str(counter) + ' : \n' + instance self.importlogger.log_action(file_info) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def log_table_entry(self, instance): """ Ensure that only import are done once :return: """ try: current_time = QDateTime() import_time = current_time.currentDateTime() log_entry = instance + ' ' + str(import_time.toPyDateTime()) self.importlogger.log_action(log_entry) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def check_previous_import(self): """ Ensure we are importing files once :return: """ try: self.importlogger.add_log_info() for files in self.instance_list: current_dir = os.path.basename(files) exist = self.importlogger.check_file_exist(current_dir) if exist: self.instance_list.remove(files) self.txt_count.setText(str(len(self.instance_list))) if self.record_count() != len(self.instance_list): msg = 'Some files have been already imported and therefore ' \ 'not enumerated' self._notif_bar_str.insertErrorNotification(msg) except IOError as io: self._notif_bar_str.insertErrorNotification(MSG + ": " + io.message) pass def available_records(self): """ Let the user know how many records have been collected and are available for inport process :return: """ self.txt_count.setText(unicode(self.record_count())) def record_count(self): """ get the count of instance dir in the selected directory :return: integer """ return len([ name for name in os.listdir(self.path) if os.path.isdir(os.path.join(self.path, name)) if name.startswith(self.profile_formater()) ]) def profile_formater(self): """ Format the profile name by removing underscore character :return: """ if self.txt_filter.text() != '': filter_text = self.txt_filter.text() return filter_text else: return self.profile def update_files_with_custom_filter(self): """ Get the new file count with the user custom filter text :return: file count """ self.available_records() self.on_dir_path() self.populate_entities_widget() def projection_settings(self): """ let user select the projections for the data :return: """ project_select = ProjectionSelector(self) projection = project_select.loadAvailableSystems() self.txt_srid.setText(str(projection)) def on_projection_select(self): """ Get the selected projection and set it during data import :return: """ vals = self.txt_srid.text().split(":") return vals[1] def on_directory_search(self): """ Let the user choose the directory with instances :return: """ home_path = 'home' if self.txt_directory.text() != '': home_path = self.txt_directory.text() dir_name = QFileDialog.getExistingDirectory(self, 'Open Directory', home_path, QFileDialog.ShowDirsOnly) if dir_name: self.txt_directory.setText(str(dir_name)) self.current_profile_changed() self.change_check_state(self.chk_all.checkState()) def feedback_message(self, msg): """ Create a dialog box to capture and display errrors related to db while importing data :param: msg :type: string :return:Qdialog """ msgbox = QMessageBox() msgbox.setStandardButtons(QMessageBox.Ok | QMessageBox.No) msgbox.setWindowTitle("Data Import") msgbox.setText(msg) msgbox.exec_() msgbox.show() return msgbox def save_instance_data_to_db(self, entities): """ Get the user selected entities and insert them into database params: selected entities rtype: list :return:Object :type: dbObject """ cu_obj = '' import_status = False self.txt_feedback.clear() self.txt_feedback.append("Import started, please wait...\n") QCoreApplication.processEvents() self._notif_bar_str.clear() mobile_field_data = self.read_instance_data() self.has_foreign_keys_parent(entities) #print self.relations if len(self.parent_table_isselected()) > 0: if QMessageBox.information( self, QApplication.translate('GeoODKMobileSettings', " Import Warning"), QApplication.translate( 'GeoODKMobileSettings', 'Some of dependent tables (entities)' 'which may not be part of the selected tables ' 'I.e: {} will be imported'.format( self.parent_table_isselected())), QMessageBox.Ok | QMessageBox.No) == QMessageBox.No: return try: counter = 0 if len(mobile_field_data) > 0: self.pgbar.setRange(counter, len(self.instance_list)) self.pgbar.setValue(0) self.importlogger.log_action("Import started ...\n") for instance_obj, instance_obj_data in mobile_field_data.iteritems( ): self.importlogger.log_action( "File {} ...\n".format(instance_obj)) parents_info = [] import_status = False counter = counter + 1 self.parent_ids = {} single_occuring, repeated_entities = self.uuid_extractor.attribute_data_from_nodelist( instance_obj_data[0]) for entity, entity_data in single_occuring.iteritems(): import_status = False if entity in self.relations.keys(): if entity in self.parent_ids: continue self.count_import_file_step(counter, entity) log_timestamp = '=== parent table import === : {0}'.format( entity) cu_obj = entity self.log_table_entry(log_timestamp) entity_add = Save2DB(entity, entity_data) entity_add.objects_from_supporting_doc( instance_obj) ref_id = entity_add.save_parent_to_db() import_status = True self.parent_ids[entity] = [ref_id, entity] #log_timestamp = ' --- import succeeded: {0}' .format(str(import_status)) #self.log_table_entry(log_timestamp) parents_info.append(entity) single_occuring.pop(entity) elif entity not in self.relations.keys(): import_status = False for fk_table_name in self.relations.keys(): if fk_table_name not in self.parent_ids: in_relations = [ _item for subitem in self.relations[fk_table_name] for _item in subitem ] if entity in in_relations: fk_table_data = single_occuring[ fk_table_name] entity_add = Save2DB( fk_table_name, fk_table_data) ref_id = entity_add.save_parent_to_db() self.parent_ids[fk_table_name] = [ ref_id, fk_table_name ] continue self.count_import_file_step(counter, entity) log_timestamp = '=== standalone table import === : {0}'.format( entity) cu_obj = entity self.log_table_entry(log_timestamp) entity_add = Save2DB(entity, entity_data, self.parent_ids) entity_add.objects_from_supporting_doc( instance_obj) child_id = entity_add.save_to_db() cu_obj = entity import_status = True parents_info.append(entity) if entity in self.parent_ids: continue else: self.parent_ids[entity] = [child_id, entity] entity_add.cleanup() if repeated_entities: #self.log_table_entry(" ========== starting import of repeated tables ============") import_status = False for repeated_entity, entity_data in repeated_entities.iteritems( ): """We are assuming that the number of repeat table cannot exceed 99""" enum_index = repeated_entity[:2] if enum_index.isdigit(): repeat_table = repeated_entity[2:] else: enum_index = repeated_entity[:1] repeat_table = repeated_entity[1:] log_timestamp = ' child table {0} >> : {1}' \ .format(repeat_table, enum_index) self.count_import_file_step(counter, repeat_table) self.importlogger.log_action(log_timestamp) if repeat_table in self.profile_entities_names( current_profile()): entity_add = Save2DB(repeat_table, entity_data, self.parent_ids) entity_add.objects_from_supporting_doc( instance_obj) child_id = entity_add.save_to_db() cu_obj = repeat_table import_status = True self.log_table_entry( " ------ import succeeded: {0} ".format( import_status)) entity_add.cleanup() else: continue if instance_obj_data[1]: '''We treat social tenure entities separately because of foreign key references''' entity_relation = EntityImporter(instance_obj) single_str, multiple_str = self.uuid_extractor.attribute_data_from_nodelist( instance_obj_data[1]) self.txt_feedback.append( '----Creating social tenure relationship') if len(single_str) > 0: entity_relation.process_social_tenure( single_str, self.parent_ids) elif len(multiple_str) > 1: for repeated_entity, entity_data in multiple_str.iteritems( ): """We are assuming that the number of repeat str cannot exceed 10""" entity_relation.process_social_tenure( entity_data, self.parent_ids) self.log_table_entry( " ----- saving social tenure relationship") entity_add.cleanup() self.txt_feedback.append( 'saving record "{0}" to database'.format(counter)) self.pgbar.setValue(counter) QCoreApplication.processEvents() self.log_instance(instance_obj) self.txt_feedback.append( 'Number of records successfully imported: {}'.format( counter)) else: self._notif_bar_str.insertErrorNotification( "No available records to import") self.pgbar.setValue(0) return except SQLAlchemyError as ae: QCoreApplication.processEvents() QApplication.restoreOverrideCursor() self.feedback_message(unicode(ae.message)) self.txt_feedback.append( "current table {0}import failed...\n".format(cu_obj)) self.txt_feedback.append(str(ae.message)) self.log_table_entry(unicode(ae.message)) return def count_import_file_step(self, count=None, table=None): """ Tracking method to record the current import activity :param count: int :param table: string :return: """ self.txt_feedback.append(' Table : {}'.format(table)) def accept(self): """ Execute the import dialog once the save button has been clicked :return: """ self.buttonBox.setEnabled(False) QApplication.setOverrideCursor(Qt.WaitCursor) try: if self.lst_widget.count() < 1: msg = 'No mobile records found for the current profile' self._notif_bar_str.insertErrorNotification(msg) self.buttonBox.setEnabled(True) QApplication.restoreOverrideCursor() return entities = self.user_selected_entities() if len(entities) < 1: if QMessageBox.information( self, QApplication.translate('MobileForms', 'Import Warning'), QApplication.translate( 'MobileForms', 'You have not ' 'selected any entity for import. All entities ' 'will be imported'), QMessageBox.Ok | QMessageBox.No) == QMessageBox.Ok: entities = self.instance_entities() else: self.buttonBox.setEnabled(True) return self.save_instance_data_to_db(entities) self.buttonBox.setEnabled(True) self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False) QApplication.restoreOverrideCursor() except Exception as ex: self.feedback_message(ex.message) self.log_table_entry(unicode(ex.message)) self.buttonBox.setEnabled(True) QApplication.restoreOverrideCursor() return def log_instance(self, instance): instance_short_name = self.importlogger.log_data_name(instance) log_data = self.importlogger.read_log_data() if len(log_data) > 0: log_data[instance_short_name] = self.importlogger.log_date() self.importlogger.write_log_data(log_data) def remove_imported_instances(self): count = 0 del_list = [] log_data = self.importlogger.read_log_data() if len(log_data) > 0: for instance in self.instance_list: instance_short_name = self.importlogger.log_data_name(instance) if instance_short_name in log_data: del_list.append(instance) count += 1 for inst in del_list: self.instance_list.remove(inst) return count
def __init__(self, **kwargs): """ :param parent: Owner of this dialog :type parent: QWidget :param kwargs: Keyword dictionary of the following parameters; column - Column you editing, None if its a new column entity - Entity you are adding the column to profile - Current profile in_db - Boolean flag to indicate if a column has been created in the database auto_add- True to automatically add a new column to the entity, default is False. """ self.form_parent = kwargs.get('parent', self) self.column = kwargs.get('column', None) self.entity = kwargs.get('entity', None) self.profile = kwargs.get('profile', None) self.in_db = kwargs.get('in_db', False) self.is_new = kwargs.get('is_new', True) self.auto_entity_add = kwargs.get('auto_add', False) QDialog.__init__(self, self.form_parent) self.FK_EXCLUDE = [u'supporting_document', u'admin_spatial_unit_set'] self.EX_TYPE_INFO = ['SUPPORTING_DOCUMENT', 'SOCIAL_TENURE', 'ADMINISTRATIVE_SPATIAL_UNIT', 'ENTITY_SUPPORTING_DOCUMENT', 'VALUE_LIST', 'ASSOCIATION_ENTITY', 'AUTO_GENERATED'] self.setupUi(self) self.dtypes = {} self.type_info = '' # dictionary to hold default attributes for each data type self.type_attribs = {} self.init_type_attribs() # dictionary to act as a work area for the form fields. self.form_fields = {} self.init_form_fields() self.fk_entities = [] self.lookup_entities = [] # Exclude column type info in the list self._exclude_col_type_info = [] if self.is_new: self.prop_set = None # why not False?? else: self.prop_set = True # the current entity should not be part of the foreign key parent table, # add it to the exclusion list self.FK_EXCLUDE.append(self.entity.short_name) self.type_names = \ [unicode(name) for name in BaseColumn.types_by_display_name().keys()] self.cboDataType.currentIndexChanged.connect(self.change_data_type) self.btnColProp.clicked.connect(self.data_type_property) self.edtColName.textChanged.connect(self.validate_text) self.notice_bar = NotificationBar(self.notif_bar) self.init_controls()
class LookupEditor(QDialog, Ui_dlgLookup): """ Form to add/edit lookup entities. """ def __init__(self, parent, profile, lookup=None): """ :param parent: Owner of this dialog :type parent: QWidget :param profile: A profile to add/edit lookup :type profile: Profile :type inplace: Flag to check if lookup creation is initiated from the 'normal' lookup creation process -inplace = False, this is the normal state. If 'inplace' = True, then creation is initiated from the the lookup selection dialog :param lookup: Value list to create, if None this is a new value list else its an edit :type lookup: ValueList """ QDialog.__init__(self, parent) self.setupUi(self) self.profile = profile self.lookup = lookup self.notice_bar = NotificationBar(self.notif_bar) self.init_gui() def init_gui(self): """ Initializes form widgets """ self.edtName.setFocus() if self.lookup: self.edtName.setText( self.lookup.short_name.replace('check_','')) self.edtName.setEnabled(not self.lookup.entity_in_database) self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(not self.lookup.entity_in_database) self.edtName.textChanged.connect(self.validate_text) def show_notification(self, message): """ Shows a warning notification bar message. :param message: The message of the notification. :type message: String """ msg = self.tr(message) self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) def validate_text(self, text): """ Validates and updates the entered text if necessary. Spaces are replaced by _ and capital letters are replaced by small. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return locale = QSettings().value("locale/userLocale")[0:2] last_character = text[-1:] if locale == 'en': name_regex = QtCore.QRegExp('^(?=.{0,40}$)[ _a-zA-Z][a-zA-Z0-9_ ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification(u'"{}" is not allowed at this position.'. format(last_character) ) text = text[:-1] # fix caps, underscores, and spaces if last_character.isupper(): text = text.lower() if last_character == ' ': text = text.replace(' ', '_') if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] text = text.replace(' ', '_').lower() self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def format_lookup_name(self, name): """ Replace spaces with underscore in a name string :param name: Name to replace spaces :type name: str :rtype: str """ formatted_name = str(name).strip() formatted_name = formatted_name.replace(' ', "_") return formatted_name.lower() def create_lookup(self, name): """ Creates a lookup entity and add it to a profile. If this is an edit, first the previous lookup is removed before adding a new one. :param name: Name of the new/edited lookup :type name: Unicode """ name = self.format_lookup_name(name) new_lookup = self.profile.create_entity(name, value_list_factory) return new_lookup def accept(self): if self.edtName.text() == '' or self.edtName.text() == '_': self.error_message( QApplication.translate( "LookupEditor","Please enter a valid lookup name." ) ) return if self.edtName.text() == 'check': self.error_message(QApplication.translate("LookupEditor", "'check' is used internally by STDM! " "Select another name for the lookup")) return short_name = unicode(self.edtName.text()) if self.lookup is None: # new lookup if self.duplicate_check(short_name): self.show_message( self.tr( "Lookup with the same name already " "exist in the current profile!" ) ) return else: new_lookup = self.create_lookup(short_name) self.profile.add_entity(new_lookup) self.lookup = new_lookup else: self.edit_lookup(short_name) self.done(1) def duplicate_check(self, name): """ Return True if we have an entity in the current profile with same 'name' :param name: entity short_name :type name: Unicode :rtype:boolean """ return self.profile.entities.has_key(name) def edit_lookup(self, short_name): short_name = short_name.replace('check_','') self.lookup.short_name = u'{0}_{1}'.format('check', short_name) def reject(self): self.done(0) def error_message(self, message): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(message) msg.exec_()
class MapperMixin(object): ''' Mixin class for use in a dialog or widget, and manages attribute mapping. ''' def __init__(self, model, entity): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self.entity = entity self._attrMappers = [] self._attr_mapper_collection = {} self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None self.is_valid = False self.saved_model = None # Get document objects self.entity_model = entity_model(entity) self.entity_model_obj = self.entity_model() #Initialize notification bar if hasattr(self, "vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self, attributeName, control, isMandatory=False, pseudoname='', valueHandler=None, preloadfunc=None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName, control, self._model, pseudoname, isMandatory, valueHandler) self.addMapper(attrMapper, preloadfunc) def addMapper(self, attributeMapper, preloadfunc=None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) self._attr_mapper_collection[ attributeMapper.attributeName()] = attributeMapper def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def attribute_mapper(self, attribute_name): """ Returns attribute mapper object corresponding to the the given attribute. :param attribute_name: Name of the attribute :type attribute_name: str :return: Attribute mapper :rtype: _AttributeMapper """ return self._attr_mapper_collection.get(attribute_name, None) def setSaveMode(self, mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self, stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self, message, mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate( "MappedDialog", "Would you like to save changes before closing?") msgResponse = QMessageBox.information( self, QApplication.translate("MappedDialog", "Save Changes"), msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) return isDirty, msgResponse def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: # We need to ignore the event so that validation and # saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self, dbmodel): ''' Executed once a record has been saved or updated. ''' self.saved_model = dbmodel def validate_all(self): """ Validate the entire form. :return: :rtype: """ errors = [] for attrMapper in self._attrMappers: error = self.validate(attrMapper) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) return errors def validate(self, attrMapper, update=False): """ Validate attribute. :param attrMapper: The attribute :type attrMapper: _AttributeMapper :param update: Whether the validation is on update or new entry :type update: Boolean :return: List of error messages or None :rtype: list or NoneType """ error = None field = attrMapper.pseudoName() column_name = attrMapper.attributeName() if column_name in self.entity.columns.keys(): column = self.entity.columns[column_name] else: return if column.unique: column_obj = getattr(self.entity_model, column_name, None) if not update: result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).first() else: id_obj = getattr(self.entity_model, 'id', None) result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).filter( id_obj != self.model().id).first() if result is not None: msg = QApplication.translate("MappedDialog", "field value should be unique.") error = '{} {}'.format(field, msg) if column.mandatory: if attrMapper.valueHandler().value() == \ attrMapper.valueHandler().default(): # Notify user msg = QApplication.translate("MappedDialog", "is a required field.") error = '{} {}'.format(field, msg) return error def submit(self, collect_model=False, save_and_new=False): """ Slot for saving or updating the model. This will close the dialog on successful submission. :param collect_model: If set to True only returns the model without saving it to the database. :type collect_model: Boolean :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ if not self.preSaveUpdate(): return self.clearNotifications() self.is_valid = True # Validate mandatory fields have been entered by the user. errors = [] for attrMapper in self._attrMappers: if self._mode == 'SAVE': error = self.validate(attrMapper) else: # update mode error = self.validate(attrMapper, True) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) if len(errors) > 0: self.is_valid = False if not self.is_valid: return # Bind model once all attributes are valid for attrMapper in self._attrMappers: attrMapper.set_model(self.model()) attrMapper.bindModel() if not collect_model: self._persistModel(save_and_new) def _persistModel(self, save_and_new): """ Saves the model to the database and shows a success message. :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ try: # Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() if not save_and_new: QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Saved"), QApplication.translate( "MappedDialog", "New record has been successfully saved.")) else: self._model.update() QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Updated"), QApplication.translate( "MappedDialog", "Record has been successfully updated.")) except Exception as ex: QMessageBox.critical( self, QApplication.translate("MappedDialog", "Data Operation Error"), QApplication.translate( "MappedDialog", u'The data could not be saved due to ' u'the error: \n{}'.format(ex.args[0]))) self.is_valid = False # Close the dialog if isinstance(self, QDialog) and self.is_valid: self.postSaveUpdate(self._model) if not save_and_new: self.accept() def clear(self): """ Clears the form values. """ for attrMapper in self._attrMappers: attrMapper.valueHandler().clear()
def __init__(self, entity, model=None, parent=None, manage_documents=True, collect_model=False, parent_entity=None, exclude_columns=[]): """ Class constructor. :param entity: Entity object corresponding to a table object. :type entity: Entity :param model: Data object for loading data into the form widgets. If the model is set, then the editor dialog is assumed to be in edit mode. :type model: object :param parent: Parent widget that the form belongs to. :type parent: QWidget :param manage_documents: True if the dialog should provide controls for managing supporting documents. Only applicable if the entity allows for supporting documents to be attached. :type manage_documents: bool :param collect_model: If set to True only returns the filled form model without saving it to the database. :type collect_model: Boolean :param parent_entity: The parent entity of the editor :type parent_entity: Object :param exclude_columns: List of columns to be excluded if in a list. :type exclude_columns: List :return: If collect_model, returns SQLAlchemy Model """ QDialog.__init__(self, parent) self.collection_suffix = self.tr('Collection') #Set minimum width self.setMinimumWidth(450) #Flag for mandatory columns self.has_mandatory = False self.reload_form = False self._entity = entity self.edit_model = model self.column_widgets = OrderedDict() self._parent = parent self.exclude_columns = exclude_columns self.entity_tab_widget = None self._disable_collections = False self.filter_val = None self.parent_entity = parent_entity self.child_models = OrderedDict() self.entity_scroll_area = None self.entity_editor_widgets = OrderedDict() #Set notification layout bar self.vlNotification = QVBoxLayout() self.vlNotification.setObjectName('vlNotification') self._notifBar = NotificationBar(self.vlNotification) # Set manage documents only if the entity supports documents if self._entity.supports_documents: self._manage_documents = manage_documents else: self._manage_documents = False # Setup entity model self._ent_document_model = None if self._entity.supports_documents: self.ent_model, self._ent_document_model = entity_model( self._entity, with_supporting_document=True) else: self.ent_model = entity_model(self._entity) if not model is None: self.ent_model = model MapperMixin.__init__(self, self.ent_model, entity) self.collect_model = collect_model self.register_column_widgets() try: if isinstance(parent._parent, EntityEditorDialog): # hide collections form child editor self._disable_collections = True except AttributeError: self._parent._parent = None self._init_gui() self.resize(430, 400) self._get_entity_editor_widgets() # Set title editor_trans = self.tr('Editor') title = u'{0} {1}'.format(format_name(self._entity.short_name), editor_trans) self.setWindowTitle(title) if isinstance(parent._parent, EntityEditorDialog): self.parent_entity = parent.parent_entity self.set_parent_values() # make the size smaller to differentiate from parent and as it # only has few tabs. self.resize(390, 400)
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 ValueEditor(QDialog, Ui_LookupValue): """ Form to add/edit values added to a lookup. Values are objects of type CodeValue """ def __init__(self, parent, lookup, code_value=None): """ :param parent: Owner of this dialog window :type parent: QWidget :param lookup: A value list object to add the value :type lookup: ValueList :param code_value: A value object to add to the lookup, if None this is a new value, else its an edit. :type code_value: CodeValue """ QDialog.__init__(self, parent) self.setupUi(self) self.lookup = lookup self.code_value = code_value self.notice_bar = NotificationBar(self.notif_bar) self.init_gui() def init_gui(self): """ initializes the form widgets """ if self.code_value: if self.code_value.updated_value == '': self.edtValue.setText(self.code_value.value) self.edtCode.setText(self.code_value.code) else: self.edtValue.setText(self.code_value.updated_value) self.edtCode.setText(self.code_value.updated_code) self.edtCode.textChanged.connect(self.validate_text) self.edtValue.textChanged.connect(self.validate_text) self.edtValue.setFocus() def show_notification(self, message): msg = self.tr(message) self.notice_bar.clear() self.notice_bar.insertErrorNotification(msg) def validate_text(self, text): """ Validates and updates the entered text if necessary. :param text: The text entered :type text: String """ text_edit = self.sender() cursor_position = text_edit.cursorPosition() text_edit.setValidator(None) if len(text) == 0: return name_regex = QtCore.QRegExp('^[ _0-9a-zA-Z][a-zA-Z0-9_/\\-()|.:,; ]*$') name_validator = QtGui.QRegExpValidator(name_regex) text_edit.setValidator(name_validator) QApplication.processEvents() last_character = text[-1:] state = name_validator.validate(text, text.index(last_character))[0] if state != QValidator.Acceptable: self.show_notification( '"{}" is not allowed at this position.'.format(last_character)) text = text[:-1] else: if len(text) > 1: if text[0] == ' ' or text[0] == '_': text = text[1:] self.blockSignals(True) text_edit.setText(text) text_edit.setCursorPosition(cursor_position) self.blockSignals(False) text_edit.setValidator(None) def add_value(self): """ Adds a code value to a lookup object. Checks first if a previous value exist then removes it and then adds the new one. """ value = unicode(self.edtValue.text().strip()) code = unicode(self.edtCode.text().strip()) # if its an edit, first remove the previous value if self.code_value: self.lookup.rename(self.code_value.value, value, code) else: self.lookup.add_code_value(CodeValue(code, value)) def accept(self): if self.edtValue.text() == '' or self.edtValue.text() == ' ': self.error_message( QApplication.translate("ValueEditor", "Please enter a valid lookup value.")) return self.add_value() self.done(1) def reject(self): self.done(0) def error_message(self, message): """ Creates a message box and displays a message :param message: message to display :type message: str """ msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("STDM") msg.setText(message) msg.exec_()
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()