Skip to content

Commit 4e5d5a0

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

File tree

5 files changed

+121
-46
lines changed

5 files changed

+121
-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: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -423,57 +423,75 @@ 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+
427+
pools = self._pools_for_status(event_status_type, pools)
426428
require_auth = validated_data.get("require_auth", False)
427429
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
430+
435431
with transaction.atomic():
436432
event = super().create(validated_data)
437433
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)
434+
pool_data = PoolCreateAndUpdateSerializer.extract_pool_data(pool)
435+
pool_serializer = PoolCreateAndUpdateSerializer(
436+
data=pool_data, context={**self.context, "event": event}
437+
)
438+
pool_serializer.is_valid(raise_exception=True)
439+
pool_serializer.save()
441440
return event
442441

443442
def update(self, instance, validated_data):
444443
pools = validated_data.pop("pools", None)
445444
event_status_type = validated_data.get(
446445
"event_status_type", Event._meta.get_field("event_status_type").default
447446
)
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
447+
448+
pools = self._pools_for_status(event_status_type, pools)
449+
455450
with transaction.atomic():
456451
if pools is not None:
457-
existing_pools = list(instance.pools.all().values_list("id", flat=True))
452+
existing_ids = set(instance.pools.values_list("id", flat=True))
458453
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()
454+
pool_id = pool.get("id")
455+
pool_instance = (
456+
Pool.objects.filter(id=pool_id, event=instance)
457+
.select_for_update()
458+
.first()
459+
if pool_id
460+
else None
461+
)
462+
if pool_instance:
463+
existing_ids.discard(pool_id)
464+
465+
pool_data = PoolCreateAndUpdateSerializer.extract_pool_data(pool)
466+
pool_serializer = PoolCreateAndUpdateSerializer(
467+
instance=pool_instance,
468+
data=pool_data,
469+
context={**self.context, "event": instance},
470+
partial=True,
471+
)
472+
pool_serializer.is_valid(raise_exception=True)
473+
pool_serializer.save()
474+
475+
if existing_ids:
476+
for pool_obj in Pool.objects.filter(
477+
event=instance, id__in=existing_ids
478+
).iterator():
479+
pool_obj.delete()
480+
475481
return super().update(instance, validated_data)
476482

483+
def _pools_for_status(self, event_status_type, pools):
484+
if pools is None:
485+
return None
486+
if event_status_type in (constants.TBA, constants.OPEN):
487+
return []
488+
if event_status_type == constants.INFINITE:
489+
if not pools:
490+
return []
491+
pools = [pools[0]]
492+
pools[0]["capacity"] = 0
493+
return pools
494+
477495

478496
class FrontpageEventSerializer(serializers.ModelSerializer):
479497
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(g, "id", g) for g 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)