TrashTool.py 8.44 KB
Newer Older
Aurel's avatar
Aurel committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
4
#                    Aurelien Calonne <aurel@nexedi.com>
Aurel's avatar
Aurel committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#
# 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.
#
##############################################################################

29
import imp, re, sys
Aurel's avatar
Aurel committed
30
from AccessControl import ClassSecurityInfo
31
from ZODB.broken import Broken
32
from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
33
from Products.ERP5Type.Globals import InitializeClass, DTMLFile
Aurel's avatar
Aurel committed
34 35 36
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions
from Products.ERP5 import _dtmldir
37
from zLOG import LOG, WARNING
Aurel's avatar
Aurel committed
38 39
from DateTime import DateTime
from Acquisition import aq_base
40
from cStringIO import StringIO
Aurel's avatar
Aurel committed
41 42 43

class TrashTool(BaseTool):
  """
Vincent Pelletier's avatar
Vincent Pelletier committed
44
    TrashTool contains objects removed/replaced during installation of business templates.
Aurel's avatar
Aurel committed
45 46 47 48
  """
  id = 'portal_trash'
  meta_type = 'ERP5 Trash Tool'
  portal_type = 'Trash Tool'
49
  title = 'Trash Bins'
Aurel's avatar
Aurel committed
50 51 52 53
  allowed_types = ('ERP5 Trash Bin',)

  # Declarative Security
  security = ClassSecurityInfo()
54

Aurel's avatar
Aurel committed
55
  security.declareProtected(Permissions.ManagePortal, 'manage_overview' )
Aurel's avatar
Aurel committed
56
  manage_overview = DTMLFile( 'explainTrashTool', _dtmldir )
Aurel's avatar
Aurel committed
57

58
  security.declarePrivate('backupObject')
59
  def backupObject(self, trashbin, container_path, object_id, save, keep_subobjects=False):
Aurel's avatar
Aurel committed
60 61
    """
      Backup an object in a trash bin
62

Aurel's avatar
Aurel committed
63 64 65 66 67 68 69 70 71
    """
#     LOG('Trash : backup object', 0, str((container_path, object_id)))
    if save:
      # recreate path of the backup object if necessary
      backup_object_container = trashbin
      for path in container_path:
        if 'portal' in path:
          path += '_items'
        if path not in backup_object_container.objectIds():
72 73 74 75 76 77 78
          if not hasattr(aq_base(backup_object_container), "newContent"):
            backup_object_container.manage_addFolder(id=path,)
            backup_object_container = backup_object_container._getOb(path)
          else:
            backup_object_container = backup_object_container.newContent(portal_type='Trash Folder', id=path,
                                                                         is_indexable=0)
            backup_object_container.edit(isHidden=1)
Aurel's avatar
Aurel committed
79 80 81 82 83
        else:
          backup_object_container = backup_object_container._getOb(path)
      # backup the object
      # here we choose export/import to copy because cut/paste
      # do too many things and check for what we want to do
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
      object_path = container_path + [object_id]
      obj = self.unrestrictedTraverse(object_path, None)
      if obj is not None:
        connection = obj._p_jar
        o = obj
        while connection is None:
          o = o.aq_parent
          connection=o._p_jar
        if obj._p_oid is None:
          LOG("Trash Tool backupObject", WARNING,
              "Trying to backup uncommitted object %s" % object_path)
          return {}
        if isinstance(obj, Broken):
          LOG("Trash Tool backupObject", WARNING,
              "Can't backup broken object %s" % object_path)
          klass = obj.__class__
          if klass.__module__[:27] in ('Products.ERP5Type.Document.',
                                        'erp5.portal_type'):
            # meta_type is required so that a broken object
            # can be removed properly from a BTreeFolder2
            # (unfortunately, we can only guess it)
            klass.meta_type = 'ERP5' + re.subn('(?=[A-Z])', ' ',
                                                klass.__name__)[0]
          return {}
        copy = connection.exportFile(obj._p_oid)
        # import object in trash
        connection = backup_object_container._p_jar
        o = backup_object_container
        while connection is None:
          o = o.aq_parent
          connection=o._p_jar
        copy.seek(0)
        try:
          backup = connection.importFile(copy)
          backup.isIndexable = ConstantGetter('isIndexable', value=False)
          # the isIndexable setting above avoids the recursion of
          # manage_afterAdd on
          # Products.ERP5Type.CopySupport.CopySupport.manage_afterAdd()
          # but not on event subscribers, so we need to suppress_events,
          # otherwise subobjects will be reindexed
          backup_object_container._setObject(object_id, backup,
                                              suppress_events=True)
        except (AttributeError, ImportError):
          # XXX we can go here due to formulator because attribute
          # field_added doesn't not exists on parent if it is a Trash
          # Folder and not a Form, or a module for the old object is
          # already removed, and we cannot backup the object
          LOG("Trash Tool backupObject", WARNING,
              "Can't backup object %s" % object_path)
          return {}
