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

        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)
Beispiel #4
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()
Beispiel #5
0
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()