Passed
Push — main ( 6dfe4e...5549e4 )
by Jochen
04:45
created

appoint_user()   B

Complexity

Conditions 5

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 26.0575

Importance

Changes 0
Metric Value
cc 5
eloc 25
nop 1
dl 0
loc 41
ccs 1
cts 18
cp 0.0556
crap 26.0575
rs 8.8133
c 0
b 0
f 0
1
"""
2
byceps.blueprints.site.ticketing.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from flask import abort, g, request
10 1
from flask_babel import gettext
11
12 1
from ....services.orga_team import service as orga_team_service
13 1
from ....services.party import service as party_service
14 1
from ....services.ticketing import (
15
    barcode_service,
16
    category_service as ticket_category_service,
17
    ticket_service,
18
    ticket_seat_management_service,
19
    ticket_user_management_service,
20
)
21 1
from ....util.framework.blueprint import create_blueprint
22 1
from ....util.framework.flash import flash_error, flash_success
23 1
from ....util.iterables import find
24 1
from ....util.framework.templating import templated
25 1
from ....util.views import login_required, redirect_to, respond_no_content
26
27 1
from .forms import SpecifyUserForm
28 1
from . import notification_service
29
30
31 1
blueprint = create_blueprint('ticketing', __name__)
32
33
34 1
@blueprint.get('/mine')
35 1
@login_required
36 1
@templated
37
def index_mine():
38
    """List tickets related to the current user."""
39 1
    if g.party_id is None:
40
        # No party is configured for the current site.
41
        abort(404)
42
43 1
    party = party_service.get_party(g.party_id)
44
45 1
    user = g.user
46
47 1
    tickets = ticket_service.find_tickets_related_to_user_for_party(
48
        user.id, party.id
49
    )
50
51 1
    tickets = [ticket for ticket in tickets if not ticket.revoked]
52
53 1
    ticket_user_ids = {ticket.used_by_id for ticket in tickets}
54 1
    orga_ids = orga_team_service.select_orgas_for_party(
55
        ticket_user_ids, g.party_id
56
    )
57
58 1
    current_user_uses_any_ticket = find(
59
        tickets, lambda t: t.used_by_id == user.id
60
    )
61
62 1
    return {
63
        'party_title': party.title,
64
        'tickets': tickets,
65
        'orga_ids': orga_ids,
66
        'current_user_uses_any_ticket': current_user_uses_any_ticket,
67
        'is_user_allowed_to_print_ticket': _is_user_allowed_to_print_ticket,
68
        'ticket_management_enabled': _is_ticket_management_enabled(),
69
    }
70
71
72 1
@blueprint.get('/tickets/<uuid:ticket_id>/printable.html')
73 1
@login_required
74 1
@templated
75
def view_printable_html(ticket_id):
76
    """Show a form to select a user to appoint for the ticket."""
77
    ticket = _get_ticket_or_404(ticket_id)
78
79
    if not _is_user_allowed_to_print_ticket(ticket, g.user.id):
80
        # Hide ticket ID validity rather than openly denying access.
81
        abort(404)
82
83
    ticket_category = ticket_category_service.find_category(ticket.category_id)
84
    party = party_service.get_party(ticket_category.party_id)
85
86
    barcode_svg = barcode_service.render_svg(ticket.code)
87
88
    # Encode SVG to be used inline as part of a data URI.
89
    # Replacements are not complete, but sufficient for this case.
90
    #
91
    # See https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
92
    # for details.
93
    barcode_svg_inline = barcode_svg \
94
            .replace('<', '%3C') \
95
            .replace('>', '%3E') \
96
            .replace('"', '\'') \
97
            .replace('\n', '%0A')
98
99
    return {
100
        'party_title': party.title,
101
        'ticket_code': ticket.code,
102
        'ticket_category_title': ticket_category.title,
103
        'ticket_owner': ticket.owned_by,
104
        'ticket_user': ticket.used_by,
105
        'occupied_seat': ticket.occupied_seat,
106
        'barcode_svg_inline': barcode_svg_inline,
107
    }
108
109
110
# -------------------------------------------------------------------- #
111
# user
112
113
114 1
@blueprint.get('/tickets/<uuid:ticket_id>/appoint_user')
115 1
@login_required
116 1
@templated
117 1
def appoint_user_form(ticket_id, erroneous_form=None):
118
    """Show a form to select a user to appoint for the ticket."""
119
    _abort_if_ticket_management_disabled()
120
121
    ticket = _get_ticket_or_404(ticket_id)
122
123
    _abort_if_ticket_user_checked_in(ticket)
124
125
    manager = g.user
126
127
    if not ticket.is_user_managed_by(manager.id):
128
        abort(403)
129
130
    form = erroneous_form if erroneous_form else SpecifyUserForm()
131
132
    return {
133
        'ticket': ticket,
134
        'form': form,
135
    }
136
137
138 1
@blueprint.post('/tickets/<uuid:ticket_id>/user')
139
def appoint_user(ticket_id):
140
    """Appoint a user for the ticket."""
141
    _abort_if_ticket_management_disabled()
142
143
    ticket = _get_ticket_or_404(ticket_id)
144
145
    _abort_if_ticket_user_checked_in(ticket)
146
147
    form = SpecifyUserForm(request.form)
148
    if not form.validate():
149
        return appoint_user_form(ticket_id, form)
150
151
    manager = g.user
152
153
    if not ticket.is_user_managed_by(manager.id):
154
        abort(403)
155
156
    previous_user = ticket.used_by if ticket.used_by_id != g.user.id else None
157
    new_user = form.user.data
158
159
    ticket_user_management_service.appoint_user(
160
        ticket.id, new_user.id, manager.id
161
    )
162
163
    flash_success(
164
        gettext(
165
            '%(screen_name)s has been assigned as user of ticket %(ticket_code)s.',
166
            screen_name=new_user.screen_name,
167
            ticket_code=ticket.code,
168
        )
169
    )
170
171
    if previous_user:
172
        notification_service.notify_withdrawn_user(
173
            ticket, previous_user, manager
174
        )
175
176
    notification_service.notify_appointed_user(ticket, new_user, manager)
177
178
    return redirect_to('.index_mine')
179
180
181 1
@blueprint.delete('/tickets/<uuid:ticket_id>/user')
182 1
@respond_no_content
183
def withdraw_user(ticket_id):
184
    """Withdraw the ticket's user and appoint its owner instead."""
