Skip to content

Commit f56f2f9

Browse files
committed
Refactor create_or_update_reservation_statistics to celery task
Optimize the function so that it can create statistics in bulk for many reservations. Optimize queries to be more efficient.
1 parent b5e78a1 commit f56f2f9

8 files changed

Lines changed: 194 additions & 115 deletions

File tree

api/graphql/types/recurring_reservation/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from reservation_units.models import ReservationUnit
2020
from reservations.enums import ReservationStateChoice, ReservationTypeStaffChoice
2121
from reservations.models import AffectingTimeSpan, RecurringReservation, Reservation
22-
from reservations.tasks import create_statistics_for_reservations_task
22+
from reservations.tasks import create_or_update_reservation_statistics
2323

2424
__all__ = [
2525
"RecurringReservationCreateSerializer",
@@ -228,7 +228,7 @@ def save(self, **kwargs: Any) -> RecurringReservation:
228228
AffectingTimeSpan.refresh()
229229

230230
if settings.SAVE_RESERVATION_STATISTICS:
231-
create_statistics_for_reservations_task.delay(
231+
create_or_update_reservation_statistics.delay(
232232
reservation_pks=[reservation.pk for reservation in reservations],
233233
)
234234

applications/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from opening_hours.utils.reservable_time_span_client import ReservableTimeSpanClient
1414
from reservations.enums import CustomerTypeChoice, ReservationStateChoice, ReservationTypeChoice
1515
from reservations.models import AffectingTimeSpan, RecurringReservation
16-
from reservations.tasks import create_statistics_for_reservations_task
16+
from reservations.tasks import create_or_update_reservation_statistics
1717
from tilavarauspalvelu.celery import app
1818
from utils.sentry import SentryLogger
1919

@@ -182,4 +182,4 @@ def generate_reservation_series_from_allocations(application_round_id: int) -> N
182182
AffectingTimeSpan.refresh()
183183

184184
if settings.SAVE_RESERVATION_STATISTICS:
185-
create_statistics_for_reservations_task.delay(reservation_pks=list(reservation_pks))
185+
create_or_update_reservation_statistics.delay(reservation_pks=list(reservation_pks))

reservations/management/commands/create_missing_statistics.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from common.date_utils import local_datetime
88
from reservations.models import Reservation
9-
from reservations.statistic_utils import create_or_update_reservation_statistics
9+
from reservations.tasks import create_or_update_reservation_statistics
1010

1111

1212
class Command(BaseCommand):
@@ -19,11 +19,12 @@ def handle(self, *args: Any, **options: Any) -> None:
1919
def create_missing_statistics() -> None:
2020
pruned_cutoff = local_datetime() - relativedelta(years=settings.REMOVE_RESERVATION_STATS_OLDER_THAN_YEARS)
2121

22-
pks = Reservation.objects.filter(
23-
reservationstatistic__isnull=True,
24-
# Don't recreate statistics for old reservations
25-
reservation_created_at__gt=pruned_cutoff,
26-
).values_list("pk", flat=True)
22+
pks: list[int] = list(
23+
Reservation.objects.filter(
24+
reservationstatistic__isnull=True,
25+
# Don't recreate statistics for old reservations
26+
created_at__gt=pruned_cutoff,
27+
).values_list("pk", flat=True)
28+
)
2729

28-
for pk in pks:
29-
create_or_update_reservation_statistics(pk)
30+
create_or_update_reservation_statistics(pks)

reservations/migrations/0051_refesh_reservation_statistics.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
from django.db import migrations
44

5-
from reservations.statistic_utils import create_or_update_reservation_statistics
5+
from reservations.tasks import create_or_update_reservation_statistics
6+
67

78
def refresh_reservation_statistics(apps, schema_editor):
89
ReservationStatistic = apps.get_model("reservations", "ReservationStatistic")
910

1011
for statistic in ReservationStatistic.objects.filter(primary_reservation_unit__isnull=True).all():
1112
if statistic.reservation is not None:
12-
create_or_update_reservation_statistics(statistic.reservation.pk)
13+
create_or_update_reservation_statistics([statistic.reservation.pk])
1314

14-
class Migration(migrations.Migration):
1515

16+
class Migration(migrations.Migration):
1617
dependencies = [
1718
("reservations", "0050_non_subsidised_price_net_decimals"),
1819
]

reservations/models/reservation_statistic.py

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1+
from __future__ import annotations
2+
13
import datetime
2-
from decimal import Decimal
4+
from typing import TYPE_CHECKING
35

4-
from django.db import models
6+
from django.db import models, transaction
57
from django.utils import timezone
68

9+
from reservations.enums import CustomerTypeChoice
10+
11+
if TYPE_CHECKING:
12+
from decimal import Decimal
13+
14+
from reservations.models import Reservation
15+
16+
717
__all__ = [
818
"ReservationStatistic",
919
"ReservationStatisticsReservationUnit",
@@ -137,6 +147,80 @@ class Meta:
137147
def __str__(self) -> str:
138148
return f"{self.reservee_uuid} - {self.begin} - {self.end}"
139149

150+
@classmethod
151+
def for_reservation(cls, reservation: Reservation, *, save: bool = True) -> ReservationStatistic: # noqa: PLR0915
152+
recurring_reservation = getattr(reservation, "recurring_reservation", None)
153+
ability_group = getattr(recurring_reservation, "ability_group", None)
154+
allocated_time_slot = getattr(recurring_reservation, "allocated_time_slot", None)
155+
156+
requires_org_name = reservation.reservee_type != CustomerTypeChoice.INDIVIDUAL
157+
requires_org_id = not reservation.reservee_is_unregistered_association and requires_org_name
158+
by_profile_user = bool(getattr(reservation.user, "profile_id", ""))
159+
160+
statistic = ( #
161+
ReservationStatistic.objects.filter(reservation=reservation).first()
162+
or ReservationStatistic(reservation=reservation)
163+
)
164+
165+
statistic.ability_group = ability_group
166+
statistic.age_group = reservation.age_group
167+
statistic.age_group_name = str(reservation.age_group)
168+
statistic.applying_for_free_of_charge = reservation.applying_for_free_of_charge
169+
statistic.begin = reservation.begin
170+
statistic.buffer_time_after = reservation.buffer_time_after
171+
statistic.buffer_time_before = reservation.buffer_time_before
172+
statistic.cancel_reason = reservation.cancel_reason
173+
statistic.cancel_reason_text = getattr(reservation.cancel_reason, "reason", "")
174+
statistic.deny_reason = reservation.deny_reason
175+
statistic.deny_reason_text = getattr(reservation.deny_reason, "reason", "")
176+
statistic.duration_minutes = (reservation.end - reservation.begin).total_seconds() / 60
177+
statistic.end = reservation.end
178+
statistic.home_city = reservation.home_city
179+
statistic.home_city_municipality_code = getattr(reservation.home_city, "municipality_code", "")
180+
statistic.home_city_name = reservation.home_city.name if reservation.home_city else ""
181+
statistic.is_applied = allocated_time_slot is not None
182+
statistic.is_recurring = recurring_reservation is not None
183+
statistic.is_subsidised = reservation.price < reservation.non_subsidised_price
184+
statistic.non_subsidised_price = reservation.non_subsidised_price
185+
statistic.non_subsidised_price_net = reservation.non_subsidised_price_net
186+
statistic.num_persons = reservation.num_persons
187+
statistic.price = reservation.price
188+
statistic.price_net = reservation.price_net
189+
statistic.purpose = reservation.purpose
190+
statistic.purpose_name = reservation.purpose.name if reservation.purpose else ""
191+
statistic.recurrence_begin_date = getattr(recurring_reservation, "begin_date", None)
192+
statistic.recurrence_end_date = getattr(recurring_reservation, "end_date", None)
193+
statistic.recurrence_uuid = getattr(recurring_reservation, "uuid", "")
194+
statistic.reservation = reservation
195+
statistic.reservation_confirmed_at = reservation.confirmed_at
196+
statistic.reservation_created_at = reservation.created_at
197+
statistic.reservation_handled_at = reservation.handled_at
198+
statistic.reservation_type = reservation.type
199+
statistic.reservee_address_zip = reservation.reservee_address_zip if by_profile_user else ""
200+
statistic.reservee_id = reservation.reservee_id if requires_org_id else ""
201+
statistic.reservee_is_unregistered_association = reservation.reservee_is_unregistered_association
202+
statistic.reservee_language = reservation.reservee_language
203+
statistic.reservee_organisation_name = reservation.reservee_organisation_name if requires_org_name else ""
204+
statistic.reservee_type = reservation.reservee_type
205+
statistic.reservee_uuid = str(reservation.user.tvp_uuid) if reservation.user else ""
206+
statistic.state = reservation.state
207+
statistic.tax_percentage_value = reservation.tax_percentage_value
208+
209+
for res_unit in reservation.reservation_unit.all():
210+
statistic.primary_reservation_unit = res_unit
211+
statistic.primary_reservation_unit_name = res_unit.name
212+
statistic.primary_unit_name = getattr(res_unit.unit, "name", "")
213+
statistic.primary_unit_tprek_id = getattr(res_unit.unit, "tprek_id", "")
214+
break
215+
216+
if statistic.is_applied and ability_group:
217+
statistic.ability_group_name = ability_group.name
218+
219+
if save:
220+
statistic.save()
221+
222+
return statistic
223+
140224

141225
class ReservationStatisticsReservationUnit(models.Model):
142226
name = models.CharField(max_length=255)
@@ -163,3 +247,32 @@ class Meta:
163247

164248
def __str__(self) -> str:
165249
return f"{self.reservation_statistics} - {self.reservation_unit}"
250+
251+
@classmethod
252+
def for_statistic(
253+
cls,
254+
statistic: ReservationStatistic,
255+
*,
256+
save: bool = True,
257+
) -> list[ReservationStatisticsReservationUnit]:
258+
to_save: list[ReservationStatisticsReservationUnit] = []
259+
for reservation_unit in statistic.reservation.reservation_unit.all():
260+
stat_unit = ReservationStatisticsReservationUnit(
261+
reservation_statistics=statistic,
262+
reservation_unit=reservation_unit,
263+
)
264+
265+
stat_unit.name = reservation_unit.name
266+
stat_unit.reservation_statistics = statistic
267+
stat_unit.reservation_unit = reservation_unit
268+
stat_unit.unit_name = getattr(reservation_unit.unit, "name", "")
269+
stat_unit.unit_tprek_id = getattr(reservation_unit.unit, "tprek_id", "")
270+
271+
to_save.append(stat_unit)
272+
273+
if save:
274+
with transaction.atomic():
275+
ReservationStatisticsReservationUnit.objects.filter(reservation_statistics=statistic).delete()
276+
ReservationStatisticsReservationUnit.objects.bulk_create(to_save)
277+
278+
return to_save

reservations/signals.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.dispatch import receiver
66

77
from reservations.models import AffectingTimeSpan, Reservation
8-
from reservations.statistic_utils import create_or_update_reservation_statistics
8+
from reservations.tasks import create_or_update_reservation_statistics
99

1010
type M2MAction = Literal["pre_add", "post_add", "pre_remove", "post_remove", "pre_clear", "post_clear"]
1111

@@ -29,7 +29,7 @@ def reservation_create(
2929
# one can be defined. If in the future we truly support reservations with multiple reservation units,
3030
# we need to change this implementation so that we either have multiple reservation units in the statistics
3131
# or we have better way to indicate which one is the primary unit.
32-
create_or_update_reservation_statistics(instance.pk)
32+
create_or_update_reservation_statistics([instance.pk])
3333

3434
if not raw and settings.UPDATE_AFFECTING_TIME_SPANS:
3535
AffectingTimeSpan.refresh(using=kwargs.get("using"))
@@ -57,7 +57,7 @@ def reservations_reservation_units_m2m(
5757
**kwargs: Any,
5858
) -> None:
5959
if action == "post_add" and not raw and not reverse and settings.SAVE_RESERVATION_STATISTICS:
60-
create_or_update_reservation_statistics(instance.pk)
60+
create_or_update_reservation_statistics([instance.pk])
6161

6262
if not raw and settings.UPDATE_AFFECTING_TIME_SPANS:
6363
AffectingTimeSpan.refresh(using=kwargs.get("using"))

reservations/statistic_utils.py

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)