134 135 136

    subobjects_dict = {}

137
    if not keep_subobjects:
138 139
      # export subobjects
      if save:
140
        obj = backup_object_container._getOb(object_id, None)
141
      else:
Alexandre Boeglin's avatar
Alexandre Boeglin committed
142
        object_path = container_path + [object_id]
143
        obj = self.unrestrictedTraverse(object_path, None)
144
      if obj is not None:
145
        for subobject_id in list(obj.objectIds()):
146 147 148 149
          subobject = obj[subobject_id]
          subobjects_dict[subobject_id] = subobject._p_jar.exportFile(
            subobject._p_oid, StringIO())

150
          if save: # remove subobjecs from backup object
151
            obj._delObject(subobject_id)
152 153 154
            if subobject_id in obj.objectIds():
              LOG('Products.ERP5.Tool.TrashTool', WARNING,
                  'Cleaning corrupted BTreeFolder2 object at %r.' % \
Łukasz Nowak's avatar
Łukasz Nowak committed
155
                                                       (subobject.getRelativeUrl(),))
156
              obj._cleanup()
Aurel's avatar
Aurel committed
157 158
    return subobjects_dict

159
  security.declarePrivate('newTrashBin')
Aurel's avatar
Aurel committed
160 161 162 163 164 165 166
  def newTrashBin(self, bt_title='trash', bt=None):
    """
      Create a new trash bin at upgrade of bt
    """
    # construct date
    date = DateTime()
    start_date = date.strftime('%Y-%m-%d')
167 168 169 170 171 172 173 174 175

    def getBaseTrashId():
      ''' A little function to get an id without leading underscore
      '''
      base_id = '%s' % start_date
      if bt_title not in ('', None):
        base_id = '%s_%s' % (bt_title, base_id)
      return base_id

Aurel's avatar
Aurel committed
176 177 178
    # generate id
    trash_ids = self.objectIds()
    n = 0
179 180 181 182
    new_trash_id = getBaseTrashId()
    while new_trash_id in trash_ids:
      n += 1
      new_trash_id = '%s_%s' % (getBaseTrashId(), n)
Aurel's avatar
Aurel committed
183
    # create trash bin
184 185 186 187 188 189
    trashbin = self.newContent( portal_type     = 'Trash Bin'
                              , id              = new_trash_id
                              , title           = bt_title
                              , start_date      = start_date
                              , causality_value = bt
                              )
Aurel's avatar
Aurel committed
190 191
    return trashbin

192
  security.declareProtected(Permissions.ManagePortal, 'getTrashBinObjectsList')
Aurel's avatar
Aurel committed
193 194 195 196
  def getTrashBinObjectsList(self, trashbin):
    """
      Return a list of trash objects for a given trash bin
    """
197
    def getChildObjects(obj):
Aurel's avatar
Aurel committed
198
      object_list = []
199 200 201 202 203
      if hasattr(aq_base(obj), 'objectValues'):
        childObjects = obj.objectValues()
      if hasattr(aq_base(obj), 'isHidden'):
        if not obj.isHidden:
          object_list.append(obj)
Aurel's avatar
Aurel committed
204
      if len(childObjects) > 0:
205
        for o in childObjects:
206
          object_list.extend(getChildObjects(o))
Aurel's avatar
Aurel committed
207
      else:
208
        object_list.append(obj)
Aurel's avatar
Aurel committed
209
      return object_list
210

Aurel's avatar
Aurel committed
211 212 213 214 215
    list = getChildObjects(trashbin)
    list.sort()
    return list

InitializeClass(TrashTool)