CategoryTool.py 8.74 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6
#
# WARNING: This program as such is intended to be used by professional
7
# programmers who take the whole responsibility of assessing all potential
Jean-Paul Smets's avatar
Jean-Paul Smets committed
8 9
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
10
# guarantees and support are strongly adviced to contract a Free Software
Jean-Paul Smets's avatar
Jean-Paul Smets committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# 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.
#
##############################################################################

"""\
Vincent Pelletier's avatar
Vincent Pelletier committed
30
ERP5 portal_categories tool.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32
"""

33
from Products.CMFCategory.CategoryTool import CategoryTool as CMFCategoryTool
34
from Products.ERP5Type.Tool.BaseTool import BaseTool
35
from AccessControl import ClassSecurityInfo
36
from Acquisition import aq_base
37
from Products.ERP5Type.Globals import InitializeClass
38
from Products.ERP5Type import Permissions
39
from Products.ERP5Type.Core.Folder import OFS_HANDLER
Sebastien Robin's avatar
Sebastien Robin committed
40
from Products.ERP5Type.CopySupport import CopyContainer
41
from Products.CMFCore.utils import getToolByName
42
from Products.ERP5Type.Cache import caching_instance_method
Jean-Paul Smets's avatar
Jean-Paul Smets committed
43 44 45

from zLOG import LOG

Sebastien Robin's avatar
Sebastien Robin committed
46
class CategoryTool(CopyContainer, CMFCategoryTool, BaseTool):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47 48 49 50 51 52 53
    """
      The CategoryTool object is the placeholder for all methods
      and algorithms related to categories and relations in ERP5.
    """

    id              = 'portal_categories'
    meta_type       = 'ERP5 Categories'
54
    portal_type     = 'Category Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55
    allowed_types   = ( 'ERP5 Base Category',)
56
    _folder_handler = OFS_HANDLER
Jean-Paul Smets's avatar
Jean-Paul Smets committed
57 58 59 60

    # Declarative Security
    security = ClassSecurityInfo()

61 62
    objectValues = BaseTool.objectValues

Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65
    # Filter content (ZMI))
    def filtered_meta_types(self, user=None):
        # Filters the list of available meta types.
66
        #all = CMFCategoryTool.inheritedAttribute('filtered_meta_types')(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
67 68 69 70 71 72
        meta_types = []
        for meta_type in self.all_meta_types():
            if meta_type['name'] in self.allowed_types:
                meta_types.append(meta_type)
        return meta_types

73 74
    # patch, so that we are able to add the BaseCategory
    allowedContentTypes = BaseTool.allowedContentTypes
75
    getVisibleAllowedContentTypeList = BaseTool.getVisibleAllowedContentTypeList
76

77 78 79
    # Override this method to resolve an inheritance problem.
    def _verifyObjectPaste(self, *args, **kw):
      return BaseTool._verifyObjectPaste(self, *args, **kw)
80 81 82

    all_meta_types = BaseTool.all_meta_types

Sebastien Robin's avatar
Sebastien Robin committed
83 84 85 86
    security.declareProtected(Permissions.View, 'hasContent')
    def hasContent(self,id):
      return id in self.objectIds()

Jean-Paul Smets's avatar
Jean-Paul Smets committed
87 88 89
    security.declareProtected(Permissions.AccessContentsInformation, 'getCategoryParentUidList')
    def getCategoryParentUidList(self, relative_url, base_category = None, strict=0):
      """
90 91 92 93 94 95 96
        Returns the uids of all categories provided in categorie. This
        method can support relative_url such as site/group/a/b/c which
        base category is site yet use categories defined in group.

        It is also able to use acquisition to create complex categories
        such as site/group/a/b/c/b1/c1 where b and b1 are both children
        categories of a.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110

        relative_url -- a single relative url of a list of
                        relative urls

        strict       -- if set to 1, only return uids of parents, not
                        relative_url
      """
      uid_dict = {}
      if type(relative_url) is type('a'): relative_url = (relative_url,)
      for path in relative_url:
        try:
          o = self.getCategoryValue(path, base_category=base_category)
          if o is not None:
            if base_category is None:
111
              my_base_category = self.getBaseCategoryId(path)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
