class LicenseAgreement(WIDGET, BASE): def __init__(self, parent=None): """ This class checks if the user has accepted the license terms and conditions or not . It shows the terms and conditions if not. :param parent: The container of the dialog :type parent: QMainWindow or None :return: None :rtype: NoneType """ QDialog.__init__(self, parent) self.setupUi(self) self.reg_config = RegistryConfig() self.notice_bar = NotificationBar(self.notifBar) self.accepted = False self.btnAccept.clicked.connect(self.accept_action) self.btnDecline.clicked.connect(self.decline_action) self.label.setStyleSheet(''' QLabel { font: bold; } ''') def check_show_license(self): """ Checks if you need to show the license page. Checks if the flag in the registry has been set. Returns True to show license. If registry key is not yet set, show the license page. :rtype: boolean """ show_lic = 1 license_key = self.reg_config.read([SHOW_LICENSE]) if len(license_key) > 0: show_lic = license_key[SHOW_LICENSE] if show_lic == 1 or show_lic == str(1): return True elif show_lic == 0 or show_lic == str(0): self.accepted = True return False def show_license(self): """ Show STDM license window if the user have never accepted the license terms and conditions. :return: None :rtype: NoneType """ # validate if to show license show_lic = self.check_show_license() # THe user didn't accept license if show_lic: license = LicenseDocument() self.termsCondArea.setText(license.read_license_info()) self.exec_() def accept_action(self): """ A slot raised when the user clicks on the Accept button. :return: None :rtype: NoneType """ if not self.checkBoxAgree.isChecked(): msg = QApplication.translate( 'LicenseAgreement', 'To use STDM, please accept the terms ' 'and conditions by selecting' ' the checkbox "I have read and agree ..."') self.notice_bar.clear() self.notice_bar.insertNotification(msg, ERROR) return else: self.reg_config.write({SHOW_LICENSE: 0}) self.accepted = True self.close() def decline_action(self): """ A slot raised when the user clicks on the decline button. :return: None :rtype: NoneType """ self.accepted = False self.close()
class MapperMixin(object): ''' Mixin class for use in a dialog or widget, and manages attribute mapping. ''' def __init__(self, model, entity): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self.entity = entity self._attrMappers = [] self._attr_mapper_collection = {} self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None self.is_valid = False self.saved_model = None # Get document objects self.entity_model = entity_model(entity) self.entity_model_obj = self.entity_model() #Initialize notification bar if hasattr(self, "vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self, attributeName, control, isMandatory=False, pseudoname='', valueHandler=None, preloadfunc=None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName, control, self._model, pseudoname, isMandatory, valueHandler) self.addMapper(attrMapper, preloadfunc) def addMapper(self, attributeMapper, preloadfunc=None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) self._attr_mapper_collection[ attributeMapper.attributeName()] = attributeMapper def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def is_update_mode(self): """ :return: Returns True if the form is in UPDATE mode, otherwise False if in SAVE mode when creating a new record. :rtype: bool """ if self._mode == UPDATE: return True return False def attribute_mapper(self, attribute_name): """ Returns attribute mapper object corresponding to the the given attribute. :param attribute_name: Name of the attribute :type attribute_name: str :return: Attribute mapper :rtype: _AttributeMapper """ return self._attr_mapper_collection.get(attribute_name, None) def setSaveMode(self, mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self, stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self, message, mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate( "MappedDialog", "Would you like to save changes before closing?") msgResponse = QMessageBox.information( self, QApplication.translate("MappedDialog", "Save Changes"), msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) return isDirty, msgResponse def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self, dbmodel): ''' Executed once a record has been saved or updated. ''' self.saved_model = dbmodel self._post_save(self.saved_model) def _post_save(self, model): """ Enables sub-classes to incorporate additional logic after form data has been saved. """ pass def validate_all(self): """ Validate the entire form. :return: :rtype: """ errors = [] for attrMapper in self._attrMappers: error = self.validate(attrMapper) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) return errors def validate(self, attrMapper, update=False): """ Validate attribute. :param attrMapper: The attribute :type attrMapper: _AttributeMapper :param update: Whether the validation is on update or new entry :type update: Boolean :return: List of error messages or None :rtype: list or NoneType """ error = None field = attrMapper.pseudoName() column_name = attrMapper.attributeName() if column_name in self.entity.columns.keys(): column = self.entity.columns[column_name] else: return if column.unique: column_obj = getattr(self.entity_model, column_name, None) if not update: result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).first() else: id_obj = getattr(self.entity_model, 'id', None) result = self.entity_model_obj.queryObject().filter( column_obj == attrMapper.valueHandler().value()).filter( id_obj != self.model().id).first() if result is not None: msg = QApplication.translate("MappedDialog", "field value should be unique.") error = u'{} {}'.format(field, msg) if column.mandatory: if attrMapper.valueHandler().value() == \ attrMapper.valueHandler().default(): # Notify user msg = QApplication.translate("MappedDialog", "is a required field.") error = u'{} {}'.format(field, msg) return error def _custom_validate(self): # Sub-classes can implement custom validation logic. return True def submit(self, collect_model=False, save_and_new=False): """ Slot for saving or updating the model. This will close the dialog on successful submission. :param collect_model: If set to True only returns the model without saving it to the database. :type collect_model: Boolean :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ if not self.preSaveUpdate(): return self.clearNotifications() self.is_valid = True # Validate mandatory fields have been entered by the user. errors = [] for attrMapper in self._attrMappers: if self._mode == 'SAVE': error = self.validate(attrMapper) else: # update mode error = self.validate(attrMapper, True) if error is not None: self._notifBar.insertWarningNotification(error) errors.append(error) if len(errors) > 0 or not self._custom_validate(): self.is_valid = False if not self.is_valid: return # Bind model once all attributes are valid for attrMapper in self._attrMappers: control = attrMapper.valueHandler().control if isinstance(control, AutoGeneratedLineEdit): if control.column.prefix_source == control.column.columns_name: if attrMapper.valueHandler().value() is None: control.on_load_foreign_key_browser() attrMapper.set_model(self.model()) attrMapper.bindModel() if not collect_model: self._persistModel(save_and_new) def _persistModel(self, save_and_new): """ Saves the model to the database and shows a success message. :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ try: # Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() if not save_and_new: QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Saved"), QApplication.translate( "MappedDialog", "New record has been successfully saved.")) else: self._model.update() QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Updated"), QApplication.translate( "MappedDialog", "Record has been successfully updated.")) STDMDb.instance().session.flush() for attrMapper in self._attrMappers: control = attrMapper.valueHandler().control if isinstance(control, ExpressionLineEdit): value = control.on_expression_triggered() print attrMapper._attrName, value setattr(self.model(), attrMapper._attrName, value) self._model.update() # STDMDb.instance().session.flush() except Exception as ex: QMessageBox.critical( self, QApplication.translate("MappedDialog", "Data Operation Error"), QApplication.translate( "MappedDialog", u'The data could not be saved due to ' u'the error: \n{}'.format(ex.args[0]))) self.is_valid = False # Close the dialog if isinstance(self, QDialog) and self.is_valid: self.postSaveUpdate(self._model) if not save_and_new: self.accept() def clear(self): """ Clears the form values. """ for attrMapper in self._attrMappers: attrMapper.valueHandler().clear()
class 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 MapperMixin(object): ''' Mixin class for use in a dialog or widget, and manages attribute mapping. ''' def __init__(self, model): ''' :param model: Callable (new instances) or instance (existing instance for updating) of STDM model. ''' if callable(model): self._model = model() self._mode = SAVE else: self._model = model self._mode = UPDATE self._attrMappers = [] self._attr_mapper_collection = {} self._dirtyTracker = ControlDirtyTrackerCollection() self._notifBar = None self.is_valid = False #Initialize notification bar if hasattr(self, "vlNotification"): self._notifBar = NotificationBar(self.vlNotification) #Flag to indicate whether to close the widget or dialog once model has been submitted #self.closeOnSubmit = True def addMapping(self, attributeName, control, isMandatory=False, pseudoname='', valueHandler=None, preloadfunc=None): ''' Specify the mapping configuration. ''' attrMapper = _AttributeMapper(attributeName, control, self._model, pseudoname, isMandatory, valueHandler) self.addMapper(attrMapper, preloadfunc) def addMapper(self, attributeMapper, preloadfunc=None): ''' Add an attributeMapper object to the collection. Preloadfunc specifies a function that can be used to prepopulate the control's value only when the control is on SAVE mode. ''' if self._mode == SAVE and preloadfunc != None: attributeMapper.valueHandler().setValue(preloadfunc) if self._mode == UPDATE: #Set control value based on the model attribute value attributeMapper.bindControl() #Add control to dirty tracker collection after control value has been set self._dirtyTracker.addControl(attributeMapper.control(), attributeMapper.valueHandler()) self._attrMappers.append(attributeMapper) self._attr_mapper_collection[ attributeMapper.attributeName()] = attributeMapper def saveMode(self): ''' Return the mode that the mapper is currently configured in. ''' return self._mode def attribute_mapper(self, attribute_name): """ Returns attribute mapper object corresponding to the the given attribute. :param attribute_name: Name of the attribute :type attribute_name: str :return: Attribute mapper :rtype: _AttributeMapper """ return self._attr_mapper_collection.get(attribute_name, None) def setSaveMode(self, mode): ''' Set the mapper's save mode. ''' self._mode = mode def setModel(self, stdmModel): ''' Set the model to be used by the mapper. ''' self._model = stdmModel def model(self): ''' Returns the model configured for the mapper. ''' return self._model def setNotificationLayout(self, layout): ''' Set the vertical layout instance that will be used to display notification messages. ''' self._notifBar = NotificationBar(layout) def insertNotification(self, message, mtype): ''' There has to be a vertical layout, named 'vlNotification', that enables for notifications to be inserted. ''' if self._notifBar: self._notifBar.insertNotification(message, mtype) def clearNotifications(self): ''' Clears all messages in the notification bar. ''' if self._notifBar: self._notifBar.clear() def checkDirty(self): ''' Asserts whether the dialog contains dirty controls. ''' isDirty = False msgResponse = None if self._dirtyTracker.isDirty(): isDirty = True msg = QApplication.translate( "MappedDialog", "Would you like to save changes before closing?") msgResponse = QMessageBox.information( self, QApplication.translate("MappedDialog", "Save Changes"), msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) return isDirty, msgResponse def closeEvent(self, event): ''' Raised when a request to close the window is received. Check the dirty state of input controls and prompt user to save if dirty. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: # We need to ignore the event so that validation and # saving operations can be executed event.ignore() self.submit() elif userResponse == QMessageBox.No: event.accept() elif userResponse == QMessageBox.Cancel: event.ignore() else: event.accept() def cancel(self): ''' Slot for closing the dialog. Checks the dirty state first before closing. ''' isDirty, userResponse = self.checkDirty() if isDirty: if userResponse == QMessageBox.Yes: self.submit() elif userResponse == QMessageBox.No: self.reject() elif userResponse == QMessageBox.Cancel: pass else: self.reject() def preSaveUpdate(self): ''' Mixin classes can override this method to specify any operations that need to be executed prior to saving or updating the model's values. It should return True prior to saving. ''' return True def postSaveUpdate(self, dbmodel): ''' Executed once a record has been saved or updated. ''' pass def submit(self, collect_model=False, save_and_new=False): """ Slot for saving or updating the model. This will close the dialog on successful submission. :param collect_model: If set to True only returns the model without saving it to the database. :type collect_model: Boolean :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ if not self.preSaveUpdate(): return self.clearNotifications() self.is_valid = True # Validate mandatory fields have been entered by the user. for attrMapper in self._attrMappers: if attrMapper.isMandatory() and \ attrMapper.valueHandler().supportsMandatory(): if attrMapper.valueHandler().value() == \ attrMapper.valueHandler().default(): #Notify user msg = QApplication.translate("MappedDialog", "'%s' is a required field.")\ %unicode(attrMapper.pseudoName()) self._notifBar.insertWarningNotification(msg) self.is_valid = False if not self.is_valid: return # Bind model once all attributes are valid for attrMapper in self._attrMappers: attrMapper.set_model(self.model()) attrMapper.bindModel() if not collect_model: self._persistModel(save_and_new) def _persistModel(self, save_and_new): """ Saves the model to the database and shows a success message. :param save_and_new: A Boolean indicating it is triggered by save and new button. :type save_and_new: Boolean """ try: # Persist the model to its corresponding store. if self._mode == SAVE: self._model.save() if not save_and_new: QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Saved"), QApplication.translate( "MappedDialog", "New record has been successfully saved.")) else: self._model.update() QMessageBox.information( self, QApplication.translate("MappedDialog", "Record Updated"), QApplication.translate( "MappedDialog", "Record has been successfully updated.")) except Exception as ex: QMessageBox.critical( self, QApplication.translate("MappedDialog", "Data Operation Error"), QApplication.translate( "MappedDialog", u'The data could not be saved due to ' u'the error: \n{}'.format(ex.args[0]))) self.is_valid = False # Close the dialog if isinstance(self, QDialog) and self.is_valid: self.postSaveUpdate(self._model) if not save_and_new: self.accept() def clear(self): """ Clears the form values. """ for attrMapper in self._attrMappers: attrMapper.valueHandler().clear()
class 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()