forked from eea/eea.workflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
archive.py
289 lines (254 loc) · 11 KB
/
archive.py
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
""" IObjectArchived implementation
"""
import logging
from DateTime import DateTime
from Products.Five import BrowserView
import transaction
from Products.Archetypes.interfaces import IBaseObject
from persistent import Persistent
from zope.annotation.factory import factory
from zope.component import adapts, queryAdapter
from zope.interface import implements, alsoProvides, noLongerProvides
from zope.event import notify
from z3c.caching.purge import Purge
from Products.ATVocabularyManager.namedvocabulary import NamedVocabulary
from Products.CMFPlone.utils import getToolByName
from eea.versions.interfaces import IGetVersions
from eea.workflow.interfaces import IObjectArchived, IObjectArchivator
from Products.CMFPlone.utils import safe_unicode
class ObjectArchivedAnnotationStorage(Persistent):
""" The IObjectArchived information stored as annotation
"""
implements(IObjectArchivator)
adapts(IBaseObject)
@property
def is_archived(self):
""" Is this object archived?
"""
return bool(getattr(self, 'archive_date', False))
def unarchive(self, context, custom_message=None, initiator=None,
reason=None):
""" Unarchive the object
:param context: object which is going to be unarchived
:param custom_message: Custom message explaining why the object was
unarchived
:param initiator: the user id or name which commissioned the archival
:param reason: reason id for which the object was archived
"""
noLongerProvides(context, IObjectArchived)
now = DateTime()
context.setExpirationDate(None)
date = DateTime()
wftool = getToolByName(context, 'portal_workflow')
has_workflow = wftool.getChainFor(context)
mtool = getToolByName(context, 'portal_membership')
if not has_workflow:
# NOP
return
state = wftool.getInfoFor(context, 'review_state')
actor = mtool.getAuthenticatedMember().getId()
if custom_message:
reason += u" (%s)" % custom_message
comments = (u"Unarchived by %(actor)s on %(date)s by request "
u"from %(initiator)s with reason: %(reason)s" % {
'actor': actor,
'initiator': initiator,
'reason': reason,
'date': date.ISO8601()
})
for wfname in context.workflow_history.keys():
history = context.workflow_history[wfname]
history += ({
'action': 'UnArchive',
'review_state': state,
'actor': actor,
'comments': comments,
'time': now,
},)
context.workflow_history[wfname] = history
context.workflow_history._p_changed = True
context.reindexObject()
notify(Purge(context))
def archive(self, context, initiator=None, reason=None, custom_message=None,
archive_date=None):
"""Archive the object
:param context: given object that should be archived
:param initiator: the user id or name which commissioned the archival
:param reason: reason id for which the object was archived
:param custom_message: Custom message explaining why the object was
archived
:param archive_date: DateTime object which sets the expiration date of
the object
"""
initiator = safe_unicode(initiator)
reason = safe_unicode(reason)
custom_message = safe_unicode(custom_message)
wftool = getToolByName(context, 'portal_workflow')
has_workflow = wftool.getChainFor(context)
if not has_workflow:
# NOP
return
date = archive_date and archive_date or DateTime()
alsoProvides(context, IObjectArchived)
context.setExpirationDate(date)
# refactor this setting from here, without these assignments to self
# the test for is_archived fails
self.archive_date = date
self.initiator = initiator
self.custom_message = custom_message
self.reason = reason
state = wftool.getInfoFor(context, 'review_state')
mtool = getToolByName(context, 'portal_membership')
actor = mtool.getAuthenticatedMember().getId()
rv = NamedVocabulary('eea.workflow.reasons')
vocab = rv.getVocabularyDict(context)
reason = vocab.get(reason, "Other")
if custom_message:
reason += u" (%s)" % custom_message
comments = (u"Archived by %(actor)s on %(date)s by request "
u"from %(initiator)s with reason: %(reason)s" % {
'actor': actor,
'initiator': initiator,
'reason': reason,
'date': date.ISO8601()
})
for wfname in context.workflow_history.keys():
history = context.workflow_history[wfname]
history += ({
'action': 'Archive',
'review_state': state,
'actor': actor,
'comments': comments,
'time': date,
},)
context.workflow_history[wfname] = history
context.workflow_history._p_changed = True
context.reindexObject()
notify(Purge(context))
archive_annotation_storage = factory(ObjectArchivedAnnotationStorage,
key="eea.workflow.archive")
# helper functions
def archive_object(context, **kwargs):
""" Archive given context
:param context: object
:param kwargs: options that are passed to the archive method directly
affecting it's results if they are passed
"""
storage = queryAdapter(context, IObjectArchivator)
storage.archive(context, **kwargs)
return context
def archive_obj_and_children(context, **kwargs):
""" Archive given context and it's children
:param context: object
:param kwargs: options that are passed to the archive method directly
affecting it's results if they are passed
"""
catalog = getToolByName(context, 'portal_catalog')
query = {'path': '/'.join(context.getPhysicalPath())}
brains = catalog.searchResults(query)
affected_objects = []
for brain in brains:
obj = brain.getObject()
storage = queryAdapter(obj, IObjectArchivator)
if not storage:
continue
storage.archive(obj, **kwargs)
affected_objects.append(obj)
return affected_objects
def archive_previous_versions(context, skip_already_archived=True,
same_archive_date=False, also_children=False,
**kwargs):
""" Archive previous versions of given object
:param object context: object
:param bool skip_already_archived: boolean indicating whether it should skip
archiving the previous version that is already archived
:param bool same_archive_date: boolean indicating whether the object being
archived should receive the same archiving date as the context
:param bool also_children: boolean indicating whether the children of the
versions should also be archived
:param dict kwargs: options that are passed to the archive method directly
affecting it's results if they are passed
:rtype list
"""
versions_adapter = IGetVersions(context)
archivator_adapter = queryAdapter(context, IObjectArchivator)
options = kwargs
if not options:
custom_message = getattr(archivator_adapter, 'custom_message', '')
reason = getattr(archivator_adapter, 'reason',
'content_is_outdated')
initiator = getattr(archivator_adapter, 'initiator', None)
options = {'custom_message': custom_message,
'initiator': initiator,
'reason': reason}
if same_archive_date and getattr(archivator_adapter, 'archive_date'):
options.update({'archive_date': archivator_adapter.archive_date})
versions = versions_adapter.versions()
previous_versions = []
uid = context.UID()
for version in versions:
if version.UID() == uid:
break
previous_versions.append(version)
affected_objects = []
for obj in previous_versions:
if skip_already_archived:
if IObjectArchived.providedBy(obj):
continue
if also_children:
affected_objects.extend(archive_obj_and_children(obj, **options))
else:
storage = queryAdapter(obj, IObjectArchivator)
storage.archive(obj, **options)
affected_objects.append(obj)
return affected_objects
class ArchivePreviousVersions(BrowserView):
""" Archives previous versions of objects that are archived
"""
def __init__(self, context, request):
super(ArchivePreviousVersions, self).__init__(context, request)
self.context = context
self.request = request
def __call__(self):
""" Call method
"""
log = logging.getLogger("archive-versions")
cat = getToolByName(self.context, 'portal_catalog')
brains = cat(object_provides="eea.workflow.interfaces.IObjectArchived",
show_inactive=True, Language="all")
also_children = self.request.get('alsoChildren')
count = 0
total = len(brains)
obj_urls = "THE FOLLOWING OBJECTS ARE ARCHIVED:\n"
log.info("Start Archiving the previous versions of %d archived objects",
total)
affected_objects = set()
for brain in brains:
previous_versions = []
brain_url = brain.getURL()
try:
obj = brain.getObject()
except Exception:
log.info("Can't retrieve %s", brain_url)
continue
if also_children:
previous_versions.extend(archive_previous_versions(obj,
also_children=True,
same_archive_date=True))
else:
previous_versions.extend(archive_previous_versions(obj,
same_archive_date=True))
for obj in previous_versions:
obj_url = obj.absolute_url()
if obj_url not in affected_objects:
log.info("Archived %s", obj_url)
obj_urls += "%s \n" % obj_url
affected_objects.add(obj_url)
count += 1
if count % 100 == 0:
transaction.commit()
log.info('Subtransaction committed to zodb (%s/%s)', count,
total)
log.info("End Archiving the previous versions of %d archived objects",
total)
return obj_urls