Completed
Push — main ( a836a1...39501b )
by Jochen
03:35
created

byceps/blueprints/site/seating/views.py (1 issue)

1
"""
2
byceps.blueprints.site.seating.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2020 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9 1
from flask import abort, g, request
10
11 1
from ....services.party import service as party_service
12 1
from ....services.seating import area_service as seating_area_service
13 1
from ....services.seating.models.seat import Seat
14 1
from ....services.seating import seat_service
15 1
from ....services.seating.transfer.models import SeatID
16 1
from ....services.ticketing.models.ticket import Ticket as DbTicket
17 1
from ....services.ticketing import (
18
    exceptions as ticket_exceptions,
19
    ticket_seat_management_service,
20
    ticket_service,
21
)
22 1
from ....services.ticketing.transfer.models import TicketID
23 1
from ....util.framework.blueprint import create_blueprint
24 1
from ....util.framework.flash import flash_error, flash_success
25 1
from ....util.framework.templating import templated
26 1
from ....util.views import respond_no_content
27
28 1
from ...admin.seating.authorization import SeatingPermission
29 1
from ...common.authentication.decorators import login_required
30 1
from ...common.authorization.registry import permission_registry
31
32 1
from . import service
33
34
35 1
blueprint = create_blueprint('seating', __name__)
36
37
38 1
permission_registry.register_enum(SeatingPermission)
39
40
41 1
@blueprint.route('/')
42 1
@templated
43
def index():
44
    """List areas."""
45
    if g.party_id is None:
46
        # No party is configured for the current site.
47
        abort(404)
48
49
    areas = seating_area_service.get_areas_for_party(g.party_id)
50
51
    return {
52
        'areas': areas,
53
    }
54
55
56 1
@blueprint.route('/areas/<slug>')
57 1
@templated
58
def view_area(slug):
59
    """View area."""
60
    if g.party_id is None:
61
        # No party is configured for the current site.
62
        abort(404)
63
64
    area = seating_area_service.find_area_for_party_by_slug(g.party_id, slug)
65
    if area is None:
66
        abort(404)
67
68
    seat_management_enabled = _is_seat_management_enabled()
69
70
    seats = seat_service.get_seats_with_tickets_for_area(area.id)
71
72
    users_by_id = service.get_users(seats, [])
73
74
    seats = service.get_seats(seats, users_by_id)
75
76
    return {
77
        'area': area,
78
        'seat_management_enabled': seat_management_enabled,
79
        'seats': seats,
80
        'manage_mode': False,
81
    }
82
83
84 1
@blueprint.route('/areas/<slug>/manage_seats')
85 1
@login_required
86 1
@templated('site/seating/view_area')
87
def manage_seats_in_area(slug):
88
    """Manage seats for assigned tickets in area."""
89
    _abort_if_seat_management_disabled()
90
91
    area = seating_area_service.find_area_for_party_by_slug(g.party_id, slug)
92
    if area is None:
93
        abort(404)
94
95
    seat_management_enabled = _is_seat_management_enabled()
96
97
    seat_manager_id = None
98
    selected_ticket_id = None
99
100
    if _is_seating_admin(g.current_user):
101
        selected_ticket = _get_selected_ticket()
102
        if selected_ticket is not None:
103
            seat_manager_id = selected_ticket.get_seat_manager().id
104
            selected_ticket_id = selected_ticket.id
105
        elif seat_management_enabled:
106
            seat_manager_id = g.current_user.id
107
108
    elif seat_management_enabled:
109
        seat_manager_id = g.current_user.id
110
111
    seats = seat_service.get_seats_with_tickets_for_area(area.id)
112
113
    if selected_ticket:
0 ignored issues
show
The variable selected_ticket does not seem to be defined for all execution paths.
Loading history...
114
        tickets = [selected_ticket]
115
    elif seat_manager_id is not None:
116
        tickets = ticket_service.find_tickets_for_seat_manager(
117
            seat_manager_id, g.party_id
118
        )
119
    else:
120
        tickets = []
121
122
    users_by_id = service.get_users(seats, tickets)
123
124
    seats = service.get_seats(seats, users_by_id)
125
126
    if seat_management_enabled:
127
        managed_tickets = list(
128
            service.get_managed_tickets(tickets, users_by_id)
129
        )
130
    else:
131
        managed_tickets = []
132
133
    return {
134
        'area': area,
135
        'seats': seats,
136
        'manage_mode': True,
137
        'seat_management_enabled': seat_management_enabled,
138
        'managed_tickets': managed_tickets,
139
        'selected_ticket_id': selected_ticket_id,
140
    }
141
142
143 1
def _get_selected_ticket():
144
    selected_ticket_code_arg = request.args.get('ticket_code')
145
    selected_ticket_id_arg = request.args.get('ticket_id')
146
147
    selected_ticket = None
148
149
    if selected_ticket_code_arg:
150
        ticket_code = selected_ticket_code_arg.upper()
151
        selected_ticket = ticket_service.find_ticket_by_code(ticket_code)
152
        if selected_ticket is None:
153
            flash_error(f'Ticket code "{ticket_code}" not found.')
154
155
    elif selected_ticket_id_arg:
156
        selected_ticket = ticket_service.find_ticket(selected_ticket_id_arg)
157
        if selected_ticket is None:
158
            flash_error(f'Ticket ID "{selected_ticket_id_arg}" not found.')
159
160
    if (selected_ticket is not None) and selected_ticket .revoked:
161
        flash_error(f'Ticket "{ticket_code}" wurde wiederrufen.')
162
        selected_ticket = None
163
164
    return selected_ticket
165
166
167 1
@blueprint.route(
168
    '/ticket/<uuid:ticket_id>/seat/<uuid:seat_id>', methods=['POST']
169
)
170 1
@login_required
171 1
@respond_no_content
172
def occupy_seat(ticket_id, seat_id):
173
    """Use ticket to occupy seat."""
174
    _abort_if_seat_management_disabled()
175
176
    ticket = _get_ticket_or_404(ticket_id)
177
178
    manager = g.current_user
179
180
    if not ticket.is_seat_managed_by(manager.id) and not _is_seating_admin(
181
        manager
182
    ):
183
        flash_error(
184
            'Du bist nicht berechtigt, den Sitzplatz '
185
            f'für Ticket {ticket.code} zu verwalten.'
186
        )
187
        return
188
189
    seat = _get_seat_or_404(seat_id)
190
191
    if seat.is_occupied:
192
        flash_error(f'{seat.label} ist bereits belegt.')
193
        return
194
195
    try:
196
        ticket_seat_management_service.occupy_seat(
197
            ticket.id, seat.id, manager.id
198
        )
199
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
200
        flash_error(
201
            f'Ticket {ticket.code} gehört zu einem Paket '
202
            'und kann nicht einzeln verwaltet werden.'
203
        )
204
        return
205
    except ticket_exceptions.TicketCategoryMismatch:
206
        flash_error(
207
            f'Ticket {ticket.code} und {seat.label} haben '
208
            'unterschiedliche Kategorien.'
209
        )
210
        return
211
    except ValueError:
212
        abort(404)
213
214
    flash_success(f'{seat.label} wurde mit Ticket {ticket.code} reserviert.')
215
216
217 1
@blueprint.route('/ticket/<uuid:ticket_id>/seat', methods=['DELETE'])
218 1
@login_required
219 1
@respond_no_content
220
def release_seat(ticket_id):
221
    """Release the seat."""
222
    _abort_if_seat_management_disabled()
223
224
    ticket = _get_ticket_or_404(ticket_id)
225
226
    if not ticket.occupied_seat:
227
        flash_error(f'Ticket {ticket.code} belegt keinen Sitzplatz.')
228
        return
229
230
    manager = g.current_user
231
232
    if not ticket.is_seat_managed_by(manager.id) and not _is_seating_admin(
233
        manager
234
    ):
235
        flash_error(
236
            'Du bist nicht berechtigt, den Sitzplatz '
237
            f'für Ticket {ticket.code} zu verwalten.'
238
        )
239
        return
240
241
    seat = ticket.occupied_seat
242
243
    try:
244
        ticket_seat_management_service.release_seat(ticket.id, manager.id)
245
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
246
        flash_error(
247
            f'Ticket {ticket.code} gehört zu einem Paket '
248
            'und kann nicht einzeln verwaltet werden.'
249
        )
250
        return
251
252
    flash_success(f'{seat.label} wurde freigegeben.')
253
254
255 1
def _abort_if_seat_management_disabled() -> None:
256
    if not _is_seat_management_enabled():
257
        flash_error('Sitzplätze können derzeit nicht verändert werden.')
258
        return
259
260
261 1
def _is_seat_management_enabled():
262
    if g.current_user.is_anonymous:
263
        return False
264
265
    if g.party_id is None:
266
        return False
267
268
    if _is_seating_admin(g.current_user):
269
        return True
270
271
    party = party_service.get_party(g.party_id)
272
    return party.seat_management_enabled
273
274
275 1
def _is_seating_admin(user) -> bool:
276
    return user.has_permission(SeatingPermission.administrate)
277
278
279 1
def _get_ticket_or_404(ticket_id: TicketID) -> DbTicket:
280
    ticket = ticket_service.find_ticket(ticket_id)
281
282
    if (ticket is None) or ticket.revoked:
283
        abort(404)
284
285
    return ticket
286
287
288 1
def _get_seat_or_404(seat_id: SeatID) -> Seat:
289
    seat = seat_service.find_seat(seat_id)
290
291
    if seat is None:
292
        abort(404)
293
294
    return seat
295