diff --git a/app/content/models/event.py b/app/content/models/event.py index 9cef512b2..bbf6de139 100644 --- a/app/content/models/event.py +++ b/app/content/models/event.py @@ -107,6 +107,20 @@ def waiting_list_count(self): """Number of users on the waiting list""" return self.get_waiting_list().count() + def move_users_from_waiting_list_to_queue(self, count): + """Moves the first x users from waiting list to queue""" + waiting_list = self.get_waiting_list().order_by("created_at") + for registration in waiting_list[:count]: + moved_registration = registration.move_from_waiting_list_to_queue() + moved_registration.save() + + def move_users_from_queue_to_waiting_list(self, count): + """Moves the last created x users from queue to waiting list""" + queue = self.get_participants().order_by("-created_at") + for registration in queue[:count]: + moved_registration = registration.move_from_queue_to_waiting_list() + moved_registration.save() + def get_has_attended(self): return self.get_participants().filter(has_attended=True) diff --git a/app/content/models/registration.py b/app/content/models/registration.py index 224e8d094..8a227251f 100644 --- a/app/content/models/registration.py +++ b/app/content/models/registration.py @@ -254,6 +254,21 @@ def move_from_waiting_list_to_queue(self): registration_move_to_queue.is_on_wait = False return registration_move_to_queue + def move_from_queue_to_waiting_list(self): + registrations_in_queue = self.event.get_participants().order_by("-created_at") + + if registrations_in_queue: + registration_move_to_waiting_list = next( + ( + registration + for registration in registrations_in_queue + if not registration.is_prioritized + ), + registrations_in_queue[0], + ) + registration_move_to_waiting_list.is_on_wait = True + return registration_move_to_waiting_list + def clean(self): """ Validates model fields. Is called upon instance save. diff --git a/app/content/serializers/event.py b/app/content/serializers/event.py index a31c89dca..b2a3e1088 100644 --- a/app/content/serializers/event.py +++ b/app/content/serializers/event.py @@ -170,7 +170,19 @@ def create(self, validated_data): def update(self, instance, validated_data): priority_pools_data = validated_data.pop("priority_pools", None) paid_information_data = validated_data.pop("paid_information", None) + limit = validated_data.get("limit") + limit_difference = 0 + if limit: + limit_difference = limit - instance.limit + event = super().update(instance, validated_data) + + if limit_difference > 0 and event.waiting_list_count > 0: + event.move_users_from_waiting_list_to_queue(limit_difference) + + if limit_difference < 0: + event.move_users_from_queue_to_waiting_list(abs(limit_difference)) + if paid_information_data and not event.is_paid_event: PaidEvent.objects.create( event=event, diff --git a/app/settings.py b/app/settings.py index 73ab2432e..cca499ab3 100644 --- a/app/settings.py +++ b/app/settings.py @@ -282,7 +282,9 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" -CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL") or "amqp://guest:guest@rabbitmq:5672" +CELERY_BROKER_URL = ( + os.environ.get("CELERY_BROKER_URL") or "amqp://guest:guest@rabbitmq:5672" +) if ENVIRONMENT == EnvironmentOptions.LOCAL: CELERY_TASK_ALWAYS_EAGER = False diff --git a/app/tests/conftest.py b/app/tests/conftest.py index dea934478..102b3462a 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -17,6 +17,7 @@ NewsFactory, PageFactory, ParentPageFactory, + PriorityPoolFactory, RegistrationFactory, ShortLinkFactory, UserFactory, @@ -30,6 +31,12 @@ from app.util.test_utils import add_user_to_group_with_name, get_api_client +def _add_user_to_group(user, group): + return MembershipFactory( + user=user, group=group, membership_type=MembershipType.MEMBER + ) + + @pytest.fixture() def request_factory(): return APIRequestFactory() @@ -200,3 +207,22 @@ def banner(): @pytest.fixture() def toddel(): return ToddelFactory() + + +@pytest.fixture() +def priority_group(): + return GroupFactory(name="Prioritized group", slug="prioritized_group") + + +@pytest.fixture() +def user_in_priority_pool(priority_group): + user = UserFactory() + _add_user_to_group(user, priority_group) + return user + + +@pytest.fixture() +def event_with_priority_pool(priority_group): + event = EventFactory(limit=1) + PriorityPoolFactory(event=event, groups=(priority_group,)) + return event diff --git a/app/tests/content/test_event_integration.py b/app/tests/content/test_event_integration.py index 49b51646d..df255569f 100644 --- a/app/tests/content/test_event_integration.py +++ b/app/tests/content/test_event_integration.py @@ -27,7 +27,11 @@ def get_events_url_detail(event=None): def get_event_data( - title="New Title", location="New Location", organizer=None, contact_person=None + title="New Title", + location="New Location", + organizer=None, + contact_person=None, + limit=0, ): start_date = timezone.now() + timedelta(days=10) end_date = timezone.now() + timedelta(days=11) @@ -37,6 +41,7 @@ def get_event_data( "start_date": start_date, "end_date": end_date, "is_paid_event": False, + "limit": limit, } if organizer: data["organizer"] = organizer @@ -224,7 +229,9 @@ def test_update_event_as_admin(permission_test_util): client = get_api_client(user=user) url = get_events_url_detail(event) - data = get_event_data(title=expected_title, organizer=new_organizer) + data = get_event_data( + title=expected_title, organizer=new_organizer, limit=event.limit + ) response = client.put(url, data) event.refresh_from_db() @@ -233,6 +240,85 @@ def test_update_event_as_admin(permission_test_util): assert event.title == expected_title +@pytest.mark.django_db +def test_update_event_with_increased_limit(admin_user, event): + """ + Admins should be able to update the limit of an event. + Then the first person on the waiting list should be moved to the queue. + Priorities should be respected. + """ + + event.limit = 1 + event.save() + + registration = RegistrationFactory(event=event) + waiting_registration = RegistrationFactory(event=event) + + assert not registration.is_on_wait + assert waiting_registration.is_on_wait + assert event.waiting_list_count == 1 + + client = get_api_client(user=admin_user) + url = get_events_url_detail(event) + data = get_event_data(limit=2) + + response = client.put(url, data) + event.refresh_from_db() + registration.refresh_from_db() + waiting_registration.refresh_from_db() + + assert response.status_code == status.HTTP_200_OK + assert event.limit == 2 + assert event.waiting_list_count == 0 + assert not waiting_registration.is_on_wait + + +@pytest.mark.django_db +def test_update_event_with_decreased_limit( + admin_user, event_with_priority_pool, user_in_priority_pool +): + """ + Admins should be able to update the limit of an event. + Then the first person on the queue should be moved to the waiting list. + Priorities should be respected. + """ + + event_with_priority_pool.limit = 4 + event_with_priority_pool.save() + + registration = RegistrationFactory(event=event_with_priority_pool) + first_queue_registration = RegistrationFactory(event=event_with_priority_pool) + second_queue_registration = RegistrationFactory(event=event_with_priority_pool) + third_queue_registration = RegistrationFactory( + event=event_with_priority_pool, user=user_in_priority_pool + ) + + assert not registration.is_on_wait + assert not first_queue_registration.is_on_wait + assert not second_queue_registration.is_on_wait + assert not third_queue_registration.is_on_wait + assert event_with_priority_pool.waiting_list_count == 0 + + client = get_api_client(user=admin_user) + url = get_events_url_detail(event_with_priority_pool) + data = get_event_data(limit=1) + + response = client.put(url, data) + event_with_priority_pool.refresh_from_db() + registration.refresh_from_db() + first_queue_registration.refresh_from_db() + second_queue_registration.refresh_from_db() + third_queue_registration.refresh_from_db() + + assert response.status_code == status.HTTP_200_OK + assert event_with_priority_pool.limit == 1 + assert event_with_priority_pool.waiting_list_count == 3 + assert registration.is_on_wait + assert first_queue_registration.is_on_wait + assert second_queue_registration.is_on_wait + assert not third_queue_registration.is_on_wait + + @pytest.mark.django_db def test_create_as_anonymous_user(default_client): """An anonymous user should not be able to create an event entity."""