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

_is_seat_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.seating.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2019 Jochen Kupperschmidt
6
:License: Modified BSD, see LICENSE for details.
7
"""
8
9
from typing import Dict, Sequence
10
11
from flask import abort, g
12
13
from ...services.party import service as party_service
14
from ...services.seating import area_service as seating_area_service
15
from ...services.seating.models.seat import Seat
16
from ...services.seating import seat_service
17
from ...services.seating.transfer.models import SeatID
18
from ...services.ticketing.models.ticket import Ticket
19
from ...services.ticketing import (
20
    exceptions as ticket_exceptions,
21
    ticket_seat_management_service,
22
    ticket_service,
23
)
24
from ...services.ticketing.transfer.models import TicketID
25
from ...services.user import service as user_service
26
from ...services.user.transfer.models import User
27
from ...typing import UserID
28
from ...util.framework.blueprint import create_blueprint
29
from ...util.framework.flash import flash_error, flash_success
30
from ...util.framework.templating import templated
31
from ...util.views import respond_no_content
32
33
from ..authentication.decorators import login_required
34
35
36
blueprint = create_blueprint('seating', __name__)
37
38
39
@blueprint.route('/')
40
@templated
41
def index():
42
    """List areas."""
43
    if g.party_id is None:
44
        # No party is configured for the current site.
45
        abort(404)
46
47
    areas = seating_area_service.get_areas_for_party(g.party_id)
48
49
    return {
50
        'areas': areas,
51
    }
52
53
54
@blueprint.route('/areas/<slug>')
55
@templated
56
def view_area(slug):
57
    """View area."""
58
    if g.party_id is None:
59
        # No party is configured for the current site.
60
        abort(404)
61
62
    area = seating_area_service.find_area_for_party_by_slug(g.party_id, slug)
63
    if area is None:
64
        abort(404)
65
66
    seat_management_enabled = _is_seat_management_enabled()
67
68
    seats = seat_service.get_seats_with_tickets_for_area(area.id)
69
70
    if seat_management_enabled:
71
        tickets = ticket_service.find_tickets_for_seat_manager(
72
            g.current_user.id, g.party_id
73
        )
74
    else:
75
        tickets = None
76
77
    users_by_id = _get_users(seats, tickets)
78
79
    return {
80
        'area': area,
81
        'seat_management_enabled': seat_management_enabled,
82
        'seats': seats,
83
        'tickets': tickets,
84
        'users_by_id': users_by_id,
85
    }
86
87
88
def _get_users(
89
    seats: Sequence[Seat], tickets: Sequence[Ticket]
90
) -> Dict[UserID, User]:
91
    user_ids = set()
92
93
    for seat in seats:
94
        if seat.has_user:
95
            user_ids.add(seat.occupied_by_ticket.used_by_id)
96
97
    for ticket in tickets:
98
        user_id = ticket.used_by_id
99
        if user_id is not None:
100
            user_ids.add(user_id)
101
102
    users = user_service.find_users(user_ids, include_avatars=True)
103
    return user_service.index_users_by_id(users)
104
105
106
@blueprint.route(
107
    '/ticket/<uuid:ticket_id>/seat/<uuid:seat_id>', methods=['POST']
108
)
109
@login_required
110
@respond_no_content
111
def occupy_seat(ticket_id, seat_id):
112
    """Use ticket to occupy seat."""
113
    _abort_if_seat_management_disabled()
114
115
    ticket = _get_ticket_or_404(ticket_id)
116
117
    manager = g.current_user
118
119
    if not ticket.is_seat_managed_by(manager.id):
120
        flash_error(
121
            'Du bist nicht berechtigt, den Sitzplatz '
122
            f'für Ticket {ticket.code} zu verwalten.'
123
        )
124
        return
125
126
    seat = _get_seat_or_404(seat_id)
127
128
    if seat.is_occupied:
129
        flash_error(f'{seat.label} ist bereits belegt.')
130
        return
131
132
    try:
133
        ticket_seat_management_service.occupy_seat(
134
            ticket.id, seat.id, manager.id
135
        )
136
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
137
        flash_error(
138
            f'Ticket {ticket.code} gehört zu einem Paket '
139
            'und kann nicht einzeln verwaltet werden.'
140
        )
141
        return
142
    except ticket_exceptions.TicketCategoryMismatch:
143
        flash_error(
144
            f'Ticket {ticket.code} und {seat.label} haben '
145
            'unterschiedliche Kategorien.'
146
        )
147
        return
148
    except ValueError:
149
        abort(404)
150
151
    flash_success(f'{seat.label} wurde mit Ticket {ticket.code} reserviert.')
152
153
154
@blueprint.route('/ticket/<uuid:ticket_id>/seat', methods=['DELETE'])
155
@login_required
156
@respond_no_content
157
def release_seat(ticket_id):
158
    """Release the seat."""
159
    _abort_if_seat_management_disabled()
160
161
    ticket = _get_ticket_or_404(ticket_id)
162
163
    if not ticket.occupied_seat:
164
        flash_error(f'Ticket {ticket.code} belegt keinen Sitzplatz.')
165
        return
166
167
    manager = g.current_user
168
169
    if not ticket.is_seat_managed_by(manager.id):
170
        flash_error(
171
            'Du bist nicht berechtigt, den Sitzplatz '
172
            f'für Ticket {ticket.code} zu verwalten.'
173
        )
174
        return
175
176
    seat = ticket.occupied_seat
177
178
    try:
179
        ticket_seat_management_service.release_seat(ticket.id, manager.id)
180
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
181
        flash_error(
182
            f'Ticket {ticket.code} gehört zu einem Paket '
183
            'und kann nicht einzeln verwaltet werden.'
184
        )
185
        return
186
187
    flash_success(f'{seat.label} wurde freigegeben.')
188
189
190
def _abort_if_seat_management_disabled() -> None:
191
    if not _is_seat_management_enabled():
192
        flash_error('Sitzplätze können derzeit nicht verändert werden.')
193
        return
194
195
196
def _is_seat_management_enabled():
197
    if g.party_id is None:
198
        return False
199
200
    party = party_service.get_party(g.party_id)
201
    return party.seat_management_enabled
202
203
204
def _get_ticket_or_404(ticket_id: TicketID) -> Ticket:
205
    ticket = ticket_service.find_ticket(ticket_id)
206
207
    if (ticket is None) or ticket.revoked:
208
        abort(404)
209
210
    return ticket
211
212
213
def _get_seat_or_404(seat_id: SeatID) -> Seat:
214
    seat = seat_service.find_seat(seat_id)
215
216
    if seat is None:
217
        abort(404)
218
219
    return seat
220