### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

# Source code for camelot.admin.validator.object_validator

    #  ============================================================================
    #
    #  Copyright (C) 2007-2011 Conceptive Engineering bvba. All rights reserved.
    #  www.conceptive.be / project-camelot@conceptive.be
    #
    #  This file is part of the Camelot Library.
    #
    #  This file may be used under the terms of the GNU General Public
    #  License version 2.0 as published by the Free Software Foundation
    #  and appearing in the file license.txt included in the packaging of
    #  this file.  Please review this information to ensure GNU
    #  General Public Licensing requirements will be met.
    #
    #  If you are unsure which license is appropriate for your use, please
    #  visit www.python-camelot.com or contact project-camelot@conceptive.be
    #
    #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    #
    #  For use of this library in commercial applications, please contact
    #  project-camelot@conceptive.be
    #
    #  ============================================================================

    from copy import copy
    import logging
    logger = logging.getLogger('camelot.admin.validator.object_validator')

    from PyQt4 import QtCore

    from camelot.view.fifo import Fifo
    from camelot.view.model_thread import post
    from camelot.core.utils import ugettext as _

    class ObjectValidator(QtCore.QObject):
        """A validator class for normal python objects.  By default this validator
        declares all objects valid.  Subclass this class and overwrite it's
        objectValidity method to change it's behaviour.
        """

        validity_changed_signal = QtCore.pyqtSignal(int)

        def __init__(self, admin, model, initial_validation=False):
            """
            :param mode: a collection proxy the validator should inspect, or None if only the objectValidity method is going
            to get used.
            :param verifiy_initial_validity: do an inital check to see if all rows in a model are valid, defaults to False,
            since this might take a lot of time on large collections.
            """
            super(ObjectValidator, self).__init__()
            self.admin = admin
            self.model = model
            self.message_cache = Fifo(10)
            if model:
                model.dataChanged.connect( self.data_changed )
                model.layoutChanged.connect( self.layout_changed )
            self._invalid_rows = set()

            if initial_validation:
                post(self.validate_all_rows)

        def validate_all_rows(self):
            """Force validation of all rows in the model"""
            for row in range(self.model.getRowCount()):
                self.isValid(row)

        def validate_invalid_rows(self):
            for row in copy(self._invalid_rows):
                self.isValid(row)

        @QtCore.pyqtSlot()
        def layout_changed(self):
            post(self.validate_invalid_rows)

        @QtCore.pyqtSlot( QtCore.QModelIndex, QtCore.QModelIndex )
        def data_changed(self, from_index, thru_index):

            def create_validity_updater(from_row, thru_row):

                def validity_updater():
                    for i in range(from_row, thru_row+1):
                        self.isValid(i)

                return validity_updater

            post(create_validity_updater(from_index.row(), thru_index.row()))

        def objectValidity(self, entity_instance):
            """:return: list of messages explaining invalid data
            empty list if object is valid
            """
            from camelot.view.controls import delegates
            messages = []
            fields_and_attributes = dict(self.admin.get_columns())
            fields_and_attributes.update(dict(self.admin.get_fields()))
            for field, attributes in fields_and_attributes.items():
                # if the field was not editable, don't waste any time
                if attributes['editable']:
                    #
                    # If the field embeds another object, that object should be valid as well
                    #
                    if attributes.get('embedded', False) and attributes.get('target', False):
                        value = getattr(entity_instance, field)
                        if value:
                            target_admin = self.admin.get_related_entity_admin(attributes['target'])
                            target_validator = target_admin.create_validator(None)
                            messages.extend( target_validator.objectValidity(value) )
                            continue
                    # if the field, is nullable, don't waste time getting its value
                    # @todo: check if field is a primary key instead of checking
                    # whether the name is id, but this should only happen in the entity validator
                    if attributes['nullable']!=True and field!='id':
                        value = getattr(entity_instance, field)
                        logger.debug('column %s is required'%(field))
                        if 'delegate' not in attributes:
                            raise Exception('no delegate specified for %s'%(field))
                        is_null = False
                        if value==None:
                            is_null = True
                        elif (attributes['delegate'] == delegates.CodeDelegate) and \
                             (sum(len(c) for c in value) == 0):
                            is_null = True
                        elif (attributes['delegate'] == delegates.PlainTextDelegate) and (len(value) == 0):
                            is_null = True
                        elif (attributes['delegate'] == delegates.Many2OneDelegate) and (not value.id):
                            is_null = True
                        elif (attributes['delegate'] == delegates.VirtualAddressDelegate) and (not value[1]):
                            is_null = True
                        if is_null:
                            messages.append(_(u'%s is a required field') % (attributes['name']))
            logger.debug(u'messages : %s'%(u','.join(messages)))
            return messages

        def number_of_invalid_rows(self):
            """
            :return: the number of invalid rows in a model, as they have been verified
            """
            return len(self._invalid_rows)

        def isValid(self, row):
            """Verify if a row in a model is 'valid' meaning it could be flushed to
            the database
            """
            messages = []
            logger.debug('isValid for row %s' % row)
            try:
                entity_instance = self.model._get_object(row)
                if entity_instance:
                    messages = self.objectValidity(entity_instance)
                    self.message_cache.add_data(row, entity_instance, messages)
            except Exception, e:
                logger.error(
                    'programming error while validating object',
                    exc_info=e
                )
            valid = (len(messages) == 0)
            if not valid:
                if row not in self._invalid_rows:
                    self._invalid_rows.add(row)
                    self.validity_changed_signal.emit( row )
            elif row in self._invalid_rows:
                self._invalid_rows.remove(row)
                self.validity_changed_signal.emit( row )
            logger.debug('valid : %s' % valid)
            return valid

        def validityMessages(self, row):
            try:
                return self.message_cache.get_data_at_row(row)
            except KeyError:
                raise Exception(
                    'Programming error : isValid should be called '
                    'before calling validityMessage'
                )

        def validityDialog(self, row, parent):
            """Return a QDialog that asks the user to discard his changes or
            continue to edit the row until it is valid.
            """
            from PyQt4 import QtGui
            return QtGui.QMessageBox(
                QtGui.QMessageBox.Warning,
                _('Invalid form'),
                '\n'.join(self.validityMessages(row)),
                QtGui.QMessageBox.Ok | QtGui.QMessageBox.Discard,
                parent
            )

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

Comments
blog comments powered by Disqus