1
|
|
|
""" |
2
|
|
|
byceps.blueprints.board.views_topic |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2006-2019 Jochen Kupperschmidt |
6
|
|
|
:License: Modified BSD, see LICENSE for details. |
7
|
|
|
""" |
8
|
|
|
|
9
|
|
|
from flask import abort, g, redirect, request |
10
|
|
|
|
11
|
|
|
from ...services.board import \ |
12
|
|
|
category_query_service as board_category_query_service, \ |
13
|
|
|
last_view_service as board_last_view_service, \ |
14
|
|
|
posting_query_service as board_posting_query_service, \ |
15
|
|
|
topic_command_service as board_topic_command_service, \ |
16
|
|
|
topic_query_service as board_topic_query_service |
17
|
|
|
from ...services.party import service as party_service |
18
|
|
|
from ...services.text_markup.service import get_smileys |
19
|
|
|
from ...services.user import service as user_service |
20
|
|
|
from ...util.framework.flash import flash_error, flash_success |
21
|
|
|
from ...util.framework.templating import templated |
22
|
|
|
from ...util.views import respond_no_content_with_location |
23
|
|
|
|
24
|
|
|
from ..authorization.decorators import permission_required |
25
|
|
|
|
26
|
|
|
from .authorization import BoardPermission, BoardTopicPermission |
27
|
|
|
from .blueprint import blueprint |
28
|
|
|
from .forms import PostingCreateForm, TopicCreateForm, TopicUpdateForm |
29
|
|
|
from . import _helpers as h, service, signals |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
@blueprint.route('/topics', defaults={'page': 1}) |
33
|
|
|
@blueprint.route('/topics/pages/<int:page>') |
34
|
|
|
@templated |
35
|
|
|
def topic_index(page): |
36
|
|
|
"""List latest topics in all categories.""" |
37
|
|
|
board_id = h.get_board_id() |
38
|
|
|
user = g.current_user |
39
|
|
|
|
40
|
|
|
h.require_board_access(board_id, user.id) |
41
|
|
|
|
42
|
|
|
topics_per_page = service.get_topics_per_page_value() |
43
|
|
|
|
44
|
|
|
topics = board_topic_query_service \ |
45
|
|
|
.paginate_topics(board_id, user, page, topics_per_page) |
46
|
|
|
|
47
|
|
|
service.add_topic_creators(topics.items) |
48
|
|
|
service.add_topic_unseen_flag(topics.items, user) |
49
|
|
|
|
50
|
|
|
return { |
51
|
|
|
'topics': topics, |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
|
55
|
|
|
@blueprint.route('/topics/<uuid:topic_id>', defaults={'page': 0}) |
56
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/pages/<int:page>') |
57
|
|
|
@templated |
58
|
|
|
def topic_view(topic_id, page): |
59
|
|
|
"""List postings for the topic.""" |
60
|
|
|
user = g.current_user |
61
|
|
|
|
62
|
|
|
topic = board_topic_query_service \ |
63
|
|
|
.find_topic_visible_for_user(topic_id, user) |
64
|
|
|
|
65
|
|
|
if topic is None: |
66
|
|
|
abort(404) |
67
|
|
|
|
68
|
|
|
board_id = h.get_board_id() |
69
|
|
|
|
70
|
|
|
if topic.category.hidden: |
71
|
|
|
abort(404) |
72
|
|
|
|
73
|
|
|
if topic.category.board_id != board_id: |
74
|
|
|
abort(404) |
75
|
|
|
|
76
|
|
|
h.require_board_access(board_id, user.id) |
77
|
|
|
|
78
|
|
|
# Copy last view timestamp for later use to compare postings |
79
|
|
|
# against it. |
80
|
|
|
last_viewed_at = board_last_view_service.find_topic_last_viewed_at( |
81
|
|
|
topic.id, user.id) |
82
|
|
|
|
83
|
|
|
postings_per_page = service.get_postings_per_page_value() |
84
|
|
|
if page == 0: |
85
|
|
|
posting = board_topic_query_service \ |
86
|
|
|
.find_default_posting_to_jump_to(topic.id, user, last_viewed_at) |
87
|
|
|
|
88
|
|
|
if posting is None: |
89
|
|
|
page = 1 |
90
|
|
|
else: |
91
|
|
|
page = service.calculate_posting_page_number(posting, |
92
|
|
|
g.current_user) |
93
|
|
|
# Jump to a specific posting. This requires a redirect. |
94
|
|
|
url = h.build_url_for_posting_in_topic_view(posting, page) |
95
|
|
|
return redirect(url, code=307) |
96
|
|
|
|
97
|
|
|
if not user.is_anonymous: |
98
|
|
|
# Mark as viewed before aborting so a user can itself remove the |
99
|
|
|
# 'new' tag from a locked topic. |
100
|
|
|
board_last_view_service.mark_topic_as_just_viewed(topic.id, user.id) |
101
|
|
|
|
102
|
|
|
postings = board_posting_query_service \ |
103
|
|
|
.paginate_postings(topic.id, user, g.party_id, page, postings_per_page) |
104
|
|
|
|
105
|
|
|
service.add_unseen_flag_to_postings(postings.items, user, last_viewed_at) |
106
|
|
|
|
107
|
|
|
is_last_page = not postings.has_next |
108
|
|
|
|
109
|
|
|
service.enrich_creators(postings.items, g.brand_id, g.party_id) |
110
|
|
|
|
111
|
|
|
party = party_service.find_party(g.party_id) |
112
|
|
|
|
113
|
|
|
context = { |
114
|
|
|
'topic': topic, |
115
|
|
|
'postings': postings, |
116
|
|
|
'is_last_page': is_last_page, |
117
|
|
|
'party_title': party.title, |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
if is_last_page: |
121
|
|
|
context.update({ |
122
|
|
|
'form': PostingCreateForm(), |
123
|
|
|
'smileys': get_smileys(), |
124
|
|
|
}) |
125
|
|
|
|
126
|
|
|
return context |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
@blueprint.route('/categories/<category_id>/create') |
130
|
|
|
@permission_required(BoardTopicPermission.create) |
131
|
|
|
@templated |
132
|
|
|
def topic_create_form(category_id, erroneous_form=None): |
133
|
|
|
"""Show a form to create a topic in the category.""" |
134
|
|
|
category = h.get_category_or_404(category_id) |
135
|
|
|
|
136
|
|
|
form = erroneous_form if erroneous_form else TopicCreateForm() |
137
|
|
|
|
138
|
|
|
return { |
139
|
|
|
'category': category, |
140
|
|
|
'form': form, |
141
|
|
|
'smileys': get_smileys(), |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
@blueprint.route('/categories/<category_id>/create', methods=['POST']) |
146
|
|
|
@permission_required(BoardTopicPermission.create) |
147
|
|
|
def topic_create(category_id): |
148
|
|
|
"""Create a topic in the category.""" |
149
|
|
|
category = h.get_category_or_404(category_id) |
150
|
|
|
|
151
|
|
|
form = TopicCreateForm(request.form) |
152
|
|
|
if not form.validate(): |
153
|
|
|
return topic_create_form(category.id, form) |
154
|
|
|
|
155
|
|
|
creator = g.current_user |
156
|
|
|
title = form.title.data.strip() |
157
|
|
|
body = form.body.data.strip() |
158
|
|
|
|
159
|
|
|
topic = board_topic_command_service \ |
160
|
|
|
.create_topic(category.id, creator.id, title, body) |
161
|
|
|
topic_url = h.build_external_url_for_topic(topic.id) |
162
|
|
|
|
163
|
|
|
flash_success('Das Thema "{}" wurde hinzugefügt.', topic.title) |
164
|
|
|
signals.topic_created.send(None, topic_id=topic.id, url=topic_url) |
165
|
|
|
|
166
|
|
|
return redirect(topic_url) |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/update') |
170
|
|
|
@permission_required(BoardTopicPermission.update) |
171
|
|
|
@templated |
172
|
|
|
def topic_update_form(topic_id, erroneous_form=None): |
173
|
|
|
"""Show form to update a topic.""" |
174
|
|
|
topic = h.get_topic_or_404(topic_id) |
175
|
|
|
url = h.build_url_for_topic(topic.id) |
176
|
|
|
|
177
|
|
|
user_may_update = topic.may_be_updated_by_user(g.current_user) |
178
|
|
|
|
179
|
|
|
if topic.locked and not user_may_update: |
180
|
|
|
flash_error( |
181
|
|
|
'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.') |
182
|
|
|
return redirect(url) |
183
|
|
|
|
184
|
|
|
if topic.hidden: |
185
|
|
|
flash_error('Das Thema darf nicht bearbeitet werden.') |
186
|
|
|
return redirect(url) |
187
|
|
|
|
188
|
|
|
if not user_may_update: |
189
|
|
|
flash_error('Du darfst dieses Thema nicht bearbeiten.') |
190
|
|
|
return redirect(url) |
191
|
|
|
|
192
|
|
|
form = erroneous_form if erroneous_form \ |
193
|
|
|
else TopicUpdateForm(obj=topic, body=topic.initial_posting.body) |
194
|
|
|
|
195
|
|
|
return { |
196
|
|
|
'form': form, |
197
|
|
|
'topic': topic, |
198
|
|
|
'smileys': get_smileys(), |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
|
202
|
|
|
@blueprint.route('/topics/<uuid:topic_id>', methods=['POST']) |
203
|
|
|
@permission_required(BoardTopicPermission.update) |
204
|
|
|
def topic_update(topic_id): |
205
|
|
|
"""Update a topic.""" |
206
|
|
|
topic = h.get_topic_or_404(topic_id) |
207
|
|
|
url = h.build_url_for_topic(topic.id) |
208
|
|
|
|
209
|
|
|
user_may_update = topic.may_be_updated_by_user(g.current_user) |
210
|
|
|
|
211
|
|
|
if topic.locked and not user_may_update: |
212
|
|
|
flash_error( |
213
|
|
|
'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.') |
214
|
|
|
return redirect(url) |
215
|
|
|
|
216
|
|
|
if topic.hidden: |
217
|
|
|
flash_error('Das Thema darf nicht bearbeitet werden.') |
218
|
|
|
return redirect(url) |
219
|
|
|
|
220
|
|
|
if not user_may_update: |
221
|
|
|
flash_error('Du darfst dieses Thema nicht bearbeiten.') |
222
|
|
|
return redirect(url) |
223
|
|
|
|
224
|
|
|
form = TopicUpdateForm(request.form) |
225
|
|
|
if not form.validate(): |
226
|
|
|
return topic_update_form(topic_id, form) |
227
|
|
|
|
228
|
|
|
board_topic_command_service \ |
229
|
|
|
.update_topic(topic, g.current_user.id, form.title.data, form.body.data) |
230
|
|
|
|
231
|
|
|
flash_success('Das Thema "{}" wurde aktualisiert.', topic.title) |
232
|
|
|
return redirect(url) |
233
|
|
|
|
234
|
|
|
|
235
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/moderate') |
236
|
|
|
@permission_required(BoardPermission.hide) |
237
|
|
|
@templated |
238
|
|
|
def topic_moderate_form(topic_id): |
239
|
|
|
"""Show a form to moderate the topic.""" |
240
|
|
|
board_id = h.get_board_id() |
241
|
|
|
topic = h.get_topic_or_404(topic_id) |
242
|
|
|
|
243
|
|
|
topic.creator = user_service.find_user(topic.creator_id) |
244
|
|
|
|
245
|
|
|
categories = board_category_query_service \ |
246
|
|
|
.get_categories_excluding(board_id, topic.category_id) |
247
|
|
|
|
248
|
|
|
return { |
249
|
|
|
'topic': topic, |
250
|
|
|
'categories': categories, |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
|
254
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/hidden', methods=['POST']) |
255
|
|
|
@permission_required(BoardPermission.hide) |
256
|
|
|
@respond_no_content_with_location |
257
|
|
|
def topic_hide(topic_id): |
258
|
|
|
"""Hide a topic.""" |
259
|
|
|
topic = h.get_topic_or_404(topic_id) |
260
|
|
|
moderator_id = g.current_user.id |
261
|
|
|
|
262
|
|
|
board_topic_command_service.hide_topic(topic, moderator_id) |
263
|
|
|
|
264
|
|
|
flash_success('Das Thema "{}" wurde versteckt.', topic.title, icon='hidden') |
265
|
|
|
|
266
|
|
|
signals.topic_hidden.send(None, topic_id=topic.id, |
267
|
|
|
moderator_id=moderator_id, |
268
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
269
|
|
|
|
270
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
271
|
|
|
|
272
|
|
|
|
273
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/hidden', methods=['DELETE']) |
274
|
|
|
@permission_required(BoardPermission.hide) |
275
|
|
|
@respond_no_content_with_location |
276
|
|
|
def topic_unhide(topic_id): |
277
|
|
|
"""Un-hide a topic.""" |
278
|
|
|
topic = h.get_topic_or_404(topic_id) |
279
|
|
|
moderator_id = g.current_user.id |
280
|
|
|
|
281
|
|
|
board_topic_command_service.unhide_topic(topic, moderator_id) |
282
|
|
|
|
283
|
|
|
flash_success( |
284
|
|
|
'Das Thema "{}" wurde wieder sichtbar gemacht.', topic.title, icon='view') |
285
|
|
|
|
286
|
|
|
signals.topic_unhidden.send(None, topic_id=topic.id, |
287
|
|
|
moderator_id=moderator_id, |
288
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
289
|
|
|
|
290
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
291
|
|
|
|
292
|
|
|
|
293
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/locked', methods=['POST']) |
294
|
|
|
@permission_required(BoardTopicPermission.lock) |
295
|
|
|
@respond_no_content_with_location |
296
|
|
|
def topic_lock(topic_id): |
297
|
|
|
"""Lock a topic.""" |
298
|
|
|
topic = h.get_topic_or_404(topic_id) |
299
|
|
|
moderator_id = g.current_user.id |
300
|
|
|
|
301
|
|
|
board_topic_command_service.lock_topic(topic, moderator_id) |
302
|
|
|
|
303
|
|
|
flash_success('Das Thema "{}" wurde geschlossen.', topic.title, icon='lock') |
304
|
|
|
|
305
|
|
|
signals.topic_locked.send(None, topic_id=topic.id, |
306
|
|
|
moderator_id=moderator_id, |
307
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
308
|
|
|
|
309
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
310
|
|
|
|
311
|
|
|
|
312
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/locked', methods=['DELETE']) |
313
|
|
|
@permission_required(BoardTopicPermission.lock) |
314
|
|
|
@respond_no_content_with_location |
315
|
|
|
def topic_unlock(topic_id): |
316
|
|
|
"""Unlock a topic.""" |
317
|
|
|
topic = h.get_topic_or_404(topic_id) |
318
|
|
|
moderator_id = g.current_user.id |
319
|
|
|
|
320
|
|
|
board_topic_command_service.unlock_topic(topic, moderator_id) |
321
|
|
|
|
322
|
|
|
flash_success('Das Thema "{}" wurde wieder geöffnet.', topic.title, |
323
|
|
|
icon='unlock') |
324
|
|
|
|
325
|
|
|
signals.topic_unlocked.send(None, topic_id=topic.id, |
326
|
|
|
moderator_id=moderator_id, |
327
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
328
|
|
|
|
329
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
330
|
|
|
|
331
|
|
|
|
332
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/pinned', methods=['POST']) |
333
|
|
|
@permission_required(BoardTopicPermission.pin) |
334
|
|
|
@respond_no_content_with_location |
335
|
|
|
def topic_pin(topic_id): |
336
|
|
|
"""Pin a topic.""" |
337
|
|
|
topic = h.get_topic_or_404(topic_id) |
338
|
|
|
moderator_id = g.current_user.id |
339
|
|
|
|
340
|
|
|
board_topic_command_service.pin_topic(topic, moderator_id) |
341
|
|
|
|
342
|
|
|
flash_success('Das Thema "{}" wurde angepinnt.', topic.title, icon='pin') |
343
|
|
|
|
344
|
|
|
signals.topic_pinned.send(None, topic_id=topic.id, |
345
|
|
|
moderator_id=moderator_id, |
346
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
347
|
|
|
|
348
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
349
|
|
|
|
350
|
|
|
|
351
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/pinned', methods=['DELETE']) |
352
|
|
|
@permission_required(BoardTopicPermission.pin) |
353
|
|
|
@respond_no_content_with_location |
354
|
|
|
def topic_unpin(topic_id): |
355
|
|
|
"""Unpin a topic.""" |
356
|
|
|
topic = h.get_topic_or_404(topic_id) |
357
|
|
|
moderator_id = g.current_user.id |
358
|
|
|
|
359
|
|
|
board_topic_command_service.unpin_topic(topic, moderator_id) |
360
|
|
|
|
361
|
|
|
flash_success('Das Thema "{}" wurde wieder gelöst.', topic.title) |
362
|
|
|
|
363
|
|
|
signals.topic_unpinned.send(None, topic_id=topic.id, |
364
|
|
|
moderator_id=moderator_id, |
365
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
366
|
|
|
|
367
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
368
|
|
|
|
369
|
|
|
|
370
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/move', methods=['POST']) |
371
|
|
|
@permission_required(BoardTopicPermission.move) |
372
|
|
|
def topic_move(topic_id): |
373
|
|
|
"""Move a topic from one category to another.""" |
374
|
|
|
topic = h.get_topic_or_404(topic_id) |
375
|
|
|
moderator_id = g.current_user.id |
376
|
|
|
|
377
|
|
|
new_category_id = request.form.get('category_id') |
378
|
|
|
if not new_category_id: |
379
|
|
|
abort(400, 'No target category ID given.') |
380
|
|
|
|
381
|
|
|
new_category = h.get_category_or_404(new_category_id) |
382
|
|
|
|
383
|
|
|
old_category = topic.category |
384
|
|
|
|
385
|
|
|
board_topic_command_service.move_topic(topic, new_category.id) |
386
|
|
|
|
387
|
|
|
flash_success('Das Thema "{}" wurde aus der Kategorie "{}" ' |
388
|
|
|
'in die Kategorie "{}" verschoben.', |
389
|
|
|
topic.title, old_category.title, new_category.title, |
390
|
|
|
icon='move') |
391
|
|
|
|
392
|
|
|
signals.topic_moved.send(None, topic_id=topic.id, |
393
|
|
|
old_category_id=old_category.id, |
394
|
|
|
new_category_id=new_category.id, |
395
|
|
|
moderator_id=moderator_id, |
396
|
|
|
url=h.build_external_url_for_topic(topic.id)) |
397
|
|
|
|
398
|
|
|
return redirect(h.build_url_for_topic_in_category_view(topic)) |
399
|
|
|
|
400
|
|
|
|
401
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/announcements', methods=['POST']) |
402
|
|
|
@permission_required(BoardPermission.announce) |
403
|
|
|
@respond_no_content_with_location |
404
|
|
|
def topic_limit_to_announcements(topic_id): |
405
|
|
|
"""Limit posting in the topic to moderators.""" |
406
|
|
|
topic = h.get_topic_or_404(topic_id) |
407
|
|
|
|
408
|
|
|
board_topic_command_service.limit_topic_to_announcements(topic) |
409
|
|
|
|
410
|
|
|
flash_success('Das Thema "{}" wurde auf Ankündigungen beschränkt.', |
411
|
|
|
topic.title, icon='announce') |
412
|
|
|
|
413
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
414
|
|
|
|
415
|
|
|
|
416
|
|
|
@blueprint.route('/topics/<uuid:topic_id>/flags/announcements', methods=['DELETE']) |
417
|
|
|
@permission_required(BoardPermission.announce) |
418
|
|
|
@respond_no_content_with_location |
419
|
|
|
def topic_remove_limit_to_announcements(topic_id): |
420
|
|
|
"""Allow non-moderators to post in the topic again.""" |
421
|
|
|
topic = h.get_topic_or_404(topic_id) |
422
|
|
|
|
423
|
|
|
board_topic_command_service.remove_limit_of_topic_to_announcements(topic) |
424
|
|
|
|
425
|
|
|
flash_success('Das Thema "{}" wurde für normale Beiträge geöffnet.', |
426
|
|
|
topic.title) |
427
|
|
|
|
428
|
|
|
return h.build_url_for_topic_in_category_view(topic) |
429
|
|
|
|