From b5122cc9b930d88b4705a9a31143e58936cff073 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Mon, 3 Aug 2026 17:16:31 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20apply=20user=20preferences?= =?UTF-8?q?=20on=20unconfigured=20room=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the API so that, when a user creates a new meeting without passing an explicit configuration, the user's persisted preferences are applied as defaults. This allows a user to, for example, enable the waiting room by default on every meeting they create. --- src/backend/core/api/viewsets.py | 23 +- .../core/tests/rooms/test_api_rooms_create.py | 205 +++++++++++++++++- 2 files changed, 225 insertions(+), 3 deletions(-) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 5f1ad94f..8a088ace 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -308,8 +308,27 @@ class RoomViewSet( return drf_response.Response(serializer.data) def perform_create(self, serializer): - """Set the current user as owner of the newly created room.""" - room = serializer.save() + """Set the current user as owner of the newly created room. + + Apply the user's default room preferences (access level and configuration) + unless the request explicitly provides its own values. + """ + user = self.request.user + save_kwargs = {} + + if ( + "access_level" not in serializer.validated_data + and user.default_room_access_level not in (None, "") + ): + save_kwargs["access_level"] = user.default_room_access_level + + user_default_configuration = user.default_room_configuration + if not serializer.validated_data.get( + "configuration" + ) and user_default_configuration not in (None, {}): + save_kwargs["configuration"] = user.default_room_configuration + + room = serializer.save(**save_kwargs) models.ResourceAccess.objects.create( resource=room, user=self.request.user, diff --git a/src/backend/core/tests/rooms/test_api_rooms_create.py b/src/backend/core/tests/rooms/test_api_rooms_create.py index 3c54d6ae..befc1374 100644 --- a/src/backend/core/tests/rooms/test_api_rooms_create.py +++ b/src/backend/core/tests/rooms/test_api_rooms_create.py @@ -3,13 +3,14 @@ Test rooms API endpoints in the Meet core app: create. """ # pylint: disable=redefined-outer-name,unused-argument +from django.conf import settings from django.core.cache import cache import pytest from rest_framework.test import APIClient from ...factories import RoomFactory, UserFactory -from ...models import Room +from ...models import Room, RoomAccessLevel pytestmark = pytest.mark.django_db @@ -109,3 +110,205 @@ def test_api_rooms_create_authenticated_existing_slug(): assert response.status_code == 400 assert response.json() == {"slug": ["Room with this Slug already exists."]} + + +def test_api_rooms_create_authenticated_user_default_access_level(): + """ + The user's default room access level should be applied to the new room + when the request does not provide one. + """ + user = UserFactory(default_room_access_level=RoomAccessLevel.RESTRICTED) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.access_level == RoomAccessLevel.RESTRICTED + + +def test_api_rooms_create_authenticated_explicit_access_level_overrides_default(): + """ + An access level explicitly provided in the request should take precedence + over the user's default room access level. + """ + user = UserFactory(default_room_access_level=RoomAccessLevel.RESTRICTED) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + "access_level": RoomAccessLevel.TRUSTED, + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.access_level == RoomAccessLevel.TRUSTED + + +def test_api_rooms_create_authenticated_no_user_default_access_level(): + """ + When the user has no default room access level, the instance default + should be applied to the new room. + """ + user = UserFactory(default_room_access_level=None) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.access_level == settings.RESOURCE_DEFAULT_ACCESS_LEVEL + + +def test_api_rooms_create_authenticated_user_default_configuration(): + """ + The user's default room configuration should be applied to the new room + when the request does not provide one. + """ + user = UserFactory(default_room_configuration={"everyone_can_mute": False}) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.configuration == {"everyone_can_mute": False} + + +def test_api_rooms_create_authenticated_explicit_configuration_overrides_default(): + """ + A configuration explicitly provided in the request should take precedence + over the user's default room configuration. + """ + user = UserFactory(default_room_configuration={"everyone_can_mute": False}) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + "configuration": {"can_publish_sources": ["camera", "microphone"]}, + }, + format="json", + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.configuration == {"can_publish_sources": ["camera", "microphone"]} + + +def test_api_rooms_create_authenticated_empty_configuration_falls_back_to_default(): + """ + An empty configuration in the request should not be considered an explicit + value: the user's default room configuration should still be applied. + """ + user = UserFactory(default_room_configuration={"everyone_can_mute": True}) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + "configuration": {}, + }, + format="json", + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.configuration == {"everyone_can_mute": True} + + +def test_api_rooms_create_authenticated_empty_user_default_configuration(): + """ + When the user's default room configuration is empty, the new room should + keep its default empty configuration. + """ + user = UserFactory(default_room_configuration={}) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.configuration == {} + + +def test_api_rooms_create_authenticated_request_precedence_over_user_empty(): + """ + When the user's default room configuration is empty, the request should take precedence. + """ + user = UserFactory(default_room_configuration={}) + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + {"name": "my room", "configuration": {"everyone_can_mute": True}}, + format="json", + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.configuration == {"everyone_can_mute": True} + + +def test_api_rooms_create_authenticated_blank_user_default_access_level(): + """ + A blank default room access level (stored as an empty string) should be + treated as unset: the instance default should be applied to the new room + instead of persisting an invalid empty access level. + """ + user = UserFactory(default_room_access_level="") + + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/rooms/", + { + "name": "my room", + }, + ) + + assert response.status_code == 201 + room = Room.objects.get() + assert room.access_level == settings.RESOURCE_DEFAULT_ACCESS_LEVEL