Passed
Push — main ( a21473...138f92 )
by Jochen
04:19
created

_group_tickets_by_party_id()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
"""
2
byceps.blueprints.admin.user.service
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from __future__ import annotations
10 1
from collections import defaultdict
11 1
from operator import attrgetter
12 1
from typing import Any, Iterator, Sequence
13
14 1
from ....services.consent import consent_service, subject_service
15 1
from ....services.newsletter import service as newsletter_service
16 1
from ....services.newsletter.transfer.models import List as NewsletterList
17 1
from ....services.party import service as party_service
18 1
from ....services.party.transfer.models import Party
19 1
from ....services.shop.order import service as order_service
20 1
from ....services.site import service as site_service
21 1
from ....services.ticketing.dbmodels.ticket import Ticket as DbTicket
22 1
from ....services.ticketing import attendance_service, ticket_service
23 1
from ....services.user import event_service
24 1
from ....services.user.dbmodels.event import (
25
    UserEvent as DbUserEvent,
26
    UserEventData,
27
)
28 1
from ....services.user import service as user_service
29 1
from ....services.user.transfer.models import User
30 1
from ....services.user_avatar import service as avatar_service
31 1
from ....services.user_badge import badge_service as user_badge_service
32 1
from ....typing import PartyID, UserID
33
34
35 1
def get_parties_and_tickets(
36
    user_id: UserID,
37
) -> list[tuple[Party, list[DbTicket]]]:
38
    """Return tickets the user uses or manages, and the related parties."""
39 1
    tickets = ticket_service.find_tickets_related_to_user(user_id)
40
41 1
    tickets_by_party_id = _group_tickets_by_party_id(tickets)
42
43 1
    party_ids = set(tickets_by_party_id.keys())
44 1
    parties_by_id = _get_parties_by_id(party_ids)
45
46 1
    parties_and_tickets = [
47
        (parties_by_id[party_id], tickets)
48
        for party_id, tickets in tickets_by_party_id.items()
49
    ]
50
51 1
    parties_and_tickets.sort(key=lambda x: x[0].starts_at, reverse=True)
52
53 1
    return parties_and_tickets
54
55
56 1
def _group_tickets_by_party_id(
57
    tickets: Sequence[DbTicket],
58
) -> dict[PartyID, list[DbTicket]]:
59 1
    tickets_by_party_id: dict[PartyID, list[DbTicket]] = defaultdict(list)
60
61 1
    for ticket in tickets:
62 1
        tickets_by_party_id[ticket.category.party_id].append(ticket)
63
64 1
    return tickets_by_party_id
65
66
67 1
def _get_parties_by_id(party_ids: set[PartyID]) -> dict[PartyID, Party]:
68 1
    parties = party_service.get_parties(party_ids)
69 1
    return {p.id: p for p in parties}
70
71
72 1
def get_attended_parties(user_id: UserID) -> list[Party]:
73
    """Return the parties attended by the user, in order."""
74 1
    attended_parties = attendance_service.get_attended_parties(user_id)
75 1
    attended_parties.sort(key=attrgetter('starts_at'), reverse=True)
76 1
    return attended_parties
77
78
79 1
def get_newsletter_subscription_states(
80
    user_id: UserID,
81
) -> Iterator[tuple[NewsletterList, bool]]:
82 1
    lists = newsletter_service.get_all_lists()
83 1
    for list_ in lists:
84
        is_subscribed = newsletter_service.is_subscribed(user_id, list_.id)
85
        yield list_, is_subscribed
86
87
88 1
def get_events(user_id: UserID) -> Iterator[UserEventData]:
89 1
    events = event_service.get_events_for_user(user_id)
90 1
    events.extend(_fake_avatar_update_events(user_id))
91 1
    events.extend(_fake_consent_events(user_id))
92 1
    events.extend(_fake_newsletter_subscription_update_events(user_id))
93 1
    events.extend(_fake_order_events(user_id))
94
95 1
    user_ids = {
96
        event.data['initiator_id']
97
        for event in events
98
        if 'initiator_id' in event.data
99
    }
100 1
    users = user_service.get_users(user_ids, include_avatars=True)
101 1
    users_by_id = {str(user.id): user for user in users}
102
103 1
    for event in events:
104 1
        data = {
105
            'event': event.event_type,
106
            'occurred_at': event.occurred_at,
107
            'data': event.data,
108
        }
109
110 1
        additional_data = _get_additional_data(event, users_by_id)
111 1
        data.update(additional_data)
112
113 1
        yield data
114
115
116 1
def _fake_avatar_update_events(user_id: UserID) -> Iterator[DbUserEvent]:
117
    """Yield the user's avatar updates as volatile events."""
