1
|
|
|
""" |
2
|
|
|
byceps.blueprints.admin.news.views |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2006-2021 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from datetime import date, datetime |
10
|
|
|
|
11
|
1 |
|
from flask import abort, g, request |
12
|
1 |
|
from flask_babel import gettext |
13
|
|
|
|
14
|
1 |
|
from ....services.brand import service as brand_service |
15
|
1 |
|
from ....services.image import service as image_service |
16
|
1 |
|
from ....services.news import channel_service as news_channel_service |
17
|
1 |
|
from ....services.news import html_service as news_html_service |
18
|
1 |
|
from ....services.news import image_service as news_image_service |
19
|
1 |
|
from ....services.news import service as news_item_service |
20
|
1 |
|
from ....services.news.transfer.models import Channel |
21
|
1 |
|
from ....services.site import service as site_service |
22
|
1 |
|
from ....services.text_diff import service as text_diff_service |
23
|
1 |
|
from ....services.user.service import UserIdRejected |
24
|
1 |
|
from ....signals import news as news_signals |
25
|
1 |
|
from ....util.authorization import register_permission_enum |
26
|
1 |
|
from ....util.datetime.format import format_datetime_short |
27
|
1 |
|
from ....util.framework.blueprint import create_blueprint |
28
|
1 |
|
from ....util.framework.flash import flash_error, flash_success |
29
|
1 |
|
from ....util.framework.templating import templated |
30
|
1 |
|
from ....util.iterables import pairwise |
31
|
1 |
|
from ....util.templatefilters import local_tz_to_utc |
32
|
1 |
|
from ....util.views import permission_required, redirect_to, respond_no_content |
33
|
|
|
|
34
|
1 |
|
from .authorization import NewsChannelPermission, NewsItemPermission |
35
|
1 |
|
from .forms import ( |
36
|
|
|
ChannelCreateForm, |
37
|
|
|
ImageCreateForm, |
38
|
|
|
ImageUpdateForm, |
39
|
|
|
ItemCreateForm, |
40
|
|
|
ItemPublishLaterForm, |
41
|
|
|
ItemUpdateForm, |
42
|
|
|
) |
43
|
|
|
|
44
|
|
|
|
45
|
1 |
|
blueprint = create_blueprint('news_admin', __name__) |
46
|
|
|
|
47
|
|
|
|
48
|
1 |
|
register_permission_enum(NewsChannelPermission) |
49
|
1 |
|
register_permission_enum(NewsItemPermission) |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
# -------------------------------------------------------------------- # |
53
|
|
|
# channels |
54
|
|
|
|
55
|
|
|
|
56
|
1 |
|
@blueprint.get('/brands/<brand_id>') |
57
|
1 |
|
@permission_required(NewsItemPermission.view) |
58
|
1 |
|
@templated |
59
|
|
|
def channel_index_for_brand(brand_id): |
60
|
|
|
"""List channels for that brand.""" |
61
|
1 |
|
brand = _get_brand_or_404(brand_id) |
62
|
|
|
|
63
|
1 |
|
channels = news_channel_service.get_channels_for_brand(brand.id) |
64
|
|
|
|
65
|
1 |
|
item_count_by_channel_id = news_item_service.get_item_count_by_channel_id() |
66
|
|
|
|
67
|
1 |
|
return { |
68
|
|
|
'brand': brand, |
69
|
|
|
'channels': channels, |
70
|
|
|
'item_count_by_channel_id': item_count_by_channel_id, |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
|
74
|
1 |
|
@blueprint.get('/for_brand/<brand_id>/channels/create') |
75
|
1 |
|
@permission_required(NewsChannelPermission.create) |
76
|
1 |
|
@templated |
77
|
1 |
|
def channel_create_form(brand_id, erroneous_form=None): |
78
|
|
|
"""Show form to create a channel.""" |
79
|
1 |
|
brand = _get_brand_or_404(brand_id) |
80
|
|
|
|
81
|
1 |
|
form = erroneous_form if erroneous_form else ChannelCreateForm() |
82
|
|
|
|
83
|
1 |
|
return { |
84
|
|
|
'brand': brand, |
85
|
|
|
'form': form, |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
|
89
|
1 |
|
@blueprint.post('/for_brand/<brand_id>/channels') |
90
|
1 |
|
@permission_required(NewsChannelPermission.create) |
91
|
|
|
def channel_create(brand_id): |
92
|
|
|
"""Create a channel.""" |
93
|
1 |
|
brand = _get_brand_or_404(brand_id) |
94
|
|
|
|
95
|
1 |
|
form = ChannelCreateForm(request.form) |
96
|
1 |
|
if not form.validate(): |
97
|
|
|
return channel_create_form(brand.id, form) |
98
|
|
|
|
99
|
1 |
|
channel_id = form.channel_id.data.strip().lower() |
100
|
1 |
|
url_prefix = form.url_prefix.data.strip() |
101
|
|
|
|
102
|
1 |
|
channel = news_channel_service.create_channel( |
103
|
|
|
brand.id, channel_id, url_prefix |
104
|
|
|
) |
105
|
|
|
|
106
|
1 |
|
flash_success( |
107
|
|
|
gettext( |
108
|
|
|
'News channel "%(channel_id)s" has been created.', |
109
|
|
|
channel_id=channel.id, |
110
|
|
|
) |
111
|
|
|
) |
112
|
1 |
|
return redirect_to('.channel_view', channel_id=channel.id) |
113
|
|
|
|
114
|
|
|
|
115
|
1 |
|
@blueprint.get('/channels/<channel_id>', defaults={'page': 1}) |
116
|
1 |
|
@blueprint.get('/channels/<channel_id>/pages/<int:page>') |
117
|
1 |
|
@permission_required(NewsItemPermission.view) |
118
|
1 |
|
@templated |
119
|
|
|
def channel_view(channel_id, page): |
120
|
|
|
"""View that channel and list its news items.""" |
121
|
1 |
|
channel = _get_channel_or_404(channel_id) |
122
|
|
|
|
123
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
124
|
|
|
|
125
|
1 |
|
channel_ids = {channel.id} |
126
|
1 |
|
per_page = request.args.get('per_page', type=int, default=15) |
127
|
|
|
|
128
|
1 |
|
items = news_item_service.get_items_paginated(channel_ids, page, per_page) |
129
|
|
|
|
130
|
1 |
|
return { |
131
|
|
|
'channel': channel, |
132
|
|
|
'brand': brand, |
133
|
|
|
'items': items, |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
|
137
|
1 |
|
@blueprint.delete('/channels/<channel_id>') |
138
|
1 |
|
@permission_required(NewsChannelPermission.create) |
139
|
1 |
|
@respond_no_content |
140
|
|
|
def channel_delete(channel_id): |
141
|
|
|
"""Delete the channel.""" |
142
|
|
|
channel = _get_channel_or_404(channel_id) |
143
|
|
|
|
144
|
|
|
if news_item_service.has_channel_items(channel.id): |
145
|
|
|
flash_error( |
146
|
|
|
gettext( |
147
|
|
|
'News channel "%(channel_id)s" cannot be deleted because it contains news items.', |
148
|
|
|
channel_id=channel.id, |
149
|
|
|
) |
150
|
|
|
) |
151
|
|
|
return |
152
|
|
|
|
153
|
|
|
sites_for_brand = site_service.get_sites_for_brand(channel.brand_id) |
154
|
|
|
linked_sites = { |
155
|
|
|
site for site in sites_for_brand if channel.id in site.news_channel_ids |
156
|
|
|
} |
157
|
|
|
if linked_sites: |
158
|
|
|
flash_error( |
159
|
|
|
gettext( |
160
|
|
|
'News channel "%(channel_id)s" cannot be deleted because it is referenced by %(site_count)s site(s).', |
161
|
|
|
channel_id=channel.id, |
162
|
|
|
site_count=len(linked_sites), |
163
|
|
|
) |
164
|
|
|
) |
165
|
|
|
return |
166
|
|
|
|
167
|
|
|
news_channel_service.delete_channel(channel.id) |
168
|
|
|
|
169
|
|
|
flash_success( |
170
|
|
|
gettext( |
171
|
|
|
'News channel "%(channel_id)s" has been deleted.', |
172
|
|
|
channel_id=channel_id, |
173
|
|
|
) |
174
|
|
|
) |
175
|
|
|
|
176
|
|
|
|
177
|
|
|
# -------------------------------------------------------------------- # |
178
|
|
|
# images |
179
|
|
|
|
180
|
|
|
|
181
|
1 |
|
@blueprint.get('/for_item/<item_id>/create') |
182
|
1 |
|
@permission_required(NewsItemPermission.update) |
183
|
1 |
|
@templated |
184
|
1 |
|
def image_create_form(item_id, erroneous_form=None): |
185
|
|
|
"""Show form to create a news image.""" |
186
|
1 |
|
item = _get_item_or_404(item_id) |
187
|
|
|
|
188
|
1 |
|
form = erroneous_form if erroneous_form else ImageCreateForm() |
189
|
|
|
|
190
|
1 |
|
image_type_names = image_service.get_image_type_names( |
191
|
|
|
news_image_service.ALLOWED_IMAGE_TYPES |
192
|
|
|
) |
193
|
|
|
|
194
|
1 |
|
return { |
195
|
|
|
'item': item, |
196
|
|
|
'form': form, |
197
|
|
|
'allowed_types': image_type_names, |
198
|
|
|
'maximum_dimensions': news_image_service.MAXIMUM_DIMENSIONS, |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
|
202
|
1 |
|
@blueprint.post('/for_item/<item_id>') |
203
|
1 |
|
@permission_required(NewsItemPermission.update) |
204
|
|
|
def image_create(item_id): |
205
|
|
|
"""Create a news image.""" |
206
|
|
|
item = _get_item_or_404(item_id) |
207
|
|
|
|
208
|
|
|
# Make `InputRequired` work on `FileField`. |
209
|
|
|
form_fields = request.form.copy() |
210
|
|
|
if request.files: |
211
|
|
|
form_fields.update(request.files) |
212
|
|
|
|
213
|
|
|
form = ImageCreateForm(form_fields) |
214
|
|
|
if not form.validate(): |
215
|
|
|
return image_create_form(item.id, form) |
216
|
|
|
|
217
|
|
|
creator_id = g.user.id |
218
|
|
|
image = request.files.get('image') |
219
|
|
|
alt_text = form.alt_text.data.strip() |
220
|
|
|
caption = form.caption.data.strip() |
221
|
|
|
attribution = form.attribution.data.strip() |
222
|
|
|
|
223
|
|
|
if not image or not image.filename: |
224
|
|
|
abort(400, 'No file to upload has been specified.') |
225
|
|
|
|
226
|
|
|
try: |
227
|
|
|
image = news_image_service.create_image( |
228
|
|
|
creator_id, |
229
|
|
|
item.id, |
230
|
|
|
image.stream, |
231
|
|
|
alt_text=alt_text, |
232
|
|
|
caption=caption, |
233
|
|
|
attribution=attribution, |
234
|
|
|
) |
235
|
|
|
except UserIdRejected as e: |
236
|
|
|
abort(400, 'Invalid creator ID') |
237
|
|
|
except image_service.ImageTypeProhibited as e: |
238
|
|
|
abort(400, str(e)) |
239
|
|
|
except FileExistsError: |
240
|
|
|
abort(409, 'File already exists, not overwriting.') |
241
|
|
|
|
242
|
|
|
flash_success( |
243
|
|
|
gettext( |
244
|
|
|
'News image #%(image_number)s has been created.', |
245
|
|
|
image_number=image.number, |
246
|
|
|
) |
247
|
|
|
) |
248
|
|
|
|
249
|
|
|
return redirect_to('.item_view', item_id=image.item_id) |
250
|
|
|
|
251
|
|
|
|
252
|
1 |
|
@blueprint.get('/images/<uuid:image_id>/update') |
253
|
1 |
|
@permission_required(NewsItemPermission.update) |
254
|
1 |
|
@templated |
255
|
1 |
|
def image_update_form(image_id, erroneous_form=None): |
256
|
|
|
"""Show form to update a news image.""" |
257
|
|
|
image = _get_image_or_404(image_id) |
258
|
|
|
item = news_item_service.find_item(image.item_id) |
259
|
|
|
|
260
|
|
|
form = erroneous_form if erroneous_form else ImageUpdateForm(obj=image) |
261
|
|
|
|
262
|
|
|
return { |
263
|
|
|
'image': image, |
264
|
|
|
'item': item, |
265
|
|
|
'form': form, |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
|
269
|
1 |
|
@blueprint.post('/images/<uuid:image_id>') |
270
|
1 |
|
@permission_required(NewsItemPermission.update) |
271
|
|
|
def image_update(image_id): |
272
|
|
|
"""Update a news image.""" |
273
|
|
|
image = _get_image_or_404(image_id) |
274
|
|
|
|
275
|
|
|
form = ImageUpdateForm(request.form) |
276
|
|
|
if not form.validate(): |
277
|
|
|
return image_update_form(image.id, form) |
278
|
|
|
|
279
|
|
|
alt_text = form.alt_text.data.strip() |
280
|
|
|
caption = form.caption.data.strip() |
281
|
|
|
attribution = form.attribution.data.strip() |
282
|
|
|
|
283
|
|
|
image = news_image_service.update_image( |
284
|
|
|
image.id, |
285
|
|
|
alt_text=alt_text, |
286
|
|
|
caption=caption, |
287
|
|
|
attribution=attribution, |
288
|
|
|
) |
289
|
|
|
|
290
|
|
|
flash_success( |
291
|
|
|
gettext( |
292
|
|
|
'News image #%(image_number)s has been updated.', |
293
|
|
|
image_number=image.number, |
294
|
|
|
) |
295
|
|
|
) |
296
|
|
|
|
297
|
|
|
return redirect_to('.item_view', item_id=image.item_id) |
298
|
|
|
|
299
|
|
|
|
300
|
|
|
# -------------------------------------------------------------------- # |
301
|
|
|
# items |
302
|
|
|
|
303
|
|
|
|
304
|
1 |
|
@blueprint.get('/items/<uuid:item_id>') |
305
|
1 |
|
@permission_required(NewsItemPermission.view) |
306
|
1 |
|
@templated('admin/news/item_view_version') |
307
|
|
|
def item_view(item_id): |
308
|
|
|
"""Show the current version of the news item.""" |
309
|
1 |
|
item = _get_item_or_404(item_id) |
310
|
|
|
|
311
|
1 |
|
version = news_item_service.get_current_item_version(item.id) |
312
|
|
|
|
313
|
1 |
|
return _render_item_version(version, item) |
314
|
|
|
|
315
|
|
|
|
316
|
1 |
|
@blueprint.get('/versions/<uuid:version_id>') |
317
|
1 |
|
@permission_required(NewsItemPermission.view) |
318
|
1 |
|
@templated |
319
|
|
|
def item_view_version(version_id): |
320
|
|
|
"""Show the news item with the given version.""" |
321
|
1 |
|
version = _find_version(version_id) |
322
|
|
|
|
323
|
1 |
|
item = news_item_service.find_item(version.item_id) |
324
|
|
|
|
325
|
1 |
|
return _render_item_version(version, item) |
326
|
|
|
|
327
|
|
|
|
328
|
1 |
|
def _render_item_version(version, item): |
329
|
|
|
"""Render the news item version.""" |
330
|
1 |
|
channel = item.channel |
331
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
332
|
|
|
|
333
|
1 |
|
current_version = news_item_service.get_current_item_version(item.id) |
334
|
1 |
|
is_current_version = version.id == current_version.id |
335
|
|
|
|
336
|
1 |
|
context = { |
337
|
|
|
'version': version, |
338
|
|
|
'brand': brand, |
339
|
|
|
'is_current_version': is_current_version, |
340
|
|
|
} |
341
|
|
|
|
342
|
1 |
|
try: |
343
|
1 |
|
rendered_body = news_html_service.render_body( |
344
|
|
|
version.body, channel.id, item.images |
345
|
|
|
) |
346
|
|
|
|
347
|
1 |
|
context.update( |
348
|
|
|
{ |
349
|
|
|
'rendered_body': rendered_body, |
350
|
|
|
'error_occurred': False, |
351
|
|
|
} |
352
|
|
|
) |
353
|
|
|
except Exception as e: |
354
|
|
|
context.update( |
355
|
|
|
{ |
356
|
|
|
'error_occurred': True, |
357
|
|
|
'error_message': str(e), |
358
|
|
|
} |
359
|
|
|
) |
360
|
|
|
|
361
|
1 |
|
return context |
362
|
|
|
|
363
|
|
|
|
364
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/versions') |
365
|
1 |
|
@permission_required(NewsItemPermission.view) |
366
|
1 |
|
@templated |
367
|
|
|
def item_list_versions(item_id): |
368
|
|
|
"""List news item's versions.""" |
369
|
1 |
|
item = _get_item_or_404(item_id) |
370
|
|
|
|
371
|
1 |
|
channel = item.channel |
372
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
373
|
|
|
|
374
|
1 |
|
versions = news_item_service.get_item_versions(item.id) |
375
|
1 |
|
versions_pairwise = list(pairwise(versions + [None])) |
376
|
|
|
|
377
|
1 |
|
return { |
378
|
|
|
'item': item, |
379
|
|
|
'brand': brand, |
380
|
|
|
'versions_pairwise': versions_pairwise, |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
|
384
|
1 |
|
@blueprint.get('/items/<uuid:from_version_id>/compare_to/<uuid:to_version_id>') |
385
|
1 |
|
@permission_required(NewsItemPermission.view) |
386
|
1 |
|
@templated |
387
|
|
|
def item_compare_versions(from_version_id, to_version_id): |
388
|
|
|
"""Show the difference between two news item versions.""" |
389
|
1 |
|
from_version = _find_version(from_version_id) |
390
|
1 |
|
to_version = _find_version(to_version_id) |
391
|
|
|
|
392
|
1 |
|
if from_version.item_id != to_version.item_id: |
393
|
|
|
abort(400, 'The versions do not belong to the same item.') |
394
|
|
|
|
395
|
1 |
|
item = news_item_service.find_item(from_version.item_id) |
396
|
1 |
|
channel = item.channel |
397
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
398
|
|
|
|
399
|
1 |
|
html_diff_title = _create_html_diff(from_version, to_version, 'title') |
400
|
1 |
|
html_diff_body = _create_html_diff(from_version, to_version, 'body') |
401
|
1 |
|
html_diff_image_url_path = _create_html_diff( |
402
|
|
|
from_version, to_version, 'image_url_path' |
403
|
|
|
) |
404
|
|
|
|
405
|
1 |
|
return { |
406
|
|
|
'brand': brand, |
407
|
|
|
'diff_title': html_diff_title, |
408
|
|
|
'diff_body': html_diff_body, |
409
|
|
|
'diff_image_url_path': html_diff_image_url_path, |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
|
413
|
1 |
|
@blueprint.get('/for_channel/<channel_id>/create') |
414
|
1 |
|
@permission_required(NewsItemPermission.create) |
415
|
1 |
|
@templated |
416
|
1 |
|
def item_create_form(channel_id, erroneous_form=None): |
417
|
|
|
"""Show form to create a news item.""" |
418
|
1 |
|
channel = _get_channel_or_404(channel_id) |
419
|
|
|
|
420
|
1 |
|
if erroneous_form: |
421
|
|
|
form = erroneous_form |
422
|
|
|
else: |
423
|
1 |
|
slug_prefix = date.today().strftime('%Y-%m-%d-') |
424
|
1 |
|
form = ItemCreateForm(slug=slug_prefix) |
425
|
|
|
|
426
|
1 |
|
return { |
427
|
|
|
'channel': channel, |
428
|
|
|
'form': form, |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
|
432
|
1 |
|
@blueprint.post('/for_channel/<channel_id>') |
433
|
1 |
|
@permission_required(NewsItemPermission.create) |
434
|
|
|
def item_create(channel_id): |
435
|
|
|
"""Create a news item.""" |
436
|
1 |
|
channel = _get_channel_or_404(channel_id) |
437
|
|
|
|
438
|
1 |
|
form = ItemCreateForm(request.form) |
439
|
1 |
|
if not form.validate(): |
440
|
|
|
return item_create_form(channel.id, form) |
441
|
|
|
|
442
|
1 |
|
slug = form.slug.data.strip().lower() |
443
|
1 |
|
creator = g.user |
444
|
1 |
|
title = form.title.data.strip() |
445
|
1 |
|
body = form.body.data.strip() |
446
|
1 |
|
image_url_path = form.image_url_path.data.strip() |
447
|
|
|
|
448
|
1 |
|
item = news_item_service.create_item( |
449
|
|
|
channel.id, slug, creator.id, title, body, image_url_path=image_url_path |
450
|
|
|
) |
451
|
|
|
|
452
|
1 |
|
flash_success( |
453
|
|
|
gettext('News item "%(title)s" has been created.', title=item.title) |
454
|
|
|
) |
455
|
|
|
|
456
|
1 |
|
return redirect_to('.item_view', item_id=item.id) |
457
|
|
|
|
458
|
|
|
|
459
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/update') |
460
|
1 |
|
@permission_required(NewsItemPermission.update) |
461
|
1 |
|
@templated |
462
|
1 |
|
def item_update_form(item_id, erroneous_form=None): |
463
|
|
|
"""Show form to update a news item.""" |
464
|
1 |
|
item = _get_item_or_404(item_id) |
465
|
|
|
|
466
|
1 |
|
current_version = news_item_service.get_current_item_version(item.id) |
467
|
|
|
|
468
|
1 |
|
form = ( |
469
|
|
|
erroneous_form |
470
|
|
|
if erroneous_form |
471
|
|
|
else ItemUpdateForm(obj=current_version, slug=item.slug) |
472
|
|
|
) |
473
|
|
|
|
474
|
1 |
|
return { |
475
|
|
|
'item': item, |
476
|
|
|
'form': form, |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
|
480
|
1 |
|
@blueprint.post('/items/<uuid:item_id>') |
481
|
1 |
|
@permission_required(NewsItemPermission.update) |
482
|
|
|
def item_update(item_id): |
483
|
|
|
"""Update a news item.""" |
484
|
|
|
item = _get_item_or_404(item_id) |
485
|
|
|
|
486
|
|
|
form = ItemUpdateForm(request.form) |
487
|
|
|
if not form.validate(): |
488
|
|
|
return item_update_form(item.id, form) |
489
|
|
|
|
490
|
|
|
creator = g.user |
491
|
|
|
slug = form.slug.data.strip().lower() |
492
|
|
|
title = form.title.data.strip() |
493
|
|
|
body = form.body.data.strip() |
494
|
|
|
image_url_path = form.image_url_path.data.strip() |
495
|
|
|
|
496
|
|
|
item = news_item_service.update_item( |
497
|
|
|
item.id, slug, creator.id, title, body, image_url_path=image_url_path |
498
|
|
|
) |
499
|
|
|
|
500
|
|
|
flash_success( |
501
|
|
|
gettext('News item "%(title)s" has been updated.', title=item.title) |
502
|
|
|
) |
503
|
|
|
|
504
|
|
|
return redirect_to('.item_view', item_id=item.id) |
505
|
|
|
|
506
|
|
|
|
507
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/publish_later') |
508
|
1 |
|
@permission_required(NewsItemPermission.publish) |
509
|
1 |
|
@templated |
510
|
1 |
|
def item_publish_later_form(item_id, erroneous_form=None): |
511
|
|
|
"""Show form to publish a news item at a time in the future.""" |
512
|
1 |
|
item = _get_item_or_404(item_id) |
513
|
|
|
|
514
|
1 |
|
form = erroneous_form if erroneous_form else ItemPublishLaterForm() |
515
|
|
|
|
516
|
1 |
|
return { |
517
|
|
|
'item': item, |
518
|
|
|
'form': form, |
519
|
|
|
} |
520
|
|
|
|
521
|
|
|
|
522
|
1 |
|
@blueprint.post('/items/<uuid:item_id>/publish_later') |
523
|
1 |
|
@permission_required(NewsItemPermission.publish) |
524
|
|
|
def item_publish_later(item_id): |
525
|
|
|
"""Publish a news item at a time in the future.""" |
526
|
1 |
|
item = _get_item_or_404(item_id) |
527
|
|
|
|
528
|
1 |
|
form = ItemPublishLaterForm(request.form) |
529
|
1 |
|
if not form.validate(): |
530
|
|
|
return item_publish_later_form(item.id, form) |
531
|
|
|
|
532
|
1 |
|
publish_at = local_tz_to_utc( |
533
|
|
|
datetime.combine(form.publish_on.data, form.publish_at.data) |
534
|
|
|
) |
535
|
|
|
|
536
|
1 |
|
event = news_item_service.publish_item( |
537
|
|
|
item.id, publish_at=publish_at, initiator_id=g.user.id |
538
|
|
|
) |
539
|
|
|
|
540
|
1 |
|
news_signals.item_published.send(None, event=event) |
541
|
|
|
|
542
|
1 |
|
flash_success( |
543
|
|
|
gettext( |
544
|
|
|
'News item "%(title)s" will be published later.', title=item.title |
545
|
|
|
) |
546
|
|
|
) |
547
|
|
|
|
548
|
1 |
|
return redirect_to('.item_view', item_id=item.id) |
549
|
|
|
|
550
|
|
|
|
551
|
1 |
|
@blueprint.post('/items/<uuid:item_id>/publish_now') |
552
|
1 |
|
@permission_required(NewsItemPermission.publish) |
553
|
1 |
|
@respond_no_content |
554
|
|
|
def item_publish_now(item_id): |
555
|
|
|
"""Publish a news item now.""" |
556
|
1 |
|
item = _get_item_or_404(item_id) |
557
|
|
|
|
558
|
1 |
|
event = news_item_service.publish_item(item.id, initiator_id=g.user.id) |
559
|
|
|
|
560
|
1 |
|
news_signals.item_published.send(None, event=event) |
561
|
|
|
|
562
|
1 |
|
flash_success( |
563
|
|
|
gettext('News item "%(title)s" has been published.', title=item.title) |
564
|
|
|
) |
565
|
|
|
|
566
|
|
|
|
567
|
|
|
# -------------------------------------------------------------------- # |
568
|
|
|
# helpers |
569
|
|
|
|
570
|
|
|
|
571
|
1 |
|
def _get_brand_or_404(brand_id): |
572
|
1 |
|
brand = brand_service.find_brand(brand_id) |
573
|
|
|
|
574
|
1 |
|
if brand is None: |
575
|
|
|
abort(404) |
576
|
|
|
|
577
|
1 |
|
return brand |
578
|
|
|
|
579
|
|
|
|
580
|
1 |
|
def _get_channel_or_404(channel_id) -> Channel: |
581
|
1 |
|
channel = news_channel_service.find_channel(channel_id) |
582
|
|
|
|
583
|
1 |
|
if channel is None: |
584
|
|
|
abort(404) |
585
|
|
|
|
586
|
1 |
|
return channel |
587
|
|
|
|
588
|
|
|
|
589
|
1 |
|
def _get_item_or_404(item_id): |
590
|
1 |
|
item = news_item_service.find_item(item_id) |
591
|
|
|
|
592
|
1 |
|
if item is None: |
593
|
|
|
abort(404) |
594
|
|
|
|
595
|
1 |
|
return item |
596
|
|
|
|
597
|
|
|
|
598
|
1 |
|
def _get_image_or_404(image_id): |
599
|
|
|
image = news_image_service.find_image(image_id) |
600
|
|
|
|
601
|
|
|
if image is None: |
602
|
|
|
abort(404) |
603
|
|
|
|
604
|
|
|
return image |
605
|
|
|
|
606
|
|
|
|
607
|
1 |
|
def _find_version(version_id): |
608
|
1 |
|
version = news_item_service.find_item_version(version_id) |
609
|
|
|
|
610
|
1 |
|
if version is None: |
611
|
|
|
abort(404) |
612
|
|
|
|
613
|
1 |
|
return version |
614
|
|
|
|
615
|
|
|
|
616
|
1 |
View Code Duplication |
def _create_html_diff(from_version, to_version, attribute_name): |
617
|
|
|
"""Create an HTML diff between the named attribute's value of each |
618
|
|
|
of the two versions. |
619
|
|
|
""" |
620
|
1 |
|
from_description = format_datetime_short(from_version.created_at) |
621
|
1 |
|
to_description = format_datetime_short(to_version.created_at) |
622
|
|
|
|
623
|
1 |
|
from_text = getattr(from_version, attribute_name) |
624
|
1 |
|
to_text = getattr(to_version, attribute_name) |
625
|
|
|
|
626
|
1 |
|
return text_diff_service.create_html_diff( |
627
|
|
|
from_text, to_text, from_description, to_description |
628
|
|
|
) |
629
|
|
|
|