112
            else:
113 114
              my_base_category = base_category
            bo = getattr(self, my_base_category, None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
115
            if bo is not None:
116 117
              bo_uid = bo.getUid()
              uid_dict[(o.getUid(), bo_uid, 1)] = 1 # Strict membership
Jean-Paul Smets's avatar
Jean-Paul Smets committed
118 119 120 121 122 123 124
              if o.meta_type == 'ERP5 Category' or o.meta_type == 'ERP5 Base Category' or \
                o.meta_type == 'CMF Category' or o.meta_type == 'CMF Base Category':
                # This goes up in the category tree
                # XXX we should also go up in some other cases....
                # ie. when some documents act as categories
                if not strict:
                  while o.meta_type == 'ERP5 Category' or o.meta_type == 'CMF Category':
125
                    o = o.aq_parent # We want acquisition here without aq_inner
126
                    uid_dict[(o.getUid(), bo_uid, 0)] = 1 # Non strict
Yoshinori Okuji's avatar
Yoshinori Okuji committed
127
        except (TypeError, KeyError):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
128 129 130 131 132 133
          LOG('WARNING: CategoriesTool',0, 'Unable to find uid for %s' % path)
      return uid_dict.keys()

    security.declareProtected(Permissions.AccessContentsInformation, 'getUids')
    getUids = getCategoryParentUidList

134
    @caching_instance_method(id='portal_categories.getBaseCategoryDict', cache_factory='erp5_content_long', cache_id_generator=lambda m, *a, **k:m)
135 136 137 138 139 140
    def getBaseCategoryDict(self):
      """
        Cached method to which resturns a dict with category names as keys, and None as values.
        This allows to search for an element existence in the list faster.
        ie: if x in self.getPortalObject().portal_categories.getBaseCategoryDict()
      """
141
      return dict.fromkeys(self.getBaseCategoryList(), None)
142

143 144
    def updateRelatedContent(self, context,
                             previous_category_url, new_category_url):
145 146 147 148 149
      """Updates categories of related objects and predicate membership.
          o context: the moved object
          o previous_category_url: the related url of this object before
            the move
          o new_category_url: the related url of the object after the move
150

151
      TODO: make this method resist to very large updates (ie. long transaction)
152
      """
153
      portal_catalog = getToolByName(context, 'portal_catalog')
154 155 156
      activate_kw = {'tag':'%s_updateRelatedContent' % context.getPath()}

      # udpate category related objects
157
      kw = {'category.category_uid': context.getUid(), 'limit': None}
158 159 160 161 162 163 164 165 166 167 168 169
      for related_object in portal_catalog(**kw):
        related_object = related_object.getObject()
        category_list = []
        for category in related_object.getCategoryList():
          new_category = self.updateRelatedCategory(category,
                                                    previous_category_url,
                                                    new_category_url)
          category_list.append(new_category)
        related_object.edit(categories=category_list,
                            activate_kw=activate_kw)

      # udpate all predicates membership
170
      kw = {'predicate_category.category_uid': context.getUid(), 'limit': None}
171 172
      for predicate in portal_catalog(**kw):
        predicate = predicate.getObject()
173 174
        membership_list = []
        for category in predicate.getMembershipCriterionCategoryList():
175 176 177
          new_category = self.updateRelatedCategory(category,
                                                    previous_category_url,
                                                    new_category_url)
178
          membership_list.append(new_category)
179 180
        predicate.edit(membership_criterion_category_list=membership_list,
                       activate_kw=activate_kw)
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

      # update related recursively if required
      aq_context = aq_base(context)
      if getattr(aq_context, 'listFolderContents', None) is not None:
        for o in context.listFolderContents():
          new_o_category_url = o.getRelativeUrl()
          # Relative Url is based on parent new_category_url so we must
          # replace new_category_url with previous_category_url to find
          # the new category_url for the subobject
          previous_o_category_url = self.updateRelatedCategory(
              new_o_category_url,
              new_category_url,
              previous_category_url)
          self.updateRelatedContent(o, previous_o_category_url,
                                    new_o_category_url)
196

Jean-Paul Smets's avatar
Jean-Paul Smets committed
197
InitializeClass( CategoryTool )