1
|
|
|
""" |
2
|
|
|
byceps.blueprints.admin.news.views |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2022 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 format_datetime, gettext, to_utc |
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 BodyFormat, 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 import service as user_service |
24
|
1 |
|
from ....services.user.service import UserIdRejected |
25
|
1 |
|
from ....signals import news as news_signals |
26
|
1 |
|
from ....util.framework.blueprint import create_blueprint |
27
|
1 |
|
from ....util.framework.flash import flash_error, flash_success |
28
|
1 |
|
from ....util.framework.templating import templated |
29
|
1 |
|
from ....util.iterables import pairwise |
30
|
1 |
|
from ....util.views import permission_required, redirect_to, respond_no_content |
31
|
|
|
|
32
|
1 |
|
from .forms import ( |
33
|
|
|
ChannelCreateForm, |
34
|
|
|
ChannelUpdateForm, |
35
|
|
|
ImageCreateForm, |
36
|
|
|
ImageUpdateForm, |
37
|
|
|
ItemCreateForm, |
38
|
|
|
ItemPublishLaterForm, |
39
|
|
|
ItemUpdateForm, |
40
|
|
|
) |
41
|
|
|
|
42
|
|
|
|
43
|
1 |
|
blueprint = create_blueprint('news_admin', __name__) |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
# -------------------------------------------------------------------- # |
47
|
|
|
# channels |
48
|
|
|
|
49
|
|
|
|
50
|
1 |
|
@blueprint.get('/brands/<brand_id>') |
51
|
1 |
|
@permission_required('news_item.view') |
52
|
1 |
|
@templated |
53
|
|
|
def channel_index_for_brand(brand_id): |
54
|
|
|
"""List channels for that brand.""" |
55
|
1 |
|
brand = _get_brand_or_404(brand_id) |
56
|
|
|
|
57
|
1 |
|
channels = news_channel_service.get_channels_for_brand(brand.id) |
58
|
|
|
|
59
|
1 |
|
announcement_site_ids = { |
60
|
|
|
channel.announcement_site_id for channel in channels |
61
|
|
|
} |
62
|
1 |
|
announcement_sites_by_channel_id = { |
63
|
|
|
site.id: site for site in site_service.get_sites(announcement_site_ids) |
64
|
|
|
} |
65
|
|
|
|
66
|
1 |
|
item_count_by_channel_id = news_item_service.get_item_count_by_channel_id() |
67
|
|
|
|
68
|
1 |
|
return { |
69
|
|
|
'brand': brand, |
70
|
|
|
'channels': channels, |
71
|
|
|
'announcement_sites_by_channel_id': announcement_sites_by_channel_id, |
72
|
|
|
'item_count_by_channel_id': item_count_by_channel_id, |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
|
76
|
1 |
|
@blueprint.get('/channels/<channel_id>', defaults={'page': 1}) |
77
|
1 |
|
@blueprint.get('/channels/<channel_id>/pages/<int:page>') |
78
|
1 |
|
@permission_required('news_item.view') |
79
|
1 |
|
@templated |
80
|
|
|
def channel_view(channel_id, page): |
81
|
|
|
"""View that channel and list its news items.""" |
82
|
1 |
|
channel = _get_channel_or_404(channel_id) |
83
|
|
|
|
84
|
1 |
|
brand = brand_service.get_brand(channel.brand_id) |
85
|
1 |
|
if channel.announcement_site_id is not None: |
86
|
|
|
announcement_site = site_service.get_site(channel.announcement_site_id) |
87
|
|
|
else: |
88
|
1 |
|
announcement_site = None |
89
|
|
|
|
90
|
1 |
|
channel_ids = {channel.id} |
91
|
1 |
|
per_page = request.args.get('per_page', type=int, default=15) |
92
|
|
|
|
93
|
1 |
|
items = news_item_service.get_items_paginated(channel_ids, page, per_page) |
94
|
|
|
|
95
|
1 |
|
user_ids = {item.current_version.creator_id for item in items.items} |
96
|
1 |
|
users = user_service.get_users(user_ids, include_avatars=True) |
97
|
1 |
|
users_by_id = user_service.index_users_by_id(users) |
98
|
|
|
|
99
|
1 |
|
return { |
100
|
|
|
'channel': channel, |
101
|
|
|
'brand': brand, |
102
|
|
|
'announcement_site': announcement_site, |
103
|
|
|
'items': items, |
104
|
|
|
'per_page': per_page, |
105
|
|
|
'users_by_id': users_by_id, |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
|
109
|
1 |
|
@blueprint.get('/for_brand/<brand_id>/channels/create') |
110
|
1 |
|
@permission_required('news_channel.administrate') |
111
|
1 |
|
@templated |
112
|
1 |
|
def channel_create_form(brand_id, erroneous_form=None): |
113
|
|
|
"""Show form to create a channel.""" |
114
|
1 |
|
brand = _get_brand_or_404(brand_id) |
115
|
|
|
|
116
|
1 |
|
form = erroneous_form if erroneous_form else ChannelCreateForm() |
117
|
1 |
|
form.set_announcement_site_id_choices(brand.id) |
118
|
|
|
|
119
|
1 |
|
return { |
120
|
|
|
'brand': brand, |
121
|
|
|
'form': form, |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
|
125
|
1 |
|
@blueprint.post('/for_brand/<brand_id>/channels') |
126
|
1 |
|
@permission_required('news_channel.administrate') |
127
|
|
|
def channel_create(brand_id): |
128
|
|
|
"""Create a channel.""" |
129
|
1 |
|
brand = _get_brand_or_404(brand_id) |
130
|
|
|
|
131
|
1 |
|
form = ChannelCreateForm(request.form) |
132
|
1 |
|
form.set_announcement_site_id_choices(brand.id) |
133
|
|
|
|
134
|
1 |
|
if not form.validate(): |
135
|
|
|
return channel_create_form(brand.id, form) |
136
|
|
|
|
137
|
1 |
|
channel_id = form.channel_id.data.strip().lower() |
138
|
1 |
|
announcement_site_id = form.announcement_site_id.data or None |
139
|
|
|
|
140
|
1 |
|
channel = news_channel_service.create_channel( |
141
|
|
|
brand.id, channel_id, announcement_site_id=announcement_site_id |
142
|
|
|
) |
143
|
|
|
|
144
|
1 |
|
flash_success( |
145
|
|
|
gettext( |
146
|
|
|
'News channel "%(channel_id)s" has been created.', |
147
|
|
|
channel_id=channel.id, |
148
|
|
|
) |
149
|
|
|
) |
150
|
|
|
|
151
|
1 |
|
return redirect_to('.channel_view', channel_id=channel.id) |
152
|
|
|
|
153
|
|
|
|
154
|
1 |
|
@blueprint.get('/channels/<channel_id>/update') |
155
|
1 |
|
@permission_required('news_channel.administrate') |
156
|
1 |
|
@templated |
157
|
1 |
|
def channel_update_form(channel_id, erroneous_form=None): |
158
|
|
|
"""Show form to update a channel.""" |
159
|
|
|
channel = _get_channel_or_404(channel_id) |
160
|
|
|
|
161
|
|
|
brand = brand_service.get_brand(channel.brand_id) |
162
|
|
|
|
163
|
|
|
form = erroneous_form if erroneous_form else ChannelUpdateForm(obj=channel) |
164
|
|
|
form.set_announcement_site_id_choices(brand.id) |
165
|
|
|
|
166
|
|
|
return { |
167
|
|
|
'brand': brand, |
168
|
|
|
'channel': channel, |
169
|
|
|
'form': form, |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
1 |
|
@blueprint.post('/channels/<channel_id>') |
174
|
1 |
|
@permission_required('news_channel.administrate') |
175
|
|
|
def channel_update(channel_id): |
176
|
|
|
"""Update a channel.""" |
177
|
|
|
channel = _get_channel_or_404(channel_id) |
178
|
|
|
|
179
|
|
|
brand = brand_service.get_brand(channel.brand_id) |
180
|
|
|
|
181
|
|
|
form = ChannelUpdateForm(request.form) |
182
|
|
|
form.set_announcement_site_id_choices(brand.id) |
183
|
|
|
|
184
|
|
|
if not form.validate(): |
185
|
|
|
return channel_update_form(channel.id, form) |
186
|
|
|
|
187
|
|
|
announcement_site_id = form.announcement_site_id.data or None |
188
|
|
|
|
189
|
|
|
channel = news_channel_service.update_channel( |
190
|
|
|
channel.id, announcement_site_id |
191
|
|
|
) |
192
|
|
|
|
193
|
|
|
flash_success(gettext('Changes have been saved.')) |
194
|
|
|
|
195
|
|
|
return redirect_to('.channel_view', channel_id=channel.id) |
196
|
|
|
|
197
|
|
|
|
198
|
1 |
|
@blueprint.delete('/channels/<channel_id>') |
199
|
1 |
|
@permission_required('news_channel.administrate') |
200
|
1 |
|
@respond_no_content |
201
|
|
|
def channel_delete(channel_id): |
202
|
|
|
"""Delete the channel.""" |
203
|
|
|
channel = _get_channel_or_404(channel_id) |
204
|
|
|
|
205
|
|
|
if news_item_service.has_channel_items(channel.id): |
206
|
|
|
flash_error( |
207
|
|
|
gettext( |
208
|
|
|
'News channel "%(channel_id)s" cannot be deleted because it contains news items.', |
209
|
|
|
channel_id=channel.id, |
210
|
|
|
) |
211
|
|
|
) |
212
|
|
|
return |
213
|
|
|
|
214
|
|
|
sites_for_brand = site_service.get_sites_for_brand(channel.brand_id) |
215
|
|
|
linked_sites = { |
216
|
|
|
site for site in sites_for_brand if channel.id in site.news_channel_ids |
217
|
|
|
} |
218
|
|
|
if linked_sites: |
219
|
|
|
flash_error( |
220
|
|
|
gettext( |
221
|
|
|
'News channel "%(channel_id)s" cannot be deleted because it is referenced by %(site_count)s site(s).', |
222
|
|
|
channel_id=channel.id, |
223
|
|
|
site_count=len(linked_sites), |
224
|
|
|
) |
225
|
|
|
) |
226
|
|
|
return |
227
|
|
|
|
228
|
|
|
news_channel_service.delete_channel(channel.id) |
229
|
|
|
|
230
|
|
|
flash_success( |
231
|
|
|
gettext( |
232
|
|
|
'News channel "%(channel_id)s" has been deleted.', |
233
|
|
|
channel_id=channel_id, |
234
|
|
|
) |
235
|
|
|
) |
236
|
|
|
|
237
|
|
|
|
238
|
|
|
# -------------------------------------------------------------------- # |
239
|
|
|
# images |
240
|
|
|
|
241
|
|
|
|
242
|
1 |
|
@blueprint.get('/for_item/<item_id>/create') |
243
|
1 |
|
@permission_required('news_item.update') |
244
|
1 |
|
@templated |
245
|
1 |
|
def image_create_form(item_id, erroneous_form=None): |
246
|
|
|
"""Show form to create a news image.""" |
247
|
1 |
|
item = _get_item_or_404(item_id) |
248
|
|
|
|
249
|
1 |
|
form = erroneous_form if erroneous_form else ImageCreateForm() |
250
|
|
|
|
251
|
1 |
|
image_type_names = image_service.get_image_type_names( |
252
|
|
|
news_image_service.ALLOWED_IMAGE_TYPES |
253
|
|
|
) |
254
|
|
|
|
255
|
1 |
|
return { |
256
|
|
|
'item': item, |
257
|
|
|
'form': form, |
258
|
|
|
'allowed_types': image_type_names, |
259
|
|
|
'maximum_dimensions': news_image_service.MAXIMUM_DIMENSIONS, |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
|
263
|
1 |
|
@blueprint.post('/for_item/<item_id>') |
264
|
1 |
|
@permission_required('news_item.update') |
265
|
|
|
def image_create(item_id): |
266
|
|
|
"""Create a news image.""" |
267
|
|
|
item = _get_item_or_404(item_id) |
268
|
|
|
|
269
|
|
|
# Make `InputRequired` work on `FileField`. |
270
|
|
|
form_fields = request.form.copy() |
271
|
|
|
if request.files: |
272
|
|
|
form_fields.update(request.files) |
273
|
|
|
|
274
|
|
|
form = ImageCreateForm(form_fields) |
275
|
|
|
if not form.validate(): |
276
|
|
|
return image_create_form(item.id, form) |
277
|
|
|
|
278
|
|
|
creator_id = g.user.id |
279
|
|
|
image = request.files.get('image') |
280
|
|
|
alt_text = form.alt_text.data.strip() |
281
|
|
|
caption = form.caption.data.strip() |
282
|
|
|
attribution = form.attribution.data.strip() |
283
|
|
|
|
284
|
|
|
if not image or not image.filename: |
285
|
|
|
abort(400, 'No file to upload has been specified.') |
286
|
|
|
|
287
|
|
|
try: |
288
|
|
|
image = news_image_service.create_image( |
289
|
|
|
creator_id, |
290
|
|
|
item.id, |
291
|
|
|
image.stream, |
292
|
|
|
alt_text=alt_text, |
293
|
|
|
caption=caption, |
294
|
|
|
attribution=attribution, |
295
|
|
|
) |
296
|
|
|
except UserIdRejected as e: |
297
|
|
|
abort(400, 'Invalid creator ID') |
298
|
|
|
except image_service.ImageTypeProhibited as e: |
299
|
|
|
abort(400, str(e)) |
300
|
|
|
except FileExistsError: |
301
|
|
|
abort(409, 'File already exists, not overwriting.') |
302
|
|
|
|
303
|
|
|
flash_success( |
304
|
|
|
gettext( |
305
|
|
|
'News image #%(image_number)s has been created.', |
306
|
|
|
image_number=image.number, |
307
|
|
|
) |
308
|
|
|
) |
309
|
|
|
|
310
|
|
|
return redirect_to('.item_view', item_id=image.item_id) |
311
|
|
|
|
312
|
|
|
|
313
|
1 |
|
@blueprint.get('/images/<uuid:image_id>/update') |
314
|
1 |
|
@permission_required('news_item.update') |
315
|
1 |
|
@templated |
316
|
1 |
|
def image_update_form(image_id, erroneous_form=None): |
317
|
|
|
"""Show form to update a news image.""" |
318
|
|
|
image = _get_image_or_404(image_id) |
319
|
|
|
item = news_item_service.find_item(image.item_id) |
320
|
|
|
|
321
|
|
|
form = erroneous_form if erroneous_form else ImageUpdateForm(obj=image) |
322
|
|
|
|
323
|
|
|
return { |
324
|
|
|
'image': image, |
325
|
|
|
'item': item, |
326
|
|
|
'form': form, |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
|
330
|
1 |
|
@blueprint.post('/images/<uuid:image_id>') |
331
|
1 |
|
@permission_required('news_item.update') |
332
|
|
|
def image_update(image_id): |
333
|
|
|
"""Update a news image.""" |
334
|
|
|
image = _get_image_or_404(image_id) |
335
|
|
|
|
336
|
|
|
form = ImageUpdateForm(request.form) |
337
|
|
|
if not form.validate(): |
338
|
|
|
return image_update_form(image.id, form) |
339
|
|
|
|
340
|
|
|
alt_text = form.alt_text.data.strip() |
341
|
|
|
caption = form.caption.data.strip() |
342
|
|
|
attribution = form.attribution.data.strip() |
343
|
|
|
|
344
|
|
|
image = news_image_service.update_image( |
345
|
|
|
image.id, |
346
|
|
|
alt_text=alt_text, |
347
|
|
|
caption=caption, |
348
|
|
|
attribution=attribution, |
349
|
|
|
) |
350
|
|
|
|
351
|
|
|
flash_success( |
352
|
|
|
gettext( |
353
|
|
|
'News image #%(image_number)s has been updated.', |
354
|
|
|
image_number=image.number, |
355
|
|
|
) |
356
|
|
|
) |
357
|
|
|
|
358
|
|
|
return redirect_to('.item_view', item_id=image.item_id) |
359
|
|
|
|
360
|
|
|
|
361
|
1 |
|
@blueprint.post('/images/<uuid:image_id>/featured') |
362
|
1 |
|
@permission_required('news_item.update') |
363
|
1 |
|
@respond_no_content |
364
|
|
|
def image_set_featured(image_id): |
365
|
|
|
"""Set the image as featured image.""" |
366
|
|
|
image = _get_image_or_404(image_id) |
367
|
|
|
|
368
|
|
|
news_item_service.set_featured_image(image.item_id, image.id) |
369
|
|
|
|
370
|
|
|
flash_success(gettext('Featured image has been set.')) |
371
|
|
|
|
372
|
|
|
|
373
|
1 |
|
@blueprint.delete('/items/<uuid:item_id>/featured') |
374
|
1 |
|
@permission_required('news_item.update') |
375
|
1 |
|
@respond_no_content |
376
|
|
|
def image_unset_featured(item_id): |
377
|
|
|
"""Unset the item's featured image.""" |
378
|
|
|
item = _get_item_or_404(item_id) |
379
|
|
|
|
380
|
|
|
news_item_service.set_featured_image(item.id, image_id=None) |
381
|
|
|
|
382
|
|
|
flash_success(gettext('Featured image has been unset.')) |
383
|
|
|
|
384
|
|
|
|
385
|
|
|
# -------------------------------------------------------------------- # |
386
|
|
|
# items |
387
|
|
|
|
388
|
|
|
|
389
|
1 |
|
@blueprint.get('/items/<uuid:item_id>') |
390
|
1 |
|
@permission_required('news_item.view') |
391
|
|
|
def item_view(item_id): |
392
|
|
|
"""Show the current version of the news item.""" |
393
|
1 |
|
item = _get_item_or_404(item_id) |
394
|
|
|
|
395
|
1 |
|
version = news_item_service.get_current_item_version(item.id) |
396
|
|
|
|
397
|
1 |
|
return item_view_version(version.id) |
398
|
|
|
|
399
|
|
|
|
400
|
1 |
|
@blueprint.get('/versions/<uuid:version_id>') |
401
|
1 |
|
@permission_required('news_item.view') |
402
|
1 |
|
@templated |
403
|
|
|
def item_view_version(version_id): |
404
|
|
|
"""Show the news item with the given version.""" |
405
|
1 |
|
version = _find_version(version_id) |
406
|
|
|
|
407
|
1 |
|
item = news_item_service.find_item(version.item_id) |
408
|
|
|
|
409
|
1 |
|
channel = item.channel |
410
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
411
|
|
|
|
412
|
1 |
|
creator = user_service.get_user(version.creator_id, include_avatar=True) |
413
|
|
|
|
414
|
1 |
|
current_version = news_item_service.get_current_item_version(item.id) |
415
|
1 |
|
is_current_version = version.id == current_version.id |
416
|
|
|
|
417
|
1 |
|
return { |
418
|
|
|
'item': item, |
419
|
|
|
'version': version, |
420
|
|
|
'brand': brand, |
421
|
|
|
'creator': creator, |
422
|
|
|
'is_current_version': is_current_version, |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
|
426
|
1 |
|
@blueprint.get('/versions/<uuid:version_id>/preview') |
427
|
1 |
|
@permission_required('news_item.view') |
428
|
1 |
|
@templated |
429
|
|
|
def item_view_version_preview(version_id): |
430
|
|
|
"""Show a preview of the news item with the given version.""" |
431
|
|
|
version = _find_version(version_id) |
432
|
|
|
|
433
|
|
|
item = news_item_service.find_item(version.item_id) |
434
|
|
|
|
435
|
|
|
try: |
436
|
|
|
rendered_body = news_html_service.render_body( |
437
|
|
|
item, version.body, version.body_format |
438
|
|
|
) |
439
|
|
|
|
440
|
|
|
return { |
441
|
|
|
'title': version.title, |
442
|
|
|
'body': rendered_body, |
443
|
|
|
'error_occurred': False, |
444
|
|
|
} |
445
|
|
|
except Exception as e: |
446
|
|
|
return { |
447
|
|
|
'error_occurred': True, |
448
|
|
|
'error_message': str(e), |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
|
452
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/versions') |
453
|
1 |
|
@permission_required('news_item.view') |
454
|
1 |
|
@templated |
455
|
|
|
def item_list_versions(item_id): |
456
|
|
|
"""List news item's versions.""" |
457
|
1 |
|
item = _get_item_or_404(item_id) |
458
|
|
|
|
459
|
1 |
|
channel = item.channel |
460
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
461
|
|
|
|
462
|
1 |
|
versions = news_item_service.get_item_versions(item.id) |
463
|
1 |
|
versions_pairwise = list(pairwise(versions + [None])) |
464
|
|
|
|
465
|
1 |
|
user_ids = {version.creator_id for version in versions} |
466
|
1 |
|
users = user_service.get_users(user_ids, include_avatars=True) |
467
|
1 |
|
users_by_id = user_service.index_users_by_id(users) |
468
|
|
|
|
469
|
1 |
|
return { |
470
|
|
|
'item': item, |
471
|
|
|
'brand': brand, |
472
|
|
|
'versions_pairwise': versions_pairwise, |
473
|
|
|
'users_by_id': users_by_id, |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
|
477
|
1 |
|
@blueprint.get('/items/<uuid:from_version_id>/compare_to/<uuid:to_version_id>') |
478
|
1 |
|
@permission_required('news_item.view') |
479
|
1 |
|
@templated |
480
|
|
|
def item_compare_versions(from_version_id, to_version_id): |
481
|
|
|
"""Show the difference between two news item versions.""" |
482
|
1 |
|
from_version = _find_version(from_version_id) |
483
|
1 |
|
to_version = _find_version(to_version_id) |
484
|
|
|
|
485
|
1 |
|
if from_version.item_id != to_version.item_id: |
486
|
|
|
abort(400, 'The versions do not belong to the same item.') |
487
|
|
|
|
488
|
1 |
|
item = news_item_service.find_item(from_version.item_id) |
489
|
1 |
|
channel = item.channel |
490
|
1 |
|
brand = brand_service.find_brand(channel.brand_id) |
491
|
|
|
|
492
|
1 |
|
html_diff_title = _create_html_diff( |
493
|
|
|
from_version, to_version, lambda version: version.title |
494
|
|
|
) |
495
|
1 |
|
html_diff_body = _create_html_diff( |
496
|
|
|
from_version, to_version, lambda version: version.body |
497
|
|
|
) |
498
|
1 |
|
html_diff_body_format = _create_html_diff( |
499
|
|
|
from_version, to_version, lambda version: version.body_format.name |
500
|
|
|
) |
501
|
1 |
|
html_diff_image_url_path = _create_html_diff( |
502
|
|
|
from_version, to_version, lambda version: version.image_url_path |
503
|
|
|
) |
504
|
|
|
|
505
|
1 |
|
return { |
506
|
|
|
'brand': brand, |
507
|
|
|
'diff_title': html_diff_title, |
508
|
|
|
'diff_body': html_diff_body, |
509
|
|
|
'diff_body_format': html_diff_body_format, |
510
|
|
|
'diff_image_url_path': html_diff_image_url_path, |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
|
514
|
1 |
|
@blueprint.get('/for_channel/<channel_id>/create') |
515
|
1 |
|
@permission_required('news_item.create') |
516
|
1 |
|
@templated |
517
|
1 |
|
def item_create_form(channel_id, erroneous_form=None): |
518
|
|
|
"""Show form to create a news item.""" |
519
|
1 |
|
channel = _get_channel_or_404(channel_id) |
520
|
|
|
|
521
|
1 |
|
if erroneous_form: |
522
|
|
|
form = erroneous_form |
523
|
|
|
else: |
524
|
1 |
|
slug_prefix = date.today().strftime('%Y-%m-%d-') |
525
|
1 |
|
form = ItemCreateForm(slug=slug_prefix) |
526
|
|
|
|
527
|
1 |
|
return { |
528
|
|
|
'channel': channel, |
529
|
|
|
'form': form, |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
|
533
|
1 |
|
@blueprint.post('/for_channel/<channel_id>') |
534
|
1 |
|
@permission_required('news_item.create') |
535
|
|
|
def item_create(channel_id): |
536
|
|
|
"""Create a news item.""" |
537
|
1 |
|
channel = _get_channel_or_404(channel_id) |
538
|
|
|
|
539
|
1 |
|
form = ItemCreateForm(request.form) |
540
|
1 |
|
if not form.validate(): |
541
|
|
|
return item_create_form(channel.id, form) |
542
|
|
|
|
543
|
1 |
|
slug = form.slug.data.strip().lower() |
544
|
1 |
|
creator = g.user |
545
|
1 |
|
title = form.title.data.strip() |
546
|
1 |
|
body = form.body.data.strip() |
547
|
1 |
|
body_format = form.body_format.data |
548
|
1 |
|
image_url_path = form.image_url_path.data.strip() |
549
|
|
|
|
550
|
1 |
|
item = news_item_service.create_item( |
551
|
|
|
channel.id, |
552
|
|
|
slug, |
553
|
|
|
creator.id, |
554
|
|
|
title, |
555
|
|
|
body, |
556
|
|
|
body_format, |
557
|
|
|
image_url_path=image_url_path, |
558
|
|
|
) |
559
|
|
|
|
560
|
1 |
|
flash_success( |
561
|
|
|
gettext('News item "%(title)s" has been created.', title=item.title) |
562
|
|
|
) |
563
|
|
|
|
564
|
1 |
|
return redirect_to('.item_view', item_id=item.id) |
565
|
|
|
|
566
|
|
|
|
567
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/update') |
568
|
1 |
|
@permission_required('news_item.update') |
569
|
1 |
|
@templated |
570
|
1 |
|
def item_update_form(item_id, erroneous_form=None): |
571
|
|
|
"""Show form to update a news item.""" |
572
|
1 |
|
item = _get_item_or_404(item_id) |
573
|
|
|
|
574
|
1 |
|
current_version = news_item_service.get_current_item_version(item.id) |
575
|
|
|
|
576
|
1 |
|
data = { |
577
|
|
|
'slug': item.slug, |
578
|
|
|
'title': current_version.title, |
579
|
|
|
'body_format': current_version.body_format.name, |
580
|
|
|
'body': current_version.body, |
581
|
|
|
'image_url_path': current_version.image_url_path, |
582
|
|
|
} |
583
|
1 |
|
form = erroneous_form if erroneous_form else ItemUpdateForm(data=data) |
584
|
|
|
|
585
|
1 |
|
return { |
586
|
|
|
'item': item, |
587
|
|
|
'form': form, |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
|
591
|
1 |
|
@blueprint.post('/items/<uuid:item_id>') |
592
|
1 |
|
@permission_required('news_item.update') |
593
|
|
|
def item_update(item_id): |
594
|
|
|
"""Update a news item.""" |
595
|
|
|
item = _get_item_or_404(item_id) |
596
|
|
|
|
597
|
|
|
form = ItemUpdateForm(request.form) |
598
|
|
|
if not form.validate(): |
599
|
|
|
return item_update_form(item.id, form) |
600
|
|
|
|
601
|
|
|
creator = g.user |
602
|
|
|
slug = form.slug.data.strip().lower() |
603
|
|
|
title = form.title.data.strip() |
604
|
|
|
body = form.body.data.strip() |
605
|
|
|
body_format = form.body_format.data |
606
|
|
|
image_url_path = form.image_url_path.data.strip() |
607
|
|
|
|
608
|
|
|
item = news_item_service.update_item( |
609
|
|
|
item.id, |
610
|
|
|
slug, |
611
|
|
|
creator.id, |
612
|
|
|
title, |
613
|
|
|
body, |
614
|
|
|
body_format, |
615
|
|
|
image_url_path=image_url_path, |
616
|
|
|
) |
617
|
|
|
|
618
|
|
|
flash_success( |
619
|
|
|
gettext('News item "%(title)s" has been updated.', title=item.title) |
620
|
|
|
) |
621
|
|
|
|
622
|
|
|
return redirect_to('.item_view', item_id=item.id) |
623
|
|
|
|
624
|
|
|
|
625
|
1 |
|
@blueprint.get('/items/<uuid:item_id>/publish_later') |
626
|
1 |
|
@permission_required('news_item.publish') |
627
|
1 |
|
@templated |
628
|
1 |
|
def item_publish_later_form(item_id, erroneous_form=None): |
629
|
|
|
"""Show form to publish a news item at a time in the future.""" |
630
|
1 |
|
item = _get_item_or_404(item_id) |
631
|
|
|
|
632
|
1 |
|
form = erroneous_form if erroneous_form else ItemPublishLaterForm() |
633
|
|
|
|
634
|
1 |
|
return { |
635
|
|
|
'item': item, |
636
|
|
|
'form': form, |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
|
640
|
1 |
|
@blueprint.post('/items/<uuid:item_id>/publish_later') |
641
|
1 |
|
@permission_required('news_item.publish') |
642
|
|
|
def item_publish_later(item_id): |
643
|
|
|
"""Publish a news item at a time in the future.""" |
644
|
1 |
|
item = _get_item_or_404(item_id) |
645
|
|
|
|
646
|
1 |
|
form = ItemPublishLaterForm(request.form) |
647
|
1 |
|
if not form.validate(): |
648
|
|
|
return item_publish_later_form(item.id, form) |
649
|
|
|
|
650
|
1 |
|
publish_at = to_utc( |
651
|
|
|
datetime.combine(form.publish_on.data, form.publish_at.data) |
652
|
|
|
) |
653
|
|
|
|
654
|
1 |
|
event = news_item_service.publish_item( |
655
|
|
|
item.id, publish_at=publish_at, initiator_id=g.user.id |
656
|
|
|
) |
657
|
|
|
|
658
|
1 |
|
news_signals.item_published.send(None, event=event) |
659
|
|
|
|
660
|
1 |
|
flash_success( |
661
|
|
|
gettext( |
662
|
|
|
'News item "%(title)s" will be published later.', title=item.title |
663
|
|
|
) |
664
|
|
|
) |
665
|
|
|
|
666
|
1 |
|
return redirect_to('.item_view', item_id=item.id) |
667
|
|
|
|
668
|
|
|
|
669
|
1 |
|
@blueprint.post('/items/<uuid:item_id>/publish_now') |
670
|
1 |
|
@permission_required('news_item.publish') |
671
|
1 |
|
@respond_no_content |
672
|
|
|
def item_publish_now(item_id): |
673
|
|
|
"""Publish a news item now.""" |
674
|
1 |
|
item = _get_item_or_404(item_id) |
675
|
|
|
|
676
|
1 |
|
event = news_item_service.publish_item(item.id, initiator_id=g.user.id) |
677
|
|
|
|
678
|
1 |
|
news_signals.item_published.send(None, event=event) |
679
|
|
|
|
680
|
1 |
|
flash_success( |
681
|
|
|
gettext('News item "%(title)s" has been published.', title=item.title) |
682
|
|
|
) |
683
|
|
|
|
684
|
|
|
|
685
|
1 |
|
@blueprint.post('/items/<uuid:item_id>/unpublish') |
686
|
1 |
|
@permission_required('news_item.publish') |
687
|
1 |
|
@respond_no_content |
688
|
|
|
def item_unpublish(item_id): |
689
|
|
|
"""Unpublish a news item.""" |
690
|
1 |
|
item = _get_item_or_404(item_id) |
691
|
|
|
|
692
|
1 |
|
news_item_service.unpublish_item(item.id) |
693
|
|
|
|
694
|
1 |
|
flash_success( |
695
|
|
|
gettext('News item "%(title)s" has been unpublished.', title=item.title) |
696
|
|
|
) |
697
|
|
|
|
698
|
|
|
|
699
|
|
|
# -------------------------------------------------------------------- # |
700
|
|
|
# helpers |
701
|
|
|
|
702
|
|
|
|
703
|
1 |
|
def _get_brand_or_404(brand_id): |
704
|
1 |
|
brand = brand_service.find_brand(brand_id) |
705
|
|
|
|
706
|
1 |
|
if brand is None: |
707
|
|
|
abort(404) |
708
|
|
|
|
709
|
1 |
|
return brand |
710
|
|
|
|
711
|
|
|
|
712
|
1 |
|
def _get_channel_or_404(channel_id) -> Channel: |
713
|
1 |
|
channel = news_channel_service.find_channel(channel_id) |
714
|
|
|
|
715
|
1 |
|
if channel is None: |
716
|
|
|
abort(404) |
717
|
|
|
|
718
|
1 |
|
return channel |
719
|
|
|
|
720
|
|
|
|
721
|
1 |
|
def _get_item_or_404(item_id): |
722
|
1 |
|
item = news_item_service.find_item(item_id) |
723
|
|
|
|
724
|
1 |
|
if item is None: |
725
|
|
|
abort(404) |
726
|
|
|
|
727
|
1 |
|
return item |
728
|
|
|
|
729
|
|
|
|
730
|
1 |
|
def _get_image_or_404(image_id): |
731
|
|
|
image = news_image_service.find_image(image_id) |
732
|
|
|
|
733
|
|
|
if image is None: |
734
|
|
|
abort(404) |
735
|
|
|
|
736
|
|
|
return image |
737
|
|
|
|
738
|
|
|
|
739
|
1 |
|
def _find_version(version_id): |
740
|
1 |
|
version = news_item_service.find_item_version(version_id) |
741
|
|
|
|
742
|
1 |
|
if version is None: |
743
|
|
|
abort(404) |
744
|
|
|
|
745
|
1 |
|
return version |
746
|
|
|
|
747
|
|
|
|
748
|
1 |
|
def _create_html_diff(from_version, to_version, attribute_getter): |
749
|
|
|
"""Create an HTML diff between the named attribute's value of each |
750
|
|
|
of the two versions. |
751
|
|
|
""" |
752
|
1 |
|
from_description = format_datetime(from_version.created_at) |
753
|
1 |
|
to_description = format_datetime(to_version.created_at) |
754
|
|
|
|
755
|
1 |
|
from_text = attribute_getter(from_version) |
756
|
1 |
|
to_text = attribute_getter(to_version) |
757
|
|
|
|
758
|
1 |
|
return text_diff_service.create_html_diff( |
759
|
|
|
from_text, to_text, from_description, to_description |
760
|
|
|
) |
761
|
|
|
|