Skip to content

Commit 39667b3

Browse files
author
Tinde von Wachenfeldt
committed
Restrict pool updates to active pools
1 parent 7801052 commit 39667b3

File tree

5 files changed

+116
-46
lines changed

5 files changed

+116
-46
lines changed

lego/apps/events/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,9 @@ def decrement(self) -> Pool:
849849
self.save(update_fields=["counter"])
850850
return self
851851

852+
def permission_group_ids(self) -> set[int]:
853+
return set(self.permission_groups.values_list("id", flat=True))
854+
852855
@abakus_cached_property
853856
def all_permission_groups(self):
854857
groups = self.permission_groups.all()

lego/apps/events/serializers/events.py

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -423,57 +423,70 @@ def create(self, validated_data):
423423
event_status_type = validated_data.get(
424424
"event_status_type", Event._meta.get_field("event_status_type").default
425425
)
426+
pools = self._pools_for_status(event_status_type, pools)
426427
require_auth = validated_data.get("require_auth", False)
427428
validated_data["require_auth"] = require_auth
428-
if event_status_type == constants.TBA:
429-
pools = []
430-
elif event_status_type == constants.OPEN:
431-
pools = []
432-
elif event_status_type == constants.INFINITE:
433-
pools = [pools[0]]
434-
pools[0]["capacity"] = 0
435429
with transaction.atomic():
436430
event = super().create(validated_data)
437431
for pool in pools:
438-
permission_groups = pool.pop("permission_groups")
439-
created_pool = Pool.objects.create(event=event, **pool)
440-
created_pool.permission_groups.set(permission_groups)
432+
pool_data = PoolCreateAndUpdateSerializer.extract_pool_data(pool)
433+
pool_serializer = PoolCreateAndUpdateSerializer(
434+
data=pool_data, context={**self.context, "event": event}
435+
)
436+
pool_serializer.is_valid(raise_exception=True)
437+
pool_serializer.save()
441438
return event
442439

443440
def update(self, instance, validated_data):
444441
pools = validated_data.pop("pools", None)
445442
event_status_type = validated_data.get(
446443
"event_status_type", Event._meta.get_field("event_status_type").default
447444
)
448-
if event_status_type == constants.TBA:
449-
pools = []
450-
elif event_status_type == constants.OPEN:
451-
pools = []
452-
elif event_status_type == constants.INFINITE:
453-
pools = [pools[0]]
454-
pools[0]["capacity"] = 0
445+
pools = self._pools_for_status(event_status_type, pools)
455446
with transaction.atomic():
456447
if pools is not None:
457-
existing_pools = list(instance.pools.all().values_list("id", flat=True))
448+
existing_ids = set(instance.pools.values_list("id", flat=True))
458449
for pool in pools:
459-
pool_id = pool.get("id", None)
460-
if pool_id in existing_pools:
461-
existing_pools.remove(pool_id)
462-
permission_groups = pool.pop("permission_groups")
463-
created_pool = Pool.objects.update_or_create(
464-
event=instance,
465-
id=pool_id,
466-
defaults={
467-
"name": pool.get("name"),
468-
"capacity": pool.get("capacity", 0),
469-
"activation_date": pool.get("activation_date"),
470-
},
471-
)[0]
472-
created_pool.permission_groups.set(permission_groups)
473-
for pool_id in existing_pools:
474-
Pool.objects.get(id=pool_id).delete()
450+
pool_id = pool.get("id")
451+
pool_instance = (
452+
Pool.objects.filter(id=pool_id, event=instance)
453+
.select_for_update()
454+
.first()
455+
if pool_id
456+
else None
457+
)
458+
if pool_instance:
459+
existing_ids.discard(pool_id)
460+
461+
pool_data = PoolCreateAndUpdateSerializer.extract_pool_data(pool)
462+
pool_serializer = PoolCreateAndUpdateSerializer(
463+
instance=pool_instance,
464+
data=pool_data,
465+
context={**self.context, "event": instance},
466+
partial=True,
467+
)
468+
pool_serializer.is_valid(raise_exception=True)
469+
pool_serializer.save()
470+
471+
if existing_ids:
472+
for pool_obj in Pool.objects.filter(
473+
event=instance, id__in=existing_ids
474+
).iterator():
475+
pool_obj.delete()
475476
return super().update(instance, validated_data)
476477

478+
def _pools_for_status(self, event_status_type, pools):
479+
if pools is None:
480+
return None
481+
if event_status_type in (constants.TBA, constants.OPEN):
482+
return []
483+
if event_status_type == constants.INFINITE:
484+
if not pools:
485+
return []
486+
pools = [pools[0]]
487+
pools[0]["capacity"] = 0
488+
return pools
489+
477490

478491
class FrontpageEventSerializer(serializers.ModelSerializer):
479492
cover = ImageField(required=False, options={"height": 500})

lego/apps/events/serializers/pools.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from rest_framework import serializers
22

33
from lego.apps.events.fields import RegistrationCountField
4-
from lego.apps.events.models import Event, Pool
4+
from lego.apps.events.models import Pool
55
from lego.apps.events.serializers.registrations import (
66
RegistrationPaymentReadSerializer,
77
RegistrationReadDetailedAllergiesSerializer,
@@ -29,14 +29,6 @@ class Meta:
2929
)
3030
read_only = True
3131

