Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
 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)
Exemplo n.º 3
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()
Exemplo n.º 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()
Exemplo n.º 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()