118 1
    avatar_updates = avatar_service.get_avatars_uploaded_by_user(user_id)
119
120 1
    for avatar_update in avatar_updates:
121
        data = {
122
            'initiator_id': str(user_id),
123
            'url_path': avatar_update.url_path,
124
        }
125
126
        yield DbUserEvent(
127
            avatar_update.occurred_at, 'user-avatar-updated', user_id, data
128
        )
129
130
131 1
def _fake_consent_events(user_id: UserID) -> Iterator[DbUserEvent]:
132
    """Yield the user's consents as volatile events."""
133 1
    consents = consent_service.get_consents_by_user(user_id)
134
135 1
    subject_ids = {consent.subject_id for consent in consents}
136 1
    subjects = subject_service.get_subjects(subject_ids)
137 1
    subjects_titles_by_id = {subject.id: subject.title for subject in subjects}
138
139 1
    for consent in consents:
140
        data = {
141
            'initiator_id': str(user_id),
142
            'subject_title': subjects_titles_by_id[consent.subject_id],
143
        }
144
145
        yield DbUserEvent(
146
            consent.expressed_at, 'consent-expressed', user_id, data
147
        )
148
149
150 1
def _fake_newsletter_subscription_update_events(
151
    user_id: UserID,
152
) -> Iterator[DbUserEvent]:
153
    """Yield the user's newsletter subscription updates as volatile events."""
154 1
    lists = newsletter_service.get_all_lists()
155 1
    lists_by_id = {list_.id: list_ for list_ in lists}
156
157 1
    updates = newsletter_service.get_subscription_updates_for_user(user_id)
158
159 1
    for update in updates:
160
        event_type = f'newsletter-{update.state.name}'
161
162
        list_ = lists_by_id[update.list_id]
163
164
        data = {
165
            'list_': list_,
166
            'initiator_id': str(user_id),
167
        }
168
169
        yield DbUserEvent(update.expressed_at, event_type, user_id, data)
170
171
172 1
def _fake_order_events(user_id: UserID) -> Iterator[DbUserEvent]:
173
    """Yield the orders placed by the user as volatile events."""
174 1
    orders = order_service.get_orders_placed_by_user(user_id)
175
176 1
    for order in orders:
177
        data = {
178
            'initiator_id': str(user_id),
179
            'order': order,
180
        }
181
182
        yield DbUserEvent(order.created_at, 'order-placed', user_id, data)
183
184
185 1
def _get_additional_data(
186
    event: DbUserEvent, users_by_id: dict[str, User]
187
) -> Iterator[tuple[str, Any]]:
188 1
    if event.event_type in {
189
        'user-created',
190
        'user-deleted',
191
        'user-details-updated',
192
        'user-email-address-changed',
193
        'user-email-address-invalidated',
194
        'user-initialized',
195
        'user-screen-name-changed',
196
        'user-suspended',
197
        'user-unsuspended',
198
        'password-updated',
199
        'user-avatar-updated',
200
        'consent-expressed',
201
        'newsletter-requested',
202
        'newsletter-declined',
203
        'order-placed',
204
        'orgaflag-added',
205
        'orgaflag-removed',
206
        'privacy-policy-accepted',
207
        'role-assigned',
208
        'role-deassigned',
209
        'user-badge-awarded',
210
    }:
211 1
        yield from _get_additional_data_for_user_initiated_event(
212
            event, users_by_id
213
        )
214
215 1
    if event.event_type == 'user-badge-awarded':
216 1
        badge = user_badge_service.find_badge(event.data['badge_id'])
217 1
        yield 'badge', badge
218
219 1
    if event.event_type == 'user-details-updated':
220
        details = {
221
            k: v
222
            for k, v in event.data.items()
223
            if k.startswith('old_') or k.startswith('new_')
224
        }
225
        yield 'details', details
226
227 1
    if event.event_type in {'user-created', 'user-logged-in'}:
228 1
        site_id = event.data.get('site_id')
229 1
        if site_id:
230
            site = site_service.find_site(site_id)
231
            if site is not None:
232
                yield 'site', site
233
234
235 1
def _get_additional_data_for_user_initiated_event(
236
    event: DbUserEvent, users_by_id: dict[str, User]
237
) -> Iterator[tuple[str, Any]]:
238 1
    initiator_id = event.data.get('initiator_id')
239 1
    if initiator_id is not None:
240
        yield 'initiator', users_by_id[initiator_id]
241