Passed
Push — master ( 61b3a3...74655b )
by Jochen
02:58
created

_is_ticket_management_enabled()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
"""
2
byceps.blueprints.ticketing.views
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, url_for
10
11
from ...services.party import service as party_service
12
from ...services.ticketing import (
13
    barcode_service,
14
    category_service as ticket_category_service,
15
    ticket_service,
16
    ticket_seat_management_service,
17
    ticket_user_management_service,
18
)
19
from ...util.framework.blueprint import create_blueprint
20
from ...util.framework.flash import flash_error, flash_success
21
from ...util.iterables import find
22
from ...util.framework.templating import templated
23
from ...util.views import respond_no_content
24
25
from ..authentication.decorators import login_required
26
27
from .forms import SpecifyUserForm
28
from . import notification_service
29
30
31
blueprint = create_blueprint('ticketing', __name__)
32
33
34
@blueprint.route('/mine')
35
@login_required
36
@templated
37
def index_mine():
38
    """List tickets related to the current user."""
39
    if g.party_id is None:
40
        # No party is configured for the current site.
41
        abort(404)
42
43
    party = party_service.get_party(g.party_id)
44
45
    current_user = g.current_user
46
47
    tickets = ticket_service.find_tickets_related_to_user_for_party(
48
        current_user.id, party.id
49
    )
50
51
    tickets = [ticket for ticket in tickets if not ticket.revoked]
52
53
    current_user_uses_any_ticket = find(
54
        lambda t: t.used_by_id == current_user.id, tickets
55
    )
56
57
    return {
58
        'party_title': party.title,
59
        'tickets': tickets,
60
        'current_user_uses_any_ticket': current_user_uses_any_ticket,
61
        'is_user_allowed_to_print_ticket': _is_user_allowed_to_print_ticket,
62
    }
63
64
65
@blueprint.route('/tickets/<uuid:ticket_id>/printable.html')
66
@login_required
67
@templated
68
def view_printable_html(ticket_id):
69
    """Show a form to select a user to appoint for the ticket."""
70
    ticket = _get_ticket_or_404(ticket_id)
71
72
    if not _is_user_allowed_to_print_ticket(ticket, g.current_user.id):
73
        # Hide ticket ID validity rather than openly denying access.
74
        abort(404)
75
76
    ticket_category = ticket_category_service.find_category(ticket.category_id)
77
    party = party_service.get_party(ticket_category.party_id)
78
79
    barcode_svg = barcode_service.render_svg(ticket.code)
80
81
    # Encode SVG to be used inline as part of a data URI.
82
    # Replacements are not complete, but sufficient for this case.
83
    #
84
    # See https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
85
    # for details.
86
    barcode_svg_inline = barcode_svg \
87
            .replace('<', '%3C') \
88
            .replace('>', '%3E') \
89
            .replace('"', '\'') \
90
            .replace('\n', '%0A')
91
92
    return {
93
        'ticket': ticket,
94
        'ticket_category': ticket_category,
95
        'party': party,
96
        'barcode_svg_inline': barcode_svg_inline,
97
    }
98
99
100
# -------------------------------------------------------------------- #
101
# user
102
103
104
@blueprint.route('/tickets/<uuid:ticket_id>/appoint_user')
105
@login_required
106
@templated
107
def appoint_user_form(ticket_id, erroneous_form=None):
108
    """Show a form to select a user to appoint for the ticket."""
109
    _abort_if_ticket_management_disabled()
110
111
    ticket = _get_ticket_or_404(ticket_id)
112
113
    manager = g.current_user.to_dto()
114
115
    if not ticket.is_user_managed_by(manager.id):
116
        abort(403)
117
118
    form = erroneous_form if erroneous_form else SpecifyUserForm()
119
120
    return {
121
        'ticket': ticket,
122
        'form': form,
123
    }
124
125
126
@blueprint.route('/tickets/<uuid:ticket_id>/user', methods=['POST'])
127
def appoint_user(ticket_id):
128
    """Appoint a user for the ticket."""
129
    _abort_if_ticket_management_disabled()
130
131
    form = SpecifyUserForm(request.form)
132
    if not form.validate():
133
        return appoint_user_form(ticket_id, form)
134
135
    ticket = _get_ticket_or_404(ticket_id)
136
137
    manager = g.current_user.to_dto()
138
139
    if not ticket.is_user_managed_by(manager.id):
140
        abort(403)
141
142
    user = form.user.data
143
144
    ticket_user_management_service.appoint_user(ticket.id, user.id, manager.id)
145
146
    flash_success(
147
        f'{user.screen_name} wurde als Nutzer/in '
148
        f'von Ticket {ticket.code} eingetragen.'
149
    )
150
151
    notification_service.notify_appointed_user(ticket, user, manager)
152
153
    return redirect(url_for('.index_mine'))
154
155
156
@blueprint.route('/tickets/<uuid:ticket_id>/user', methods=['DELETE'])
157
@respond_no_content
158
def withdraw_user(ticket_id):
159
    """Withdraw the ticket's user and appoint its owner instead."""
