Commit 9d8ccb42 authored by Jérome Perrin's avatar Jérome Perrin

revive Folder.py from r11362



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@11949 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 4d4f26c7
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from AccessControl import ClassSecurityInfo
from Acquisition import aq_base, aq_self
from OFS.History import Historical
import ExtensionClass
from Products.CMFCore.utils import _getAuthenticatedUser
from Products.CMFCore.CMFCatalogAware import CMFCatalogAware
from Products.ERP5Type.Base import Base
from Products.ERP5Type.CopySupport import CopyContainer
from Products.ERP5Type import PropertySheet, Permissions
from Products.ERP5Type.XMLExportImport import Folder_asXML
from Products.ERP5Type.Cache import CachingMethod
from Products.ERP5Type.Utils import sortValueList
try:
from Products.CMFCore.CMFBTreeFolder import CMFBTreeFolder
except ImportError:
from Products.BTreeFolder2.CMFBTreeFolder import CMFBTreeFolder
from AccessControl import getSecurityManager
from Products.ERP5Type import Permissions
from random import randint
import os
from zLOG import LOG, PROBLEM
# Dummy Functions for update / upgrade
def dummyFilter(object,REQUEST=None):
return 1
def dummyTestAfter(object,REQUEST=None):
return []
class FolderMixIn(ExtensionClass.Base):
"""A mixin class for folder operations, add content, delete content etc.
"""
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
security.declarePublic('newContent')
def newContent(self, id=None, portal_type=None, id_group=None,
default=None, method=None, immediate_reindex=0,
container=None, created_by_builder=0, activate_kw=None,
is_indexable=None, temp_object=0, **kw):
"""Creates a new content.
This method is public, since TypeInformation.constructInstance will perform
the security check.
"""
if container is None:
container = self
if id is None:
new_id = str(container.generateNewId(id_group = id_group,
default=default, method=method))
else:
new_id = str(id)
if portal_type is None:
# XXX This feature is very confusing
# And made the code more difficult to update
portal_type = container.allowedContentTypes()[0].id
if temp_object:
from Products.ERP5Type import Document
# we get an object from factory only for first temp container object
# otherwise we get an id so we can use the classic way
if not hasattr(container, 'isTempObject') or \
(hasattr(container, 'isTempObject') and not container.isTempObject()):
factory_name = 'newTemp%s' %(portal_type.replace(' ', ''))
m = getattr(Document, factory_name)
return m(container, new_id)
self.portal_types.constructContent(type_name=portal_type,
container=container,
id=new_id,
created_by_builder=created_by_builder,
activate_kw=activate_kw,
is_indexable=is_indexable
) # **kw) removed due to CMF bug
# TODO :the **kw makes it impossible to create content not based on
# ERP5TypeInformation, because factory method often do not support
# keywords arguments.
new_instance = container[new_id]
if kw != {} : new_instance._edit(force_update=1, **kw)
if immediate_reindex: new_instance.immediateReindexObject()
return new_instance
security.declareProtected(
Permissions.DeletePortalContent, 'deleteContent')
def deleteContent(self, id):
""" delete items in this folder.
`id` can be a list or a string.
"""
if isinstance(id, str):
self._delObject(id)
elif isinstance(id, list) or isinstance(id, tuple):
for my_id in id:
self._delObject(my_id)
else:
raise TypeError, 'deleteContent only accepts string or list, '\
'not %s' % type(id)
def _generateRandomId(self):
"""
Generate a random Id.
10000 factor makes the odd to generate an already existing Id of 1 out
of 10000, not depending on the number of objects present in this folder.
len(self)+1 to make sure generation works on an empty Folder.
"""
return '%X' % (randint(1, 10000 * (len(self) + 1)), )
def _generateNextId(self):
"""
Get the last generated Id, increment it until no object with generated
Id exist, then save the Id.
"""
try:
my_id = int(self.getLastId())
except TypeError:
my_id = 1
while self.hasContent(str(my_id)):
my_id = my_id + 1
my_id = str(my_id)
self._setLastId(my_id) # Make sure no reindexing happens
return my_id
# Automatic ID Generation method
security.declareProtected(Permissions.View, 'generateNewId')
def generateNewId(self,id_group=None,default=None,method=None):
"""
Generate a new Id which has not been taken yet in this folder.
Eventually increment the id number until an available id
can be found
Permission is view because we may want to add content to a folder
without changing the folder content itself.
XXX
"""
my_id = None
if id_group is None:
id_group = self.getIdGroup()
if id_group in (None, 'None'):
id_generator = self.getIdGenerator()
if not isinstance(id_generator, str):
LOG('Folder.generateNewId', 0, '%s.id_generator is not a string. Falling back on default behaviour.' % (self.absolute_url(), ))
id_generator = ''
if id_generator != '': # Custom aq_dynamic function (like the one defined on WebSite objects) can find an object which has no name. So we must recognise the default value of id_generator and force safe fallback in this case.
idGenerator = getattr(self, id_generator, None)
if idGenerator is None:
idGenerator = self._generateNextId
else:
idGenerator = self._generateNextId
my_id = idGenerator()
while self.hasContent(my_id):
my_id = _generateNextId()
else:
my_id = str(self.portal_ids.generateNewId(id_group=id_group,default=default,method=method))
return my_id
security.declareProtected(Permissions.View, 'hasContent')
def hasContent(self,id):
return self.hasObject(id)
# Get the content
security.declareProtected(Permissions.View, 'searchFolder')
def searchFolder(self, **kw):
"""
Search the content of a folder by calling
the portal_catalog.
"""
if not kw.has_key('parent_uid'): #WHY ????
kw['parent_uid'] = self.getUid()
# Make sure that if we use parent base category
# We do not have conflicting parent uid values
delete_parent_uid = 0
if kw.has_key('selection_domain'):
if kw['selection_domain'].asDomainDict().has_key('parent'):
delete_parent_uid = 1
if kw.has_key('selection_report'):
if kw['selection_report'].asDomainDict().has_key('parent'):
delete_parent_uid = 1
if delete_parent_uid:
del kw['parent_uid']
kw2 = {}
# Remove useless matter before calling the
# catalog. In particular, consider empty
# strings as None values
for cname in kw.keys():
if kw[cname] != '' and kw[cname] != None:
kw2[cname] = kw[cname]
# The method to call to search the folder
# content has to be called z_search_folder
method = self.portal_catalog.portal_catalog
return method(**kw2)
security.declareProtected(Permissions.View, 'countFolder')
def countFolder(self, **kw):
"""
Search the content of a folder by calling
the portal_catalog.
"""
if not kw.has_key('parent_uid'): #WHY ????
kw['parent_uid'] = self.getUid()
# Make sure that if we use parent base category
# We do not have conflicting parent uid values
delete_parent_uid = 0
if kw.has_key('selection_domain'):
if kw['selection_domain'].asDomainDict().has_key('parent'):
delete_parent_uid = 1
if kw.has_key('selection_report'):
if kw['selection_report'].asDomainDict().has_key('parent'):
delete_parent_uid = 1
if delete_parent_uid:
del kw['parent_uid']
kw2 = {}
# Remove useless matter before calling the
# catalog. In particular, consider empty
# strings as None values
for cname in kw.keys():
if kw[cname] != '' and kw[cname]!=None:
kw2[cname] = kw[cname]
# The method to call to search the folder
# content has to be called z_search_folder
method = self.portal_catalog.countResults
return method(**kw2)
# Count objects in the folder
security.declarePrivate('_count')
def _count(self, **kw):
"""
Returns the number of items in the folder.
"""
return self.countFolder(**kw)[0][0]
class Folder( CopyContainer, CMFBTreeFolder, Base, FolderMixIn):
"""
A Folder is a subclass of Base but not of XMLObject.
Folders are not considered as documents and are therefore
not synchronisable.
ERP5 folders are implemented as CMFBTreeFolder objects
and can store up to a million documents on a standard
computer.
ERP5 folders will eventually use in the near future the
AdaptableStorage implementation in order to reach performances
of 10 or 100 millions of documents in a single folder.
ERP5 folders include an automatic id generation feature
which allows user not to define an id when they create
a new document in a folder.
ERP5 folders use the ZSQLCatalog to search for objects
or display content. This requires a method called
*z_search_folder* to be put inside the ZSQLCatalog object
of the ERP5 portal.
An ERP5 Binder document class will eventually be defined
in order to implement a binder of documents which can itself
be categorized.
"""
meta_type = 'ERP5 Folder'
portal_type = 'Folder'
add_permission = Permissions.AddPortalContent
isPortalContent = 1
isRADContent = 1
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
manage_options = ( CMFBTreeFolder.manage_options +
Historical.manage_options +
CMFCatalogAware.manage_options
)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.SimpleItem
, PropertySheet.Folder
)
# Class inheritance fixes
security.declareProtected( Permissions.ModifyPortalContent, 'edit' )
edit = Base.edit
security.declareProtected( Permissions.ModifyPortalContent, '_edit' )
_edit = Base._edit
_setPropValue = Base._setPropValue
_propertyMap = Base._propertyMap # are there any others XXX ?
# Override Zope default by folder id generation
def _get_id(self, id):
if self._getOb(id, None) is None :
return id
return self.generateNewId()
#security.declareProtected( Permissions.DeletePortalContent, 'manage_delObjects' )
#manage_delObjects = CopyContainer.manage_delObjects
# Implementation
# security.declarePrivate('_setObject')
# def _setObject(self, id, object, roles=None, user=None, set_owner=1):
# """
# This method is here in order to dynamically update old
# folders into the new BTree folder type.
# This method is destructive in the sens that objects
# of the old folder will be lost during the update
# """
# # First make sur the folder has been initialized
# if not hasattr(self, '_tree'):
# CMFBTreeFolder.__init__(self, self.id)
# if not self._tree:
# CMFBTreeFolder.__init__(self, self.id)
# # Then insert the object
# CMFBTreeFolder._setObject(self, id, object, roles=roles, user=user, set_owner=set_owner)
# This method destroys the title when we create new object in empty folder
security.declareProtected(Permissions.View, 'hasContent')
def hasContent(self,id):
return self.hasObject(id)
security.declareProtected( Permissions.ModifyPortalContent, 'exportAll' )
def exportAll(self,dir=None):
"""
Allows to export all object inside a particular folder, one by one
"""
folder_id = self.getId()
if dir != None:
for id in self.objectIds():
f = os.path.join(dir, '%s___%s.zexp' % (folder_id,id))
ob = self._getOb(id)
ob._p_jar.exportFile(ob._p_oid,f)
get_transaction().commit()
security.declareProtected( Permissions.ModifyPortalContent, 'recursiveApply')
def recursiveApply(self, filter=dummyFilter, method=None,
test_after=dummyTestAfter, include=1, REQUEST=None, **kw):
"""
Apply a method to self and to all children
filter -- only instances which return 1 when applied filter
are considered
method -- the method to apply to acceptable instances
test_after -- test to apply after calling method in order to search
for inconsistencies
include -- if set to 1 (default), apply method to self
REQUEST -- the http REQUEST (if needed)
**kw -- optional parameters passed to method
"""
update_list = []
#LOG('Folder, recursiveApply ',0,"first one self.path: %s" % self.getPath())
# Only apply method to self if filter is to 1 and filter returns 1
if include==1 and filter(object=self.getObject(),REQUEST=REQUEST):
method_message = method(object=self.getObject(),REQUEST=REQUEST, **kw)
if type(method_message) is type([]):
update_list += method_message
update_list += test_after(object=self.getObject(),REQUEST=REQUEST)
for o in self.objectValues(): # contentValues sometimes fail in BTreeFolder
# Test on each sub object if method should be applied
if filter(object=o,REQUEST=REQUEST):
method_message = method(object=o,REQUEST=REQUEST, **kw)
if type(method_message) is type([]):
update_list += method_message
update_list += test_after(o,REQUEST=REQUEST)
# And commit subtransaction
#get_transaction().commit(1)
get_transaction().commit() # we may use commit(1) some day XXX
# Recursively call recursiveApply if o has a recursiveApply method (not acquired)
obase = aq_base(o)
if hasattr(obase, 'recursiveApply'):
#LOG('Found recursiveApply', 0, o.absolute_url())
update_list += o.recursiveApply(filter=filter, \
method=method, test_after=test_after,REQUEST=REQUEST,include=0,**kw)
return update_list
security.declareProtected( Permissions.ModifyPortalContent, 'updateAll' )
def updateAll(self, filter=None, method=None, test_after=None, request=None, include=1,**kw):
"""
update all objects inside this particular folder wich
returns not None to the test.
filter have to be a method with one parameter (the object)
wich returns None if we must not update the object
test_after have to be a method with one parameter (the object)
wich returns a string
method is the update method with also one parameter
"""
update_list = []
#LOG('Folder, updateAll ',0,"first one self.path: %s" % self.getPath())
if include==1 and filter(object=self.getObject(),request=request):
method_message = method(object=self.getObject(),request=request)
if type(method_message) is type([]):
update_list += method_message
update_list += test_after(object=self.getObject(),request=request)
for o in self.objectValues():
# Test if we must apply the upgrade
if filter(object=o,request=request):
method_message = method(object=o,request=request)
if type(method_message) is type([]):
update_list += method_message
update_list += test_after(object=o,request=request)
#for object in o.objectValues():
#LOG('Folder, updateAll ',0,"object.id: %s" % object.id)
obase = aq_base(o)
get_transaction().commit()
if hasattr(obase, 'updateAll'):
update_list += o.updateAll(filter=filter, \
method=method, test_after=test_after,request=request,include=0,**kw)
return update_list
security.declareProtected( Permissions.ModifyPortalContent, 'upgradeObjectClass' )
def upgradeObjectClass(self, test_before=None, from_class=None,\
to_class=None, test_after=None):
"""
upgrade the class of all objects inside this
particular folder
test have to be a method with one parameter
migrations is a dictionnary of class, { from_class : to_class }
"""
#LOG("upradeObjectClass: folder ",0,self.id)
test_list = []
folder = self.getObject()
for o in self.listFolderContents():
# Make sure this sub object is not the same as object
if o.getPhysicalPath() != self.getPhysicalPath():
id = o.getId()
obase = aq_base(o)
# Check if the subobject have to also be upgraded
if hasattr(obase,'upgradeObjectClass'):
test_list += o.upgradeObjectClass(test_before=test_before, \
from_class=from_class, to_class=to_class,
test_after=test_after)
# Test if we must apply the upgrade
if test_before(o) is not None:
LOG("upradeObjectClass: id ",0,id)
klass = obase.__class__
LOG("upradeObjectClass: klass ",0,str(klass))
LOG("upradeObjectClass: from_class ",0,str(from_class))
if klass == from_class:
try:
newob = to_class(obase.id)
newob.id = obase.id # This line activates obase.
except AttributeError:
newob = to_class(id)
newob.id = id
keys = obase.__dict__.keys()
for k in keys:
if k not in ('id', 'meta_type', '__class__'):
setattr(newob,k,obase.__dict__[k])
self.manage_delObjects(id)
LOG("upradeObjectClass: ",0,"add new object: %s" % str(newob.id))
get_transaction().commit() # XXX this commit should be after _setObject
LOG("upradeObjectClass: ",0,"newob.__class__: %s" % str(newob.__class__))
self._setObject(id, newob)
object_to_test = self._getOb(id)
test_list += test_after(object_to_test)
return test_list
# Catalog related
security.declarePublic( 'reindexObject' )
def reindexObject(self, *args, **kw):
"""
Fixes the hierarchy structure (use of Base class)
XXXXXXXXXXXXXXXXXXXXXXXX
BUG here : when creating a new base category
"""
return Base.reindexObject(self, *args, **kw)
security.declareProtected(Permissions.ModifyPortalContent, 'reindexObjectSecurity')
def reindexObjectSecurity(self):
"""
Reindex security-related indexes on the object
(and its descendants).
"""
# In ERP5, simply reindex all objects.
self.recursiveReindexObject()
security.declarePublic( 'recursiveReindexObject' )
def recursiveReindexObject(self, *args, **kw):
"""
Fixes the hierarchy structure (use of Base class)
XXXXXXXXXXXXXXXXXXXXXXXX
BUG here : when creating a new base category
"""
if self.isIndexable:
self.activate(group_method_id='portal_catalog/catalogObjectList', expand_method_id='getIndexableChildValueList', alternate_method_id='alternateReindexObject', **kw).recursiveImmediateReindexObject(*args, **kw)
security.declareProtected( Permissions.AccessContentsInformation, 'getIndexableChildValueList' )
def getIndexableChildValueList(self):
"""
Get indexable childen recursively.
"""
value_list = []
if self.isIndexable:
value_list.append(self)
for c in self.objectValues():
if hasattr(aq_base(c), 'getIndexableChildValueList'):
value_list.extend(c.getIndexableChildValueList())
return value_list
security.declarePublic( 'recursiveImmediateReindexObject' )
def recursiveImmediateReindexObject(self, *args, **kw):
"""
Applies immediateReindexObject recursively
"""
# Reindex self
root_indexable = int(getattr(self.getPortalObject(),'isIndexable',1))
if self.isIndexable and root_indexable:
self.flushActivity(invoke = 0, method_id='immediateReindexObject') # This might create a recursive lock
self.flushActivity(invoke = 0, method_id='recursiveImmediateReindexObject') # This might create a recursive lock
self.immediateReindexObject(*args, **kw)
# Reindex contents
#LOG('recursiveImmediateReindexObject', 0, 'self = %r, self.objectValues = %r' % (self, self.objectValues()))
for c in self.objectValues():
if hasattr(aq_base(c), 'recursiveImmediateReindexObject'):
c.recursiveImmediateReindexObject(*args, **kw)
security.declareProtected( Permissions.ModifyPortalContent, 'recursiveMoveObject' )
def recursiveMoveObject(self):
"""
Called when the base of a hierarchy is renamed
"""
# Reindex self
if self.isIndexable:
self.moveObject()
# Reindex contents
for c in self.objectValues():
if hasattr(aq_base(c), 'recursiveMoveObject'):
c.recursiveMoveObject()
# Special Relation keyword : 'content' and 'container'
security.declareProtected( Permissions.AccessContentsInformation, '_getCategoryMembershipList' )
def _getCategoryMembershipList(self, category,
spec=(), filter=None, portal_type=(), base=0 ):
if category == 'content':
content_list = self.searchFolder(portal_type=spec)
return map(lambda x: x.relative_url, content_list)
else:
return Base.getCategoryMembershipList(self, category,
spec=spec, filter=filter, portal_type=portal_type, base=base)
# Alias - class inheritance resolution
security.declareProtected( Permissions.View, 'Title' )
Title = Base.Title
security.declareProtected(Permissions.AccessContentsInformation,
'checkConsistency')
def checkConsistency(self, fixit=0):
"""
Check the consistency of this object, then
check recursively the consistency of every sub object.
"""
error_list = []
# Fix BTree
if fixit:
btree_ok = self._cleanup()
if not btree_ok:
# We must commit if we want to keep on recursing
get_transaction().commit(1)
error_list += [(self.getRelativeUrl(), 'BTree Inconsistency',
199, '(fixed)')]
# Call superclass
error_list += Base.checkConsistency(self, fixit=fixit)
# We must commit before listing folder contents
# in case we erased some data
if fixit:
get_transaction().commit(1)
# Then check the consistency on all sub objects
for obj in self.contentValues():
if fixit:
extra_errors = obj.fixConsistency()
else:
extra_errors = obj.checkConsistency()
if len(extra_errors) > 0:
error_list += extra_errors
# We should also return an error if any
return error_list
security.declareProtected( Permissions.AccessContentsInformation, 'asXML' )
def asXML(self, ident=0):
"""
Generate an xml text corresponding to the content of this object
"""
return Folder_asXML(self,ident=ident)
# Optimized Menu System
security.declarePublic('getVisibleAllowedContentTypeList')
def getVisibleAllowedContentTypeList(self):
"""
List portal_types' names wich can be added in this folder / object.
Cache results.
This function is *much* similar to allowedContentTypes, except it does
not returns portal types but their ids and filter out those listed as
hidden content types. It allows to be much faster when only the type id
is needed.
"""
if not getSecurityManager().checkPermission(
Permissions.AddPortalContent, self):
return []
portal = self.getPortalObject()
def _getVisibleAllowedContentTypeList():
hidden_type_list = portal.portal_types.getTypeInfo(self).getHiddenContentTypeList()
return [type.id for type in CMFBTreeFolder.allowedContentTypes(self) if type.id not in hidden_type_list]
user = str(_getAuthenticatedUser(self))
portal_type = self.getPortalType()
portal_path = portal.getPhysicalPath()
_getVisibleAllowedContentTypeList = CachingMethod(_getVisibleAllowedContentTypeList,
id=("_getAllowedContentTypeTitleList",
user, portal_path, portal_type),
cache_duration=None)
return _getVisibleAllowedContentTypeList()
security.declarePublic('allowedContentTypes')
def allowedContentTypes( self ):
""" List portal_types which can be added in this folder / object.
Cache results.
Only paths are cached, because we must not cache objects.
This makes the result, even if based on cache, O(n) so it becomes quite
costly with many allowed content types.
Example:
on Person (12 allowed content types): 1000 calls take 3s.
on Person Module (1 allowed content type): 1000 calls take 0.3s.
"""
# if we don't have add portal content permission, return directly.
# this prevents returning cached allowed types when the user no longer have
# the permission to any content type. (security definitions in workflows
# usually remove some permission once an object is "Valid")
# This also prevents filling the cache with an empty list, when the user
# does not have the permission to add any content yet.
# XXX this works just fine, unless some objects can be added with another
# permission that "Add portal content". For now, this is only the case for
# Role Definition objects, but this shows that generally speaking, this is
# not the right approach.
if not getSecurityManager().checkPermission(
Permissions.AddPortalContent, self):
return []
def _allowedContentTypes( portal_type=None, user=None, portal_path=None ):
# Sort the list for convenience -yo
# XXX This is not the best solution, because this does not take
# account i18n into consideration.
# XXX So sorting should be done in skins, after translation is performed.
def compareTypes(a, b): return cmp(a.title or a.id, b.title or b.id)
type_list = CMFBTreeFolder.allowedContentTypes(self)
type_list.sort(compareTypes)
return ['/'.join(x.getPhysicalPath()) for x in type_list]
_allowedContentTypes = CachingMethod( _allowedContentTypes,
id = 'allowedContentTypes',
cache_duration = None)
user = str(_getAuthenticatedUser(self))
portal_type = self.getPortalType()
portal = self.getPortalObject()
portal_path = portal.getPhysicalPath()
return [portal.restrictedTraverse(path) for path in
_allowedContentTypes( portal_type = portal_type,
user = user,
portal_path = portal_path )]
# Multiple Inheritance Priority Resolution
_setProperty = Base._setProperty
setProperty = Base.setProperty
getProperty = Base.getProperty
hasProperty = Base.hasProperty
view = Base.view
# Aliases
getObjectIds = CMFBTreeFolder.objectIds
# Overloading
security.declareProtected( Permissions.AccessContentsInformation, 'getParentSqlExpression' )
def getParentSqlExpression(self, table = 'catalog', strict_membership = 0):
"""
Builds an SQL expression to search children and subclidren
"""
if strict_membership:
return Base.getParentSqlExpression(self, table=table, strict_membership=strict_membership)
result = "%s.parent_uid = %s" % (table, self.getUid())
for o in self.objectValues():
if hasattr(aq_base(o), 'objectValues'):
# Do not consider non folder objects
result = "%s OR %s" % (result, o.getParentSqlExpression(table=table, strict_membership=strict_membership))
return "( %s )" % result
def mergeContent(self,from_object=None,to_object=None, delete=1,**kw):
"""
This method will merge two objects.
When we have to different objects wich represent the same content, we
may want to merge them. In this case, we want to be sure to report
"""
if from_object is None or to_object is None:
return
from_object_related_object_list = self.portal_categories.getRelatedValueList(from_object)
to_object_url = to_object.getRelativeUrl()
from_object_url = from_object.getRelativeUrl()
corrected_list = []
for object in from_object_related_object_list:
#LOG('Folder.mergeContent, working on object:',0,object)
object_url = object.getRelativeUrl()
new_category_list = []
found = 0
for category in object.getCategoryList(): # so ('destination/person/1',...)
#LOG('Folder.mergeContent, working on category:',0,category)
linked_object_url = '/'.join(category.split('/')[1:])
if linked_object_url == from_object_url:
base_category = category.split('/')[0]
found = 1
new_category_list.append(base_category + '/' + to_object_url)
else:
new_category_list.append(category)
if found:
corrected_list.append(object)
object.setCategoryList(new_category_list)
object.immediateReindexObject()
if delete:
if len(from_object.portal_categories.getRelatedValueList(from_object))==0:
parent = from_object.getParentValue()
parent.manage_delObjects(from_object.getId())
return corrected_list
security.declareProtected( Permissions.AccessContentsInformation,
'objectValues' )
def objectValues(self, spec=None, meta_type=None, portal_type=None,
sort_on=None, sort_order=None, **kw):
"""
Returns a list containing object contained in this folder.
"""
if meta_type is not None:
spec = meta_type
# when an object inherits from Folder after it was instanciated, it lacks
# its BTreeFolder properties.
if getattr(self, '_tree', None) is None:
try:
self._initBTrees()
except AttributeError:
from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
BTreeFolder2Base.__init__(self, self.getId())
object_list = CMFBTreeFolder.objectValues(self, spec=spec)
if portal_type is not None:
if type(portal_type) == type(''):
portal_type = (portal_type,)
object_list = filter(lambda x: x.getPortalType() in portal_type, object_list)
object_list = sortValueList(object_list, sort_on, sort_order, **kw)
return object_list
security.declareProtected( Permissions.AccessContentsInformation,
'contentValues' )
def contentValues(self, spec=None, meta_type=None, portal_type=None,
sort_on=None, sort_order=None, **kw):
"""
Returns a list containing object contained in this folder.
Filter objects with appropriate permissions (as in contentValues)
"""
if meta_type is not None:
spec = meta_type
if portal_type is not None:
kw['portal_type'] = portal_type
filter = kw.pop('filter', {}) or {}
kw.update(filter)
object_list = CMFBTreeFolder.contentValues(self, spec=spec, filter=kw)
object_list = sortValueList(object_list, sort_on, sort_order, **kw)
return object_list
# Override security declaration of CMFCore/PortalFolder (used by CMFBTreeFolder)
security.declareProtected(Permissions.ModifyPortalContent,'setDescription')
security.declareProtected( Permissions.ModifyPortalContent, 'setTitle' )
security.declareProtected( Permissions.AccessContentsInformation, 'manage_copyObjects' ) # XXX Why this one doesn't work in CopySupport ?
security.declareProtected( Permissions.AddPortalContent, 'manage_pasteObjects' ) # XXX Why this one doesn't work in CopySupport ?
# Template Management
security.declareProtected(Permissions.View, 'getDocumentTemplateList')
def getDocumentTemplateList(self) :
"""
Returns the list of allowed templates for this folder
by calling the preference tool
"""
return self.getPortalObject().portal_preferences.getDocumentTemplateList(self)
security.declareProtected(Permissions.ModifyPortalContent,'makeTemplate')
def makeTemplate(self):
"""
Make document behave as a template.
A template is no longer indexable
TODO:
- prevent from changing templates or invoking workflows
"""
Base.makeTemplate(self)
for o in self.objectValues():
if hasattr(aq_base(o), 'makeTemplate'): o.makeTemplate()
security.declareProtected(Permissions.ModifyPortalContent,'makeTemplateInstance')
def makeTemplateInstance(self):
"""
Make document behave as standard document (indexable)
"""
Base.makeTemplateInstance(self)
for o in self.objectValues():
if hasattr(aq_base(o), 'makeTemplateInstance'): o.makeTemplateInstance()
def _delObject(self, id, dp=1):
"""
_delObject is redefined here in order to make sure
we do not do silent except while we remove objects
from catalog
"""
object = self._getOb(id)
object.manage_beforeDelete(object, self)
self._delOb(id)
# Overwrite Zope setTitle()
Folder.setTitle = Base.setTitle
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment