Passed
Pull Request — 2.x (#1911)
by Jordi
12:55 queued 07:53
created

senaite.core.migration.migrator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 135
dl 0
loc 241
rs 10
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A ContentMigrator.copy_uid() 0 9 3
A ContentMigrator.catalog_object() 0 4 1
A ContentMigrator.__init__() 0 3 1
A ContentMigrator.migrate() 0 4 1
A ContentMigrator.copy_fields() 0 11 3
A ContentMigrator.uncatalog_object() 0 9 1
A FieldMigrator.migrate() 0 2 1
A FieldMigrator.__init__() 0 4 1
A ContentMigrator.copy_marker_interfaces() 0 4 1
A ContentMigrator.copy_creators() 0 4 1
A ContentMigrator.delete_object() 0 6 1
A ATDXContentMigrator.migrate() 0 39 3
A ContentMigrator.copy_dates() 0 7 1
B ATDXFieldMigrator.migrate() 0 46 8
A ContentMigrator.copy_workflow_history() 0 7 2
A ContentMigrator.copy_snapshots() 0 7 1
1
# -*- coding: utf-8 -*-
2
3
import json
4
5
import six
6
7
from Acquisition import aq_parent
8
from bika.lims import api
9
from bika.lims import logger
10
from bika.lims.interfaces import IAuditable
11
from plone.dexterity.interfaces import IDexterityContent
12
from Products.Archetypes.interfaces import IBaseObject
13
from Products.Archetypes.interfaces import IField
14
from senaite.core.interfaces import IContentMigrator
15
from senaite.core.interfaces import IFieldMigrator
16
from senaite.core.migration.utils import copyPermMap
17
from z3c.form.interfaces import IDataManager
18
from zope.component import adapts
19
from zope.component import getMultiAdapter
20
from zope.interface import alsoProvides
21
from zope.interface import directlyProvidedBy
22
from zope.interface import implementer
23
24
SKIP_FIELDS = [
25
    "id",
26
    "allowDiscussion",
27
    "subject",
28
    "location",
29
    "contributors",
30
    "creators",
31
    "effectiveDate",
32
    "expirationDate",
33
    "language",
34
    "rights",
35
    "creation_date",
36
]
37
38
39
@implementer(IContentMigrator)
40
class ContentMigrator(object):
41
    """SENAITE content migrator
42
    """
43
    def __init__(self, src, target):
44
        self.src = src
45
        self.target = target
46
47
    def migrate(self):
48
        """Run the migration
49
        """
50
        raise NotImplementedError("Must be implemented by subclass")
51
52
    def uncatalog_object(self, obj):
53
        """Uncatalog the object for all catalogs
54
        """
55
        # uncatalog from registered catalogs
56
        obj.unindexObject()
57
        # explicitly uncatalog from uid_catalog
58
        uid_catalog = api.get_tool("uid_catalog")
59
        url = "/".join(obj.getPhysicalPath()[2:])
60
        uid_catalog.uncatalog_object(url)
61
62
    def catalog_object(self, obj):
63
        """Catalog the object
64
        """
65
        obj.reindexObject()
66
67
    def copy_uid(self, obj, uid):
68
        """Set uid on object
69
        """
70
        if api.is_dexterity_content(obj):
71
            setattr(obj, "_plone.uuid", uid)
72
        elif api.is_at_content(obj):
73
            setattr(obj, "_at_uid", uid)
74
        else:
75
            raise TypeError("Cannot set UID on that object")
76
77
    def copy_dates(self, src, target):
78
        """copy modification/creation date
79
        """
80
        created = api.get_creation_date(src)
81
        modified = api.get_modification_date(src)
82
        target.creation_date = created
83
        target.setModificationDate(modified)
84
85
    def copy_creators(self, src, target):
86
        """Copy creators
87
        """
88
        target.setCreators(src.listCreators())
89
90
    def copy_workflow_history(self, src, target):
91
        """Copy workflow history
92
        """
93
        wfh = getattr(src, "workflow_history", None)
94
        if wfh:
95
            wfh = copyPermMap(wfh)
96
            target.workflow_history = wfh
97
98
    def copy_marker_interfaces(self, src, target):
99
        """Copy marker interfaces
100
        """
101
        alsoProvides(target, directlyProvidedBy(src))
102
103
    def copy_snapshots(self, src, target):
104
        """copy over snapshots from source -> target
105
        """
106
        snapshots = api.snapshot.get_snapshots(src)
107
        storage = api.snapshot.get_storage(target)
108
        storage[:] = map(json.dumps, snapshots)[:]
109
        alsoProvides(target, IAuditable)
110
111
    def delete_object(self, obj):
112
        """delete the object w/o firing events
113
        """
114
        self.uncatalog_object(obj)
115
        parent = aq_parent(obj)
116
        parent._delObject(obj.getId(), suppress_events=True)
117
118
    def copy_fields(self, src, target, mapping):
119
        """Copy fields
120
        """
121
        src_fields = api.get_fields(src)
122
        for fname, field in src_fields.items():
123
            if fname in SKIP_FIELDS:
124
                continue
125
            field_migrator = getMultiAdapter(
126
                (field, src, target), interface=IFieldMigrator)
127
            # migrate the field
128
            field_migrator.migrate(mapping)
129
130
131
class ATDXContentMigrator(ContentMigrator):
132
    """Migrate from AT to DX contents
133
    """
134
    adapts(IBaseObject, IDexterityContent)
135
136
    def migrate(self, mapping=None, delete_src=True):
137
        """Migrate AT content to DX
138
139
        :param mapping: a mapping from source schema field name to a tuple of
140
                        (accessor name, target field name, default value)
141
        """
142
        if mapping is None:
143
            mapping = {}
144
145
        # copy_fields
146
        self.copy_fields(self.src, self.target, mapping)
147
148
        # copy the UID
149
        self.copy_uid(self.src, self.target)
150
151
        # copy auditlog
152
        self.copy_snapshots(self.src, self.target)
153
154
        # copy creators
155
        self.copy_creators(self.src, self.target)
156
157
        # copy workflow history
158
        self.copy_workflow_history(self.src, self.target)
159
160
        # copy marker interfaces
161
        self.copy_marker_interfaces(self.src, self.target)
162
163
        # copy dates
164
        self.copy_dates(self.src, self.target)
165
166
        # uncatalog the source object
167
        self.uncatalog_object(self.src)
168
169
        # reindex the new object
170
        self.catalog_object(self.target)
171
172
        # delete source object if requested
173
        if delete_src:
174
            self.delete_object(self.src)
175
176
177
@implementer(IFieldMigrator)
178
class FieldMigrator(object):
179
    """SENAITE field migrator
180
    """
181
    def __init__(self, field, src, target):
182
        self.field = field
183
        self.src = src
184
        self.target = target
185
186
    def migrate(self):
187
        raise NotImplementedError("Must be implemented by subclass")
188
189
190
class ATDXFieldMigrator(FieldMigrator):
191
    """SENAITE AT to DX field migrator
192
    """
193
    adapts(IField, IBaseObject, IDexterityContent)
194
195
    def migrate(self, mapping):
196
        # get all fields on the target
197
        target_fields = api.get_fields(self.target)
198
199
        fieldname = self.field.getName()
200
201
        # check if we have a mapping for this field
202
        accessor, target_fieldname, default = (None, None, None, )
203
        if fieldname in mapping:
204
            accessor, target_fieldname, default = mapping[fieldname]
205
206
        target_field = None
207
        if target_fieldname:
208
            # get the target field with the mapped name
209
            target_field = target_fields.get(target_fieldname)
210
        else:
211
            # check for a field with the same name on the target
212
            target_field = target_fields.get(fieldname)
213
214
        # no target field found ...
215
        if target_field is None:
216
            logger.info("Skipping migration for field '%s'" % fieldname)
217
            return False
218
219
        if accessor:
220
            # get the source field value from the accessor
221
            value = getattr(self.src, accessor, default)
222
            if callable(value):
223
                value = value()
224
        else:
225
            # get the source value with the default getter from the field
226
            value = self.field.get(fieldname)
227
228
        # always convert string values to unicode for dexterity fields
229
        if isinstance(value, six.string_types):
230
            value = api.safe_unicode(value)
231
232
        # set the value with the datamanager (another point to override)
233
        dm = getMultiAdapter(
234
            (self.target, target_field), interface=IDataManager)
235
        if dm:
236
            dm.set(value)
237
        else:
238
            target_field.set(self.target, value)
239
240
        logger.info("Migrated field %s -> %s" % (fieldname, target_fieldname))
241