160
    _abort_if_ticket_management_disabled()
161
162
    ticket = _get_ticket_or_404(ticket_id)
163
164
    manager = g.current_user.to_dto()
165
166
    if not ticket.is_user_managed_by(manager.id):
167
        abort(403)
168
169
    ticket_user_management_service.appoint_user(
170
        ticket.id, manager.id, manager.id
171
    )
172
173
    flash_success(
174
        f'Du wurdest als Nutzer/in von Ticket {ticket.code} eingetragen.'
175
    )
176
177
178
# -------------------------------------------------------------------- #
179
# user manager
180
181
182
@blueprint.route('/tickets/<uuid:ticket_id>/appoint_user_manager')
183
@login_required
184
@templated
185
def appoint_user_manager_form(ticket_id, erroneous_form=None):
186
    """Show a form to select a user to appoint as user manager for the ticket."""
187
    _abort_if_ticket_management_disabled()
188
189
    ticket = _get_ticket_or_404(ticket_id)
190
191
    manager = g.current_user.to_dto()
192
193
    if not ticket.is_owned_by(manager.id):
194
        abort(403)
195
196
    form = erroneous_form if erroneous_form else SpecifyUserForm()
197
198
    return {
199
        'ticket': ticket,
200
        'form': form,
201
    }
202
203
204
@blueprint.route('/tickets/<uuid:ticket_id>/user_manager', methods=['POST'])
205
def appoint_user_manager(ticket_id):
206
    """Appoint a user manager for the ticket."""
207
    _abort_if_ticket_management_disabled()
208
209
    form = SpecifyUserForm(request.form)
210
    if not form.validate():
211
        return appoint_user_manager_form(ticket_id, form)
212
213
    ticket = _get_ticket_or_404(ticket_id)
214
215
    manager = g.current_user.to_dto()
216
217
    if not ticket.is_owned_by(manager.id):
218
        abort(403)
219
220
    user = form.user.data
221
222
    ticket_user_management_service.appoint_user_manager(
223
        ticket.id, user.id, manager.id
224
    )
225
226
    flash_success(
227
        f'{user.screen_name} wurde als Nutzer-Verwalter/in '
228
        f'von Ticket {ticket.code} eingetragen.'
229
    )
230
231
    notification_service.notify_appointed_user_manager(ticket, user, manager)
232
233
    return redirect(url_for('.index_mine'))
234
235
236
@blueprint.route('/tickets/<uuid:ticket_id>/user_manager', methods=['DELETE'])
237
@respond_no_content
238
def withdraw_user_manager(ticket_id):
239
    """Withdraw the ticket's user manager."""
240
    _abort_if_ticket_management_disabled()
241
242
    ticket = _get_ticket_or_404(ticket_id)
243
244
    manager = g.current_user.to_dto()
245
246
    if not ticket.is_owned_by(manager.id):
247
        abort(403)
248
249
    user = ticket.user_managed_by
250
251
    ticket_user_management_service.withdraw_user_manager(ticket.id, manager.id)
