# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved. # Julien Muchembled <jm@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsibility of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # guarantees and support are strongly adviced to contract a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # ############################################################################## from AccessControl import ClassSecurityInfo from Acquisition import aq_base from Products.ERP5Type import Permissions from Products.ERP5Type.Cache import transactional_cached from Products.ERP5.Document.Predicate import Predicate from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery @transactional_cached() def _getEffectiveModel(self, start_date=None, stop_date=None): """Return the most appropriate model using effective_date, expiration_date and version number. An effective model is a model which start and stop_date are equal (or excluded) to the range of the given start and stop_date and with the higher version number (if there is more than one) XXX Should we moved this function to a class ? Which one ? What about reusing IVersionable ? """ reference = self.getReference() if not reference: return self query_list = [Query(reference=reference), Query(portal_type=self.getPortalType()), Query(validation_state=('deleted', 'invalidated'), operator='NOT')] if start_date is not None: query_list.append(ComplexQuery(Query(effective_date=None), Query(effective_date=start_date, range='ngt'), logical_operator='OR')) if stop_date is not None: query_list.append(ComplexQuery(Query(expiration_date=None), Query(expiration_date=stop_date, range='min'), logical_operator='OR')) # XXX What to do the catalog returns nothing (either because 'self' was just # created and not yet indexed, or because it was invalidated) ? # For the moment, we raise. model_list = self.getPortalObject().portal_catalog.unrestrictedSearchResults( query=ComplexQuery(logical_operator='AND', *query_list), sort_on=(('version', 'descending'),)) return model_list[0].getObject() @transactional_cached() def _findPredicateList(*container_list): predicate_list = [] reference_dict = {} line_count = 0 for container in container_list: for ob in container.contentValues(): if isinstance(ob, Predicate): # reference is used to hide lines on farther containers reference = ob.getProperty('reference') if reference: reference_set = reference_dict.setdefault(ob.getPortalType(), set()) if reference in reference_set: continue reference_set.add(reference) id = str(line_count) line_count += 1 predicate_list.append(aq_base(ob.asContext(id=id))) return predicate_list class asComposedDocument(object): """Return a temporary object which is the composition of all effective models The returned value is a temporary copy of the given object. The list of all effective models (specialise values) is stored in a private attribute. Collecting predicates (from effective models) is done lazily. Predicates can be accessed through standard Folder API (ex: contentValues). """ def __new__(cls, orig_self): if '_effective_model_list' in orig_self.__dict__: assert False, "not used yet (remove this line if needed)" return orig_self # if asComposedDocument is called on a composed # document after any access to its subobjects self = orig_self.asContext() self._initBTrees() base_class = self.__class__ # this allows to intercept first access to '_folder_handler' self.__class__ = type(base_class.__name__, (cls, base_class), {'__module__': base_class.__module__}) self._effective_model_list = orig_self._findEffectiveSpecialiseValueList() return self def __init__(self, orig_self): # __new__ does not call __init__ because returned object # is wrapped in an acquisition context. assert False def asComposedDocument(self): assert False, "not used yet (remove this line if needed)" return self # if asComposedDocument is called on a composed # document before any access to its subobjects @property def _folder_handler(self): # restore the original class # because we don't need to hook _folder_handler anymore self.__class__ = self.__class__.__bases__[1] # we filter out objects without any subobject to make the cache of # '_findPredicateList' useful. Otherwise, the key would be always different # (starting with 'orig_self'). for ob in _findPredicateList(*filter(len, self._effective_model_list)): self._setOb(ob.id, ob) return self._folder_handler class CompositionMixin: """ """ # Declarative security security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareProtected(Permissions.AccessContentsInformation, 'asComposedDocument') asComposedDocument = transactional_cached()(asComposedDocument) # XXX add accessors to get properties from '_effective_model_list' ? # (cf PaySheetModel) def _findEffectiveSpecialiseValueList(self): """Return a list of effective specialised objects that is the inheritance tree. An effective object is an object which have start_date and stop_date included to the range of the given parameters start_date and stop_date. This algorithm uses Breadth First Search. """ start_date = self.getStartDate() stop_date = self.getStopDate() def getEffectiveModel(model): return _getEffectiveModel(model, start_date, stop_date) model_list = [self] model_set = set(model_list) model_index = 0 while model_index < len(model_list): model = model_list[model_index] model_index += 1 # we don't use getSpecialiseValueList to avoid acquisition on the parent for model in map(getEffectiveModel, model.getValueList('specialise')): if model not in model_set: model_set.add(model) if 1: #model.test(self): # XXX model_list.append(model) try: parent_asComposedDocument = self.getParentValue().asComposedDocument except AttributeError: pass else: model_list += [model for model in parent_asComposedDocument()._effective_model_list if model not in model_set] return model_list del asComposedDocument