-
Notifications
You must be signed in to change notification settings - Fork 6
/
publish.py
384 lines (328 loc) · 13.5 KB
/
publish.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
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
"""
Publish
-------
Copy and publish Surveys from the admin to the client database.
"""
from Acquisition import aq_inner
from Acquisition import aq_parent
from euphorie.client import MessageFactory as _
from euphorie.content.interfaces import ICustomRisksModule
from euphorie.content.interfaces import ObjectPublishedEvent
from euphorie.content.utils import IToolTypesInfo
from plone import api
from plone.scale.storage import AnnotationStorage
from plonetheme.nuplone.utils import getPortal
from Products.CMFCore.utils import getToolByName
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.statusmessages.interfaces import IStatusMessage
from z3c.form import button
from z3c.form import form
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.event import notify
from zope.interface import alsoProvides
import datetime
import logging
import random
log = logging.getLogger(__name__)
def CopyToClient(survey, preview=False):
"""Copy the survey to the online client part of the site.
:param survey: the survey to copy
:param bool preview: indicates if this is a preview or a normal publication
:rtype: :py:class:`euphorie.content.survey.Survey`
The public area is hardcoded to be a container with id ``client``
within the site root.
The ''id'' and ''title'' of the survey group will be used for the
published survey. If another object with the same ''id'' already exists
it will be removed first. Any missing country and sector folders are
created if needed.
If this is a preview (as indicated by the ``preview`` parameter) the
id of the survey will be set to ``preview``, guaranteeing that an
existing published survey will not be replaced. This also means only
a sector can only have one preview online.
This method assumes the current user has permissions to create content
in the online client. This is normally done by using the
:py:func:`PublishToClient` function which switches the current user
for the copy operation.
Returns the new public survey instance.
"""
# This is based on OFS.CopyContainer.manage_clone, modified to
# use the sector id and title, skip security checks and remove
# an existing object with the same id.
client = getPortal(survey).client
source = aq_inner(survey)
surveygroup = aq_parent(source)
sector = aq_parent(surveygroup)
country = aq_parent(sector)
from euphorie.content.sector import ISector
assert ISector.providedBy(sector)
if country.id not in client:
client.invokeFactory(
"euphorie.clientcountry",
country.id,
title=country.title,
country_type=country.country_type,
)
cl_country = client[country.id]
if sector.id not in cl_country:
cl_country.invokeFactory("euphorie.clientsector", sector.id)
target = cl_country[sector.id]
target.title = sector.title
target.logo = sector.logo
# Clear any scaled logos
AnnotationStorage(target).storage.clear()
if preview:
new_id = "preview"
else:
new_id = surveygroup.id
if new_id in target:
api.content.delete(obj=target[new_id], check_linkintegrity=False)
copy = api.content.copy(source, target, id=new_id)
copy.title = surveygroup.title
copy.obsolete = surveygroup.obsolete
copy.evaluation_algorithm = surveygroup.evaluation_algorithm
copy.version = source.id
copy.published = datetime.datetime.now()
copy.preview = preview
copy.reindexObject()
notify(ObjectPublishedEvent(source))
return copy
def EnableCustomRisks(survey):
"""In order to allow the user to add custom risks, we create a new special
module (marked with ICustomRisksModule) in which they may be created."""
if not api.portal.get_registry_record("euphorie.allow_user_defined_risks"):
return 0
if "custom-risks" in survey.keys():
# We don't want to create the custom risks module twice.
return 0
args = dict(
container=survey,
type="euphorie.module",
id="custom-risks",
title=_("label_custom_risks", default="Custom risks"),
safe_id=False,
description=_(
"description_other_risks",
default="In case you have identified "
"risks not included in the tool, you are able to add them now:",
),
optional=True,
question=_(
"question_other_risks",
default="<p><strong>Important:"
"</strong> In order to avoid duplicating risks, we strongly "
"recommend you to go first through all the previous modules, if "
"you have not done it yet.</p><p>If you don't need to add risks, "
"please continue.</p>",
),
)
try:
module = api.content.create(**args)
except api.exc.InvalidParameterError:
args["id"] = "custom-risks-" + str(random.randint(0, 99999999))
module = api.content.create(**args)
alsoProvides(module, ICustomRisksModule)
return args["id"]
def PublishToClient(survey, preview=False):
"""Publish a survey in the online client part of the site.
:param survey: the survey to copy
:param bool preview: indicates if this is a preview or a normal publication
:rtype: :py:class:`euphorie.content.survey.Survey`
This is a wrapper around :py:func:`CopyToClient`, which temporarily changes
the currently active Zope user to make sure content can be created in the
client.
"""
tti = getUtility(IToolTypesInfo)
tool_types_info = tti()
tool_type_data = tool_types_info.get(
survey.tool_type, tool_types_info.get(tti.default_tool_type)
)
with api.env.adopt_user("client"):
with api.env.adopt_roles(["Manager"]):
survey = CopyToClient(survey, preview)
if tool_type_data.get("use_omega_risks", True):
EnableCustomRisks(survey)
survey.published = (survey.id, survey.title, datetime.datetime.now())
return survey
def handleSurveyPublish(survey, event):
"""Event handler (subscriber) for succesfull workflow transitions for
:py:obj:`ISurvey` objects. This handler copies the survey to the
client.
"""
if event.action not in ["publish", "update"]:
return
PublishToClient(survey, False)
def handleSurveyUnpublish(survey, event):
"""Event handler (subscriber) to take care of unpublishing a survey from
the client."""
surveygroup = aq_parent(survey)
sector = aq_parent(surveygroup)
country = aq_parent(sector)
with api.env.adopt_user("client"):
with api.env.adopt_roles(["Manager"]):
client = getPortal(survey).client
try:
clientcountry = client[country.id]
clientsector = clientcountry[sector.id]
clientsector[surveygroup.id]
except KeyError:
log.info(
"Trying to unpublish unpublished survey %s",
"/".join(survey.getPhysicalPath()),
)
return
clientsector.manage_delObjects([surveygroup.id])
if not clientsector.keys():
clientcountry.manage_delObjects([clientsector.id])
class PublishSurvey(form.Form):
"""Publish a survey.
Publishing a survey copies it from the content editing environment to the
public client environment and makes several changes to prepare it for use
by the client. The client environment is assumed to be located in a
container with id ''client'' at the root of the site.
View name: @@publish
"""
template = ViewPageTemplateFile("templates/publish.pt")
def publish(self):
survey = aq_inner(self.context)
survey.published = datetime.datetime.now()
wt = getToolByName(survey, "portal_workflow")
state = wt.getInfoFor(survey, "review_state")
if state == "draft":
wt.doActionFor(survey, "publish")
else:
wt.doActionFor(survey, "update")
def is_surveygroup_published(self):
"""Check if this surveygroup has been published before."""
source = aq_inner(self.context)
surveygroup = aq_parent(source)
return bool(surveygroup.published)
def is_this_survey_published(self):
"""Check if this survey is currently published."""
source = aq_inner(self.context)
surveygroup = aq_parent(source)
return surveygroup.published == source.id
def _has_same_structure(self, source, target):
for child_id in target.objectIds():
if child_id == "custom-risks":
continue
if child_id not in source:
return False
if not self._has_same_structure(source[child_id], target[child_id]):
return False
return True
def is_structure_changed(self):
source = aq_inner(self.context)
surveygroup = aq_parent(source)
sector = aq_parent(surveygroup)
country = aq_parent(sector)
client = getPortal(self.context).client
if country.id not in client:
return False
cl_country = client[country.id]
if sector.id not in cl_country:
return False
cl_sector = cl_country[sector.id]
if surveygroup.id not in cl_sector:
return False
target = cl_sector[surveygroup.id]
return not self._has_same_structure(source, target)
def client_url(self):
"""Return the URL this survey will have after it is published."""
client_url = api.portal.get_registry_record("euphorie.client_url", default="")
if client_url:
client_url = client_url.rstrip("/")
else:
client_url = getPortal(self.context).client.absolute_url()
source = aq_inner(self.context)
surveygroup = aq_parent(source)
sector = aq_parent(surveygroup)
country = aq_parent(sector)
return "/".join([client_url, country.id, sector.id, surveygroup.id])
@button.buttonAndHandler(_("button_cancel", default="Cancel"))
def handleCancel(self, action):
state = getMultiAdapter(
(aq_inner(self.context), self.request), name="plone_context_state"
)
self.request.response.redirect(state.view_url())
@button.buttonAndHandler(_("button_publish", default="Publish"))
def handlePublish(self, action):
self.publish()
IStatusMessage(self.request).add(
_(
"no_translate_link_published_success",
default=(
'${text_message_publish_success}: <a href="${url}">${url}</a>.'
),
mapping={
"url": self.client_url(),
"text_message_publish_success": _(
"message_publish_success",
default=(
"Succesfully published the OiRA Tool. It can be accessed "
"at"
),
),
},
),
type="success",
)
state = getMultiAdapter(
(aq_inner(self.context), self.request), name="plone_context_state"
)
self.request.response.redirect(state.view_url())
class PreviewSurvey(form.Form):
"""Generate a preview for a survey. A preview is exactly like a normally
published survey, except for two differences: there can only be one preview
for a sector, which has the id `preview`, and after previewing the user is
redirected to the preview instead of the original context.
View name: @@preview
"""
template = ViewPageTemplateFile("templates/preview.pt")
def publish(self):
survey = aq_inner(self.context)
return PublishToClient(survey, True)
def preview_url(self):
"""Return the URL the preview will have."""
client_url = api.portal.get_registry_record("euphorie.client_url", default="")
if client_url:
client_url = client_url.rstrip("/")
else:
client_url = getPortal(self.context).client.absolute_url()
source = aq_inner(self.context)
surveygroup = aq_parent(source)
sector = aq_parent(surveygroup)
country = aq_parent(sector)
return "/".join([client_url, country.id, sector.id, "preview"])
@button.buttonAndHandler(_("button_cancel", default="Cancel"))
def handleCancel(self, action):
state = getMultiAdapter(
(aq_inner(self.context), self.request), name="plone_context_state"
)
self.request.response.redirect(state.view_url())
@button.buttonAndHandler(_("button_preview", default="Create preview"))
def handlePreview(self, action):
self.publish()
IStatusMessage(self.request).add(
_(
"no_translate_link_preview_success",
default=(
'${text_message_preview_success}: <a href="${url}">${url}</a>.'
),
mapping={
"url": self.preview_url(),
"text_message_preview_success": _(
"message_preview_success",
default=(
"Succesfully created a preview for the OiRA Tool. It can "
"be accessed at"
),
),
},
),
type="success",
)
state = getMultiAdapter(
(aq_inner(self.context), self.request), name="plone_context_state"
)
self.request.response.redirect(state.view_url())