File: MigrationTool.py

package info (click to toggle)
zope-cmfplone 2.5.1-4etch3
  • links: PTS
  • area: main
  • in suites: etch
  • size: 7,752 kB
  • ctags: 5,237
  • sloc: python: 28,264; xml: 3,723; php: 129; makefile: 99; sh: 2
file content (417 lines) | stat: -rw-r--r-- 16,245 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
from Globals import InitializeClass, DTMLFile, DevelopmentMode
from AccessControl import ClassSecurityInfo
from OFS.SimpleItem import SimpleItem
from ZODB.POSException import ConflictError

from Products.CMFCore.utils import UniqueObject, getToolByName
from Products.CMFCore.permissions import ManagePortal, View
from Products.CMFPlone.PloneBaseTool import PloneBaseTool
from Products.CMFPlone.utils import versionTupleFromString
from Products.CMFPlone.utils import log
from Products.CMFPlone.utils import log_deprecated
import transaction

import logging
import traceback
import sys

_upgradePaths = {}
_widgetRegistry = {}

class MigrationTool(PloneBaseTool, UniqueObject, SimpleItem):
    """Handles migrations between Plone releases"""

    id = 'portal_migration'
    meta_type = 'Plone Migration Tool'
    toolicon = 'skins/plone_images/site_icon.gif'

    __implements__ = (PloneBaseTool.__implements__, SimpleItem.__implements__, )

    _needRecatalog = 0
    _needUpdateRole = 0

    manage_options = (
        { 'label' : 'Overview', 'action' : 'manage_overview' },
        { 'label' : 'Migrate', 'action' : 'manage_migrate' },
        { 'label' : 'Setup', 'action' : 'manage_setup' },
        )

    security = ClassSecurityInfo()

    security.declareProtected(ManagePortal, 'manage_overview')
    security.declareProtected(ManagePortal, 'manage_results')
    security.declareProtected(ManagePortal, 'manage_migrate')
    security.declareProtected(ManagePortal, 'manage_setup')

    manage_migrate = DTMLFile('www/migrationRun', globals())
    manage_overview = DTMLFile('www/migrationTool', globals())
    manage_results = DTMLFile('www/migrationResults', globals())
    manage_setup = DTMLFile('www/migrationSetup', globals())

    # Add a visual note
    def om_icons(self):
        icons = ({
                    'path':'misc_/CMFPlone/tool.gif',
                    'alt':self.meta_type,
                    'title':self.meta_type,
                 },)
        if self.needUpgrading() \
           or self.needUpdateRole() \
           or self.needRecatalog():
            icons = icons + ({
                     'path':'misc_/PageTemplates/exclamation.gif',
                     'alt':'Error',
                     'title':'This Plone instance needs updating'
                  },)

        return icons

    ##############################################################
    # Public methods
    #
    # versions methods

    security.declareProtected(ManagePortal, 'getInstanceVersion')
    def getInstanceVersion(self):
        """ The version this instance of plone is on """
        if getattr(self, '_version', None) is None:
            self.setInstanceVersion(self.getFileSystemVersion())
        return self._version.lower()

    security.declarePublic('isPloneOne')
    def isPloneOne(self):
        """ is this still a plone 1 instance? Needed for require login"""
        log_deprecated("isPloneOne is deprecated and will be removed in "
                       "Plone 3.0.")
        ver = self.getInstanceVersion().strip()
        if ver.startswith('1'):
            return 1

    security.declareProtected(ManagePortal, 'setInstanceVersion')
    def setInstanceVersion(self, version):
        """ The version this instance of plone is on """
        self._version = version

    security.declareProtected(ManagePortal, 'knownVersions')
    def knownVersions(self):
        """ All known version ids, except current one """
        return _upgradePaths.keys()

    security.declareProtected(ManagePortal, 'getFileSystemVersion')
    def getFileSystemVersion(self):
        """ The version this instance of plone is on """
        return self.Control_Panel.Products.CMFPlone.version.lower()

    security.declareProtected(View, 'getFSVersionTuple')
    def getFSVersionTuple(self):
        """ returns tuple representing filesystem version """
        v_str = self.getFileSystemVersion()
        return versionTupleFromString(v_str)

    security.declareProtected(View, 'getInstanceVersionTuple')
    def getInstanceVersionTuple(self):
        """ returns tuple representing instance version """
        v_str = self.getInstanceVersion()
        return versionTupleFromString(v_str)

    security.declareProtected(ManagePortal, 'needUpgrading')
    def needUpgrading(self):
        """ Need upgrading? """
        return self.getInstanceVersion() != self.getFileSystemVersion()

    security.declareProtected(ManagePortal, 'coreVersions')
    def coreVersions(self):
        """ Useful core information """
        vars = {}
        cp = self.Control_Panel
        vars['Zope'] = cp.version_txt
        vars['Python'] = cp.sys_version
        vars['Platform'] = cp.sys_platform
        vars['Plone Instance'] = self.getInstanceVersion()
        vars['Plone File System'] = self.getFileSystemVersion()
        vars['CMF'] = cp.Products.CMFCore.version
        vars['Five'] = cp.Products.Five.version
        vars['Debug mode'] = DevelopmentMode and 'Yes' or 'No'
        try:
            from PIL.Image import VERSION
        except ImportError:
            VERSION = ''
        vars['PIL'] = VERSION
        return vars

    security.declareProtected(ManagePortal, 'coreVersionsList')
    def coreVersionsList(self):
        """ Useful core information """
        res = self.coreVersions().items()
        res.sort()
        return res

    security.declareProtected(ManagePortal, 'needUpdateRole')
    def needUpdateRole(self):
        """ Do roles need to be updated? """
        return self._needUpdateRole

    security.declareProtected(ManagePortal, 'needRecatalog')
    def needRecatalog(self):
        """ Does this thing now need recataloging? """
        return self._needRecatalog

    security.declareProtected(ManagePortal,'getProductInfo')
    def getProductInfo(self):
        """Provide information about installed products for error reporting"""
        zope_products = self.getPhysicalRoot().Control_Panel.Products.objectValues()
        installed_products = getToolByName(self, 'portal_quickinstaller').listInstalledProducts(showHidden=1)
        products = {}
        for p in zope_products:
            product_info = {'id':p.id, 'version':p.version}
            for ip in installed_products:
                if ip['id'] == p.id:
                    product_info['status'] = ip['status']
                    product_info['hasError'] = ip['hasError']
                    product_info['installedVersion'] = ip['installedVersion']
                    break
            products[p.id] = product_info
        return products

    security.declareProtected(ManagePortal,'getPILVersion')
    def getPILVersion(self):
        """The version of the installed Python Imaging Library."""
        log_deprecated("getPILVersion is deprecated and will be removed in "
                       "Plone 3.5. Please use coreVersions instead.")
        try:
            from PIL.Image import VERSION
        except ImportError:
            VERSION = None
        return VERSION

    ##############################################################
    # the setup widget registry
    # this is a whole bunch of wrappers
    # Really an unprotected sub object
    # declaration could do this...

    def _getWidget(self, widget):
        """ We cant instantiate widgets at run time
        but can send all get calls through here... """
        log_deprecated("_getWidget is deprecated and will be removed in "
                       "Plone 3.0.")
        _widget = _widgetRegistry[widget]
        obj = getToolByName(self, 'portal_url').getPortalObject()
        return _widget(obj)

    security.declareProtected(ManagePortal, 'listWidgets')
    def listWidgets(self):
        """ List all the widgets """
        log_deprecated("listWidgets is deprecated and will be removed in "
                       "Plone 3.0.")
        return _widgetRegistry.keys()

    security.declareProtected(ManagePortal, 'getDescription')
    def getDescription(self, widget):
        """ List all the widgets """
        log_deprecated("getDescription is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).description

    security.declareProtected(ManagePortal, 'listAvailable')
    def listAvailable(self, widget):
        """  List all the Available things """
        log_deprecated("listAvailable is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).available()

    security.declareProtected(ManagePortal, 'listInstalled')
    def listInstalled(self, widget):
        """  List all the installed things """
        log_deprecated("listInstalled is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).installed()

    security.declareProtected(ManagePortal, 'listNotInstalled')
    def listNotInstalled(self, widget):
        """ List all the not installed things """
        log_deprecated("listNotInstalled is deprecated and will be removed in "
                       "Plone 3.0.")
        avail = self.listAvailable(widget)
        install = self.listInstalled(widget)
        return [ item for item in avail if item not in install ]

    security.declareProtected(ManagePortal, 'activeWidget')
    def activeWidget(self, widget):
        """ Show the state """
        log_deprecated("activeWidget is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).active()

    security.declareProtected(ManagePortal, 'setupWidget')
    def setupWidget(self, widget):
        """ Show the state """
        log_deprecated("setupWidget is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).setup()

    security.declareProtected(ManagePortal, 'alterItems')
    def alterItems(self, widget=None, items=[]):
        """ Figure out which items to install and which to uninstall """
        log_deprecated("alterItems is deprecated and will be removed in "
                       "Plone 3.0.")
        installed = self.listInstalled(widget)

        toAdd = [ item for item in items if item not in installed ]
        toDel = [ install for install in installed if install not in items ]

        out = []
        if toAdd: out += self.installItems(widget, toAdd)
        if toDel: out += self.uninstallItems(widget, toDel)
        try:
            return self.manage_results(self, out=out)
        except NameError:
            pass

    security.declareProtected(ManagePortal, 'installItems')
    def installItems(self, widget, items):
        """ Install the items """
        log_deprecated("installItems is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).addItems(items)

    security.declareProtected(ManagePortal, 'uninstallItems')
    def uninstallItems(self, widget, items):
        """ Uninstall the items """
        log_deprecated("unInstallItems is deprecated and will be removed in "
                       "Plone 3.0.")
        return self._getWidget(widget).delItems(items)

    ##############################################################

    security.declareProtected(ManagePortal, 'upgrade')
    def upgrade(self, REQUEST=None, dry_run=None, swallow_errors=1):
        """ perform the upgrade """
        # keep it simple
        out = []

        self._check()

        if dry_run:
            out.append(("Dry run selected.", logging.INFO))

        # either get the forced upgrade instance or the current instance
        newv = getattr(REQUEST, "force_instance_version",
                       self.getInstanceVersion())

        out.append(("Starting the migration from "
                    "version: %s" % newv, logging.INFO))
        while newv is not None:
            out.append(("Attempting to upgrade from: %s" % newv, logging.INFO))
            try:
                newv, msgs = self._upgrade(newv)
                if msgs:
                    for msg in msgs:
                        # if string make list
                        if type(msg) == type(''):
                            msg = [msg,]
                        # if no status, add one
                        if len(msg) == 1:
                            msg.append(logging.INFO)
                        out.append(msg)
                if newv is not None:
                    out.append(("Upgrade to: %s, completed" % newv, logging.INFO))
                    self.setInstanceVersion(newv)

            except ConflictError:
                raise
            except:
                out.append(("Upgrade aborted", logging.ERROR))
                out.append(("Error type: %s" % sys.exc_type, logging.ERROR))
                out.append(("Error value: %s" % sys.exc_value, logging.ERROR))
                for line in traceback.format_tb(sys.exc_traceback):
                    out.append((line, logging.ERROR))

                # set newv to None
                # to break the loop
                newv = None
                if not swallow_errors:
                    for msg, sev in out: log(msg, severity=sev)
                    raise
                else:
                    # abort transaction to safe the zodb
                    transaction.abort()

        out.append(("End of upgrade path, migration has finished", logging.INFO))

        if self.needUpgrading():
            out.append((("The upgrade path did NOT reach "
                        "current version"), logging.ERROR))
            out.append(("Migration has failed", logging.ERROR))
        else:
            out.append((("Your ZODB and Filesystem Plone "
                         "instances are now up-to-date."), logging.INFO))

        # do this once all the changes have been done
        if self.needRecatalog():
            try:
                self.portal_catalog.refreshCatalog()
                self._needRecatalog = 0
            except ConflictError:
                raise
            except:
                out.append(("Exception was thrown while cataloging",
                            logging.ERROR))
                out += traceback.format_tb(sys.exc_traceback)
                if not swallow_errors:
                    for msg, sev in out: log(msg, severity=sev)
                    raise

        if self.needUpdateRole():
            try:
                self.portal_workflow.updateRoleMappings()
                self._needUpdateRole = 0
            except ConflictError:
                raise
            except:
                out.append((("Exception was thrown while updating "
                             "role mappings"), logging.ERROR))
                out += traceback.format_tb(sys.exc_traceback)
                if not swallow_errors:
                    for msg, sev in out: log(msg, severity=sev)
                    raise

        if dry_run:
            out.append(("Dry run selected, transaction aborted", logging.INFO))
            transaction.abort()

        # log all this
        for msg, sev in out: log(msg, severity=sev)
        try:
            return self.manage_results(self, out=out)
        except NameError:
            pass

    ##############################################################
    # Private methods

    def _check(self):
        """ Are we inside a Plone site?  Are we allowed? """
        if getattr(self,'portal_url', []) == []:
            raise AttributeError, 'You must be in a Plone site to migrate.'

    def _upgrade(self, version):
        version = version.lower()
        if not _upgradePaths.has_key(version):
            return None, ("Migration completed at version %s" % version,)

        newversion, function = _upgradePaths[version]
        res = function(self.aq_parent)
        return newversion, res

def registerUpgradePath(oldversion, newversion, function):
    """ Basic register func """
    _upgradePaths[oldversion.lower()] = [newversion.lower(), function]

def registerSetupWidget(widget):
    """ Basic register things """
    log_deprecated("registerSetupWidget is deprecated and will be removed in "
                   "Plone 3.0.")
    _widgetRegistry[widget.type] = widget

InitializeClass(MigrationTool)