1
|
|
|
""" |
2
|
|
|
byceps.blueprints.admin.orga_team.views |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2024 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from flask import abort, request |
10
|
1 |
|
from flask_babel import gettext |
11
|
|
|
|
12
|
1 |
|
from byceps.services.orga_team import orga_team_service |
13
|
1 |
|
from byceps.services.party import party_service |
14
|
1 |
|
from byceps.services.user import user_service |
15
|
1 |
|
from byceps.util.framework.blueprint import create_blueprint |
16
|
1 |
|
from byceps.util.framework.flash import flash_error, flash_success |
17
|
1 |
|
from byceps.util.framework.templating import templated |
18
|
1 |
|
from byceps.util.views import ( |
19
|
|
|
permission_required, |
20
|
|
|
redirect_to, |
21
|
|
|
respond_no_content, |
22
|
|
|
) |
23
|
|
|
|
24
|
1 |
|
from .forms import ( |
25
|
|
|
MembershipCreateForm, |
26
|
|
|
MembershipUpdateForm, |
27
|
|
|
OrgaTeamCreateForm, |
28
|
|
|
OrgaTeamsCopyForm, |
29
|
|
|
) |
30
|
|
|
|
31
|
|
|
|
32
|
1 |
|
blueprint = create_blueprint('orga_team_admin', __name__) |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
# -------------------------------------------------------------------- # |
36
|
|
|
# teams |
37
|
|
|
|
38
|
|
|
|
39
|
1 |
|
@blueprint.get('/teams/<party_id>') |
40
|
1 |
|
@permission_required('orga_team.view') |
41
|
1 |
|
@templated |
42
|
1 |
|
def teams_for_party(party_id): |
43
|
|
|
"""List organizer teams for that party.""" |
44
|
1 |
|
party = _get_party_or_404(party_id) |
45
|
|
|
|
46
|
1 |
|
teams_and_members = orga_team_service.get_teams_and_members_for_party( |
47
|
|
|
party.id |
48
|
|
|
) |
49
|
|
|
|
50
|
1 |
|
def sort_members(members): |
51
|
|
|
return sorted( |
52
|
|
|
members, |
53
|
|
|
key=lambda m: user_service.get_sort_key_for_screen_name(m.user), |
54
|
|
|
) |
55
|
|
|
|
56
|
1 |
|
teams_and_members = sorted(teams_and_members, key=lambda tam: tam[0].title) |
57
|
1 |
|
teams_and_members = [ |
58
|
|
|
(teams, sort_members(members)) for teams, members in teams_and_members |
59
|
|
|
] |
60
|
|
|
|
61
|
1 |
|
return { |
62
|
|
|
'party': party, |
63
|
|
|
'teams_and_members': teams_and_members, |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
|
67
|
1 |
|
@blueprint.get('/teams/<party_id>/create') |
68
|
1 |
|
@permission_required('orga_team.create') |
69
|
1 |
|
@templated |
70
|
1 |
|
def team_create_form(party_id, erroneous_form=None): |
71
|
|
|
"""Show form to create an organizer team for a party.""" |
72
|
1 |
|
party = _get_party_or_404(party_id) |
73
|
|
|
|
74
|
1 |
|
form = erroneous_form if erroneous_form else OrgaTeamCreateForm() |
75
|
|
|
|
76
|
1 |
|
return { |
77
|
|
|
'party': party, |
78
|
|
|
'form': form, |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
|
82
|
1 |
|
@blueprint.post('/teams/<party_id>') |
83
|
1 |
|
@permission_required('orga_team.create') |
84
|
1 |
|
def team_create(party_id): |
85
|
|
|
"""Create an organizer team for a party.""" |
86
|
1 |
|
party = _get_party_or_404(party_id) |
87
|
|
|
|
88
|
1 |
|
form = OrgaTeamCreateForm(request.form) |
89
|
1 |
|
if not form.validate(): |
90
|
|
|
return team_create_form(party.id, form) |
91
|
|
|
|
92
|
1 |
|
title = form.title.data.strip() |
93
|
|
|
|
94
|
1 |
|
team = orga_team_service.create_team(party.id, title) |
95
|
|
|
|
96
|
1 |
|
flash_success( |
97
|
|
|
gettext( |
98
|
|
|
'Team "%(team_title)s" for party "%(party_title)s" has been created.', |
99
|
|
|
team_title=team.title, |
100
|
|
|
party_title=party.title, |
101
|
|
|
) |
102
|
|
|
) |
103
|
1 |
|
return redirect_to('.teams_for_party', party_id=party.id) |
104
|
|
|
|
105
|
|
|
|
106
|
1 |
|
@blueprint.delete('/teams/<uuid:team_id>') |
107
|
1 |
|
@permission_required('orga_team.delete') |
108
|
1 |
|
@respond_no_content |
109
|
1 |
|
def team_delete(team_id): |
110
|
|
|
"""Delete the team.""" |
111
|
1 |
|
team = _get_team_or_404(team_id) |
112
|
|
|
|
113
|
1 |
|
if orga_team_service.has_team_memberships(team.id): |
114
|
|
|
flash_error( |
115
|
|
|
gettext( |
116
|
|
|
'Team "%(team_title)s" cannot be deleted because it has members.', |
117
|
|
|
team_title=team.title, |
118
|
|
|
) |
119
|
|
|
) |
120
|
|
|
return |
121
|
|
|
|
122
|
1 |
|
title = team.title |
123
|
|
|
|
124
|
1 |
|
orga_team_service.delete_team(team.id) |
125
|
|
|
|
126
|
1 |
|
flash_success(gettext('Team "%(title)s" has been deleted.', title=title)) |
127
|
|
|
|
128
|
|
|
|
129
|
1 |
|
@blueprint.get('/teams/<target_party_id>/copy') |
130
|
1 |
|
@permission_required('orga_team.create') |
131
|
1 |
|
@templated |
132
|
1 |
|
def teams_copy_form(target_party_id, erroneous_form=None): |
133
|
|
|
"""Show form to copy all organizer teams from another party.""" |
134
|
1 |
|
target_party = _get_party_or_404(target_party_id) |
135
|
|
|
|
136
|
1 |
|
team_count = orga_team_service.count_teams_for_party(target_party.id) |
137
|
1 |
|
if team_count: |
138
|
1 |
|
flash_error( |
139
|
|
|
gettext( |
140
|
|
|
'This party already has teams. No additional teams can be copied to it.' |
141
|
|
|
) |
142
|
|
|
) |
143
|
1 |
|
return redirect_to('.teams_for_party', party_id=target_party.id) |
144
|
|
|
|
145
|
1 |
|
parties = party_service.get_parties_for_brand(target_party.brand_id) |
146
|
|
|
|
147
|
|
|
# Do not offer to copy teams from target party. |
148
|
1 |
|
parties = [p for p in parties if p.id != target_party.id] |
149
|
|
|
|
150
|
1 |
|
party_ids = {party.id for party in parties} |
151
|
1 |
|
team_count_per_party = orga_team_service.count_teams_for_parties(party_ids) |
152
|
|
|
|
153
|
|
|
# Exclude parties without orga teams. |
154
|
1 |
|
parties = [p for p in parties if team_count_per_party.get(p.id, 0)] |
155
|
|
|
|
156
|
1 |
|
if not parties: |
157
|
1 |
|
flash_error( |
158
|
|
|
gettext('No other parties exist from which teams could be copied.') |
159
|
|
|
) |
160
|
1 |
|
return redirect_to('.teams_for_party', party_id=target_party.id) |
161
|
|
|
|
162
|
1 |
|
parties.sort(key=lambda p: p.starts_at, reverse=True) |
163
|
|
|
|
164
|
1 |
|
form = erroneous_form if erroneous_form else OrgaTeamsCopyForm() |
165
|
1 |
|
form.set_party_choices(parties, team_count_per_party) |
166
|
|
|
|
167
|
1 |
|
return { |
168
|
|
|
'party': target_party, |
169
|
|
|
'form': form, |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
1 |
|
@blueprint.post('/teams/<target_party_id>/copy') |
174
|
1 |
|
@permission_required('orga_team.create') |
175
|
1 |
|
def teams_copy(target_party_id): |
176
|
|
|
"""Copy all organizer teams from another party.""" |
177
|
1 |
|
target_party = _get_party_or_404(target_party_id) |
178
|
|
|
|
179
|
1 |
|
target_team_count = orga_team_service.count_teams_for_party(target_party.id) |
180
|
1 |
|
if target_team_count: |
181
|
|
|
flash_error( |
182
|
|
|
gettext( |
183
|
|
|
'This party already has teams. No additional teams can be copied to it.' |
184
|
|
|
) |
185
|
|
|
) |
186
|
|
|
return redirect_to('.teams_for_party', party_id=target_party.id) |
187
|
|
|
|
188
|
1 |
|
parties = party_service.get_parties_for_brand(target_party.brand_id) |
189
|
|
|
|
190
|
1 |
|
form = OrgaTeamsCopyForm(request.form) |
191
|
1 |
|
form.set_party_choices(parties) |
192
|
1 |
|
if not form.validate(): |
193
|
|
|
return teams_copy_form(target_party.id, form) |
194
|
|
|
|
195
|
1 |
|
source_party = party_service.get_party(form.party_id.data) |
196
|
|
|
|
197
|
1 |
|
copied_teams_count = orga_team_service.copy_teams_and_memberships( |
198
|
|
|
source_party.id, target_party.id |
199
|
|
|
) |
200
|
|
|
|
201
|
1 |
|
flash_success( |
202
|
|
|
gettext( |
203
|
|
|
'%(copied_teams_count)s team(s) has/have been copied from party ' |
204
|
|
|
'"%(source_party_title)s" to party "%(target_party_title)s".', |
205
|
|
|
copied_teams_count=copied_teams_count, |
206
|
|
|
source_party_title=source_party.title, |
207
|
|
|
target_party_title=target_party.title, |
208
|
|
|
) |
209
|
|
|
) |
210
|
|
|
|
211
|
1 |
|
return redirect_to('.teams_for_party', party_id=target_party.id) |
212
|
|
|
|
213
|
|
|
|
214
|
|
|
# -------------------------------------------------------------------- # |
215
|
|
|
# memberships |
216
|
|
|
|
217
|
|
|
|
218
|
1 |
|
@blueprint.get('/teams/<uuid:team_id>/memberships/create') |
219
|
1 |
|
@permission_required('orga_team.administrate_memberships') |
220
|
1 |
|
@templated |
221
|
1 |
|
def membership_create_form(team_id, erroneous_form=None): |
222
|
|
|
"""Show form to assign an organizer to that team.""" |
223
|
1 |
|
team = _get_team_or_404(team_id) |
224
|
|
|
|
225
|
1 |
|
party = party_service.get_party(team.party_id) |
226
|
|
|
|
227
|
1 |
|
unassigned_orgas = orga_team_service.get_unassigned_orgas_for_team(team) |
228
|
|
|
|
229
|
1 |
|
if not unassigned_orgas: |
230
|
1 |
|
return { |
231
|
|
|
'team': team, |
232
|
|
|
'party': party, |
233
|
|
|
'unassigned_orgas_available': False, |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
unassigned_orgas = sorted( |
237
|
|
|
unassigned_orgas, key=user_service.get_sort_key_for_screen_name |
238
|
|
|
) |
239
|
|
|
|
240
|
|
|
form = erroneous_form if erroneous_form else MembershipCreateForm() |
241
|
|
|
form.set_user_choices(unassigned_orgas) |
242
|
|
|
|
243
|
|
|
return { |
244
|
|
|
'form': form, |
245
|
|
|
'team': team, |
246
|
|
|
'party': party, |
247
|
|
|
'unassigned_orgas_available': True, |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
|
251
|
1 |
|
@blueprint.post('/teams/<uuid:team_id>/memberships') |
252
|
1 |
|
@permission_required('orga_team.administrate_memberships') |
253
|
1 |
|
def membership_create(team_id): |
254
|
|
|
"""Assign an organizer to that team.""" |
255
|
1 |
|
team = _get_team_or_404(team_id) |
256
|
|
|
|
257
|
1 |
|
unassigned_orgas = orga_team_service.get_unassigned_orgas_for_team(team) |
258
|
|
|
|
259
|
1 |
|
form = MembershipCreateForm(request.form) |
260
|
1 |
|
form.set_user_choices(unassigned_orgas) |
261
|
|
|
|
262
|
1 |
|
if not form.validate(): |
263
|
|
|
return membership_create_form(team.id, form) |
264
|
|
|
|
265
|
1 |
|
user = user_service.get_user(form.user_id.data) |
266
|
1 |
|
duties = form.duties.data.strip() |
267
|
|
|
|
268
|
1 |
|
orga_team_service.create_membership(team.id, user.id, duties) |
269
|
|
|
|
270
|
1 |
|
flash_success( |
271
|
|
|
gettext( |
272
|
|
|
'%(screen_name)s has been added to team "%(team_title)s".', |
273
|
|
|
screen_name=user.screen_name, |
274
|
|
|
team_title=team.title, |
275
|
|
|
) |
276
|
|
|
) |
277
|
1 |
|
return redirect_to('.teams_for_party', party_id=team.party_id) |
278
|
|
|
|
279
|
|
|
|
280
|
1 |
|
@blueprint.get('/memberships/<uuid:membership_id>/update') |
281
|
1 |
|
@permission_required('orga_team.administrate_memberships') |
282
|
1 |
|
@templated |
283
|
1 |
|
def membership_update_form(membership_id, erroneous_form=None): |
284
|
|
|
"""Show form to update a membership.""" |
285
|
1 |
|
membership = _get_membership_or_404(membership_id) |
286
|
|
|
|
287
|
1 |
|
user = user_service.get_user(membership.user_id) |
288
|
1 |
|
team = orga_team_service.find_team(membership.orga_team_id) |
289
|
1 |
|
party = party_service.get_party(team.party_id) |
290
|
|
|
|
291
|
1 |
|
teams = orga_team_service.get_teams_for_party(team.party_id) |
292
|
|
|
|
293
|
1 |
|
form = ( |
294
|
|
|
erroneous_form |
295
|
|
|
if erroneous_form |
296
|
|
|
else MembershipUpdateForm(obj=membership) |
297
|
|
|
) |
298
|
1 |
|
form.set_orga_team_choices(teams) |
299
|
|
|
|
300
|
1 |
|
return { |
301
|
|
|
'form': form, |
302
|
|
|
'membership': membership, |
303
|
|
|
'user': user, |
304
|
|
|
'team': team, |
305
|
|
|
'party': party, |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
|
309
|
1 |
|
@blueprint.post('/memberships/<uuid:membership_id>') |
310
|
1 |
|
@permission_required('orga_team.administrate_memberships') |
311
|
1 |
|
def membership_update(membership_id): |
312
|
|
|
"""Update a membership.""" |
313
|
1 |
|
membership = _get_membership_or_404(membership_id) |
314
|
|
|
|
315
|
1 |
|
user = user_service.get_user(membership.user_id) |
316
|
1 |
|
team = orga_team_service.find_team(membership.orga_team_id) |
317
|
|
|
|
318
|
1 |
|
teams = orga_team_service.get_teams_for_party(team.party_id) |
319
|
|
|
|
320
|
1 |
|
form = MembershipUpdateForm(request.form) |
321
|
1 |
|
form.set_orga_team_choices(teams) |
322
|
|
|
|
323
|
1 |
|
if not form.validate(): |
324
|
|
|
return membership_update_form(membership.id, form) |
325
|
|
|
|
326
|
1 |
|
team_id = form.orga_team_id.data |
327
|
1 |
|
team = orga_team_service.find_team(team_id) |
328
|
1 |
|
duties = form.duties.data.strip() or None |
329
|
|
|
|
330
|
1 |
|
orga_team_service.update_membership(membership.id, team.id, duties) |
331
|
|
|
|
332
|
1 |
|
flash_success( |
333
|
|
|
gettext( |
334
|
|
|
'Membership of %(screen_name)s has been updated.', |
335
|
|
|
screen_name=user.screen_name, |
336
|
|
|
) |
337
|
|
|
) |
338
|
1 |
|
return redirect_to('.teams_for_party', party_id=team.party_id) |
339
|
|
|
|
340
|
|
|
|
341
|
1 |
|
@blueprint.delete('/memberships/<uuid:membership_id>') |
342
|
1 |
|
@permission_required('orga_team.administrate_memberships') |
343
|
1 |
|
@respond_no_content |
344
|
1 |
|
def membership_remove(membership_id): |
345
|
|
|
"""Remove an organizer from a team.""" |
346
|
1 |
|
membership = _get_membership_or_404(membership_id) |
347
|
|
|
|
348
|
1 |
|
user = user_service.get_user(membership.user_id) |
349
|
1 |
|
team = orga_team_service.find_team(membership.orga_team_id) |
350
|
|
|
|
351
|
1 |
|
orga_team_service.delete_membership(membership.id) |
352
|
|
|
|
353
|
1 |
|
flash_success( |
354
|
|
|
gettext( |
355
|
|
|
'%(screen_name)s has been removed from team "%(team_title)s".', |
356
|
|
|
screen_name=user.screen_name, |
357
|
|
|
team_title=team.title, |
358
|
|
|
) |
359
|
|
|
) |
360
|
|
|
|
361
|
|
|
|
362
|
|
|
# -------------------------------------------------------------------- # |
363
|
|
|
# helpers |
364
|
|
|
|
365
|
|
|
|
366
|
1 |
|
def _get_party_or_404(party_id): |
367
|
1 |
|
party = party_service.find_party(party_id) |
368
|
|
|
|
369
|
1 |
|
if party is None: |
370
|
|
|
abort(404) |
371
|
|
|
|
372
|
1 |
|
return party |
373
|
|
|
|
374
|
|
|
|
375
|
1 |
|
def _get_team_or_404(team_id): |
376
|
1 |
|
team = orga_team_service.find_team(team_id) |
377
|
|
|
|
378
|
1 |
|
if team is None: |
379
|
|
|
abort(404) |
380
|
|
|
|
381
|
1 |
|
return team |
382
|
|
|
|
383
|
|
|
|
384
|
1 |
|
def _get_membership_or_404(membership_id): |
385
|
1 |
|
membership = orga_team_service.find_membership(membership_id) |
386
|
|
|
|
387
|
1 |
|
if membership is None: |
388
|
|
|
abort(404) |
389
|
|
|
|
390
|
|
|
return membership |
391
|
|
|
|