185
    _abort_if_ticket_management_disabled()
186
187
    ticket = _get_ticket_or_404(ticket_id)
188
189
    _abort_if_ticket_user_checked_in(ticket)
190
191
    manager = g.user
192
193
    if not ticket.is_user_managed_by(manager.id):
194
        abort(403)
195
196
    previous_user = ticket.used_by if ticket.used_by_id != g.user.id else None
197
198
    ticket_user_management_service.appoint_user(
199
        ticket.id, manager.id, manager.id
200
    )
201
202
    flash_success(
203
        gettext(
204
            'You have been assigned as user of ticket %(ticket_code)s.',
205
            ticket_code=ticket.code,
206
        )
207
    )
208
209
    if previous_user:
210
        notification_service.notify_withdrawn_user(
211
            ticket, previous_user, manager
212
        )
213
214
215
# -------------------------------------------------------------------- #
216
# user manager
217
218
219 1
@blueprint.get('/tickets/<uuid:ticket_id>/appoint_user_manager')
220 1
@login_required
221 1
@templated
222 1
def appoint_user_manager_form(ticket_id, erroneous_form=None):
223
    """Show a form to select a user to appoint as user manager for the ticket."""
224
    _abort_if_ticket_management_disabled()
225
226
    ticket = _get_ticket_or_404(ticket_id)
227
228
    _abort_if_ticket_user_checked_in(ticket)
229
230
    manager = g.user
231
232
    if not ticket.is_owned_by(manager.id):
233
        abort(403)
234
235
    form = erroneous_form if erroneous_form else SpecifyUserForm()
236
237
    return {
238
        'ticket': ticket,
239
        'form': form,
240
    }
241
242
243 1 View Code Duplication
@blueprint.post('/tickets/<uuid:ticket_id>/user_manager')
244
def appoint_user_manager(ticket_id):
245
    """Appoint a user manager for the ticket."""
246
    _abort_if_ticket_management_disabled()
247
248
    ticket = _get_ticket_or_404(ticket_id)
249
250
    _abort_if_ticket_user_checked_in(ticket)
251
252
    form = SpecifyUserForm(request.form)
253
    if not form.validate():
254
        return appoint_user_manager_form(ticket_id, form)
255
256
    manager = g.user
257
258
    if not ticket.is_owned_by(manager.id):
259
        abort(403)
260
261
    user = form.user.data
262
263
    ticket_user_management_service.appoint_user_manager(
264
        ticket.id, user.id, manager.id
265
    )
266
267
    flash_success(
268
        gettext(
269
            '%(screen_name)s has been assigned as user manager '
270
            'of ticket %(ticket_code)s.',
271
            screen_name=user.screen_name,
272
            ticket_code=ticket.code,
273
        )
274
    )
275
276
    notification_service.notify_appointed_user_manager(ticket, user, manager)
277
278
    return redirect_to('.index_mine')
279
280
281 1
@blueprint.delete('/tickets/<uuid:ticket_id>/user_manager')
282 1
@respond_no_content
283
def withdraw_user_manager(ticket_id):
284
    """Withdraw the ticket's user manager."""
285
    _abort_if_ticket_management_disabled()
286
287
    ticket = _get_ticket_or_404(ticket_id)
288
289
    _abort_if_ticket_user_checked_in(ticket)
