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