Last chunk of portal type classes / zodb property sheets.
[erp5.git] / product / ERP5Type / Base.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2002-2003 Nexedi SARL and Contributors. All Rights Reserved.
5 # Jean-Paul Smets-Solanes <jp@nexedi.com>
6 #
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
12 # Service Company
13 #
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 #
28 ##############################################################################
29
30 from struct import unpack
31 from copy import copy
32 import warnings
33 import types
34 import threading
35
36 from Products.ERP5Type.Globals import InitializeClass, DTMLFile
37 from AccessControl import ClassSecurityInfo
38 from AccessControl.Permission import pname, Permission
39 from AccessControl.PermissionRole import rolesForPermissionOn
40 from AccessControl.SecurityManagement import getSecurityManager
41 from AccessControl.ZopeGuards import guarded_getattr
42 from Acquisition import aq_base, aq_inner, aq_acquire, aq_chain
43 import OFS.History
44 from OFS.SimpleItem import SimpleItem
45 from OFS.PropertyManager import PropertyManager
46 from zExceptions import NotFound, Unauthorized
47
48 from ZopePatch import ERP5PropertyManager
49
50 from Products.CMFCore.PortalContent import PortalContent
51 from Products.CMFCore.Expression import Expression
52 from Products.CMFCore.utils import getToolByName
53 from Products.CMFCore.WorkflowCore import ObjectDeleted, ObjectMoved
54 from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
55
56 from Products.DCWorkflow.Transitions import TRIGGER_WORKFLOW_METHOD, TRIGGER_USER_ACTION
57
58 from Products.ERP5Type import _dtmldir
59 from Products.ERP5Type import PropertySheet
60 from Products.ERP5Type import interfaces
61 from Products.ERP5Type import Permissions
62 from Products.ERP5Type.Utils import UpperCase
63 from Products.ERP5Type.Utils import convertToUpperCase, convertToMixedCase
64 from Products.ERP5Type.Utils import createExpressionContext
65 from Products.ERP5Type.Accessor.Accessor import Accessor
66 from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
67 from Products.ERP5Type.Accessor.TypeDefinition import list_types
68 from Products.ERP5Type.Accessor import Base as BaseAccessor
69 from Products.ERP5Type.mixin.property_translatable import PropertyTranslatableBuiltInDictMixIn
70 from Products.ERP5Type.XMLExportImport import Base_asXML
71 from Products.ERP5Type.Cache import CachingMethod, clearCache, getReadOnlyTransactionCache
72 from Accessor import WorkflowState
73 from Products.ERP5Type.Log import log as unrestrictedLog
74 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
75 from Products.ERP5Type.Accessor.TypeDefinition import type_definition
76
77 from CopySupport import CopyContainer, CopyError,\
78 tryMethodCallWithTemporaryPermission
79 from Errors import DeferredCatalogError, UnsupportedWorkflowMethod
80 from Products.CMFActivity.ActiveObject import ActiveObject
81 from Products.ERP5Type.Accessor.Accessor import Accessor as Method
82 from Products.ERP5Type.Accessor.TypeDefinition import asDate
83 from Products.ERP5Type.Message import Message
84 from Products.ERP5Type.ConsistencyMessage import ConsistencyMessage
85 from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
86
87 from zope.interface import classImplementsOnly, implementedBy
88
89 from string import join
90 import sys, re
91 from Products.ERP5Type.PsycoWrapper import psyco
92
93 from cStringIO import StringIO
94 from socket import gethostname, gethostbyaddr
95 import random
96
97 import inspect
98 from pprint import pformat
99
100 import zope.interface
101
102 from ZODB.POSException import ConflictError
103 from zLOG import LOG, INFO, ERROR, WARNING
104
105 _MARKER = []
106
107 global registered_workflow_method_set
108 wildcard_interaction_method_id_match = re.compile(r'[[.?*+{(\\]').search
109 workflow_method_registry = [] # XXX A set() would be better but would require a hash in WorkflowMethod class
110
111 def resetRegisteredWorkflowMethod(portal_type=None):
112 """
113 TODO: unwrap workflow methos which were standard methods initially
114 """
115 for method in workflow_method_registry:
116 method.reset(portal_type=portal_type)
117
118 class WorkflowMethod(Method):
119
120 def __init__(self, method, id=None, reindex=1):
121 """
122 method - a callable object or a method
123
124 id - the workflow transition id. This is useful
125 to emulate "old" CMF behaviour but is
126 somehow inconsistent with the new registration based
127 approach implemented here.
128
129 We store id as _transition_id and use it
130 to register the transition for each portal
131 type and each workflow for which it is
132 applicable.
133 """
134 self._m = method
135 if id is None:
136 self._transition_id = method.__name__
137 else:
138 self._transition_id = id
139 # Only publishable methods can be published as interactions
140 # A pure private method (ex. _doNothing) can not be published
141 # This is intentional to prevent methods such as submit, share to
142 # be called from a URL. If someone can show that this way
143 # is wrong (ex. for remote operation of a site), let us know.
144 if not method.__name__.startswith('_'):
145 self.__name__ = method.__name__
146 for func_id in ['func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']:
147 setattr(self, func_id, getattr(method, func_id, None))
148 self._invoke_once = {}
149 self._invoke_always = {} # Store in a dict all workflow IDs which require to
150 # invoke wrapWorkflowMethod at every call
151 # during the same transaction
152
153 def getTransitionId(self):
154 return self._transition_id
155
156 def __call__(self, instance, *args, **kw):
157 """
158 Invoke the wrapped method, and deal with the results.
159 """
160 if getattr(self, '__name__', None) in ('getPhysicalPath', 'getId'):
161 # To prevent infinite recursion, 2 methods must have special treatment
162 # this is clearly not the best way to implement this but it is
163 # already better than what we had. I (JPS) would prefer to use
164 # critical sections in this part of the code and a
165 # thread variable which tells in which semantic context the code
166 # should ne executed. - XXX
167 return self._m(instance, *args, **kw)
168
169 # New implementation does not use any longer wrapWorkflowMethod
170 # but directly calls the workflow methods
171 try:
172 wf = getattr(instance.getPortalObject(), 'portal_workflow')
173 except AttributeError:
174 # XXX instance is unwrapped(no acquisition)
175 # XXX I must think that what is a correct behavior.(Yusei)
176 return self._m(instance, *args, **kw)
177
178 # Build a list of transitions which may need to be invoked
179 instance_path = instance.getPhysicalPath()
180 portal_type = instance.portal_type
181 transactional_variable = getTransactionalVariable()
182 invoke_once_dict = self._invoke_once.get(portal_type, {})
183 valid_invoke_once_item_list = []
184 # Only keep those transitions which were never invoked
185 for wf_id, transition_list in invoke_once_dict.iteritems():
186 valid_transition_list = []
187 for transition_id in transition_list:
188 once_transition_key = ('Products.ERP5Type.Base.WorkflowMethod.__call__',
189 wf_id, transition_id, instance_path)
190 try:
191 already_called_transition = transactional_variable[once_transition_key]
192 except KeyError:
193 already_called_transition = 0
194 transactional_variable[once_transition_key] = 1
195 if not already_called_transition:
196 valid_transition_list.append(transition_id)
197 if valid_transition_list:
198 valid_invoke_once_item_list.append((wf_id, valid_transition_list))
199 candidate_transition_item_list = valid_invoke_once_item_list + \
200 self._invoke_always.get(portal_type, {}).items()
201
202 #LOG('candidate_transition_item_list %s' % self.__name__, 0, str(candidate_transition_item_list))
203
204 # Try to return immediately if there are no transition to invoke
205 if not candidate_transition_item_list:
206 return apply(self.__dict__['_m'], (instance,) + args, kw)
207
208 # Prepare a list of transitions which should be invoked.
209 # This list is based on the results of isWorkflowMethodSupported.
210 # An interaction is ignored if the guard prevents execution.
211 # Otherwise, an exception is raised if the workflow transition does not
212 # exist from the current state, or if the guard rejects it.
213 valid_transition_item_list = []
214 for wf_id, transition_list in candidate_transition_item_list:
215 candidate_workflow = wf[wf_id]
216 valid_list = []
217 for transition_id in transition_list:
218 if candidate_workflow.isWorkflowMethodSupported(instance, transition_id):
219 valid_list.append(transition_id)
220 elif candidate_workflow.__class__.__name__ == 'DCWorkflowDefinition':
221 raise UnsupportedWorkflowMethod(instance, wf_id, transition_id)
222 # XXX Keep the log for projects that needs to comment out
223 # the previous line.
224 LOG("WorkflowMethod.__call__", ERROR,
225 "Transition %s/%s on %r is ignored. Current state is %r."
226 % (wf_id, transition_id, instance,
227 candidate_workflow._getWorkflowStateOf(instance, id_only=1)))
228 if valid_list:
229 valid_transition_item_list.append((wf_id, valid_list))
230
231 #LOG('valid_transition_item_list %s' % self.__name__, 0, str(valid_transition_item_list))
232
233 # Call whatever must be called before changing states
234 for wf_id, transition_list in valid_transition_item_list:
235 wf[wf_id].notifyBefore(instance, transition_list, args=args, kw=kw)
236
237 # Compute expected result
238 result = apply(self.__dict__['_m'], (instance,) + args, kw)
239
240 # Change the state of statefull workflows
241 for wf_id, transition_list in valid_transition_item_list:
242 try:
243 wf[wf_id].notifyWorkflowMethod(instance, transition_list, args=args, kw=kw)
244 except ObjectDeleted:
245 # Re-raise with a different result.
246 raise ObjectDeleted(result)
247 except ObjectMoved, ex:
248 # Re-raise with a different result.
249 raise ObjectMoved(ex.getNewObject(), result)
250
251 # Call whatever must be called after changing states
252 for wf_id, transition_list in valid_transition_item_list:
253 wf[wf_id].notifySuccess(instance, transition_list, result, args=args, kw=kw)
254
255 # Return result finally
256 return result
257
258 def registerTransitionAlways(self, portal_type, workflow_id, transition_id):
259 """
260 Transitions registered as always will be invoked always
261 """
262 transition_list = self._invoke_always.setdefault(portal_type, {}).setdefault(workflow_id, [])
263 if transition_id not in transition_list: transition_list.append(transition_id)
264 self.register()
265
266 def registerTransitionOncePerTransaction(self, portal_type, workflow_id, transition_id):
267 """
268 Transitions registered as one per transactions will be invoked
269 only once per transaction
270 """
271 transition_list = self._invoke_once.setdefault(portal_type, {}).setdefault(workflow_id, [])
272 if transition_id not in transition_list: transition_list.append(transition_id)
273 self.register()
274
275 def register(self):
276 """
277 Registers the method so that _aq_reset may later reset it
278 """
279 workflow_method_registry.append(self)
280
281 def reset(self, portal_type=None):
282 """
283 Reset the list of registered interactions or transitions
284 """
285 if portal_type:
286 self._invoke_once[portal_type] = {}
287 self._invoke_always[portal_type] = {}
288 else:
289 self._invoke_once = {}
290 self._invoke_always = {}
291
292 def _aq_reset():
293 # using clear to prevent changing the reference
294 Base.aq_method_generated.clear()
295 Base.aq_portal_type.clear()
296 Base.aq_related_generated = 0
297 try:
298 from Products.ERP5Form.PreferenceTool import PreferenceTool
299 PreferenceTool.aq_preference_generated = False
300 except ImportError:
301 LOG('ERP5Type', LOG, "ERP5Form.PreferenceTool not found")
302
303 # Some method generations are based on portal methods, and portal methods cache results.
304 # So it is safer to invalidate the cache.
305 clearCache()
306
307 # Reset workflow methods so that they no longer invoke workflows
308 resetRegisteredWorkflowMethod()
309
310 global method_registration_cache
311 method_registration_cache = {}
312
313
314 class PropertyHolder(object):
315 isRADContent = 1
316 WORKFLOW_METHOD_MARKER = ('Base._doNothing',)
317 RESERVED_PROPERTY_SET = set(('_constraints', '_properties', '_categories',
318 '__implements__', 'property_sheets',
319 '__ac_permissions__',
320 '_erp5_properties'))
321
322 def __init__(self, name='PropertyHolder'):
323 self.__name__ = name
324 self.security = ClassSecurityInfo() # We create a new security info object
325 self.workflow_method_registry = {}
326
327 self._categories = []
328 self._properties = []
329 self._constraints = []
330 self.constraints = []
331
332 def _getPropertyHolderItemList(self):
333 return [x for x in self.__dict__.items() if x[0] not in
334 PropertyHolder.RESERVED_PROPERTY_SET]
335
336 # Accessor generation
337 def createAccessor(self, id):
338 """
339 Invokes appropriate factory and create an accessor
340 """
341 fake_accessor = getattr(self, id)
342 ptype = getattr(self, '_portal_type', None)
343 if ptype is None:
344 ptype = self.portal_type
345 if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER:
346 # Case 1 : a workflow method only
347 accessor = Base._doNothing
348 else:
349 # Case 2 : a workflow method over an accessor
350 (accessor_class, accessor_args, key) = fake_accessor
351 accessor = accessor_class(id, key, *accessor_args)
352 for wf_id, tr_id, once in self.workflow_method_registry.get(id, ()):
353 if not isinstance(accessor, WorkflowMethod):
354 accessor = WorkflowMethod(accessor)
355 if once:
356 accessor.registerTransitionOncePerTransaction(ptype, wf_id, tr_id)
357 else:
358 accessor.registerTransitionAlways(ptype, wf_id, tr_id)
359 else:
360 if once:
361 accessor.registerTransitionOncePerTransaction(ptype, wf_id, tr_id)
362 else:
363 accessor.registerTransitionAlways(ptype, wf_id, tr_id)
364 setattr(self, id, accessor)
365 return accessor
366
367 def registerAccessor(self, id, key, accessor_class, accessor_args):
368 """
369 Saves in a dictionary all parameters required to create an accessor
370 The goal here is to minimize memory occupation. We have found the following:
371
372 - the size of a tuple with simple types and the size
373 of None are the same (a pointer)
374
375 - the size of a pointer to a class is the same as the
376 size of None
377
378 - the python caching system for tuples is efficient for tuples
379 which contain simple types (string, int, etc.) but innefficient
380 for tuples which contain a pointer
381
382 - as a result, it is better to create separate dicts if
383 values contain pointers and single dict if value is
384 a tuple of simple types
385
386 Parameters:
387
388 id -- The id the accessor (ex. getFirstName)
389
390 key -- The id of the property (ex. first_name) or the id of the
391 method for Alias accessors
392 """
393 #LOG('registerAccessor', 0, "%s %s %s" % (id , self._portal_type, accessor_args))
394 # First we try to compress the information required
395 # to build a new accessor in such way that
396 # if the same information is provided twice, we
397 # shall keep it once only
398 new_accessor_args = []
399 for arg in accessor_args:
400 if type(arg) is types.ListType:
401 new_accessor_args.append(tuple(arg))
402 else:
403 new_accessor_args.append(arg)
404 accessor_args = tuple(new_accessor_args)
405 original_registration_tuple = (accessor_class, accessor_args, key)
406 registration_tuple = method_registration_cache.get(original_registration_tuple)
407 if registration_tuple is None:
408 registration_tuple = original_registration_tuple
409 method_registration_cache[registration_tuple] = registration_tuple
410 # Use the cached tuple (same value, different pointer)
411 setattr(self, id, registration_tuple)
412
413 def registerWorkflowMethod(self, id, wf_id, tr_id, once_per_transaction=0):
414 #LOG('registerWorkflowMethod', 0, "%s %s %s %s %s" % (self._portal_type, id, wf_id, tr_id, once_per_transaction))
415 signature = (wf_id, tr_id, once_per_transaction)
416 signature_list = self.workflow_method_registry.get(id, ())
417 if signature not in signature_list:
418 self.workflow_method_registry[id] = signature_list + (signature,)
419 if getattr(self, id, None) is None:
420 setattr(self, id, PropertyHolder.WORKFLOW_METHOD_MARKER)
421 self.createAccessor(id)
422
423 def declareProtected(self, permission, accessor_name):
424 """
425 It is possible to gain 30% of accessor RAM footprint
426 by postponing security declaration.
427
428 WARNING: we optimize size by not setting security if
429 it is the same as the default. This may be a bit
430 dangerous if classes use another default
431 security.
432 """
433 if permission not in (Permissions.AccessContentsInformation, Permissions.ModifyPortalContent):
434 self.security.declareProtected(permission, accessor_name)
435
436 # Inspection methods
437 def getAccessorMethodItemList(self):
438 """
439 Return a list of tuple (id, method) for every accessor
440 """
441 accessor_method_item_list = []
442 accessor_method_item_list_append = accessor_method_item_list.append
443 for x, y in self._getPropertyHolderItemList():
444 if isinstance(y, tuple):
445 if y is PropertyHolder.WORKFLOW_METHOD_MARKER or x == '__ac_permissions__':
446 continue
447 if len(y) == 0:
448 raise ValueError("problem at %s %s" % (self._portal_type, x))
449 if not issubclass(y[0], Accessor):
450 continue
451 elif not isinstance(y, Accessor):
452 continue
453 accessor_method_item_list_append((x, y))
454 return accessor_method_item_list
455
456 def getAccessorMethodIdList(self):
457 """
458 Return the list of accessor IDs
459 """
460 return [ x[0] for x in self.getAccessorMethodItemList() ]
461
462 def getWorkflowMethodItemList(self):
463 """
464 Return a list of tuple (id, method) for every workflow method
465 """
466 return [x for x in self._getPropertyHolderItemList() if isinstance(x[1], WorkflowMethod)
467 or (isinstance(x[1], types.TupleType)
468 and x[1] is PropertyHolder.WORKFLOW_METHOD_MARKER)]
469
470 def getWorkflowMethodIdList(self):
471 """
472 Return the list of workflow method IDs
473 """
474 return [x[0] for x in self.getWorkflowMethodItemList()]
475
476 def _getClassDict(self, klass, inherited=1, local=1):
477 """
478 Return a dict for every property of a class
479 """
480 result = {}
481 if inherited:
482 for parent in reversed(klass.mro()):
483 result.update(parent.__dict__)
484 if local:
485 result.update(klass.__dict__)
486 return result
487
488 def _getClassItemList(self, klass, inherited=1, local=1):
489 """
490 Return a list of tuple (id, method) for every property of a class
491 """
492 return self._getClassDict(klass, inherited=inherited, local=local).items()
493
494 def getClassMethodItemList(self, klass, inherited=1, local=1):
495 """
496 Return a list of tuple (id, method, module) for every class method
497 """
498 return [x for x in self._getClassItemList(klass, inherited=inherited,
499 local=local) if callable(x[1])]
500
501 def getClassMethodIdList(self, klass, inherited=1, local=1):
502 """
503 Return the list of class method IDs
504 """
505 return [x[0] for x in self.getClassMethodItemList(klass,
506 inherited=inherited, local=local)]
507
508 def getClassPropertyItemList(self, klass, inherited=1, local=1):
509 """
510 Return a list of tuple (id, method) for every class method
511 """
512 return [x for x in self._getClassItemList(klass, inherited=inherited,
513 local=local) if not callable(x[1])]
514
515 def getClassPropertyIdList(self, klass, inherited=1, local=1):
516 """
517 Return the list of class method IDs
518 """
519 return [x[0] for x in self.getClassPropertyItemList(klass,
520 inherited=inherited, local=local)]
521
522 def getClassPropertyList(klass):
523 ps_list = getattr(klass, 'property_sheets', ())
524 ps_list = tuple(ps_list)
525 for super_klass in klass.__bases__:
526 if getattr(super_klass, 'isRADContent', 0):
527 ps_list = ps_list + tuple([p for p in getClassPropertyList(super_klass)
528 if p not in ps_list])
529 return ps_list
530
531 def initializeClassDynamicProperties(self, klass):
532 if klass not in Base.aq_method_generated:
533 # Recurse to superclasses
534 for super_klass in klass.__bases__:
535 if getattr(super_klass, 'isRADContent', 0):
536 initializeClassDynamicProperties(self, super_klass)
537 # Initialize default properties
538 from Utils import setDefaultClassProperties
539 if not getattr(klass, 'isPortalContent', None):
540 if getattr(klass, 'isRADContent', 0):
541 setDefaultClassProperties(klass)
542 # Mark as generated
543 Base.aq_method_generated.add(klass)
544
545 def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal):
546 raise ValueError("No reason to go through this no more with portal type classes")
547
548 ## Init CachingMethod which implements caching for ERP5
549 from Products.ERP5Type.Cache import initializePortalCachingProperties
550 initializePortalCachingProperties(portal)
551
552 if aq_key not in Base.aq_portal_type:
553 # Mark as generated
554 prop_holder = PropertyHolder()
555 # Recurse to parent object
556 parent_object = self.aq_inner.aq_parent
557 parent_klass = parent_object.__class__
558 parent_type = parent_object.portal_type
559 if getattr(parent_klass, 'isRADContent', 0) and \
560 (ptype != parent_type or klass != parent_klass):
561 parent_aq_key = parent_object._aq_key()
562 if parent_aq_key not in Base.aq_portal_type:
563 initializePortalTypeDynamicProperties(parent_object, parent_klass,
564 parent_type,
565 parent_aq_key, portal)
566
567 prop_list = list(getattr(klass, '_properties', []))
568 cat_list = list(getattr(klass, '_categories', []))
569 constraint_list = list(getattr(klass, '_constraints', []))
570 ps_definition_dict = {'_properties': prop_list,
571 '_categories': cat_list,
572 '_constraints': constraint_list}
573
574 # Initialize portal_type properties (XXX)
575 # Always do it before processing klass.property_sheets (for compatibility).
576 # Because of the order we generate accessors, it is still possible
577 # to overload data access for some accessors.
578 ptype_object = portal.portal_types.getTypeInfo(ptype)
579 if ptype_object is not None:
580 ptype_object.updatePropertySheetDefinitionDict(ps_definition_dict)
581
582 for base in getClassPropertyList(klass):
583 # FIXME: With ZODB Property Sheets, there should only be the
584 # name of the Property Sheet as a string. aq_dynamic should not
585 # be necessary as soon as all the Documents and Property Sheets
586 # have been migrated. This is kept for backward compatility with
587 # the filesystem Property Sheet (see ERP5Type.dynamic.portal_type_class)
588 if isinstance(base, basestring):
589 # The property Sheet might be in ERP5PropertySheetLegacy,
590 # give it a try. If it's not there, no need to warn or log
591 # heavily: it just means that it will be a ZODB propertysheet
592 from Products.ERP5Type import PropertySheet
593 base = getattr(PropertySheet, base)
594 if isinstance(base, basestring):
595 continue
596
597 for list_name, current_list in ps_definition_dict.items():
598 try:
599 current_list += getattr(base, list_name, ())
600 except TypeError:
601 raise ValueError("%s is not a list for %s" % (list_name, base))
602
603 prop_holder._portal_type = ptype
604 prop_holder._properties = prop_list
605 prop_holder._categories = cat_list
606 prop_holder._constraints = constraint_list
607 from Utils import setDefaultClassProperties, setDefaultProperties
608 setDefaultClassProperties(prop_holder)
609 setDefaultProperties(prop_holder, object=self, portal=portal)
610 #LOG('initializeDefaultProperties: %s' % ptype, 0, str(prop_holder.__dict__))
611 initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
612 portal)
613 # We can now associate it after initialising security
614 InitializeClass(prop_holder)
615 prop_holder.__propholder__ = prop_holder
616 # For now, this line below is commented, because this breaks
617 # _aq_dynamic without JP's patch to Zope for an unknown reason.
618 #klass.__ac_permissions__ = prop_holder.__ac_permissions__
619 Base.aq_portal_type[aq_key] = prop_holder
620
621 def initializePortalTypeDynamicWorkflowMethods(ptype_klass, portal_workflow):
622 """We should now make sure workflow methods are defined
623 and also make sure simulation state is defined."""
624 # aq_inner is required to prevent extra name lookups from happening
625 # infinitely. For instance, if a workflow is missing, and the acquisition
626 # wrapper contains an object with _aq_dynamic defined, the workflow id
627 # is looked up with _aq_dynamic, thus causes infinite recursions.
628
629 portal_workflow = aq_inner(portal_workflow)
630 portal_type = ptype_klass.__name__
631
632 dc_workflow_dict = dict()
633 interaction_workflow_dict = dict()
634
635 for wf in portal_workflow.getWorkflowsFor(portal_type):
636 wf_id = wf.id
637 wf_type = wf.__class__.__name__
638 if wf_type == "DCWorkflowDefinition":
639 # Create state var accessor
640 # and generate methods that support the translation of workflow states
641 state_var = wf.variables.getStateVar()
642 for method_id, getter in (
643 ('get%s' % UpperCase(state_var), WorkflowState.Getter),
644 ('get%sTitle' % UpperCase(state_var), WorkflowState.TitleGetter),
645 ('getTranslated%s' % UpperCase(state_var),
646 WorkflowState.TranslatedGetter),
647 ('getTranslated%sTitle' % UpperCase(state_var),
648 WorkflowState.TranslatedTitleGetter)):
649 if not hasattr(ptype_klass, method_id):
650 method = getter(method_id, wf_id)
651 # Attach to portal_type
652 setattr(ptype_klass, method_id, method)
653 ptype_klass.security.declareProtected(
654 Permissions.AccessContentsInformation,
655 method_id )
656
657 storage = dc_workflow_dict
658 transitions = wf.transitions
659 elif wf_type == "InteractionWorkflowDefinition":
660 storage = interaction_workflow_dict
661 transitions = wf.interactions
662 else:
663 continue
664
665 # extract Trigger transitions from workflow definitions for later
666 transition_id_set = set(transitions.objectIds())
667 trigger_dict = dict()
668 for tr_id in transition_id_set:
669 tdef = transitions.get(tr_id, None)
670 if tdef.trigger_type == TRIGGER_WORKFLOW_METHOD:
671 trigger_dict[tr_id] = tdef
672
673 storage[wf_id] = (transition_id_set, trigger_dict)
674
675 for wf_id, v in dc_workflow_dict.iteritems():
676 transition_id_set, trigger_dict = v
677 for tr_id, tdef in trigger_dict.iteritems():
678 method_id = convertToMixedCase(tr_id)
679 method = getattr(ptype_klass, method_id, _MARKER)
680 if method is _MARKER:
681 ptype_klass.security.declareProtected(Permissions.AccessContentsInformation,
682 method_id)
683 ptype_klass.registerWorkflowMethod(method_id, wf_id, tr_id)
684 continue
685
686 # Wrap method
687 if not callable(method):
688 LOG('initializePortalTypeDynamicWorkflowMethods', 100,
689 'WARNING! Can not initialize %s on %s' % \
690 (method_id, portal_type))
691 continue
692
693 if not isinstance(method, WorkflowMethod):
694 method = WorkflowMethod(method)
695 setattr(ptype_klass, method_id, method)
696 else:
697 # We must be sure that we
698 # are going to register class defined
699 # workflow methods to the appropriate transition
700 transition_id = method.getTransitionId()
701 if transition_id in transition_id_set:
702 method.registerTransitionAlways(portal_type, wf_id, transition_id)
703 method.registerTransitionAlways(portal_type, wf_id, tr_id)
704
705 if not interaction_workflow_dict:
706 return
707
708 # all methods in mro of portal type class: that contains all
709 # workflow methods and accessors you could possibly ever need
710 class_method_id_list = ptype_klass.getClassMethodIdList(ptype_klass)
711
712 interaction_queue = []
713 # XXX This part is (more or less...) a copy and paste
714 for wf_id, v in interaction_workflow_dict.iteritems():
715 transition_id_set, trigger_dict = v
716 for tr_id, tdef in trigger_dict.iteritems():
717 if (tdef.portal_type_filter is not None and \
718 portal_type not in tdef.portal_type_filter):
719 continue
720 for imethod_id in tdef.method_id:
721 if wildcard_interaction_method_id_match(imethod_id):
722 # Interactions workflows can use regexp based wildcard methods
723 # XXX What happens if exception ?
724 method_id_matcher = re.compile(imethod_id).match
725
726 # queue transitions using regexps for later examination
727 interaction_queue.append((wf_id,
728 tr_id,
729 transition_id_set,
730 tdef.once_per_transaction,
731 method_id_matcher))
732
733 # XXX - class stuff is missing here
734 method_id_list = filter(method_id_matcher, class_method_id_list)
735 else:
736 # Single method
737 # XXX What if the method does not exist ?
738 # It's not consistent with regexp based filters.
739 method_id_list = [imethod_id]
740 for method_id in method_id_list:
741 method = getattr(ptype_klass, method_id, _MARKER)
742 if method is _MARKER:
743 # set a default security, if this method is not already
744 # protected.
745 if method_id not in ptype_klass.security.names:
746 ptype_klass.security.declareProtected(
747 Permissions.AccessContentsInformation, method_id)
748 ptype_klass.registerWorkflowMethod(method_id, wf_id, tr_id,
749 tdef.once_per_transaction)
750 continue
751
752 # Wrap method
753 if not callable(method):
754 LOG('initializePortalTypeDynamicWorkflowMethods', 100,
755 'WARNING! Can not initialize %s on %s' % \
756 (method_id, portal_type))
757 continue
758 if not isinstance(method, WorkflowMethod):
759 method = WorkflowMethod(method)
760 setattr(ptype_klass, method_id, method)
761 else:
762 # We must be sure that we
763 # are going to register class defined
764 # workflow methods to the appropriate transition
765 transition_id = method.getTransitionId()
766 if transition_id in transition_id_set:
767 method.registerTransitionAlways(portal_type, wf_id, transition_id)
768 if tdef.once_per_transaction:
769 method.registerTransitionOncePerTransaction(portal_type, wf_id, tr_id)
770 else:
771 method.registerTransitionAlways(portal_type, wf_id, tr_id)
772
773 if not interaction_queue:
774 return
775
776 # the only methods that could have appeared since last check are
777 # workflow methods
778 # TODO we could just queue the ids of methods that are attached to the
779 # portal type class in the previous loop, to improve performance
780 new_method_set = set(ptype_klass.getWorkflowMethodIdList())
781 added_method_set = new_method_set.difference(class_method_id_list)
782 # We need to run this part twice in order to handle interactions of interactions
783 # ex. an interaction workflow creates a workflow method which matches
784 # the regexp of another interaction workflow
785 for wf_id, tr_id, transition_id_set, once, method_id_matcher in interaction_queue:
786 for method_id in filter(method_id_matcher, added_method_set):
787 # method must already exist and be a workflow method
788 method = getattr(ptype_klass, method_id)
789 transition_id = method.getTransitionId()
790 if transition_id in transition_id_set:
791 method.registerTransitionAlways(portal_type, wf_id, transition_id)
792 if once:
793 method.registerTransitionOncePerTransaction(portal_type, wf_id, tr_id)
794 else:
795 method.registerTransitionAlways(portal_type, wf_id, tr_id)
796
797 class Base( CopyContainer,
798 PortalContent,
799 ActiveObject,
800 OFS.History.Historical,
801 ERP5PropertyManager,
802 PropertyTranslatableBuiltInDictMixIn
803 ):
804 """
805 This is the base class for all ERP5 Zope objects.
806 It defines object attributes which are necessary to implement
807 relations and data synchronisation
808
809 id -- the standard object id
810 rid -- the standard object id in the master ODB the object was
811 subsribed from
812 uid -- a global object id which is unique
813 sid -- the id of the subscribtion/syncrhonisation object which
814 this object was generated from
815
816 sync_status -- The status of this document in the synchronization
817 process (NONE, SENT, ACKNOWLEDGED, SYNCHRONIZED)
818 could work as a workflow but CPU expensive
819
820 """
821 meta_type = 'ERP5 Base Object'
822 portal_type = 'Base Object'
823 #_local_properties = () # no need since getattr
824 isRADContent = 1 #
825 isPortalContent = ConstantGetter('isPortalContent', value=True)
826 isCapacity = ConstantGetter('isCapacity', value=False)
827 isCategory = ConstantGetter('isCategory', value=False)
828 isBaseCategory = ConstantGetter('isBaseCategory', value=False)
829 isInventoryMovement = ConstantGetter('isInventoryMovement', value=False)
830 isDelivery = ConstantGetter('isDelivery', value=False)
831 isInventory = ConstantGetter('isInventory', value=False)
832 # If set to 0, reindexing will not happen (useful for optimization)
833 isIndexable = ConstantGetter('isIndexable', value=True)
834 isPredicate = ConstantGetter('isPredicate', value=False)
835 isTemplate = ConstantGetter('isTemplate', value=False)
836 isDocument = ConstantGetter('isDocument', value=False)
837 isTempDocument = ConstantGetter('isTempDocument', value=False)
838
839 # Dynamic method acquisition system (code generation)
840 aq_method_lock = threading.RLock()
841 aq_method_generated = set()
842 aq_method_generating = []
843 aq_portal_type = {}
844 aq_related_generated = 0
845
846 # Declarative security - in ERP5 we use AccessContentsInformation to
847 # define the right of accessing content properties as opposed
848 # to view which is the right to view the object with a form
849 security = ClassSecurityInfo()
850 security.declareObjectProtected(Permissions.AccessContentsInformation)
851
852 # Declarative properties
853 property_sheets = ( PropertySheet.Base, )
854
855 # Declarative interfaces
856 zope.interface.implements(interfaces.ICategoryAccessProvider,
857 interfaces.IValueAccessProvider,
858 )
859
860 # We want to use a default property view
861 manage_main = manage_propertiesForm = DTMLFile( 'properties', _dtmldir )
862 manage_main._setName('manage_main')
863
864 manage_options = ( PropertyManager.manage_options +
865 SimpleItem.manage_options +
866 OFS.History.Historical.manage_options +
867 CMFCatalogAware.manage_options
868 )
869
870 # Place for all is... method
871 security.declareProtected(Permissions.AccessContentsInformation, 'isMovement')
872 def isMovement(self):
873 return 0
874
875 security.declareProtected( Permissions.ModifyPortalContent, 'setTitle' )
876 def setTitle(self, value):
877 """ sets the title. (and then reindexObject)"""
878 self._setTitle(value)
879 self.reindexObject()
880
881 security.declareProtected( Permissions.ModifyPortalContent, 'setDescription' )
882 def setDescription(self, value):
883 """
884 Set the description and reindex.
885 Overrides CMFCore/PortalFolder.py implementation, which does not reindex
886 and is guarded by inappropriate security check.
887 """
888 self._setDescription(value)
889 self.reindexObject()
890
891 security.declareProtected( Permissions.AccessContentsInformation, 'test_dyn' )
892 def test_dyn(self):
893 """
894 """
895 initializeClassDynamicProperties(self, self.__class__)
896
897 security.declarePublic('provides')
898 def provides(cls, interface_name):
899 """
900 Check if the current class provides a particular interface
901 """
902 for interface in implementedBy(cls):
903 if interface.getName() == interface_name:
904 return True
905 return False
906 provides = classmethod(CachingMethod(provides, 'Base.provides',
907 cache_factory='erp5_ui_long'))
908
909 def _aq_key(self):
910 return (self.portal_type, self.__class__)
911
912 def _propertyMap(self):
913 """ Method overload - properties are now defined on the ptype """
914 klass = self.__class__
915 property_list = []
916 # Get all the accessor holders for this portal type
917 if hasattr(klass, 'getAccessorHolderPropertyList'):
918 property_list += \
919 self.__class__.getAccessorHolderPropertyList()
920
921 property_list += getattr(self, '_local_properties', [])
922 return tuple(property_list)
923
924 def manage_historyCompare(self, rev1, rev2, REQUEST,
925 historyComparisonResults=''):
926 return Base.inheritedAttribute('manage_historyCompare')(
927 self, rev1, rev2, REQUEST,
928 historyComparisonResults=OFS.History.html_diff(
929 pformat(rev1.__dict__),
930 pformat(rev2.__dict__)))
931
932 def initializePortalTypeDynamicProperties(self):
933 """
934 Test purpose
935 """
936 ptype = self.portal_type
937 klass = self.__class__
938 aq_key = self._aq_key()
939 initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \
940 self.getPortalObject())
941 from Products.ERP5Form.PreferenceTool import createPreferenceToolAccessorList
942 createPreferenceToolAccessorList(self.getPortalObject())
943
944 def _aq_dynamic(self, id):
945 # ahah! disabled, thanks to portal type classes
946 return None
947
948 # _aq_dynamic has been created so that callable objects
949 # and default properties can be associated per portal type
950 # and per class. Other uses are possible (ex. WebSection).
951 ptype = self.portal_type
952 klass = self.__class__
953 aq_key = (ptype, klass) # We do not use _aq_key() here for speed
954
955 # If this is a portal_type property and everything is already defined
956 # for that portal_type, try to return a value ASAP
957 try:
958 property_holder = Base.aq_portal_type[aq_key]
959 except KeyError:
960 pass
961 else:
962 accessor = getattr(property_holder, id, None)
963 if type(accessor) is tuple and id not in PropertyHolder.RESERVED_PROPERTY_SET:
964 accessor = property_holder.createAccessor(id)
965 return accessor
966
967 Base.aq_method_lock.acquire()
968 try:
969 if aq_key in Base.aq_portal_type:
970 # Another thread generated accessors just before we acquired the lock
971 # so we must simply retry.
972 return getattr(self, id, None)
973 if aq_key in Base.aq_method_generating:
974 # We are already generating accessors for this aq_key.
975 # Return immediately to prevent infinite loops.
976 return
977 # Store that we are generating for this aq_key, because _aq_dynamic may
978 # be called recursively. A typical example is that to generate methods
979 # for a document, we'll have to generate methods for Types Tool and
980 # Base Category portal.
981 Base.aq_method_generating.append(aq_key)
982 try:
983 # Proceed with property generation
984 # Generate class methods
985 initializeClassDynamicProperties(self, klass)
986
987 # Iterate until an ERP5 Site is obtained.
988 portal = self.getPortalObject()
989 while portal.portal_type != 'ERP5 Site':
990 portal = portal.aq_parent.aq_inner.getPortalObject()
991
992 # Generate portal_type methods
993 initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal)
994 finally:
995 del Base.aq_method_generating[-1]
996
997 # Generate Related Accessors
998 if not Base.aq_related_generated:
999 Base.aq_related_generated = 1
1000 from Utils import createRelatedValueAccessors
1001 portal_types = getToolByName(portal, 'portal_types', None)
1002 generated_bid = set()
1003 econtext = createExpressionContext(object=self, portal=portal)
1004 # Use 'items' instead of 'iteritems' because PropertySheet.__dict__ may
1005 # be modified by another thread (that, for example, installs a BT).
1006 for pid, ps in PropertySheet.__dict__.items():
1007 if pid[0] != '_':
1008 base_category_list = []
1009 for cat in getattr(ps, '_categories', ()):
1010 if isinstance(cat, Expression):
1011 result = cat(econtext)
1012 if isinstance(result, (list, tuple)):
1013 base_category_list.extend(result)
1014 else:
1015 base_category_list.append(result)
1016 else:
1017 base_category_list.append(cat)
1018 for bid in base_category_list:
1019 if bid not in generated_bid:
1020 createRelatedValueAccessors(None, bid)
1021 generated_bid.add(bid)
1022 for ptype in portal_types.listTypeInfo():
1023 for bid in ptype.getTypeBaseCategoryList():
1024 if bid not in generated_bid :
1025 createRelatedValueAccessors(None, bid)
1026 generated_bid.add(bid)
1027
1028 # We suppose that if we reach this point
1029 # then it means that all code generation has succeeded
1030 # (no except should hide that). We can safely return None
1031 # if id does not exist as a dynamic property
1032 # Baseline: accessor generation failures should always
1033 # raise an exception up to the user
1034 return getattr(self, id, None)
1035 finally:
1036 Base.aq_method_lock.release()
1037
1038 # Proceed with standard acquisition
1039 return None
1040
1041 psyco.bind(_aq_dynamic)
1042
1043 # Constructor
1044 def __init__(self, id, uid=None, rid=None, sid=None, **kw):
1045 self.id = id
1046 if uid is not None :
1047 self.uid = uid # Else it will be generated when we need it
1048 self.sid = sid
1049
1050 # XXX This is necessary to override getId which is also defined in SimpleItem.
1051 security.declareProtected( Permissions.AccessContentsInformation, 'getId' )
1052 getId = BaseAccessor.Getter('getId', 'id', 'string')
1053
1054 # Debug
1055 def getOid(self):
1056 """
1057 Return ODB oid
1058 """
1059 return self._p_oid
1060
1061 def getOidRepr(self):
1062 """
1063 Return ODB oid, in an 'human' readable form.
1064 """
1065 from ZODB.utils import oid_repr
1066 return oid_repr(self._p_oid)
1067
1068 def getSerial(self):
1069 """Return ODB Serial."""
1070 return self._p_serial
1071
1072 def getHistorySerial(self):
1073 """Return ODB Serial, in the same format used for history keys"""
1074 return '.'.join([str(x) for x in unpack('>HHHH', self._p_serial)])
1075
1076 # Utils
1077 def _getCategoryTool(self):
1078 return aq_inner(self.getPortalObject().portal_categories)
1079
1080 def _getTypesTool(self):
1081 return aq_inner(self.getPortalObject().portal_types)
1082
1083 def _doNothing(self, *args, **kw):
1084 # A method which does nothing (and can be used to build WorkflowMethods which trigger worklow transitions)
1085 pass
1086
1087 # Generic accessor
1088 def _getDefaultAcquiredProperty(self, key, default_value, null_value,
1089 acquisition_object_id=None, base_category=None, portal_type=None,
1090 copy_value=0, mask_value=0, sync_value=0, accessor_id=None, depends=None,
1091 storage_id=None, alt_accessor_id=None, is_list_type=0, is_tales_type=0,
1092 checked_permission=None):
1093 """
1094 This method implements programmable acquisition of values in ERP5.
1095
1096 The principle is that some object attributes should be looked up,
1097 copied or synchronized from the values of another object which relates
1098 to the first thereof.
1099
1100 The parameters which define value acquisition are:
1101
1102 base_category -- a single base category or a list of base categories
1103 to look up related objects
1104
1105 portal_type -- a single portal type or a list of portal types to filter the
1106 list of related objects
1107
1108 copy_value -- if set to 1, the looked up value will be copied
1109 as an attribute of self
1110
1111 mask_value -- if set to 1, the value of the attribute of self
1112 has priority on the looked up value
1113
1114 sync_value -- if set to 1, keep self and looked up value in sync
1115
1116 accessor_id -- the id of the accessor to call on the related filtered objects
1117
1118 depends -- a list of parameters to propagate in the look up process
1119
1120 acquisition_object_id -- List of object Ids where look up properties
1121 before looking up on acquired objects
1122 The purpose of copy_value / mask_value / sync_value is to solve issues
1123 related to relations and synchronisation of data. copy_value determines
1124 if a value should be copied as an attribute of self. Copying a value is
1125 useful for example when we do invoices and want to remember the price at
1126 a given point of time. mask_value allows to give priority to the value
1127 holded by self, rather than to the lookup through related objects.
1128 This is for example useful for invoices (again) for which we want the value
1129 not to change in time.
1130
1131 Another case is the case of independent modules on multiple Zope. If for example
1132 a sales opportunity modules runs on a Zope No1 and an Organisation module runs
1133 on a Zope No 2. We want to enter peoples's names on the Zope No1. They will be entered
1134 as strings and stored as such in attributes. When such opportunities are synchronized
1135 on the Zope No 2, we want to be able to augment content locally by adding some
1136 category information (in order to say for example that M. Lawno is client/person/23)
1137 and eventually want M. Lawno to be displayed as "James Lawno". So, we want to give
1138 priority to the looked up attribute rather than to the attribute. However,
1139 we may want Zope No 1 to still display "James Lawno" as "M. Lawno". This means
1140 that we do not want to synchronize back this attribute.
1141
1142 Other case : we add relation after entering information...
1143
1144 Other case : we want to change the phone number of a related object without
1145 going to edit the related object
1146 """
1147 # Push context to prevent loop
1148 tv = getTransactionalVariable()
1149 if isinstance(portal_type, list):
1150 portal_type = tuple(portal_type)
1151 elif portal_type is None:
1152 portal_type = ()
1153 acquisition_key = ('_getDefaultAcquiredProperty', self.getPath(), key,
1154 acquisition_object_id, base_category, portal_type,
1155 copy_value, mask_value, sync_value, accessor_id, depends,
1156 storage_id, alt_accessor_id, is_list_type, is_tales_type,
1157 checked_permission)
1158 if acquisition_key in tv:
1159 return null_value[0]
1160
1161 tv[acquisition_key] = 1
1162
1163 try:
1164 if storage_id is None: storage_id=key
1165 #LOG("Get Acquired Property storage_id",0,str(storage_id))
1166 # Test presence of attribute without acquisition
1167 # if present, get it in its context, thus we keep acquisition if
1168 # returned value is an object
1169 d = getattr(aq_base(self), storage_id, _MARKER)
1170 if d is not _MARKER:
1171 value = getattr(self, storage_id, None)
1172 else:
1173 value = None
1174 local_value = value
1175 # If we hold an attribute and mask_value is set, return the attribute
1176 if mask_value and value not in null_value:
1177 # Pop context
1178 if is_tales_type:
1179 expression = Expression(value)
1180 econtext = createExpressionContext(self)
1181 return expression(econtext)
1182 else:
1183 if is_list_type:
1184 # We must provide the first element of the acquired list
1185 if value in null_value:
1186 result = None
1187 else:
1188 if isinstance(value, (list, tuple)):
1189 if len(value) is 0:
1190 result = None
1191 else:
1192 result = value[0]
1193 else:
1194 result = value
1195 else:
1196 # Value is a simple type
1197 result = value
1198 return result
1199
1200 #Look at acquisition object before call acquisition
1201 if acquisition_object_id is not None:
1202 value = None
1203 if isinstance(acquisition_object_id, str):
1204 acquisition_object_id = tuple(acquisition_object_id)
1205 for object_id in acquisition_object_id:
1206 try:
1207 value = self[object_id]
1208 if value not in null_value:
1209 break
1210 except (KeyError, AttributeError):
1211 pass
1212 if copy_value:
1213 if getattr(self, storage_id, None) is None:
1214 # Copy the value if it does not already exist as an attribute of self
1215 # Like in the case of orders / invoices
1216 setattr(self, storage_id, value)
1217 if is_list_type:
1218 # We must provide the first element of the acquired list
1219 if value in null_value:
1220 result = None
1221 else:
1222 if isinstance(value, (list, tuple)):
1223 if len(value) is 0:
1224 result = None
1225 else:
1226 result = value[0]
1227 else:
1228 result = value
1229 else:
1230 # Value is a simple type
1231 result = value
1232 else:
1233 result = None
1234 if result not in null_value:
1235 return result
1236
1237 # Retrieve the list of related objects
1238 #LOG("Get Acquired Property self",0,str(self))
1239 #LOG("Get Acquired Property portal_type",0,str(portal_type))
1240 #LOG("Get Acquired Property base_category",0,str(base_category))
1241 #super_list = self._getValueList(base_category, portal_type=portal_type) # We only do a single jump
1242 super_list = self._getAcquiredValueList(base_category, portal_type=portal_type,
1243 checked_permission=checked_permission) # Full acquisition
1244 super_list = [o for o in super_list if o.getPhysicalPath() != self.getPhysicalPath()] # Make sure we do not create stupid loop here
1245 #LOG("Get Acquired Property super_list",0,str(super_list))
1246 #LOG("Get Acquired Property accessor_id",0,str(accessor_id))
1247 if len(super_list) > 0:
1248 super = super_list[0]
1249 # Retrieve the value
1250 if accessor_id is None:
1251 value = super.getProperty(key)
1252 else:
1253 method = getattr(super, accessor_id)
1254 value = method() # We should add depends here XXXXXX
1255 # There is also a strong risk here of infinite loop
1256 if copy_value:
1257 if getattr(self, storage_id, None) is None:
1258 # Copy the value if it does not already exist as an attribute of self
1259 # Like in the case of orders / invoices
1260 setattr(self, storage_id, value)
1261 if is_list_type:
1262 # We must provide the first element of the acquired list
1263 if value in null_value:
1264 result = None
1265 else:
1266 if isinstance(value, (list, tuple)):
1267 if len(value) is 0:
1268 result = None
1269 else:
1270 result = value[0]
1271 else:
1272 result = value
1273 else:
1274 # Value is a simple type
1275 result = value
1276 else:
1277 result = None
1278 if result not in null_value:
1279 return result
1280 elif local_value not in null_value:
1281 # Nothing has been found by looking up
1282 # through acquisition documents, fallback by returning
1283 # at least local_value
1284 return local_value
1285 else:
1286 #LOG("alt_accessor_id",0,str(alt_accessor_id))
1287 if alt_accessor_id is not None:
1288 for id in alt_accessor_id:
1289 #LOG("method",0,str(id))
1290 method = getattr(self, id, None)
1291 if callable(method):
1292 try:
1293 result = method(checked_permission=checked_permission)
1294 except TypeError:
1295 result = method()
1296 if result not in null_value:
1297 if is_list_type:
1298 if isinstance(result, (list, tuple)):
1299 # We must provide the first element of the alternate result
1300 if len(result) > 0:
1301 return result[0]
1302 else:
1303 return result
1304 else:
1305 # Result is a simple type
1306 return result
1307
1308 if copy_value:
1309 return getattr(self,storage_id, default_value)
1310 else:
1311 # Return the default value defined at the class level XXXXXXXXXXXXXXX
1312 return default_value
1313 finally:
1314 # Pop the acquisition context.
1315 try:
1316 del tv[acquisition_key]
1317 except KeyError:
1318 pass
1319
1320 def _getAcquiredPropertyList(self, key, default_value, null_value,
1321 base_category, portal_type=None, copy_value=0, mask_value=0, sync_value=0, append_value=0,
1322 accessor_id=None, depends=None, storage_id=None, alt_accessor_id=None,
1323 acquisition_object_id=None,
1324 is_list_type=0, is_tales_type=0, checked_permission=None):
1325 """
1326 Default accessor. Implements the default
1327 attribute accessor.
1328
1329 portal_type
1330 copy_value
1331 depends
1332
1333 """
1334 # Push context to prevent loop
1335 tv = getTransactionalVariable()
1336 if isinstance(portal_type, list):
1337 portal_type = tuple(portal_type)
1338 elif portal_type is None:
1339 portal_type = ()
1340 acquisition_key = ('_getAcquiredPropertyList', self.getPath(), key, base_category,
1341 portal_type, copy_value, mask_value, sync_value,
1342 accessor_id, depends, storage_id, alt_accessor_id,
1343 acquisition_object_id, is_list_type, is_tales_type,
1344 checked_permission)
1345 if acquisition_key in tv:
1346 return null_value
1347
1348 tv[acquisition_key] = 1
1349
1350 try:
1351 if storage_id is None: storage_id=key
1352 value = getattr(self, storage_id, None)
1353 if mask_value and value is not None:
1354 if is_tales_type:
1355 expression = Expression(value)
1356 econtext = createExpressionContext(self)
1357 return expression(econtext)
1358 else:
1359 return value
1360 super_list = []
1361 if acquisition_object_id is not None:
1362 if isinstance(acquisition_object_id, str):
1363 acquisition_object_id = tuple(acquisition_object_id)
1364 for object_id in acquisition_object_id:
1365 try:
1366 acquisition_object = self[object_id]
1367 super_list.append(acquisition_object)
1368 except (KeyError, AttributeError):
1369 pass
1370 super_list.extend(self._getAcquiredValueList(
1371 base_category,
1372 portal_type=portal_type,
1373 checked_permission=checked_permission))
1374 # Full acquisition
1375 super_list = [o for o in super_list if o.getPhysicalPath() != self.getPhysicalPath()] # Make sure we do not create stupid loop here
1376 if len(super_list) > 0:
1377 value = []
1378 for super in super_list:
1379 if accessor_id is None:
1380 if is_list_type:
1381 result = super.getPropertyList(key)
1382 if isinstance(result, (list, tuple)):
1383 value += result
1384 else:
1385 value += [result]
1386 else:
1387 value += [super.getProperty(key)]
1388 else:
1389 method = getattr(super, accessor_id)
1390 if is_list_type:
1391 result = method() # We should add depends here
1392 if isinstance(result, (list, tuple)):
1393 value += result
1394 else:
1395 value += [result]
1396 else:
1397 value += [method()] # We should add depends here
1398 if copy_value:
1399 if not hasattr(self, storage_id):
1400 setattr(self, storage_id, value)
1401 return value
1402 else:
1403 # ?????
1404 if copy_value:
1405 return getattr(self,storage_id, default_value)
1406 else:
1407 return default_value
1408 finally:
1409 # Pop the acquisition context.
1410 try:
1411 del tv[acquisition_key]
1412 except KeyError:
1413 pass
1414
1415 security.declareProtected( Permissions.AccessContentsInformation, 'getProperty' )
1416 def getProperty(self, key, d=_MARKER, **kw):
1417 """getProperty is the generic accessor to all properties and categories
1418 defined on this object.
1419 If an accessor exists for this property, the accessor will be called,
1420 default value will be passed to the accessor as first positional argument.
1421 """
1422 accessor_name = 'get' + UpperCase(key)
1423 aq_self = aq_base(self)
1424 if getattr(aq_self, accessor_name, None) is not None:
1425 method = getattr(self, accessor_name)
1426 if d is not _MARKER:
1427 try:
1428 # here method is a method defined on the class, we don't know if the
1429 # method supports default argument or not, so we'll try and if the
1430 # method doesn't accepts it, we ignore default argument.
1431 return method(d, **kw)
1432 except TypeError:
1433 pass
1434 return method(**kw)
1435 # Try a mono valued accessor if it is available
1436 # and return it as a list
1437 if accessor_name.endswith('List'):
1438 mono_valued_accessor_name = accessor_name[:-4]
1439 method = getattr(self, mono_valued_accessor_name, None)
1440 if method is not None:
1441 # We have a monovalued property
1442 if d is _MARKER:
1443 result = method(**kw)
1444 else:
1445 try:
1446 result = method(d, **kw)
1447 except TypeError:
1448 result = method(**kw)
1449 if not isinstance(result, (list, tuple)):
1450 result = [result]
1451 return result
1452 if d is not _MARKER:
1453 return ERP5PropertyManager.getProperty(self, key, d=d, **kw)
1454 return ERP5PropertyManager.getProperty(self, key, **kw)
1455
1456 security.declareProtected( Permissions.AccessContentsInformation, 'getPropertyList' )
1457 def getPropertyList(self, key, d=None):
1458 """Same as getProperty, but for list properties.
1459 """
1460 return self.getProperty('%s_list' % key, d=d)
1461
1462 security.declareProtected( Permissions.ModifyPortalContent, 'setPropertyList' )
1463 def setPropertyList(self, key, value, **kw):
1464 """Same as setProperty, but for list properties.
1465 """
1466 return self.setProperty('%s_list' % key, value, **kw)
1467
1468 security.declareProtected( Permissions.ModifyPortalContent, 'setProperty' )
1469 def setProperty(self, key, value, type='string', **kw):
1470 """
1471 Previous Name: setValue
1472
1473 New Name: we use the naming convention of
1474 /usr/lib/zope/lib/python/OFS/PropertySheets.py
1475
1476 TODO: check possible conflicts
1477
1478 Generic accessor. Calls the real accessor
1479 """
1480 self._setProperty(key, value, type=type, **kw)
1481 self.reindexObject()
1482
1483 def _setProperty(self, key, value, type=None, **kw):
1484 """
1485 Previous Name: _setValue
1486
1487 Generic accessor. Calls the real accessor
1488
1489 **kw allows to call setProperty as a generic setter (ex. setProperty(value_uid, portal_type=))
1490 """
1491 if type is not None: # Speed
1492 if type in list_types: # Patch for OFS PropertyManager
1493 key += '_list'
1494 accessor_name = '_set' + UpperCase(key)
1495 aq_self = aq_base(self)
1496 # We must use aq_self
1497 # since we will change the value on self
1498 # rather than through implicit aquisition
1499 if getattr(aq_self, accessor_name, None) is not None:
1500 method = getattr(self, accessor_name)
1501 return method(value, **kw)
1502 public_accessor_name = 'set' + UpperCase(key)
1503 if getattr(aq_self, public_accessor_name, None) is not None:
1504 method = getattr(self, public_accessor_name)
1505 return method(value, **kw)
1506 # Try a mono valued setter if it is available
1507 # and call it
1508 if accessor_name.endswith('List'):
1509 mono_valued_accessor_name = accessor_name[:-4]
1510 mono_valued_public_accessor_name = public_accessor_name[:-4]
1511 method = None
1512 if hasattr(self, mono_valued_accessor_name):
1513 method = getattr(self, mono_valued_accessor_name)
1514 elif hasattr(self, mono_valued_public_accessor_name):
1515 method = getattr(self, mono_valued_public_accessor_name)
1516 if method is not None:
1517 if isinstance(value, (list, tuple)):
1518 value_len = len(value)
1519 if value_len == 1:
1520 mono_value = value[0]
1521 return method(mono_value, **kw)
1522 raise TypeError, \
1523 "A mono valued property must be set with a list of len 1"
1524 # Finaly use standard PropertyManager
1525 #LOG("Changing attr: ",0, key)
1526 # If we are here, this means we do not use a property that
1527 # comes from an ERP5 PropertySheet, we should use the
1528 # PropertyManager
1529 if ERP5PropertyManager.hasProperty(self,key):
1530 ERP5PropertyManager._updateProperty(self, key, value)
1531 else:
1532 ERP5PropertyManager._setProperty(self, key, value, type=type)
1533 # This should not be there, because this ignore all checks made by
1534 # the PropertyManager. If there is problems, please complain to
1535 # seb@nexedi.com
1536 #except:
1537 # # This should be removed if we want strict property checking
1538 # setattr(self, key, value)
1539 return (self,)
1540
1541 def _setPropValue(self, key, value, **kw):
1542 self._wrapperCheck(value)
1543 if isinstance(value, list):
1544 value = tuple(value)
1545 accessor_name = '_set' + UpperCase(key)
1546 aq_self = aq_base(self)
1547 # We must use aq_self
1548 # since we will change the value on self
1549 # rather than through implicit aquisition
1550 if hasattr(aq_self, accessor_name):
1551 method = getattr(self, accessor_name)
1552 method(value, **kw)
1553 return
1554 public_accessor_name = 'set' + UpperCase(key)
1555 if hasattr(aq_self, public_accessor_name):
1556 method = getattr(self, public_accessor_name)
1557 method(value, **kw)
1558 return
1559 # Finaly use standard PropertyManager
1560 #LOG("Changing attr: ",0, key)
1561 #try:
1562 ERP5PropertyManager._setPropValue(self, key, value)
1563 #except ConflictError:
1564 # raise
1565 # This should not be there, because this ignore all checks made by
1566 # the PropertyManager. If there is problems, please complain to
1567 # seb@nexedi.com
1568 #except:
1569 # # This should be removed if we want strict property checking
1570 # setattr(self, key, value)
1571
1572 security.declareProtected(Permissions.AccessContentsInformation,
1573 'hasProperty')
1574 def hasProperty(self, key):
1575 """
1576 Previous Name: hasValue
1577
1578 Generic accessor. Calls the real accessor
1579 and returns 0 if it fails.
1580
1581 The idea of hasProperty is to call the tester methods.
1582 It will return True only if a property was defined on the object.
1583 (either by calling a Tester accessor or by checking if a local
1584 property was added).
1585
1586 It will return False if the property is not part of the list
1587 of valid properties (ie. the list of properties defined in
1588 PropertySheets) or if the property has never been updated.
1589
1590 NOTE - One possible issue in the current implementation is that a
1591 property which is set to its default value will be considered
1592 as not being defined.
1593
1594 Ex. self.hasProperty('first_name') on a Person object
1595 returns False if the first name was never defined and
1596 even if self.getProperty('first_name') returns ''
1597 """
1598 method = getattr(self, 'has' + UpperCase(key), _MARKER)
1599 if method is _MARKER:
1600 # Check in local properties (which obviously were defined at some point)
1601 return key in self.propertyIds()
1602 try:
1603 return method()
1604 except ConflictError:
1605 raise
1606 except:
1607 return 0
1608
1609 security.declareProtected(Permissions.AccessContentsInformation,
1610 'hasCategory')
1611 def hasCategory(self, key):
1612 """
1613 Previous Name: hasValue
1614
1615 Generic accessor. Calls the real accessor
1616 and returns 0 if it fails
1617 """
1618 return key in self.getCategoryList()
1619
1620 # Accessors are not workflow methods by default
1621 # Ping provides a dummy method to trigger automatic methods
1622 # XXX : maybe an empty edit is enough (self.edit())
1623 def ping(self):
1624 pass
1625
1626 ping = WorkflowMethod(ping)
1627
1628 # Object attributes update method
1629 def _edit(self, REQUEST=None, force_update=0, reindex_object=0,
1630 keep_existing=0, activate_kw=None, edit_order=[], restricted=0, **kw):
1631 """
1632 Generic edit Method for all ERP5 object
1633 The purpose of this method is to update attributed, eventually do
1634 some kind of type checking according to the property sheet and index
1635 the object.
1636
1637 Each time attributes of an object are updated, they should
1638 be updated through this generic edit method
1639
1640 Modification date is supported by edit_workflow in ERP5
1641 There is no need to change it here.
1642
1643 keep_existing -- if set to 1 or True, only those properties for which
1644 hasProperty is False will be updated.
1645 """
1646 key_list = kw.keys()
1647 if len(key_list) == 0:
1648 return
1649 modified_property_dict = self._v_modified_property_dict = {}
1650 modified_object_dict = {}
1651
1652 unordered_key_list = [k for k in key_list if k not in edit_order]
1653 ordered_key_list = [k for k in edit_order if k in key_list]
1654 restricted_method_set = set()
1655 default_permission_set = set(('Access contents information',
1656 'Modify portal content'))
1657 if restricted:
1658 # retrieve list of accessors which doesn't use default permissions
1659 for ancestor in self.__class__.mro():
1660 for permissions in getattr(ancestor, '__ac_permissions__', ()):
1661 if permissions[0] not in default_permission_set:
1662 for method in permissions[1]:
1663 if method.startswith('set'):
1664 restricted_method_set.add(method)
1665
1666 getProperty = self.getProperty
1667 hasProperty = self.hasProperty
1668 _setProperty = self._setProperty
1669
1670 def setChangedPropertyList(key_list):
1671 not_modified_list = []
1672 for key in key_list:
1673 # We only change if the value is different
1674 # This may be very long...
1675 old_value = None
1676 if not force_update:
1677 try:
1678 old_value = getProperty(key, evaluate=0)
1679 except TypeError:
1680 old_value = getProperty(key)
1681
1682 if old_value != kw[key] or force_update:
1683 # We keep in a thread var the previous values
1684 # this can be useful for interaction workflow to implement lookups
1685 # XXX If iteraction workflow script is triggered by edit and calls
1686 # edit itself, this is useless as the dict will be overwritten
1687 # If the keep_existing flag is set to 1, we do not update properties which are defined
1688 if not keep_existing or not hasProperty(key):
1689 if restricted:
1690 accessor_name = 'set' + UpperCase(key)
1691 if accessor_name in restricted_method_set:
1692 # will raise Unauthorized when not allowed
1693 guarded_getattr(self, accessor_name)
1694 modified_property_dict[key] = old_value
1695 if key != 'id':
1696 modified_object_list = _setProperty(key, kw[key])
1697 # BBB: if the setter does not return anything, assume
1698 # that self has been modified.
1699 if modified_object_list is None:
1700 modified_object_list = (self,)
1701 for o in modified_object_list:
1702 # XXX using id is not quite nice, but getUID causes a
1703 # problem at the bootstrap of an ERP5 site. Therefore,
1704 # objects themselves cannot be used as keys.
1705 modified_object_dict[id(o)] = o
1706 else:
1707 self.setId(kw['id'], reindex=reindex_object)
1708 else:
1709 not_modified_list.append(key)
1710 return not_modified_list
1711
1712 unmodified_key_list = setChangedPropertyList(unordered_key_list)
1713 setChangedPropertyList(unmodified_key_list)
1714 # edit_order MUST be enforced, and done at the complete end
1715 setChangedPropertyList(ordered_key_list)
1716
1717 if reindex_object:
1718 for o in modified_object_dict.itervalues():
1719 o.reindexObject(activate_kw=activate_kw)
1720
1721 security.declareProtected( Permissions.ModifyPortalContent, 'setId' )
1722 def setId(self, id, reindex = 1):
1723 """
1724 changes id of an object by calling the Zope machine
1725 """
1726 tryMethodCallWithTemporaryPermission(self, 'Copy or Move',
1727 self.aq_inner.aq_parent.manage_renameObject, (self.id, id), {}, CopyError)
1728 # Do not flush any more, because it generates locks
1729
1730 security.declareProtected( Permissions.ModifyPortalContent,
1731 'updateRelatedContent' )
1732 def updateRelatedContent(self, previous_category_url, new_category_url):
1733 """
1734 updateRelatedContent is implemented by portal_categories
1735 """
1736 self._getCategoryTool().updateRelatedContent(self,
1737 previous_category_url, new_category_url)
1738
1739 security.declareProtected(Permissions.ModifyPortalContent, 'edit')
1740 def edit(self, REQUEST=None, force_update=0, reindex_object=1, **kw):
1741 """
1742 Generic edit Method for all ERP5 object
1743 """
1744 return self._edit(REQUEST=REQUEST, force_update=force_update,
1745 reindex_object=reindex_object, restricted=1, **kw)
1746
1747 # XXX Is this useful ? (Romain)
1748 edit = WorkflowMethod(edit)
1749
1750 # Accessing object property through ERP5ish interface
1751 security.declareProtected( Permissions.View, 'getPropertyIdList' )
1752 def getPropertyIdList(self):
1753 return self.propertyIds()
1754
1755 security.declareProtected( Permissions.View, 'getPropertyValueList' )
1756 def getPropertyValueList(self):
1757 return self.propertyValues()
1758
1759 security.declareProtected( Permissions.View, 'getPropertyItemList' )
1760 def getPropertyItemList(self):
1761 return self.propertyItems()
1762
1763 security.declareProtected( Permissions.View, 'getPropertyMap' )
1764 def getPropertyMap(self):
1765 return self.propertyMap()
1766
1767 # ERP5 content properties interface
1768 security.declareProtected( Permissions.View, 'getContentPropertyIdList' )
1769 def getContentPropertyIdList(self):
1770 """
1771 Return content properties of the current instance.
1772 Content properties are filtered out in getPropertyIdList so
1773 that rendering in ZMI is compatible with Zope standard properties
1774 """
1775 return [property['id'] for property in self._erp5_properties if property['type'] == 'content']
1776
1777 security.declareProtected( Permissions.View, 'getStandardPropertyIdList' )
1778 def getStandardPropertyIdList(self):
1779 """
1780 Return standard properties of the current instance.
1781 Unlike getPropertyIdList, properties are not converted or rewritten here.
1782 """
1783 return [property['id'] for property in self._erp5_properties if property['type'] != 'content']
1784
1785 # Catalog Related
1786 security.declareProtected( Permissions.View, 'getObject' )
1787 def getObject(self, relative_url = None, REQUEST=None):
1788 """
1789 Returns self - useful for ListBox when we do not know
1790 if the getObject is called on a brain object or on the actual object
1791 """
1792 return self
1793
1794 def getDocumentInstance(self):
1795 """
1796 Returns self
1797 Returns instance if category through document_instance relation
1798 """
1799 return self
1800
1801 def asSQLExpression(self, strict_membership=0, table='category', base_category = None):
1802 """
1803 Any document can be used as a Category. It can therefore
1804 serve in a Predicate and must be rendered as an sql expression. This
1805 can be useful to create reporting trees based on the
1806 ZSQLCatalog whenever documents are used rather than categories
1807
1808 TODO:
1809 - strict_membership is not implemented
1810 """
1811 if isinstance(base_category, str):
1812 base_category = self.portal_categories[base_category]
1813 if base_category is None:
1814 sql_text = '(%s.category_uid = %s)' % \
1815 (table, self.getUid())
1816 else:
1817 sql_text = '(%s.category_uid = %s AND %s.base_category_uid = %s)' % \
1818 (table, self.getUid(), table, base_category.getBaseCategoryUid())
1819 return sql_text
1820
1821 security.declareProtected( Permissions.AccessContentsInformation,
1822 'getParentSQLExpression' )
1823 def getParentSQLExpression(self, table = 'catalog', strict_membership = 0):
1824 """
1825 Builds an SQL expression to search children and subclidren
1826 """
1827 return "%s.parent_uid = %s" % (table, self.getUid())
1828
1829 security.declareProtected( Permissions.AccessContentsInformation,
1830 'getParentUid' )
1831 def getParentUid(self):
1832 """
1833 Returns the UID of the parent of the current object. Used
1834 for the implementation of the ZSQLCatalog based listing
1835 of objects.
1836 """
1837 return self.aq_inner.aq_parent.getUid()
1838
1839 security.declareProtected( Permissions.AccessContentsInformation,
1840 'getParentTitleOrId' )
1841 def getParentTitleOrId(self):
1842 """
1843 Returns the title or the id of the parent
1844 """
1845 return self.aq_inner.aq_parent.getTitleOrId()
1846
1847 security.declareProtected( Permissions.AccessContentsInformation,
1848 'getParentRelativeUrl' )
1849 def getParentRelativeUrl(self):
1850 """
1851 Returns the title or the id of the parent
1852 """
1853 return self.aq_inner.aq_parent.getRelativeUrl()
1854
1855 security.declareProtected( Permissions.AccessContentsInformation,
1856 'getParentId' )
1857 def getParentId(self):
1858 """
1859 Returns the id of the parent
1860 """
1861 return self.aq_inner.aq_parent.getId()
1862
1863 security.declareProtected( Permissions.AccessContentsInformation,
1864 'getParentTitle' )
1865 def getParentTitle(self):
1866 """
1867 Returns the title or of the parent
1868 """
1869 return self.aq_inner.aq_parent.getTitle()
1870
1871 security.declareProtected( Permissions.AccessContentsInformation,
1872 'getParentValue' )
1873 def getParentValue(self):
1874 """
1875 Returns the parent of the current object.
1876 """
1877 return self.aq_inner.aq_parent
1878
1879 security.declareProtected( Permissions.AccessContentsInformation, 'getParent' )
1880 def getParent(self):
1881 """Returns the parent of the current object (whereas it should return the
1882 relative_url of the parent for consistency with CMFCategory.
1883
1884 This method still uses this behaviour, because some part of the code still
1885 uses getParent instead of getParentValue. This may change in the future.
1886 """
1887 warnings.warn("getParent implementation still returns the parent object, "\
1888 "which is inconsistant with CMFCategory API. "\
1889 "Use getParentValue instead", FutureWarning)
1890 return self.getParentValue() # Compatibility
1891
1892 security.declareProtected( Permissions.AccessContentsInformation, 'getUid' )
1893 def getUid(self):
1894 """
1895 Returns the UID of the object. Eventually reindexes
1896 the object in order to make sure there is a UID
1897 (useful for import / export).
1898
1899 WARNING : must be updated for circular references issues
1900 """
1901 uid = getattr(aq_base(self), 'uid', None)
1902 if uid is None:
1903 self.uid = self.portal_catalog.newUid()
1904 uid = getattr(aq_base(self), 'uid', None)
1905 if uid is None:
1906 raise DeferredCatalogError('Could neither access uid nor generate it', self)
1907 return uid
1908
1909 security.declareProtected(Permissions.AccessContentsInformation, 'getLogicalPath')
1910 def getLogicalPath(self, REQUEST=None) :
1911 """
1912 Returns the absolute path of an object, using titles when available
1913 """
1914 pathlist = self.getPhysicalPath()
1915 objectlist = [self.getPhysicalRoot()]
1916 for element in pathlist[1:] :
1917 objectlist.append(objectlist[-1][element])
1918 return '/' + join([object.getTitle() for object in objectlist[1:]], '/')
1919
1920 security.declareProtected(Permissions.AccessContentsInformation, 'getCompactLogicalPath')
1921 def getCompactLogicalPath(self, REQUEST=None) :
1922 """
1923 Returns a compact representation of the absolute path of an object
1924 using compact titles when available
1925 """
1926 pathlist = self.getPhysicalPath()
1927 objectlist = [self.getPhysicalRoot()]
1928 for element in pathlist[1:] :
1929 objectlist.append(objectlist[-1][element])
1930 return '/' + join([object.getCompactTitle() for object in objectlist[1:]], '/')
1931
1932 security.declareProtected(Permissions.AccessContentsInformation, 'getUrl')
1933 def getUrl(self, REQUEST=None):
1934 """
1935 Returns the absolute path of an object
1936 """
1937 return join(self.getPhysicalPath(),'/')
1938
1939 # Old name - for compatibility
1940 security.declareProtected(Permissions.AccessContentsInformation, 'getPath')
1941 getPath = getUrl
1942
1943 security.declareProtected(Permissions.AccessContentsInformation, 'getRelativeUrl')
1944 def getRelativeUrl(self):
1945 """
1946 Returns the url of an object relative to the portal site.
1947 """
1948 return self.getPortalObject().portal_url.getRelativeUrl(self)
1949
1950 security.declareProtected(Permissions.AccessContentsInformation,
1951 'getAbsoluteUrl')
1952 def getAbsoluteUrl(self):
1953 """
1954 Returns the absolute url of an object.
1955 """
1956 return self.absolute_url()
1957
1958 security.declarePublic('getPortalObject')
1959 def getPortalObject(self):
1960 """
1961 Returns the portal object
1962 """
1963 return self.aq_inner.aq_parent.getPortalObject()
1964
1965 security.declareProtected(Permissions.AccessContentsInformation, 'getWorkflowIds')
1966 def getWorkflowIds(self):
1967 """
1968 Returns the list of workflows
1969 """
1970 return self.portal_workflow.getWorkflowIds()
1971
1972 # Object Database Management
1973 security.declareProtected( Permissions.ManagePortal, 'upgrade' )
1974 def upgrade(self, REQUEST=None):
1975 """
1976 Upgrade an object and do whatever necessary
1977 to make sure it is compatible with the latest
1978 version of a class
1979 """
1980 pass
1981
1982 # For Debugging
1983 security.declareProtected( Permissions.ManagePortal, 'showDict' )
1984 def showDict(self):
1985 """
1986 Returns the dictionnary of the object
1987 Only for debugging
1988 """
1989 d = copy(self.__dict__)
1990 klass = self.__class__
1991 d['__class__'] = '%s.%s' % (klass.__module__, klass.__name__)
1992 return d
1993
1994 security.declareProtected( Permissions.ManagePortal, 'showPermissions' )
1995 def showPermissions(self, all=1):
1996 """
1997 Return the tuple of permissions
1998 Only for debugging
1999 """
2000 permission_list = []
2001 for permission in self.ac_inherited_permissions(all=all):
2002 name, value = permission[:2]
2003 role_list = Permission(name, value, self).getRoles(default=[])
2004 permission_list.append((name, role_list))
2005
2006 return tuple(permission_list)
2007
2008 security.declareProtected( Permissions.AccessContentsInformation, 'getViewPermissionOwner' )
2009 def getViewPermissionOwner(self):
2010 """Returns the user ID of the user with 'Owner' local role on this
2011 document, if the Owner role has View permission.
2012
2013 If there is more than one Owner local role, the result is undefined.
2014 """
2015 if 'Owner' in rolesForPermissionOn(Permissions.View, self):
2016 owner_list = self.users_with_local_role('Owner')
2017 if owner_list:
2018 return owner_list[0]
2019
2020 # Private accessors for the implementation of relations based on
2021 # categories
2022 def _setValue(self, id, target, spec=(), filter=None, portal_type=(), keep_default=1,
2023 checked_permission=None):
2024 start_string = "%s/" % id
2025 start_string_len = len(start_string)
2026 if target is None :
2027 path = target
2028 elif isinstance(target, str):
2029 # We have been provided a string
2030 path = target
2031 if path.startswith(start_string): path = path[start_string_len:] # Prevent duplicating base category
2032 elif isinstance(target, (tuple, list, set, frozenset)):
2033 # We have been provided a list or tuple
2034 path_list = []
2035 for target_item in target:
2036 if isinstance(target_item, str):
2037 path = target_item
2038 else:
2039 path = target_item.getRelativeUrl()
2040 if path.startswith(start_string): path = path[start_string_len:] # Prevent duplicating base category
2041 path_list += [path]
2042 path = path_list
2043 else:
2044 # We have been provided an object
2045 # Find the object
2046 path = target.getRelativeUrl()
2047 if path.startswith(start_string): path = path[start_string_len:] # Prevent duplicating base category
2048 self._setCategoryMembership(id, path, spec=spec, filter=filter, portal_type=portal_type,
2049 base=0, keep_default=keep_default,
2050 checked_permission=checked_permission)
2051
2052 security.declareProtected( Permissions.ModifyPortalContent, '_setValueList' )
2053 _setValueList = _setValue
2054
2055 security.declareProtected( Permissions.ModifyPortalContent, 'setValue' )
2056 def setValue(self, id, target, spec=(), filter=None, portal_type=(), keep_default=1, checked_permission=None):
2057 self._setValue(id, target, spec=spec, filter=filter, portal_type=portal_type, keep_default=keep_default,
2058 checked_permission=checked_permission)
2059 self.reindexObject()
2060
2061 security.declareProtected( Permissions.ModifyPortalContent, 'setValueList' )
2062 setValueList = setValue
2063
2064 def _setDefaultValue(self, id, target, spec=(), filter=None, portal_type=(), checked_permission=None):
2065 start_string = "%s/" % id
2066 start_string_len = len(start_string)
2067 if target is None :
2068 path = target
2069 elif isinstance(target, str):
2070 # We have been provided a string
2071 path = target
2072 if path.startswith(start_string): path = path[start_string_len:] # Prevent duplicating base category
2073 else:
2074 # We have been provided an object
2075 # Find the object
2076 path = target.getRelativeUrl()
2077 if path.startswith(start_string): path = path[start_string_len:] # Prevent duplicating base category
2078 self._setDefaultCategoryMembership(id, path, spec=spec, filter=filter,
2079 portal_type=portal_type, base=0,
2080 checked_permission=checked_permission)
2081
2082 security.declareProtected(Permissions.ModifyPortalContent, 'setDefaultValue' )
2083 def setDefaultValue(self, id, target, spec=(), filter=None, portal_type=()):
2084 self._setDefaultValue(id, target, spec=spec, filter=filter, portal_type=portal_type,
2085 checked_permission=None)
2086 self.reindexObject()
2087
2088 def _getDefaultValue(self, id, spec=(), filter=None, portal_type=(), checked_permission=None):
2089 path = self._getDefaultCategoryMembership(id, spec=spec, filter=filter,
2090 portal_type=portal_type,base=1,
2091 checked_permission=checked_permission)
2092 if path is None:
2093 return None
2094 else:
2095 return self._getCategoryTool().resolveCategory(path)
2096
2097 security.declareProtected(Permissions.AccessContentsInformation,
2098 'getDefaultValue')
2099 getDefaultValue = _getDefaultValue
2100
2101 def _getValueList(self, id, spec=(), filter=None, portal_type=(), checked_permission=None):
2102 ref_list = []
2103 for path in self._getCategoryMembershipList(id, spec=spec, filter=filter,
2104 portal_type=portal_type, base=1,
2105 checked_permission=checked_permission):
2106 # LOG('_getValueList',0,str(path))
2107 try:
2108 value = self._getCategoryTool().resolveCategory(path)
2109 if value is not None: ref_list.append(value)
2110 except ConflictError:
2111 raise
2112 except:
2113 LOG("ERP5Type WARNING",0,"category %s has no object value" % path, error=sys.exc_info())
2114 return ref_list
2115
2116 security.declareProtected(Permissions.AccessContentsInformation,
2117 'getValueList')
2118 getValueList = _getValueList
2119
2120 def _getDefaultAcquiredValue(self, id, spec=(), filter=None, portal_type=(),
2121 evaluate=1, checked_permission=None, **kw):
2122 path = self._getDefaultAcquiredCategoryMembership(id, spec=spec, filter=filter,
2123 portal_type=portal_type, base=1,
2124 checked_permission=checked_permission,
2125 **kw)
2126 if path is None:
2127 return None
2128 else:
2129 return self._getCategoryTool().resolveCategory(path)
2130
2131 security.declareProtected(Permissions.AccessContentsInformation,
2132 'getDefaultAcquiredValue')
2133 getDefaultAcquiredValue = _getDefaultAcquiredValue
2134
2135 def _getAcquiredValueList(self, id, spec=(), filter=None, **kw):
2136 ref_list = []
2137 for path in self._getAcquiredCategoryMembershipList(id, base=1,
2138 spec=spec, filter=filter, **kw):
2139 category = self._getCategoryTool().resolveCategory(path)
2140 if category is not None:
2141 ref_list.append(category)
2142 return ref_list
2143
2144 security.declareProtected(Permissions.AccessContentsInformation,
2145 'getAcquiredValueList')
2146 getAcquiredValueList = _getAcquiredValueList
2147
2148 def _getDefaultRelatedValue(self, id, spec=(), filter=None, portal_type=(),
2149 strict_membership=0, strict="deprecated",
2150 checked_permission=None):
2151 # backward compatibility to keep strict keyword working
2152 if strict != "deprecated" :
2153 strict_membership = strict
2154 value_list = self._getRelatedValueList(
2155 id, spec=spec, filter=filter,
2156 portal_type=portal_type,
2157 strict_membership=strict_membership,
2158 checked_permission=checked_permission)
2159 try:
2160 return value_list[0]
2161 except IndexError:
2162 return None
2163
2164 security.declareProtected(Permissions.AccessContentsInformation,
2165 'getDefaultRelatedValue')
2166 getDefaultRelatedValue = _getDefaultRelatedValue
2167
2168 def _getRelatedValueList(self, *args, **kw):
2169 # backward compatibility to keep strict keyword working
2170 if 'strict' in kw:
2171 kw['strict_membership'] = kw.pop('strict')
2172 return self._getCategoryTool().getRelatedValueList(self, *args, **kw)
2173
2174 security.declareProtected(Permissions.AccessContentsInformation,
2175 'getRelatedValueList')
2176 getRelatedValueList = _getRelatedValueList
2177
2178 def _getDefaultRelatedProperty(self, id, property_name, spec=(), filter=None,
2179 portal_type=(), strict_membership=0,
2180 checked_permission=None):
2181 property_list = self._getCategoryTool().getRelatedPropertyList(self, id,
2182 property_name=property_name,
2183 spec=spec, filter=filter,
2184 portal_type=portal_type,
2185 strict_membership=strict_membership,
2186 checked_permission=checked_permission)
2187 try:
2188 return property_list[0]
2189 except IndexError:
2190 return None
2191
2192 security.declareProtected(Permissions.AccessContentsInformation,
2193 'getDefaultRelatedProperty')
2194 getDefaultRelatedProperty = _getDefaultRelatedProperty
2195
2196
2197 def _getRelatedPropertyList(self, id, property_name, spec=(), filter=None,
2198 portal_type=(), strict_membership=0,
2199 checked_permission=None):
2200 return self._getCategoryTool().getRelatedPropertyList(self, id,
2201 property_name=property_name,
2202 spec=spec, filter=filter,
2203 portal_type=portal_type,
2204 strict_membership=strict_membership,
2205 checked_permission=checked_permission)
2206
2207 security.declareProtected( Permissions.AccessContentsInformation,
2208 'getRelatedPropertyList' )
2209 getRelatedPropertyList = _getRelatedPropertyList
2210
2211 security.declareProtected(Permissions.AccessContentsInformation,
2212 'getValueUidList')
2213 def getValueUidList(self, id, spec=(), filter=None, portal_type=(), checked_permission=None):
2214 uid_list = []
2215 for o in self._getValueList(id, spec=spec, filter=filter, portal_type=portal_type,
2216 checked_permission=checked_permission):
2217 uid_list.append(o.getUid())
2218 return uid_list
2219
2220 security.declareProtected( Permissions.View, 'getValueUids' )
2221 getValueUids = getValueUidList # DEPRECATED
2222
2223 def _setValueUidList(self, id, uids, spec=(), filter=None, portal_type=(), keep_default=1,
2224 checked_permission=None):
2225 # We must do an ordered list so we can not use the previous method
2226 # self._setValue(id, self.portal_catalog.getObjectList(uids), spec=spec)
2227 references = []
2228 if type(uids) not in (type(()), type([])):
2229 uids = [uids]
2230 for uid in uids:
2231 references.append(self.portal_catalog.getObject(uid))
2232 self._setValue(id, references, spec=spec, filter=filter, portal_type=portal_type,
2233 keep_default=keep_default, checked_permission=checked_permission)
2234
2235 security.declareProtected( Permissions.ModifyPortalContent, '_setValueUidList' )
2236 _setValueUids = _setValueUidList # DEPRECATED
2237
2238 security.declareProtected( Permissions.ModifyPortalContent, 'setValueUidList' )
2239 def setValueUidList(self, id, uids, spec=(), filter=None, portal_type=(), keep_default=1, checked_permission=None):
2240 self._setValueUids(id, uids, spec=spec, filter=filter, portal_type=portal_type,
2241 keep_default=keep_default, checked_permission=checked_permission)
2242 self.reindexObject()
2243
2244 security.declareProtected( Permissions.ModifyPortalContent, 'setValueUidList' )
2245 setValueUids = setValueUidList # DEPRECATED
2246
2247 def _setDefaultValueUid(self, id, uid, spec=(), filter=None, portal_type=(),
2248 checked_permission=None):
2249 # We must do an ordered list so we can not use the previous method
2250 # self._setValue(id, self.portal_catalog.getObjectList(uids), spec=spec)
2251 references = self.portal_catalog.getObject(uid)
2252 self._setDefaultValue(id, references, spec=spec, filter=filter, portal_type=portal_type,
2253 checked_permission=checked_permission)
2254
2255 security.declareProtected( Permissions.ModifyPortalContent, 'setDefaultValueUid' )
2256 def setDefaultValueUid(self, id, uid, spec=(), filter=None, portal_type=(), checked_permission=None):
2257 self._setDefaultValueUid(id, uid, spec=spec, filter=filter, portal_type=portal_type,
2258 checked_permission=checked_permission)
2259 self.reindexObject()
2260
2261 # Private accessors for the implementation of categories
2262 def _setCategoryMembership(self, *args, **kw):
2263 self._getCategoryTool()._setCategoryMembership(self, *args, **kw)
2264 #self.activate().edit() # Do nothing except call workflow method
2265 # XXX This is a problem - it is used to circumvent a lack of edit
2266
2267 security.declareProtected( Permissions.ModifyPortalContent, 'setCategoryMembership' )
2268 def setCategoryMembership(self, *args, **kw):
2269 self._setCategoryMembership(*args, **kw)
2270 self.reindexObject()
2271
2272 def _setDefaultCategoryMembership(self, category, node_list,
2273 spec=(), filter=None, portal_type=(), base=0,
2274 checked_permission=None):
2275 self._getCategoryTool().setDefaultCategoryMembership(self, category,
2276 node_list, spec=spec, filter=filter, portal_type=portal_type, base=base,
2277 checked_permission=checked_permission)
2278
2279 security.declareProtected( Permissions.ModifyPortalContent, 'setDefaultCategoryMembership' )
2280 def setDefaultCategoryMembership(self, category, node_list,
2281 spec=(), filter=None, portal_type=(), base=0,
2282 checked_permission=None):
2283 self._setDefaultCategoryMembership(category, node_list, spec=spec, filter=filter,
2284 portal_type=portal_type, base=base,
2285 checked_permission=checked_permission)
2286 self.reindexObject()
2287
2288 def _getCategoryMembershipList(self, category, spec=(), filter=None,
2289 portal_type=(), base=0, keep_default=1, checked_permission=None, **kw):
2290 """
2291 This returns the list of categories for an object
2292 """
2293 return self._getCategoryTool().getCategoryMembershipList(self, category,
2294 spec=spec, filter=filter, portal_type=portal_type, base=base,
2295 keep_default=keep_default, checked_permission=checked_permission, **kw)
2296
2297 security.declareProtected( Permissions.AccessContentsInformation, 'getCategoryMembershipList' )
2298 getCategoryMembershipList = _getCategoryMembershipList
2299
2300 def _getAcquiredCategoryMembershipList(self, category, spec=(), filter=None,
2301 portal_type=(), base=0, keep_default=1, checked_permission=None, **kw):
2302 """
2303 Returns the list of acquired categories
2304 """
2305 return self._getCategoryTool().getAcquiredCategoryMembershipList(self,
2306 category, spec=spec, filter=filter,
2307 portal_type=portal_type, base=base,
2308 keep_default=keep_default,
2309 checked_permission=checked_permission, **kw )
2310
2311 security.declareProtected( Permissions.AccessContentsInformation,
2312 'getAcquiredCategoryMembershipList' )
2313 getAcquiredCategoryMembershipList = _getAcquiredCategoryMembershipList
2314
2315 def _getCategoryMembershipItemList(self, category, spec=(), filter=None, portal_type=(), base=0,
2316 checked_permission=None):
2317 membership_list = self._getCategoryMembershipList(category,
2318 spec=spec, filter=filter, portal_type=portal_type, base=base,
2319 checked_permission=checked_permission)
2320 return [(x, x) for x in membership_list]
2321
2322 def _getAcquiredCategoryMembershipItemList(self, category, spec=(),
2323 filter=None, portal_type=(), base=0, method_id=None, sort_id='default',
2324 checked_permission=None):
2325 # Standard behaviour - should be OK
2326 # sort_id should be None for not sort - default behaviour in other methods
2327 if method_id is None and sort_id in (None, 'default'):
2328 membership_list = self._getAcquiredCategoryMembershipList(category,
2329 spec = spec, filter=filter, portal_type=portal_type, base=base,
2330 checked_permission=checked_permission)
2331 if sort_id == 'default':
2332 membership_list.sort()
2333 return [(x, x) for x in membership_list]
2334 # Advanced behaviour XXX This is new and needs to be checked
2335 membership_list = self._getAcquiredCategoryMembershipList(category,
2336 spec = spec, filter=filter, portal_type=portal_type, base=1,
2337 checked_permission=checked_permission)
2338 result = []
2339 for path in membership_list:
2340 value = self._getCategoryTool().resolveCategory(path)
2341 if value is not None:
2342 result += [value]
2343 result.sort(key=lambda x: getattr(x,sort_id)())
2344 if method_id is None:
2345 return [(x, x) for x in membership_list]
2346 return [(x,getattr(x, method_id)()) for x in membership_list]
2347
2348 def _getDefaultCategoryMembership(self, category, spec=(), filter=None,
2349 portal_type=(), base=0, default=None, checked_permission=None, **kw):
2350 membership = self._getCategoryMembershipList(category,
2351 spec=spec, filter=filter, portal_type=portal_type, base=base,
2352 checked_permission=checked_permission, **kw)
2353 if len(membership) > 0:
2354 return membership[0]
2355 else:
2356 return default
2357
2358 def _getDefaultAcquiredCategoryMembership(self, category, spec=(),
2359 filter=None, portal_type=(), base=0, default=None,
2360 checked_permission=None, **kw):
2361 membership = self._getAcquiredCategoryMembershipList(category,
2362 spec=spec, filter=filter, portal_type=portal_type, base=base,
2363 checked_permission=checked_permission, **kw)
2364 if len(membership) > 0:
2365 return membership[0]
2366 else:
2367 return default
2368
2369 security.declareProtected(Permissions.AccessContentsInformation,
2370 'getDefaultAcquiredCategoryMembership')
2371 getDefaultAcquiredCategoryMembership = _getDefaultAcquiredCategoryMembership
2372
2373 security.declareProtected(Permissions.AccessContentsInformation,
2374 'getCategoryList')
2375 def getCategoryList(self):
2376 """
2377 Returns the list of local categories
2378 """
2379 return self._getCategoryTool().getCategoryList(self)
2380
2381 def _getCategoryList(self):
2382 return self._getCategoryTool()._getCategoryList(self)
2383
2384 security.declareProtected(Permissions.AccessContentsInformation,
2385 'getAcquiredCategoryList')
2386 def getAcquiredCategoryList(self):
2387 """
2388 Returns the list of acquired categories
2389 """
2390 return self._getCategoryTool().getAcquiredCategoryList(self)
2391
2392 def _getAcquiredCategoryList(self):
2393 return self._getCategoryTool()._getAcquiredCategoryList(self)
2394
2395 security.declareProtected( Permissions.ModifyPortalContent, 'setCategoryList' )
2396 def setCategoryList(self, path_list):
2397 self.portal_categories.setCategoryList(self, path_list)
2398
2399 def _setCategoryList(self, path_list):
2400 self.portal_categories._setCategoryList(self, path_list)
2401
2402 security.declareProtected(Permissions.AccessContentsInformation,
2403 'getBaseCategoryList')
2404 def getBaseCategoryList(self):
2405 """
2406 Lists the base_category ids which apply to this instance
2407 """
2408 return self._getCategoryTool().getBaseCategoryList(context=self)
2409
2410 security.declareProtected( Permissions.View, 'getBaseCategoryIds' )
2411 getBaseCategoryIds = getBaseCategoryList
2412
2413 security.declareProtected(Permissions.AccessContentsInformation,
2414 'getBaseCategoryValueList')
2415 def getBaseCategoryValueList(self):
2416 return self._getCategoryTool().getBaseCategoryValues(context=self)
2417
2418 security.declareProtected( Permissions.View, 'getBaseCategoryValues' )
2419 getBaseCategoryValues = getBaseCategoryValueList
2420
2421 def _cleanupCategories(self):
2422 self._getCategoryTool()._cleanupCategories()
2423
2424 # Category testing
2425 security.declareProtected( Permissions.AccessContentsInformation, 'isMemberOf' )
2426 def isMemberOf(self, category, **kw):
2427 """
2428 Tests if an object if member of a given category
2429 """
2430 return self._getCategoryTool().isMemberOf(self, category, **kw)
2431
2432 security.declareProtected( Permissions.AccessContentsInformation, 'isAcquiredMemberOf' )
2433 def isAcquiredMemberOf(self, category):
2434 """
2435 Tests if an object if member of a given category
2436 """
2437 return self._getCategoryTool().isAcquiredMemberOf(self, category)
2438
2439 # Aliases
2440 security.declareProtected(Permissions.AccessContentsInformation,
2441 'getTitleOrId')
2442 def getTitleOrId(self):
2443 """
2444 Returns the title or the id if the id is empty
2445 """
2446 title = self.getTitle()
2447 if title is not None:
2448 title = str(title)
2449 if title == '' or title is None:
2450 return self.getId()
2451 else:
2452 return title
2453 return self.getId()
2454
2455 security.declareProtected(Permissions.AccessContentsInformation, 'Title' )
2456 Title = getTitleOrId # Why ???
2457
2458 # CMF Compatibility
2459 security.declareProtected(Permissions.AccessContentsInformation, 'title_or_id' )
2460 title_or_id = getTitleOrId
2461
2462 security.declareProtected(Permissions.AccessContentsInformation,
2463 'getTitleAndId')
2464 def getTitleAndId(self):
2465 """
2466 Returns the title and the id in parenthesis
2467 """
2468 return self.title_and_id()
2469
2470 security.declareProtected(Permissions.AccessContentsInformation,
2471 'getTranslatedShortTitleOrId')
2472 def getTranslatedShortTitleOrId(self):
2473 """
2474 Returns the translated short title or the id if the id is empty
2475 """
2476 title = self.getTranslatedShortTitle()
2477 if title is not None:
2478 title = str(title)
2479 if title == '' or title is None:
2480 return self.getId()
2481 else:
2482 return title
2483 return self.getId()
2484
2485 security.declareProtected(Permissions.AccessContentsInformation,
2486 'getTranslatedTitleOrId')
2487 def getTranslatedTitleOrId(self):
2488 """
2489 Returns the translated title or the id if the id is empty
2490 """
2491 title = self.getTranslatedTitle()
2492 if title is not None:
2493 title = str(title)
2494 if title == '' or title is None:
2495 return self.getId()
2496 else:
2497 return title
2498 return self.getId()
2499
2500 security.declarePublic('getIdTranslationDict')
2501 def getIdTranslationDict(self):
2502 """Returns the mapping which is used to translate IDs.
2503 """
2504 property_dict = {
2505 'Address': dict(default_address='Default Address'),
2506 'Telephone': dict(default_telephone='Default Telephone',
2507 mobile_telephone='Mobile Telephone',),
2508 'Fax': dict(default_fax='Default Fax'),
2509 'Email': dict(default_email='Default Email',
2510 alternate_email='Alternate Email'),
2511 'Career': dict(default_career='Default Career'),
2512 'Payment Condition': dict(default_payment_condition=
2513 'Default Payment Condition'),
2514 'Annotation Line': dict(
2515 work_time_annotation_line='Work Time Annotation Line',
2516 social_insurance_annotation_line='Social Insurance Annotation Line',
2517 overtime_annotation_line='Overtime Annotation Line'),
2518 'Image': dict(default_image='Default Image'),
2519 'Internal Supply Line': dict(internal_supply_line=
2520 'Default Internal Supply Line'),
2521 'Purchase Supply Line': dict(purchase_supply_line=
2522 'Default Purchase Supply Line'),
2523 'Sale Supply Line': dict(sale_supply_line=
2524 'Default Sale Supply Line'),
2525 'Accounting Transaction Line': dict(bank='Bank',
2526 payable='Payable',
2527 receivable='Receivable'),
2528 'Purchase Invoice Transaction Line': dict(expense='Expense',
2529 payable='Payable',
2530 refundable_vat='Refundable VAT'),
2531 'Sale Invoice Transaction Line': dict(income='Income',
2532 collected_vat='Collected VAT',
2533 receivable='Receivable'),
2534 }
2535 method = self._getTypeBasedMethod('getIdTranslationDict')
2536 if method is not None:
2537 user_dict = method()
2538 for k in user_dict.keys():
2539 if property_dict.get(k, None) is not None:
2540 property_dict[k].update(user_dict[k])
2541 else:
2542 property_dict[k] = user_dict[k]
2543 return property_dict
2544
2545
2546 security.declareProtected(Permissions.AccessContentsInformation,
2547 'getTranslatedId')
2548 def getTranslatedId(self):
2549 """Returns the translated ID, if the ID of the current document has a
2550 special meaning, otherwise returns None.
2551 """
2552 global_translation_dict = self.getIdTranslationDict()
2553 ptype_translation_dict = global_translation_dict.get(
2554 self.portal_type, None)
2555 if ptype_translation_dict is not None:
2556 id_ = self.getId()
2557 if id_ in ptype_translation_dict:
2558 return str(Message('erp5_ui', ptype_translation_dict[id_]))
2559
2560 security.declareProtected(Permissions.AccessContentsInformation,
2561 'getCompactTitle')
2562 def getCompactTitle(self):
2563 """
2564 Returns the first non-null value from the following:
2565 - "getCompactTitle" type based method
2566 - short title
2567 - reference
2568 - title
2569 - id
2570 """
2571 method = self._getTypeBasedMethod('getCompactTitle')
2572 if method is not None:
2573 r = method()
2574 if r: return r
2575 if self.hasShortTitle():
2576 r = self.getShortTitle()
2577 if r: return r
2578 return (self.getProperty('reference') or
2579 # No need to test existence since all Base instances have this method
2580 # Also useful whenever title is calculated
2581 self._baseGetTitle() or
2582 self.getId())
2583
2584 security.declareProtected(Permissions.AccessContentsInformation,
2585 'getCompactTranslatedTitle')
2586 def getCompactTranslatedTitle(self):
2587 """
2588 Returns the first non-null value from the following:
2589 - "getCompactTranslatedTitle" type based method
2590 - "getCompactTitle" type based method
2591 - translated short title
2592 - short title
2593 - reference
2594 - translated title
2595 - title
2596 - id
2597 """
2598 method = self._getTypeBasedMethod('getCompactTranslatedTitle')
2599 if method is not None:
2600 r = method()
2601 if r: return r
2602 method = self._getTypeBasedMethod('getCompactTitle')
2603 if method is not None:
2604 r = method()
2605 if r: return r
2606 if self.hasShortTitle():
2607 r = self.getTranslatedShortTitle()
2608 if r: return r
2609 r = self.getShortTitle()
2610 if r: return r
2611 return (self.getProperty('reference') or
2612 # No need to test existence since all Base instances have this method
2613 # Also useful whenever title is calculated
2614 self._baseGetTranslatedTitle() or
2615 self.getId())
2616
2617 # This method allows to sort objects in list is a more reasonable way
2618 security.declareProtected(Permissions.AccessContentsInformation, 'getIntId')
2619 def getIntId(self):
2620 try:
2621 id_string = self.getId()
2622 return int(id_string)
2623 except (ValueError, TypeError):
2624 try:
2625 return int(id_string, 16)
2626 except (ValueError, TypeError):
2627 return None
2628
2629 def _renderDefaultView(self, view, **kw):
2630 ti = self.getTypeInfo()
2631 if ti is None:
2632 raise NotFound('Cannot find default %s for %r' % (view, self.getPath()))
2633 method = ti.getDefaultViewFor(self, view)
2634 if getattr(aq_base(method), 'isDocTemp', 0):
2635 return method(self, self.REQUEST, self.REQUEST['RESPONSE'], **kw)
2636 else:
2637 return method(**kw)
2638
2639 security.declareProtected(Permissions.View, 'view')
2640 def view(self):
2641 """Returns the default view even if index_html is overridden"""
2642 return self._renderDefaultView('view')
2643
2644 # Default views - the default security in CMFCore
2645 # is View - however, security was not defined on
2646 # __call__ - to be consistent, between view and
2647 # __call__ we have to define permission here to View
2648 security.declareProtected(Permissions.View, '__call__')
2649 __call__ = view
2650
2651 # This special value informs ZPublisher to use __call__. We define it here
2652 # since Products.CMFCore.PortalContent.PortalContent stopped defining it on
2653 # CMF 2.x. They use aliases and Zope3 style views now and make pretty sure
2654 # not to let zpublisher reach this value.
2655 index_html = None
2656 # By the Way, Products.ERP5.Document.File and .Image define their own
2657 # index_html to make sure this value here is not used so that they're
2658 # downloadable by their naked URL.
2659
2660 security.declareProtected(Permissions.View, 'list')
2661 def list(self, reset=0):
2662 """Returns the default list even if folder_contents is overridden"""
2663 return self._renderDefaultView('list', reset=reset)
2664
2665 # Proxy methods for security reasons
2666 security.declareProtected(Permissions.AccessContentsInformation, 'getOwnerInfo')
2667 def getOwnerInfo(self):
2668 """
2669 this returns the Owner Info
2670 """
2671 return self.owner_info()
2672
2673 # Missing attributes
2674 security.declareProtected(Permissions.AccessContentsInformation, 'getPortalType')
2675 def getPortalType(self):
2676 """
2677 This returns the portal_type
2678 """
2679 return self.portal_type
2680
2681 security.declareProtected(Permissions.AccessContentsInformation,
2682 'getTranslatedPortalType')
2683 def getTranslatedPortalType(self):
2684 """
2685 This returns the translated portal_type
2686 """
2687 localizer = self.getPortalObject().Localizer
2688 return localizer.erp5_ui.gettext(self.portal_type).encode('utf8')
2689
2690 security.declareProtected(Permissions.AccessContentsInformation, 'getMetaType')
2691 def getMetaType(self):
2692 """
2693 This returns the Meta Type
2694 """
2695 return self.meta_type
2696
2697 # def _recursiveApply(self,f):
2698 # """
2699 # """
2700 # error_list = []
2701 # for o in self.objectValues():
2702 # try:
2703 # error_list += f(o)
2704 # error_list += o.recursiveApply(f)
2705 # except:
2706 # LOG('ERP5Type.Base',0,"error in recursiveApply : %s, %s on %s"
2707 # % (str(sys.exc_type),str(sys.exc_value),o.getPath()))
2708 #
2709 # return error_list
2710 #
2711 # def recursiveApply(self,f):
2712 # """
2713 # This allows to apply a function, f, on the current object
2714 # and all subobjects.
2715 #
2716 # This function can be created inside a python script on the
2717 # zope management interface, then we just have to call recursiveApply.
2718 # """
2719 # return self._recursiveApply(f)
2720
2721 # Content consistency implementation
2722 def _checkConsistency(self, fixit=False):
2723 """
2724 Check the constitency of objects.
2725
2726 Private method.
2727 """
2728 return []
2729
2730 def _fixConsistency(self):
2731 """
2732 Fix the constitency of objects.
2733
2734 Private method.
2735 """
2736 return self._checkConsistency(fixit=True)
2737
2738 security.declareProtected(Permissions.AccessContentsInformation, 'checkConsistency')
2739 def checkConsistency(self, fixit=False, filter=None, **kw):
2740 """
2741 Check the constitency of objects.
2742
2743 For example we can check if every Organisation has at least one Address.
2744
2745 This method looks the constraints defined inside the propertySheets then
2746 check each of them
2747
2748 Here, we try to check consistency without security, because
2749 consistency should not depend on the user. But if the user does not
2750 have enough permission, the detail of the error should be hidden.
2751 """
2752 def getUnauthorizedErrorMessage(constraint):
2753 return ConsistencyMessage(constraint,
2754 object_relative_url=self.getRelativeUrl(),
2755 message='There is something wrong.')
2756 error_list = UnrestrictedMethod(self._checkConsistency)(fixit=fixit)
2757 if len(error_list) > 0:
2758 try:
2759 self._checkConsistency(fixit=fixit)
2760 except Unauthorized:
2761 error_list = [getUnauthorizedErrorMessage(self)]
2762
2763 # We are looking inside all instances in constraints, then we check
2764 # the consistency for all of them
2765
2766 for constraint_instance in self._filteredConstraintList(filter):
2767 if fixit:
2768 error_list2 = UnrestrictedMethod(
2769 constraint_instance.fixConsistency)(self, **kw)
2770 else:
2771 error_list2 = UnrestrictedMethod(
2772 constraint_instance.checkConsistency)(self, **kw)
2773 if len(error_list2) > 0:
2774 try:
2775 if fixit:
2776 constraint_instance.fixConsistency(self, **kw)
2777 else:
2778 constraint_instance.checkConsistency(self, **kw)
2779 except Unauthorized:
2780 error_list.append(getUnauthorizedErrorMessage(constraint_instance))
2781 else:
2782 error_list += error_list2
2783
2784 if fixit and len(error_list) > 0:
2785 self.reindexObject()
2786
2787 return error_list
2788
2789 security.declareProtected(Permissions.ManagePortal, 'fixConsistency')
2790 def fixConsistency(self, filter=None, **kw):
2791 """
2792 Fix the constitency of objects.
2793 """
2794 return self.checkConsistency(fixit=True, filter=filter, **kw)
2795
2796 def _filteredConstraintList(self, filt):
2797 """
2798 Returns a list of constraints filtered by filt argument.
2799 """
2800 # currently only 'id' is supported.
2801 constraints = self.constraints
2802 if filt is not None:
2803 id_list = filt.get('id', None)
2804 if not isinstance(id_list, (list, tuple)):
2805 id_list = [id_list]
2806 constraints = filter(lambda x:x.id in id_list, constraints)
2807 return constraints
2808
2809 # Context related methods
2810 security.declarePublic('asContext')
2811 def asContext(self, context=None, REQUEST=None, **kw):
2812 """
2813 Allows to have a kind of temp copy of an object edited with kw
2814 parameters. This is somewhat equivalent to use tempObject.
2815
2816 ex : joe_person = person_module.bob_person.asContext(first_name='Joe')
2817 """
2818 if context is None:
2819 pt = self._getTypesTool()
2820 portal_type = self.getPortalType()
2821 type_info = pt.getTypeInfo(portal_type)
2822 if type_info is None:
2823 raise ValueError('No such content type: %s' % portal_type)
2824
2825 context = type_info.constructInstance(
2826 container=self.getParentValue(),
2827 id=self.getId(),
2828 temp_object=True,
2829 is_indexable=False)
2830
2831 context.__dict__.update(self.__dict__)
2832 # Copy REQUEST properties to self
2833 if REQUEST is not None:
2834 # Avoid copying a SESSION object, because it is newly created
2835 # implicitly when not present, thus it may induce conflict errors.
2836 # As ERP5 does not use Zope sessions, it is better to skip SESSION.
2837 for k in REQUEST.keys():
2838 if k != 'SESSION':
2839 setattr(context, k, REQUEST[k])
2840 # Set the original document
2841 kw['_original'] = self
2842 # Define local properties
2843 context.__dict__.update(kw)
2844 return context
2845 else:
2846 return context.asContext(REQUEST=REQUEST, **kw)
2847
2848 security.declarePublic('getOriginalDocument')
2849 def getOriginalDocument(self, context=None, REQUEST=None, **kw):
2850 """
2851 This method returns:
2852 * the original document for an asContext() result document.
2853 * self for a real document.
2854 * None for a temporary document.
2855 """
2856 if not self.isTempObject():
2857 return self
2858 else:
2859 original = getattr(self, '_original', None)
2860 if original is not None:
2861 return aq_inner(original)
2862 else:
2863 return None
2864
2865 security.declarePublic('isTempObject')
2866 def isTempObject(self):
2867 """Return true if self is an instance of a temporary document class.
2868 """
2869 isTempDocument = getattr(self.__class__, 'isTempDocument', None)
2870 if isTempDocument is not None:
2871 return isTempDocument()
2872 else:
2873 return False
2874
2875 security.declareProtected(Permissions.AccessContentsInformation,
2876 'isDeleted')
2877 def isDeleted(self):
2878 """Test if the context is in 'deleted' state"""
2879 for wf in self.getPortalObject().portal_workflow.getWorkflowsFor(self):
2880 state = wf._getWorkflowStateOf(self)
2881 if state is not None and state.getId() == 'deleted':
2882 return True
2883 return False
2884
2885 security.declareProtected(Permissions.AccessContentsInformation,
2886 'getRelationCountForDeletion')
2887 def getRelationCountForDeletion(self):
2888 """Count number of related objects preventing deletion"""
2889 portal = self.getPortalObject()
2890 getRelatedValueList = portal.portal_categories.getRelatedValueList
2891 ignore_list = [x.getPhysicalPath() for x in (
2892 portal.portal_simulation,
2893 portal.portal_trash,
2894 self)]
2895 related_list = [(related.getPhysicalPath(), related)
2896 for o in self.getIndexableChildValueList()
2897 for related in getRelatedValueList(o)]
2898 related_list.sort()
2899 ignored = None
2900 related_count = 0
2901 for related_path, related in related_list:
2902 if ignored is None or related_path[:len(ignored)] != ignored:
2903 for ignored in ignore_list:
2904 if related_path[:len(ignored)] == ignored:
2905 break
2906 else:
2907 if related.isDeleted():
2908 ignored = related_path
2909 else:
2910 ignored = None
2911 related_count += 1
2912 return related_count
2913
2914 # Workflow Related Method
2915 security.declarePublic('getWorkflowStateItemList')
2916 def getWorkflowStateItemList(self):
2917 """
2918 Returns a list of tuples {id:workflow_id, state:workflow_state}
2919 """
2920 result = []
2921 for wf in self.portal_workflow.getWorkflowsFor(self):
2922 result += [(wf.id, wf._getWorkflowStateOf(self, id_only=1))]
2923 return result
2924
2925 security.declarePublic('getWorkflowInfo')
2926 def getWorkflowInfo(self, name='state', wf_id=None):
2927 """
2928 Returns a list of tuples {id:workflow_id, state:workflow_state}
2929 """
2930 portal_workflow = self.portal_workflow
2931 return portal_workflow.getInfoFor(self, name, wf_id=wf_id)
2932
2933 # Hide Acquisition to prevent loops (ex. in cells)
2934 # Another approach is to use XMLObject everywhere
2935 # DIRTY TRICK XXX
2936 # def objectValues(self, *args, **kw):
2937 # return []
2938 #
2939 # def contentValues(self, *args, **kw):
2940 # return []
2941 #
2942 # def objectIds(self, *args, **kw):
2943 # return []
2944 #
2945 # def contentIds(self, *args, **kw):
2946 # return []
2947
2948 security.declarePublic('immediateReindexObject')
2949 def immediateReindexObject(self, *args, **kw):
2950 """
2951 Reindexes an object - also useful for testing
2952 """
2953 root_indexable = int(getattr(self.getPortalObject(),'isIndexable',1))
2954 if self.isIndexable and root_indexable:
2955 #LOG("immediateReindexObject",0,self.getRelativeUrl())
2956 PortalContent.reindexObject(self, *args, **kw)
2957 else:
2958 pass
2959 #LOG("No reindex now",0,self.getRelativeUrl())
2960
2961 security.declarePublic('recursiveImmediateReindexObject')
2962 recursiveImmediateReindexObject = immediateReindexObject
2963
2964 security.declarePublic('reindexObject')
2965 def reindexObject(self, *args, **kw):
2966 """
2967 Reindexes an object
2968 args / kw required since we must follow API
2969 """
2970 self._reindexObject(*args, **kw)
2971
2972 def _reindexObject(self, activate_kw=None, **kw):
2973 # When the activity supports group methods, portal_catalog/catalogObjectList is called instead of
2974 # immediateReindexObject.
2975 # Do not check if root is indexable, it is done into catalogObjectList,
2976 # so we will save time
2977 if self.isIndexable:
2978 if activate_kw is None:
2979 activate_kw = {}
2980
2981 reindex_kw = self.getDefaultReindexParameterDict()
2982 if reindex_kw is not None:
2983 reindex_activate_kw = reindex_kw.pop('activate_kw', None)
2984 if reindex_activate_kw is not None:
2985 reindex_activate_kw = reindex_activate_kw.copy()
2986 if activate_kw is not None:
2987 # activate_kw parameter takes precedence
2988 reindex_activate_kw.update(activate_kw)
2989 activate_kw = reindex_activate_kw
2990 kw.update(reindex_kw)
2991
2992 group_id_list = []
2993 if kw.get("group_id", "") not in ('', None):
2994 group_id_list.append(kw.get("group_id", ""))
2995 if kw.get("sql_catalog_id", "") not in ('', None):
2996 group_id_list.append(kw.get("sql_catalog_id", ""))
2997 group_id = ' '.join(group_id_list)
2998
2999 self.activate(group_method_id='portal_catalog/catalogObjectList',
3000 alternate_method_id='alternateReindexObject',
3001 group_id=group_id,
3002 serialization_tag=self.getRootDocumentPath(),
3003 **activate_kw).immediateReindexObject(**kw)
3004
3005 security.declarePublic('recursiveReindexObject')
3006 recursiveReindexObject = reindexObject
3007
3008 def getRootDocumentPath(self):
3009 # Return the path of its root document, or itself if no root document.
3010 self_path_list = self.getPhysicalPath()
3011 portal_depth = len(self.getPortalObject().getPhysicalPath())
3012 return '/'.join(self_path_list[:portal_depth+2])
3013
3014 security.declareProtected( Permissions.AccessContentsInformation, 'getIndexableChildValueList' )
3015 def getIndexableChildValueList(self):
3016 """
3017 Get indexable childen recursively.
3018 """
3019 if self.isIndexable:
3020 return [self]
3021 return []
3022
3023 security.declareProtected(Permissions.ModifyPortalContent, 'reindexObjectSecurity')
3024 def reindexObjectSecurity(self, *args, **kw):
3025 """
3026 Reindex security-related indexes on the object
3027 (and its descendants).
3028 """
3029 # In ERP5, simply reindex all objects.
3030 #LOG('reindexObjectSecurity', 0, 'self = %r, self.getPath() = %r' % (self, self.getPath()))
3031 self.reindexObject(*args, **kw)
3032
3033 security.declareProtected( Permissions.AccessContentsInformation, 'asXML' )
3034 def asXML(self, root=None):
3035 """
3036 Generate an xml text corresponding to the content of this object
3037 """
3038 return Base_asXML(self, root=root)
3039
3040 # Optimized Menu System
3041 security.declarePublic('allowedContentTypes')
3042 def allowedContentTypes( self ):
3043 """
3044 List portal_types which can be added in this folder / object.
3045 """
3046 return []
3047
3048 security.declarePublic('getVisibleAllowedContentTypeList')
3049 def getVisibleAllowedContentTypeList(self):
3050 """
3051 List portal_types which can be added in this folder / object.
3052 """
3053 return []
3054
3055 security.declareProtected(Permissions.View, 'getBinaryData')
3056 def getBinaryData(self):
3057 """
3058 Return the binary data
3059 """
3060 bin = None
3061 if hasattr(self,'_original'):
3062 bin = self._original._data()
3063 elif hasattr(self,'_data'):
3064 bin = self._data
3065 elif hasattr(self,'data'):
3066 bin = self.data
3067 if bin is not None:
3068 return StringIO(str(bin))
3069 return None
3070
3071 security.declareProtected(Permissions.ModifyPortalContent, 'setBinaryData')
3072 def setBinaryData(self, data):
3073 """
3074 Set the binary data, data must be a cStringIO
3075 """
3076 self.edit(file=data)
3077 #LOG('Base.setBinaryData',0,'data: %s' % str(data))
3078 #obj=''
3079 #if hasattr(self,'_original'):
3080 # LOG('Base.setBinaryData',0,'_original for : %s' % str(self))
3081 # self._original.data = data
3082 #elif hasattr(self,'_data'):
3083 # LOG('Base.setBinaryData',0,'_data for : %s' % str(self))
3084 # self._data = data
3085 #elif hasattr(self,'data'):
3086 # LOG('Base.setBinaryData',0,'data for : %s' % str(self))
3087 # self.data = data
3088
3089 security.declareProtected(Permissions.AccessContentsInformation,
3090 'getRedirectParameterDictAfterAdd')
3091 def getRedirectParameterDictAfterAdd(self, container, **kw):
3092 """Return a dict of parameters to specify where the user is redirected
3093 to after a new object is added in the UI."""
3094 method = self._getTypeBasedMethod('getRedirectParameterDictAfterAdd',
3095 'Base_getRedirectParameterDictAfterAdd')
3096 if method is not None:
3097 return method(container, **kw)
3098
3099 # XXX this should not happen, unless the Business Template is broken.
3100 return dict(redirect_url=container.absolute_url(),
3101 selection_index=None, selection_name=None)
3102
3103 # Hash method
3104 def __hash__(self):
3105 return hash(self.getUid())
3106
3107 security.declareProtected(Permissions.ModifyPortalContent, 'setGuid')
3108 def setGuid(self):
3109 """
3110 This generate a global and unique id
3111 It will be defined like this :
3112 full dns name + portal_name + uid + random
3113 the guid should be defined only one time for each object
3114 """
3115 if not hasattr(self, 'guid'):
3116 guid = ''
3117 # Set the dns name
3118 guid += gethostbyaddr(gethostname())[0]
3119 guid += '_' + self.portal_url.getPortalPath()
3120 guid += '_' + str(self.uid)
3121 guid += '_' + str(random.randrange(1,2147483600))
3122 setattr(self,'guid',guid)
3123
3124 security.declareProtected(Permissions.AccessContentsInformation, 'getGuid')
3125 def getGuid(self):
3126 """
3127 Get the global and unique id
3128 """
3129 return getattr(self,'guid',None)
3130
3131 # Type Casting
3132 def _getTypeBasedMethod(self, method_id, fallback_script_id=None,
3133 script_id=None,**kw):
3134 """
3135 Looks up for a zodb script wich ends with what is given as method_id
3136 and starts with the name of the portal type or meta type.
3137
3138 For example, method_id can be "asPredicate" and we will on a sale
3139 packing list line:
3140 SalePackingListLine_asPredicate
3141 DeliveryLine_asPredicate
3142
3143 fallback_script_id : the script to use if nothing is found
3144 """
3145 # script_id should not be used any more, keep compatibility
3146 if script_id is not None:
3147 LOG('ERP5Type/Base.getTypeBaseMethod',0,
3148 'DEPRECATED script_id parameter is used')
3149 fallback_script_id=script_id
3150
3151 # use a transactional variable to cache results within the same
3152 # transaction
3153 portal_type = self.getPortalType()
3154 tv = getTransactionalVariable()
3155 type_base_cache = tv.setdefault('Base.type_based_cache', {})
3156
3157 cache_key = (portal_type, method_id)
3158 try:
3159 script = type_base_cache[cache_key]
3160 except KeyError:
3161 class_name_list = [portal_type, self.getMetaType()] + \
3162 [base_class.__name__ for base_class in self.__class__.mro()
3163 if issubclass(base_class, Base)]
3164 script_name_end = '_' + method_id
3165 for script_name_begin in class_name_list:
3166 script_id = script_name_begin.replace(' ','') + script_name_end
3167 script = getattr(self, script_id, None)
3168 if script is not None:
3169 type_base_cache[cache_key] = aq_inner(script)
3170 return script
3171 type_base_cache[cache_key] = None
3172
3173 if script is not None:
3174 return script.__of__(self)
3175 if fallback_script_id is not None:
3176 return getattr(self, fallback_script_id)
3177
3178 # Predicate handling
3179 security.declareProtected(Permissions.AccessContentsInformation, 'asPredicate')
3180 def asPredicate(self, script_id=None):
3181 """
3182 This method tries to convert the current Document into a predicate
3183 looking up methods named Class_asPredictae, MetaType_asPredicate, PortalType_asPredicate
3184 """
3185 script = self._getTypeBasedMethod('asPredicate', script_id=script_id)
3186 if script is not None:
3187 return script()
3188 return None
3189
3190 def _getAcquireLocalRoles(self):
3191 """This method returns the value of acquire_local_roles of the object's
3192 portal_type.
3193 - True means local roles are acquired, which is the standard behavior of
3194 Zope objects.
3195 - False means that the role acquisition chain is cut.
3196
3197 The code to support this is on the user class, see
3198 ERP5Security.ERP5UserFactory.ERP5User
3199 """
3200 def cached_getAcquireLocalRoles(portal_type):
3201 ti = self._getTypesTool().getTypeInfo(portal_type)
3202 return ti is None or ti.getTypeAcquireLocalRole()
3203
3204 cached_getAcquireLocalRoles = CachingMethod(cached_getAcquireLocalRoles,
3205 id='Base__getAcquireLocalRoles',
3206 cache_factory='erp5_content_short')
3207 return cached_getAcquireLocalRoles(portal_type=self.getPortalType())
3208
3209 security.declareProtected(Permissions.AccessContentsInformation,
3210 'get_local_permissions')
3211 def get_local_permissions(self):
3212 """
3213 This works like get_local_roles. It allows to get all
3214 permissions defined locally
3215 """
3216 local_permission_list = ()
3217 for permission in self.possible_permissions():
3218 permission_role = getattr(self,pname(permission),None)
3219 if permission_role is not None:
3220 local_permission_list += ((permission,permission_role),)
3221 return local_permission_list
3222
3223 security.declareProtected(Permissions.ManagePortal, 'manage_setLocalPermissions')
3224 def manage_setLocalPermissions(self,permission,local_permission_list=None):
3225 """
3226 This works like manage_setLocalRoles. It allows to set all
3227 permissions defined locally
3228 """
3229 permission_name = pname(permission)
3230 if local_permission_list is None:
3231 if hasattr(self,permission_name):
3232 delattr(self,permission_name)
3233 else:
3234 if isinstance(local_permission_list, str):
3235 local_permission_list = (local_permission_list,)
3236 setattr(self,permission_name,tuple(local_permission_list))
3237
3238 ### Content accessor methods
3239 security.declareProtected(Permissions.View, 'getSearchableText')
3240 def getSearchableText(self, md=None):
3241 """
3242 Used by the catalog for basic full text indexing.
3243 """
3244 searchable_text_list = []
3245 portal_type = self.portal_types.getTypeInfo(self)
3246 if portal_type is None:
3247 # it can be a temp object or a tool (i.e. Activity Tool) for which we have no portal_type definition
3248 # so use definition of 'Base Type' for searchable methods & properties
3249 portal_type = self.portal_types.getTypeInfo('Base Type')
3250 searchable_text_method_id_list = []
3251 # generated from properties methods and add explicitly defined method_ids as well
3252 for searchable_text_property_id in portal_type.getSearchableTextPropertyIdList():
3253 method_id = convertToUpperCase(searchable_text_property_id)
3254 searchable_text_method_id_list.extend(['get%s' %method_id])
3255 searchable_text_method_id_list.extend(portal_type.getSearchableTextMethodIdList())
3256 for method_id in searchable_text_method_id_list:
3257 # XXX: how to exclude exclude acquisition (not working)
3258 #if getattr(aq_base(self), method_id, None) is not None:
3259 # method = getattr(self, method_id, None)
3260 # should we do it as ZSQLCatalog should care for calling this method on proper context?
3261 method = getattr(self, method_id, None)
3262 if method is not None:
3263 method_value = method()
3264 if method_value is not None:
3265 if isinstance(method_value, (list, tuple)):
3266 searchable_text_list.extend(method_value)
3267 else:
3268 searchable_text_list.append(method_value)
3269 searchable_text = ' '.join([str(x) for x in searchable_text_list])
3270 return searchable_text
3271
3272 # Compatibility with CMF Catalog / CPS sites
3273 SearchableText = getSearchableText
3274
3275 security.declareProtected(Permissions.View, 'newError')
3276 def newError(self, **kw):
3277 """
3278 Create a new Error object
3279 """
3280 from Products.ERP5Type.Error import Error
3281 return Error(**kw)
3282
3283
3284 _temp_isIndexable = 0
3285
3286 def _temp_reindexObject(self, *args, **kw):
3287 pass
3288
3289 def _temp_recursiveReindexObject(self, *args, **kw):
3290 pass
3291
3292 def _temp_activate(self, *args, **kw):
3293 return self
3294
3295 def _temp_setUid(self, value):
3296 self.uid = value # Required for Listbox so that no casting happens when we use TempBase to create new objects
3297
3298 def _temp_getUid(self):
3299 try:
3300 return getattr(aq_base(self), 'uid')
3301 except AttributeError:
3302 value = self.getId()
3303 self.setUid(value)
3304 return value
3305
3306 def _temp_setTitle(self, value):
3307 """
3308 Required so that getProperty('title') will work on tempBase objects
3309 The dynamic acquisition work very well for a lot of properties, but
3310 not for title. For example, if we do setProperty('organisation_url'), then
3311 even if organisation_url is not in a propertySheet, the method getOrganisationUrl
3312 will be generated. But this does not work for title, because I(seb)'m almost sure
3313 there is somewhere a method '_setTitle' or 'setTitle' with no method getTitle on Base.
3314 That why setProperty('title') and getProperty('title') does not work.
3315 """
3316 self.title = value
3317
3318 def _temp_getTitle(self):
3319 return getattr(self,'title',None)
3320
3321 security.declarePublic('log')
3322 def log(self, description, content='', level=INFO):
3323 """Put a log message """
3324 warnings.warn("The usage of Base.log is deprecated.\n"
3325 "Please use Products.ERP5Type.Log.log instead.",
3326 DeprecationWarning)
3327 unrestrictedLog(description, content = content, level = level)
3328
3329 # Dublin Core Emulation for CMF interoperatibility
3330 # CMF Dublin Core Compatibility
3331 def Subject(self):
3332 return self.getSubjectList()
3333
3334 def Description(self):
3335 return self.getDescription('')
3336
3337 def EffectiveDate(self):
3338 return self.getEffectiveDate('None')
3339
3340 def ExpirationDate(self):
3341 return self.getExpirationDate('None')
3342
3343 def Contributors(self):
3344 return self.getContributorList()
3345
3346 def Format(self):
3347 return self.getFormat('')
3348
3349 def Language(self):
3350 return self.getLanguage('')
3351
3352 def Rights(self):
3353 return self.getRight('')
3354
3355 # Creation and modification date support through workflow
3356 security.declareProtected(Permissions.AccessContentsInformation, 'getCreationDate')
3357 def getCreationDate(self):
3358 """
3359 Returns the creation date of the document based on workflow information
3360 """
3361 # Check if edit_workflow defined
3362 portal_workflow = getToolByName(self.getPortalObject(), 'portal_workflow')
3363 wf = portal_workflow.getWorkflowById('edit_workflow')
3364 wf_list = list(portal_workflow.getWorkflowsFor(self))
3365 if wf is not None: wf_list = [wf] + wf_list
3366 for wf in wf_list:
3367 try:
3368 history = wf.getInfoFor(self, 'history', None)
3369 except KeyError:
3370 history = None
3371 if history is not None:
3372 if len(history):
3373 # Then get the first line of edit_workflow
3374 return history[0].get('time', None)
3375 if getattr(aq_base(self), 'CreationDate', None) is not None:
3376 return asDate(self.CreationDate())
3377 return None # JPS-XXX - try to find a way to return a creation date instead of None
3378
3379 security.declareProtected(Permissions.AccessContentsInformation, 'getModificationDate')
3380 def getModificationDate(self):
3381 """
3382 Returns the modification date of the document based on workflow information
3383
3384 NOTE: this method is not generic enough. Suggestion: define a modification_date
3385 variable on the workflow which is an alias to time.
3386 """
3387 # Check if edit_workflow defined
3388 portal_workflow = getToolByName(self.getPortalObject(), 'portal_workflow')
3389 wf = portal_workflow.getWorkflowById('edit_workflow')
3390 wf_list = list(portal_workflow.getWorkflowsFor(self))
3391 if wf is not None:
3392 wf_list = [wf] + wf_list
3393 max_date = None
3394 for wf in wf_list:
3395 try:
3396 history = wf.getInfoFor(self, 'history', None)
3397 except KeyError:
3398 history = None
3399 if history is not None and len(history):
3400 date = history[-1].get('time', None)
3401 # Then get the last line of edit_workflow
3402 if date > max_date:
3403 max_date = date
3404 return max_date
3405
3406 # Layout management
3407 security.declareProtected(Permissions.AccessContentsInformation, 'getApplicableLayout')
3408 def getApplicableLayout(self):
3409 """
3410 The applicable layout of a standard document in the content layout.
3411
3412 However, if we are displaying a Web Section as its default document,
3413 we should use the container layout.
3414 """
3415 try:
3416 # Default documents should be displayed in the layout of the container
3417 if self.REQUEST.get('is_web_section_default_document', None):
3418 return self.REQUEST.get('current_web_section').getContainerLayout()
3419 # ERP5 Modules should be displayed as containers
3420 # XXX - this shows that what is probably needed is a more sophisticated
3421 # mapping system between contents and layouts.
3422 if self.getParentValue().meta_type == 'ERP5 Site':
3423 return self.getContainerLayout()
3424 return self.getContentLayout() or self.getContainerLayout()
3425 except AttributeError:
3426 return None
3427
3428 security.declarePublic('isWebMode')
3429 def isWebMode(self):
3430 """
3431 return True if we are in web mode
3432 """
3433 if self.getApplicableLayout() is None:
3434 return False
3435 if getattr(self.REQUEST, 'ignore_layout', 0):
3436 return False
3437 return True
3438
3439 security.declarePublic('isEditableWebMode')
3440 def isEditableWebMode(self):
3441 """
3442 return True if we are in editable mode
3443 """
3444 return getattr(self.REQUEST, 'editable_mode', 0)
3445
3446 security.declarePublic('isEditableMode')
3447 isEditableMode = isEditableWebMode # for backwards compatability
3448
3449
3450 security.declareProtected(Permissions.ChangeLocalRoles,
3451 'updateLocalRolesOnSecurityGroups')
3452 def updateLocalRolesOnSecurityGroups(self, **kw):
3453 """Assign Local Roles to Groups on self, based on Portal Type Role
3454 Definitions and "ERP5 Role Definition" objects contained inside self.
3455 """
3456 self._getTypesTool().getTypeInfo(self) \
3457 .updateLocalRolesOnDocument(self, **kw)
3458
3459 security.declareProtected(Permissions.ModifyPortalContent,
3460 'assignRoleToSecurityGroup')
3461 def assignRoleToSecurityGroup(self, **kw):
3462 """DEPRECATED. This is basically the same as
3463 `updateLocalRolesOnSecurityGroups`, but with a different permission.
3464 """
3465 warnings.warn('assignRoleToSecurityGroup is a deprecated alias to '
3466 'updateLocalRolesOnSecurityGroups. Please note that the '
3467 'permission changed to "Change Local Roles".',
3468 DeprecationWarning)
3469 self.updateLocalRolesOnSecurityGroups(**kw)
3470
3471 security.declareProtected(Permissions.ManagePortal,
3472 'updateRoleMappingsFor')
3473 def updateRoleMappingsFor(self, wf_id, **kw):
3474 """
3475 Update security policy according to workflow settings given by wf_id
3476
3477 There's no check that the document is actually chained to the workflow,
3478 it's caller responsability to perform this check.
3479 """
3480 workflow = self.portal_workflow.getWorkflowById(wf_id)
3481 if workflow is not None:
3482 changed = workflow.updateRoleMappingsFor(self)
3483 if changed:
3484 self.reindexObjectSecurity(activate_kw={'priority':4})
3485
3486 # Template Management
3487 security.declareProtected(Permissions.View, 'getDocumentTemplateList')
3488 def getDocumentTemplateList(self) :
3489 """
3490 Returns an empty list of allowed templates
3491 (this is not a folder)
3492 """
3493 return []
3494
3495 security.declareProtected(Permissions.ModifyPortalContent,'makeTemplate')
3496 def makeTemplate(self):
3497 """
3498 Make document behave as a template.
3499 A template is no longer indexable
3500
3501 TODO:
3502 - make template read only, acquired local roles, etc.
3503 - stronger security model
3504 - prevent from changing templates or invoking workflows
3505 """
3506 parent = self.getParentValue()
3507 if parent.getPortalType() != "Preference" and not parent.isTemplate:
3508 raise ValueError, "Template documents can not be created outside Preferences"
3509 self.isTemplate = ConstantGetter('isTemplate', value=True)
3510 # XXX reset security here
3511
3512 security.declareProtected(Permissions.ModifyPortalContent,'makeTemplateInstance')
3513 def makeTemplateInstance(self):
3514 """
3515 Make document behave as standard document (indexable)
3516 """
3517 if self.getParentValue().getPortalType() == "Preference":
3518 raise ValueError, "Template instances can not be created within Preferences"
3519 # We remove attributes from the instance
3520 # We do this rather than self.isIndexable = 0 because we want to
3521 # go back to previous situation (class based definition)
3522 if self.__dict__.has_key('isIndexable'): delattr(self, 'isIndexable')
3523 if self.__dict__.has_key('isTemplate'): delattr(self, 'isTemplate')
3524
3525 # Add to catalog
3526 self.reindexObject()
3527
3528 # ZODB Transaction Management
3529 security.declarePublic('serialize')
3530 def serialize(self):
3531 """Make the transaction accessing to this object atomic
3532 """
3533 self.id = self.id
3534
3535 # Helpers
3536 def getQuantityPrecisionFromResource(self, resource, d=2):
3537 """
3538 Provides a quick access to precision without accessing the resource
3539 value in ZODB. Here resource is the relative_url of the resource, such as
3540 the result of self.getResource().
3541 """
3542 def cached_getQuantityPrecisionFromResource(resource):
3543 if resource:
3544 resource_value = self.portal_categories.resolveCategory(resource)
3545 if resource_value is not None:
3546 return resource_value.getQuantityPrecision()
3547 return None
3548
3549 cached_getQuantityPrecisionFromResource = CachingMethod(
3550 cached_getQuantityPrecisionFromResource,
3551 id='Base_getQuantityPrecisionFromResource',
3552 cache_factory='erp5_content_short')
3553
3554 precision = cached_getQuantityPrecisionFromResource(resource)
3555 if precision is None:
3556 precision = d
3557 return precision
3558
3559
3560 # Documentation Helpers
3561 security.declareProtected( Permissions.ManagePortal, 'asDocumentationHelper' )
3562 def asDocumentationHelper(self, item_id=None, REQUEST=None):
3563 """
3564 Fills and return a DocHelper object from context.
3565 Overload this in any object the has to fill the DocHelper in its own way.
3566
3567 item_id : If specified, the documented item will be
3568 getattr(self, item_title) if it exists, otherwise None will
3569 be returned.
3570
3571 TODO:
3572 - Check that filtering is correct : display only and every things
3573 defined on the class itself.
3574 - Add a list of all accessible things in the documented item context
3575 (heritated methods & attributes) in a light way that still allows to
3576 link directly to the corresponding documentation.
3577 - Rewrite accessor generation system so that it can generate accessor
3578 names when given a property/category.
3579
3580 KEEPMEs:
3581 There are pieces of code in this function that can have interesting
3582 results, but who are also very verbose. They are disabled (commented
3583 out) by default, but they should be kept.
3584 Explanation of the interest :
3585 The accessors are gathered from 2 sources : the ones defined on the
3586 PortalType, and systematically generated names, and tested for
3587 presence on the documented item.
3588 There are 4 cases then :
3589 -Accessor whose name was generated exists both on the PortalType and
3590 on the documented item. That's normal.
3591 -Accessor whose name was generated exists neither on the PortalType
3592 nor on the documented item. That's normal, but could mean that the
3593 accessor name generator isn't optimal.
3594 -Accessor whose name was generated is found on the object but not on
3595 the PortalType. This is a problem.
3596 -Accessors gathered from PortalType aren't all found by guessing
3597 systematically the names. That means that the accessor name
3598 generation is not perfect and requires improvement.
3599
3600 nb: the accessors are gathered from 2 sources, the first is somehow
3601 accidental when searching for workflow methods defined on the
3602 PortalType, and are browsed a second time to be able to group them
3603 by property or category.
3604 """
3605 # New implementation of DocumentationHelper (ongoing work)
3606 if 0:
3607 from DocumentationHelper import PortalTypeInstanceDocumentationHelper
3608 helper = PortalTypeInstanceDocumentationHelper(self.getRelativeUrl()).__of__(self)
3609 if REQUEST is not None:
3610 return helper.view()
3611 return helper
3612
3613 if item_id is None:
3614 documented_item = self
3615 item_id = documented_item.getTitle()
3616 elif getattr(self, item_id, None) is not None:
3617 documented_item = getattr(self, item_id)
3618 else:
3619 return None
3620
3621 # The documented object is an instance (or not) of this class.
3622 item_class = getattr(documented_item, '__bases__', None) is None \
3623 and documented_item.__class__ \
3624 or documented_item
3625
3626 # XXX assume that we dont care about the portal type class
3627 while item_class.__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'):
3628 item_class = item_class.__bases__[0]
3629
3630 static_method_list = [] # Class methods
3631 static_property_list = [] # Class attributes