290
291
    manager = g.user
292
293
    if not ticket.is_owned_by(manager.id):
294
        abort(403)
295
296
    user = ticket.user_managed_by
297
298
    ticket_user_management_service.withdraw_user_manager(ticket.id, manager.id)
299
300
    flash_success(
301
        gettext(
302
            'User manager of ticket %(ticket_code)s has been removed.',
303
            ticket_code=ticket.code,
304
        )
305
    )
306
307
    notification_service.notify_withdrawn_user_manager(ticket, user, manager)
308
309
310
# -------------------------------------------------------------------- #
311
# seat manager
312
313
314 1
@blueprint.get('/tickets/<uuid:ticket_id>/appoint_seat_manager')
315 1
@login_required
316 1
@templated
317 1
def appoint_seat_manager_form(ticket_id, erroneous_form=None):
318
    """Show a form to select a user to appoint as seat manager for the ticket."""
319
    _abort_if_ticket_management_disabled()
320
321
    ticket = _get_ticket_or_404(ticket_id)
322
323
    manager = g.user
324
325
    if not ticket.is_owned_by(manager.id):
326
        abort(403)
327
328
    form = erroneous_form if erroneous_form else SpecifyUserForm()
329
330
    return {
331
        'ticket': ticket,
332
        'form': form,
333
    }
334
335
336 1 View Code Duplication
@blueprint.post('/tickets/<uuid:ticket_id>/seat_manager')
1 ignored issue
show
Duplication introduced by Jochen Kupperschmidt
This code seems to be duplicated in your project.
Loading history...
337
def appoint_seat_manager(ticket_id):
338
    """Appoint a seat manager for the ticket."""
339
    _abort_if_ticket_management_disabled()
340
341
    form = SpecifyUserForm(request.form)
342
    if not form.validate():
343
        return appoint_seat_manager_form(ticket_id, form)
344
345
    ticket = _get_ticket_or_404(ticket_id)
346
347
    manager = g.user
348
349
    if not ticket.is_owned_by(manager.id):
350
        abort(403)
351
352
    user = form.user.data
353
354
    ticket_seat_management_service.appoint_seat_manager(
355
        ticket.id, user.id, manager.id
356
    )
357
358
    flash_success(
359
        gettext(
360
            '%(screen_name)s has been assigned as seat manager '
361
            'of ticket %(ticket_code)s.',
362
            screen_name=user.screen_name,
363
            ticket_code=ticket.code,
364
        )
365
    )
366
367
    notification_service.notify_appointed_seat_manager(ticket, user, manager)
368
369
    return redirect_to('.index_mine')
370
371
372 1
@blueprint.delete('/tickets/<uuid:ticket_id>/seat_manager')
373 1
@respond_no_content
374
def withdraw_seat_manager(ticket_id):
375
    """Withdraw the ticket's seat manager."""
376
    _abort_if_ticket_management_disabled()
377
378
    ticket = _get_ticket_or_404(ticket_id)
379
380
    manager = g.user
381
382
    if not ticket.is_owned_by(manager.id):
383
        abort(403)
384
385
    user = ticket.seat_managed_by
386
387
    ticket_seat_management_service.withdraw_seat_manager(ticket.id, manager.id)
388
389
    flash_success(
390
        gettext(
391
            'Seat manager of ticket %(ticket_code)s has been removed.',
392
            ticket_code=ticket.code,
393
        )
394
    )
395
396
    notification_service.notify_withdrawn_seat_manager(ticket, user, manager)
397
398
399
# -------------------------------------------------------------------- #
400
401
402 1
def _abort_if_ticket_management_disabled():
403
    if not _is_ticket_management_enabled():
404
        flash_error(gettext('Tickets cannot be updated at this time.'))
405
        abort(403)
406
407
408 1
def _is_ticket_management_enabled():
409 1
    if not g.user.authenticated:
410
        return False
411
412 1
    if g.party_id is None:
413
        return False
414
415 1
    party = party_service.get_party(g.party_id)
416 1
    return party.ticket_management_enabled
417
418
419 1
def _get_ticket_or_404(ticket_id):
420
    ticket = ticket_service.find_ticket(ticket_id)
421
422
    if (ticket is None) or ticket.revoked:
423
        abort(404)
424
425
    return ticket
426
427
428 1
def _abort_if_ticket_user_checked_in(ticket):
429
    if ticket.user_checked_in:
430
        flash_error(
431
            gettext('Somebody has already been checked in with this ticket.')
432
        )
433
        abort(403)
434
435
436 1
def _is_user_allowed_to_print_ticket(ticket, user_id):
437
    """Return `True` only if the user is allowed to print the ticket."""
438
    return (
439
        ticket.is_owned_by(user_id)
440
        or ticket.is_managed_by(user_id)
441
        or ticket.used_by_id == user_id
442
    )
443