32-
def create(self, validated_data):
33-
event = Event.objects.get(pk=self.context["view"].kwargs["event_pk"])
34-
permission_groups = validated_data.pop("permission_groups")
35-
pool = Pool.objects.create(event=event, **validated_data)
36-
pool.permission_groups.set(permission_groups)
37-
38-
return pool
39-
4032

4133
class PoolReadAuthSerializer(PoolReadSerializer):
4234
registrations = serializers.SerializerMethodField()
@@ -90,10 +82,38 @@ class Meta:
9082
"registrations": {"read_only": True},
9183
}
9284

85+
def validate(self, attrs):
86+
instance = getattr(self, "instance", None)
87+
if not instance or not instance.is_activated:
88+
return attrs
89+
90+
if "permission_groups" in attrs:
91+
new_ids = {getattr(gr, "id", gr) for gr in attrs["permission_groups"]}
92+
old_ids = instance.permission_group_ids()
93+
if new_ids != old_ids:
94+
raise serializers.ValidationError(
95+
{"permission_groups": "Group edits are disabled for active pools."}
96+
)
97+
if "activation_date" in attrs:
98+
if attrs["activation_date"] != instance.activation_date:
99+
raise serializers.ValidationError(
100+
{"activation_date": "Time travel is disabled for active pools."}
101+
)
102+
return attrs
103+
93104
def create(self, validated_data):
94-
event = Event.objects.get(pk=self.context["view"].kwargs["event_pk"])
105+
event = validated_data.pop("event", None) or self.context.get("event")
95106
permission_groups = validated_data.pop("permission_groups")
96107
pool = Pool.objects.create(event=event, **validated_data)
97108
pool.permission_groups.set(permission_groups)
98-
99109
return pool
110+
111+
@staticmethod
112+
def extract_pool_data(pool):
113+
permission_groups = pool.get("permission_groups", [])
114+
return {
115+
"name": pool.get("name"),
116+
"capacity": pool.get("capacity"),
117+
"activation_date": pool.get("activation_date"),
118+
"permission_groups": [getattr(gr, "id", gr) for gr in permission_groups],
119+
}

lego/apps/events/tests/test_events_api.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,33 @@ def test_delete_pool_without_registrations_as_abakus(self):
10001000
pool_response = self.client.delete(_get_pools_detail_url(1, pool.id))
10011001
self.assertEqual(pool_response.status_code, status.HTTP_403_FORBIDDEN)
10021002

1003+
def test_patch_permission_groups_after_activation(self):
1004+
"""Test that change of permission group is not possible in active pool"""
1005+
AbakusGroup.objects.get(name="Bedkom").add_user(self.abakus_user)
1006+
self.client.force_authenticate(self.abakus_user)
1007+
pool = Event.objects.get(pk=1).pools.first()
1008+
new_group = AbakusGroup.objects.get(name="Webkom")
1009+
pool_response = self.client.patch(
1010+
_get_pools_detail_url(1, pool.id),
1011+
{"permissionGroups": [new_group.id]},
1012+
format="json",
1013+
)
1014+
self.assertEqual(pool_response.status_code, status.HTTP_400_BAD_REQUEST)
1015+
self.assertIn("permissionGroups", pool_response.json())
1016+
1017+
def test_patch_activation_date_after_activation(self):
1018+
"""Test that change of activation date is not possible in active pool"""
1019+
AbakusGroup.objects.get(name="Bedkom").add_user(self.abakus_user)
1020+
self.client.force_authenticate(self.abakus_user)
1021+
pool = Event.objects.get(pk=1).pools.first()
1022+
pool_response = self.client.patch(
1023+
_get_pools_detail_url(1, pool.id),
1024+
{"activationDate": timezone.now().isoformat()},
1025+
format="json",
1026+
)
1027+
self.assertEqual(pool_response.status_code, status.HTTP_400_BAD_REQUEST)
1028+
self.assertIn("activationDate", pool_response.json())
1029+
10031030

10041031
@mock.patch("lego.apps.events.views.verify_captcha", return_value=True)
10051032
class RegistrationsTransactionTestCase(BaseAPITransactionTestCase):

lego/apps/events/views.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,11 +346,18 @@ class PoolViewSet(
346346
serializer_class = PoolCreateAndUpdateSerializer
347347

348348
def get_queryset(self):
349-
event_id = self.kwargs.get("event_pk", None)
349+
event_id = self.kwargs.get("event_pk")
350350
return Pool.objects.filter(event=event_id).prefetch_related(
351351
"permission_groups", "registrations"
352352
)
353353

354+
def get_serializer_context(self):
355+
context = super().get_serializer_context()
356+
event_id = self.kwargs.get("event_pk")
357+
if event_id:
358+
context["event"] = get_object_or_404(Event, pk=event_id)
359+
return context
360+
354361
def destroy(self, request, *args, **kwargs):
355362
try:
356363
return super().destroy(request, *args, **kwargs)

0 commit comments

Comments
 (0)