252
253
    flash_success(
254
        f'Der Nutzer-Verwalter von Ticket {ticket.code} wurde entfernt.'
255
    )
256
257
    notification_service.notify_withdrawn_user_manager(ticket, user, manager)
258
259
260
# -------------------------------------------------------------------- #
261
# seat manager
262
263
264
@blueprint.route('/tickets/<uuid:ticket_id>/appoint_seat_manager')
265
@login_required
266
@templated
267
def appoint_seat_manager_form(ticket_id, erroneous_form=None):
268
    """Show a form to select a user to appoint as seat manager for the ticket."""
269
    _abort_if_ticket_management_disabled()
270
271
    ticket = _get_ticket_or_404(ticket_id)
272
273
    manager = g.current_user.to_dto()
274
275
    if not ticket.is_owned_by(manager.id):
276
        abort(403)
277
278
    form = erroneous_form if erroneous_form else SpecifyUserForm()
279
280
    return {
281
        'ticket': ticket,
282
        'form': form,
283
    }
284
285
286
@blueprint.route('/tickets/<uuid:ticket_id>/seat_manager', methods=['POST'])
287
def appoint_seat_manager(ticket_id):
288
    """Appoint a seat manager for the ticket."""
289
    _abort_if_ticket_management_disabled()
290
291
    form = SpecifyUserForm(request.form)
292
    if not form.validate():
293
        return appoint_seat_manager_form(ticket_id, form)
294
295
    ticket = _get_ticket_or_404(ticket_id)
296
297
    manager = g.current_user.to_dto()
298
299
    if not ticket.is_owned_by(manager.id):
300
        abort(403)
301
302
    user = form.user.data
303
304
    ticket_seat_management_service.appoint_seat_manager(
305
        ticket.id, user.id, manager.id
306
    )
307
308
    flash_success(
309
        f'{user.screen_name} wurde als Sitzplatz-Verwalter/in '
310
        f'von Ticket {ticket.code} eingetragen.'
311
    )
312
313
    notification_service.notify_appointed_seat_manager(ticket, user, manager)
314
315
    return redirect(url_for('.index_mine'))
316
317
318
@blueprint.route('/tickets/<uuid:ticket_id>/seat_manager', methods=['DELETE'])
319
@respond_no_content
320
def withdraw_seat_manager(ticket_id):
321
    """Withdraw the ticket's seat manager."""
322
    _abort_if_ticket_management_disabled()
323
324
    ticket = _get_ticket_or_404(ticket_id)
325
326
    manager = g.current_user.to_dto()
327
328
    if not ticket.is_owned_by(manager.id):
329
        abort(403)
330
331
    user = ticket.seat_managed_by
332
333
    ticket_seat_management_service.withdraw_seat_manager(ticket.id, manager.id)
334
335
    flash_success(
336
        f'Der Sitzplatz-Verwalter von Ticket {ticket.code} wurde entfernt.'
337
    )
338
339
    notification_service.notify_withdrawn_seat_manager(ticket, user, manager)
340
341
342
# -------------------------------------------------------------------- #
343
344
345
def _abort_if_ticket_management_disabled():
346
    if not _is_ticket_management_enabled():
347
        flash_error('Tickets können derzeit nicht verändert werden.')
348
        abort(403)
349
350
351
def _is_ticket_management_enabled():
352
    if g.party_id is None:
353
        return False
354
355
    party = party_service.get_party(g.party_id)
356
    return party.ticket_management_enabled
357
358
359
def _get_ticket_or_404(ticket_id):
360
    ticket = ticket_service.find_ticket(ticket_id)
361
362
    if (ticket is None) or ticket.revoked:
363
        abort(404)
364
365
    return ticket
366
367
368
def _is_user_allowed_to_print_ticket(ticket, user_id):
369
    """Return `True` only if the user is allowed to print the ticket."""
370
    return ticket.is_owned_by(user_id) \
371
        or ticket.is_managed_by(user_id) \
372
        or ticket.used_by_id == user_id
373