Source code for camelot.admin.validator.object_validator
# ============================================================================
#
# Copyright (C) 2007-2013 Conceptive Engineering bvba. All rights reserved.
# www.conceptive.be / info@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 info@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
# info@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 _
[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
objectValidity method to change it's behaviour.
"""
validity_changed_signal = QtCore.pyqtSignal(int)
def __init__(self, admin, model = None, initial_validation = False):
"""
:param model: 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()
self._related_validators = dict()
if initial_validation:
post(self.validate_all_rows)
[docs] 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()))
[docs] def objectValidity(self, entity_instance):
"""deprecated, use `validate_object` instead
"""
return self.validate_object( entity_instance )
[docs] def validate_object( self, obj ):
""":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, is nullable, don't waste time getting its value
if attributes['nullable'] != 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) 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 (value == None):
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']))
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 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 != None:
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'
)
[docs] 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
)