CategoryTool.py 8.61 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 Globals import InitializeClass
38
from Products.ERP5Type import Permissions
Sebastien Robin's avatar
Sebastien Robin committed
39
from Products.ERP5Type.CopySupport import CopyContainer
40
from Products.CMFCore.utils import getToolByName
41
from Products.ERP5Type.Cache import CachingMethod
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43 44

from zLOG import LOG

Sebastien Robin's avatar
Sebastien Robin committed
45
class CategoryTool(CopyContainer, CMFCategoryTool, BaseTool):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47 48 49 50 51 52
    """
      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'
53
    portal_type     = 'Category Tool'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
54 55 56 57 58 59 60 61
    allowed_types   = ( 'ERP5 Base Category',)

    # Declarative Security
    security = ClassSecurityInfo()

    # Filter content (ZMI))
    def filtered_meta_types(self, user=None):
        # Filters the list of available meta types.
62
        #all = CMFCategoryTool.inheritedAttribute('filtered_meta_types')(self)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
63 64 65 66 67 68
        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

69 70
    # patch, so that we are able to add the BaseCategory
    allowedContentTypes = BaseTool.allowedContentTypes
71
    getVisibleAllowedContentTypeList = BaseTool.getVisibleAllowedContentTypeList
72

73 74 75
    # Override this method to resolve an inheritance problem.
    def _verifyObjectPaste(self, *args, **kw):
      return BaseTool._verifyObjectPaste(self, *args, **kw)
76 77 78

    all_meta_types = BaseTool.all_meta_types

Sebastien Robin's avatar
Sebastien Robin committed
79 80 81 82
    security.declareProtected(Permissions.View, 'hasContent')
    def hasContent(self,id):
      return id in self.objectIds()

Jean-Paul Smets's avatar
Jean-Paul Smets committed
83 84 85
    security.declareProtected(Permissions.AccessContentsInformation, 'getCategoryParentUidList')
    def getCategoryParentUidList(self, relative_url, base_category = None, strict=0):
      """
86 87 88 89 90 91 92
        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
93 94 95 96 97 98 99 100 101 102 103 104 105 106

        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:
107
              my_base_category = self.getBaseCategoryId(path)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
108
            else:
109 110
              my_base_category = base_category
            bo = getattr(self, my_base_category, None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
111
            if bo is not None:
112 113
              bo_uid = bo.getUid()
              uid_dict[(o.getUid(), bo_uid, 1)] = 1 # Strict membership
Jean-Paul Smets's avatar
Jean-Paul Smets committed
114 115 116 117 118 119 120
              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':
121
                    o = o.aq_parent # We want acquisition here without aq_inner
122
                    uid_dict[(o.getUid(), bo_uid, 0)] = 1 # Non strict
Yoshinori Okuji's avatar
Yoshinori Okuji committed
123
        except (TypeError, KeyError):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
124 125 126 127 128 129
          LOG('WARNING: CategoriesTool',0, 'Unable to find uid for %s' % path)
      return uid_dict.keys()

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

130 131 132 133 134 135 136 137
    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()
      """
      def getBaseCategoryDict(self):
        return dict.fromkeys(self.getBaseCategoryList(), None)
Aurel's avatar
Aurel committed
138
      return CachingMethod(getBaseCategoryDict, 'portal_categories.getBaseCategoryDict', cache_factory='erp5_content_long')(self)
139

140 141
    def updateRelatedContent(self, context,
                             previous_category_url, new_category_url):
142 143 144 145 146
      """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
147

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

      # udpate category related objects
154
      kw = {'category.category_uid': context.getUid(), 'limit': None}
155 156 157 158 159 160 161 162 163 164 165 166
      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
167
      kw = {'predicate_category.category_uid': context.getUid(), 'limit': None}
168 169
      for predicate in portal_catalog(**kw):
        predicate = predicate.getObject()
170 171
        membership_list = []
        for category in predicate.getMembershipCriterionCategoryList():
172 173 174
          new_category = self.updateRelatedCategory(category,
                                                    previous_category_url,
                                                    new_category_url)
175
          membership_list.append(new_category)
176 177
        predicate.edit(membership_criterion_category_list=membership_list,
                       activate_kw=activate_kw)
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

      # 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)
193

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