# ============================================================================
#
# Copyright (C) 2007-2016 Conceptive Engineering bvba.
# www.conceptive.be / info@conceptive.be
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Conceptive Engineering nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ============================================================================
import copy
import logging
logger = logging.getLogger('camelot.admin.validator.object_validator')
import six
from ...core.qt import QtCore
from camelot.core.utils import ugettext as _
[docs]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
`validate_object` method to change it's behaviour.
"""
validity_changed_signal = QtCore.qt_signal(int)
def __init__(self, admin, model = None):
"""
:param model: a collection proxy the validator should inspect, or None
if only the `validate_object` 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._invalid_rows = dict()
self._related_validators = dict()
self._all_fields = None
self._all_field_field_attributes = dict()
[docs] def validate_all_rows(self):
"""Force validation of all rows in the model"""
for row in range(self.model.rowCount()):
self.isValid(row)
def validate_invalid_rows(self):
for row in copy.copy(six.iterkeys(self._invalid_rows)):
self.isValid(row)
[docs] def validate_object( self, obj ):
"""
:return: list of messages explaining invalid data, an empty list if
the object is valid
"""
from camelot.view.controls import delegates
messages = []
#
# initialize cached static field attributes on first use
#
if self._all_fields is None:
self._all_fields = [fn for fn,_fa in six.iteritems(self.admin.get_all_fields_and_attributes())]
for field_name, static_fa in zip(self._all_fields, self.admin.get_static_field_attributes(self._all_fields)):
self._all_field_field_attributes[field_name] = static_fa
#
# get dynamic field attributes on each use
#
for field_name, dynamic_fa in zip(self._all_fields, self.admin.get_dynamic_field_attributes(obj, self._all_fields)):
self._all_field_field_attributes[field_name].update(dynamic_fa)
for field, attributes in six.iteritems(self._all_field_field_attributes):
# if the field was not editable, don't waste any time
if attributes.get('editable', False):
# if the field, is nullable, don't waste time getting its value
if attributes.get('nullable', True) != True:
value = getattr(obj, 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 or issubclass(attributes['delegate'],delegates.CodeDelegate)) and \
(sum(len(c) for c in value) == 0):
is_null = True
elif (attributes['delegate'] == delegates.PlainTextDelegate or issubclass(attributes['delegate'],delegates.PlainTextDelegate)) and (len(value) == 0):
is_null = True
elif (attributes['delegate'] == delegates.LocalFileDelegate or issubclass(attributes['delegate'],delegates.LocalFileDelegate)) and (len(value) == 0):
is_null = True
elif (attributes['delegate'] == delegates.VirtualAddressDelegate or issubclass(attributes['delegate'],delegates.VirtualAddressDelegate)) and (not value[1]):
is_null = True
if is_null:
messages.append(_(u'%s is a required field') % (attributes['name']))
if not len( messages ):
# if the object itself is valid, dig deeper within the compounding
# objects
for compound_obj in self.admin.get_compounding_objects( obj ):
related_validator = self.get_related_validator( type( compound_obj ) )
messages.extend( related_validator.validate_object( compound_obj ) )
logger.debug(u'messages : %s'%(u','.join(messages)))
return messages
[docs] 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)
[docs] def get_first_invalid_row(self):
"""
:return: the row number of the first invalid row (where the first row
has number 0)
"""
return min(six.iterkeys(self._invalid_rows))
def get_messages(self, row):
return self._invalid_rows.get(row, [])
[docs] 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 is not None:
messages = self.validate_object(entity_instance)
except Exception as e:
logger.error(
'programming error while validating object',
exc_info=e
)
valid = (len(messages) == 0)
# check the status of the row before modifiying the
# invalid rows
row_in_invalid_rows = (row in self._invalid_rows)
if not valid:
self._invalid_rows[row] = messages
else:
self._invalid_rows.pop(row, None)
# check the status after modifying the invalid rows and emit a signal
# if the status of the row has changed
if row_in_invalid_rows != (row in self._invalid_rows):
self.validity_changed_signal.emit(row)
logger.debug('valid : %s' % valid)
return valid