mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-31 12:47:58 +00:00
✨(backend) persist user preferences for room defaults on the User model
Add attributes on the User model to persist per-user preferences for the default link access level and the default room configuration. The frontend will let users update these preferences and then reuse them when generating a link through the webapp. Persisting them on the backend (rather than in application memory only) ensures the preferences survive across sessions and devices.
This commit is contained in:
@@ -31,9 +31,28 @@ class UserSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = ["id", "email", "full_name", "short_name", "timezone", "language"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"email",
|
||||||
|
"full_name",
|
||||||
|
"short_name",
|
||||||
|
"timezone",
|
||||||
|
"language",
|
||||||
|
"default_room_access_level",
|
||||||
|
"default_room_configuration",
|
||||||
|
]
|
||||||
read_only_fields = ["id", "email", "full_name", "short_name"]
|
read_only_fields = ["id", "email", "full_name", "short_name"]
|
||||||
|
|
||||||
|
def validate_default_room_configuration(self, value):
|
||||||
|
"""Validate the default room configuration against the RoomConfiguration schema."""
|
||||||
|
if value is None or value == {}:
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
RoomConfiguration.model_validate(value)
|
||||||
|
except PydanticValidationError as e:
|
||||||
|
raise serializers.ValidationError(e.errors()) from e
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class UserLightSerializer(serializers.ModelSerializer):
|
class UserLightSerializer(serializers.ModelSerializer):
|
||||||
"""Serialize users with limited fields."""
|
"""Serialize users with limited fields."""
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.2.14 on 2026-08-03 13:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0021_recording_external_process_id_alter_recording_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_room_access_level',
|
||||||
|
field=models.CharField(blank=True, choices=[('public', 'Public Access'), ('trusted', 'Trusted Access'), ('restricted', 'Restricted Access')], help_text='Access level applied by default to new rooms created by this user. When empty, the instance default is used.', max_length=50, null=True, verbose_name='default room access level'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='default_room_configuration',
|
||||||
|
field=models.JSONField(blank=True, default=dict, help_text='Configurations applied by default to new rooms created by this user.', verbose_name='default room configuration'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -189,6 +189,25 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
|||||||
default=settings.TIME_ZONE,
|
default=settings.TIME_ZONE,
|
||||||
help_text=_("The timezone in which the user wants to see times."),
|
help_text=_("The timezone in which the user wants to see times."),
|
||||||
)
|
)
|
||||||
|
default_room_access_level = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=RoomAccessLevel.choices,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_("default room access level"),
|
||||||
|
help_text=_(
|
||||||
|
"Access level applied by default to new rooms created by this user. "
|
||||||
|
"When empty, the instance default is used."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
default_room_configuration = models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
default=dict,
|
||||||
|
verbose_name=_("default room configuration"),
|
||||||
|
help_text=_(
|
||||||
|
"Configurations applied by default to new rooms created by this user."
|
||||||
|
),
|
||||||
|
)
|
||||||
is_device = models.BooleanField(
|
is_device = models.BooleanField(
|
||||||
_("device"),
|
_("device"),
|
||||||
default=False,
|
default=False,
|
||||||
|
|||||||
@@ -453,6 +453,8 @@ def test_api_rooms_retrieve_administrators(
|
|||||||
{
|
{
|
||||||
"id": str(other_user_access.id),
|
"id": str(other_user_access.id),
|
||||||
"user": {
|
"user": {
|
||||||
|
"default_room_access_level": None,
|
||||||
|
"default_room_configuration": {},
|
||||||
"id": str(other_user_access.user.id),
|
"id": str(other_user_access.user.id),
|
||||||
"email": other_user_access.user.email,
|
"email": other_user_access.user.email,
|
||||||
"full_name": other_user_access.user.full_name,
|
"full_name": other_user_access.user.full_name,
|
||||||
@@ -466,6 +468,8 @@ def test_api_rooms_retrieve_administrators(
|
|||||||
{
|
{
|
||||||
"id": str(user_access.id),
|
"id": str(user_access.id),
|
||||||
"user": {
|
"user": {
|
||||||
|
"default_room_access_level": None,
|
||||||
|
"default_room_configuration": {},
|
||||||
"id": str(user_access.user.id),
|
"id": str(user_access.user.id),
|
||||||
"email": user_access.user.email,
|
"email": user_access.user.email,
|
||||||
"full_name": user_access.user.full_name,
|
"full_name": user_access.user.full_name,
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ def test_api_users_retrieve_me_authenticated(settings):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
|
"default_room_access_level": None,
|
||||||
|
"default_room_configuration": {},
|
||||||
"id": str(user.id),
|
"id": str(user.id),
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"full_name": user.full_name,
|
"full_name": user.full_name,
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
Test the default room preferences exposed on the users API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
|
from core import factories
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_me_includes_default_room_preferences():
|
||||||
|
"""The "me" endpoint should expose the user's default room preferences."""
|
||||||
|
user = factories.UserFactory(
|
||||||
|
default_room_access_level="restricted",
|
||||||
|
default_room_configuration={"everyone_can_mute": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get("/api/v1.0/users/me/")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
content = response.json()
|
||||||
|
assert content["default_room_access_level"] == "restricted"
|
||||||
|
assert content["default_room_configuration"] == {"everyone_can_mute": False}
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_update_default_room_preferences():
|
||||||
|
"""Users should be able to update their own default room preferences."""
|
||||||
|
user = factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1.0/users/{user.id!s}/",
|
||||||
|
{
|
||||||
|
"default_room_access_level": "trusted",
|
||||||
|
"default_room_configuration": {
|
||||||
|
"can_publish_sources": ["microphone", "camera"],
|
||||||
|
"everyone_can_mute": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
user.refresh_from_db()
|
||||||
|
assert user.default_room_access_level == "trusted"
|
||||||
|
assert user.default_room_configuration == {
|
||||||
|
"can_publish_sources": ["microphone", "camera"],
|
||||||
|
"everyone_can_mute": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_update_default_room_access_level_invalid():
|
||||||
|
"""An invalid access level should be rejected."""
|
||||||
|
user = factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1.0/users/{user.id!s}/",
|
||||||
|
{"default_room_access_level": "invalid"},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
user.refresh_from_db()
|
||||||
|
assert user.default_room_access_level is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_update_default_room_configuration_invalid():
|
||||||
|
"""An invalid room configuration should be rejected."""
|
||||||
|
user = factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1.0/users/{user.id!s}/",
|
||||||
|
{"default_room_configuration": {"unknown_field": True}},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
user.refresh_from_db()
|
||||||
|
assert user.default_room_configuration == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_users_update_other_user_default_room_preferences_forbidden():
|
||||||
|
"""Users should not be able to update someone else's preferences."""
|
||||||
|
user = factories.UserFactory()
|
||||||
|
other_user = factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1.0/users/{other_user.id!s}/",
|
||||||
|
{"default_room_access_level": "restricted"},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
other_user.refresh_from_db()
|
||||||
|
assert other_user.default_room_access_level is None
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { BackendLanguage } from '@/utils/languages'
|
import { BackendLanguage } from '@/utils/languages'
|
||||||
|
import type {
|
||||||
|
ApiAccessLevel,
|
||||||
|
RoomConfiguration,
|
||||||
|
} from '@/features/rooms/api/ApiRoom'
|
||||||
|
|
||||||
export type ApiUser = {
|
export type ApiUser = {
|
||||||
id: string
|
id: string
|
||||||
@@ -7,4 +11,6 @@ export type ApiUser = {
|
|||||||
last_name: string
|
last_name: string
|
||||||
language: BackendLanguage
|
language: BackendLanguage
|
||||||
timezone: string
|
timezone: string
|
||||||
|
default_room_access_level?: ApiAccessLevel | null
|
||||||
|
default_room_configuration?: RoomConfiguration | null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user