✨(project) add Room, Ressource, Access models from Magnify
I picked few models from Magnify to build our MVP: - Resource: A generic model representing any type of resource. Though currently used only by Room, it encapsulates a meaningful business logic as an abstract model. - Room: The primary object we manipulate, representing a meeting room with access and permission controls. - ResourceAccess Ensures relevant users have the appropriate permissions for a given room. ** What’s different from Magnify ? ** Removed group logic; it will be added later. For now, we rely on the user model's property to get its groups via desk. Removed any logic or method related to Jitsi or LiveKit. These servers will be integrated in the upcomming commits. Focus on Room-related models to maintain a minimal and functional product (KISS principle) until we achieve product-market fit (PMF). Creating simple public and private, permanent and temporary rooms is sufficient for building our MVP. The Meeting model in Magnify, which supports recurrence, should be handled by the collaborative calendar instead. Adapted the unit test to use Pytest, and linted all the sources using Ruff linter. (Migrations will be squashed before releasing the MVP)
This commit is contained in:
@@ -4,6 +4,7 @@ Core application factories
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.utils.text import slugify
|
||||
|
||||
import factory.fuzzy
|
||||
from faker import Faker
|
||||
@@ -23,3 +24,43 @@ class UserFactory(factory.django.DjangoModelFactory):
|
||||
email = factory.Faker("email")
|
||||
language = factory.fuzzy.FuzzyChoice([lang[0] for lang in settings.LANGUAGES])
|
||||
password = make_password("password")
|
||||
|
||||
|
||||
class ResourceFactory(factory.django.DjangoModelFactory):
|
||||
"""Create fake resources for testing."""
|
||||
|
||||
class Meta:
|
||||
model = models.Resource
|
||||
|
||||
is_public = factory.Faker("boolean", chance_of_getting_true=50)
|
||||
|
||||
@factory.post_generation
|
||||
def users(self, create, extracted, **kwargs):
|
||||
"""Add users to resource from a given list of users."""
|
||||
if create and extracted:
|
||||
for item in extracted:
|
||||
if isinstance(item, models.User):
|
||||
UserResourceAccessFactory(resource=self, user=item)
|
||||
else:
|
||||
UserResourceAccessFactory(resource=self, user=item[0], role=item[1])
|
||||
|
||||
|
||||
class UserResourceAccessFactory(factory.django.DjangoModelFactory):
|
||||
"""Create fake resource user accesses for testing."""
|
||||
|
||||
class Meta:
|
||||
model = models.ResourceAccess
|
||||
|
||||
resource = factory.SubFactory(ResourceFactory)
|
||||
user = factory.SubFactory(UserFactory)
|
||||
role = factory.fuzzy.FuzzyChoice(models.RoleChoices.values)
|
||||
|
||||
|
||||
class RoomFactory(ResourceFactory):
|
||||
"""Create fake rooms for testing."""
|
||||
|
||||
class Meta:
|
||||
model = models.Room
|
||||
|
||||
name = factory.Faker("catch_phrase")
|
||||
slug = factory.LazyAttribute(lambda o: slugify(o.name))
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# Generated by Django 5.0.3 on 2024-06-24 15:37
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_create_pg_trgm_extension'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Resource',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
|
||||
('is_public', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Resource',
|
||||
'verbose_name_plural': 'Resources',
|
||||
'db_table': 'impress_resource',
|
||||
},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='documentaccess',
|
||||
name='document',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='invitation',
|
||||
name='document',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='documentaccess',
|
||||
name='user',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='invitation',
|
||||
name='issuer',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='templateaccess',
|
||||
name='template',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='templateaccess',
|
||||
name='user',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='language',
|
||||
field=models.CharField(choices="(('en-us', 'English'), ('fr-fr', 'French'))", default='en-us', help_text='The language in which the user wants to see the interface.', max_length=10, verbose_name='language'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Room',
|
||||
fields=[
|
||||
('name', models.CharField(max_length=500)),
|
||||
('resource', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.resource')),
|
||||
('slug', models.SlugField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('configuration', models.JSONField(blank=True, default={}, help_text='Values for Magnify parameters to configure the room.', verbose_name='Magnify room configuration')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Room',
|
||||
'verbose_name_plural': 'Rooms',
|
||||
'db_table': 'impress_room',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
bases=('core.resource',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ResourceAccess',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='primary key for the record as UUID', primary_key=True, serialize=False, verbose_name='id')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='date and time at which a record was created', verbose_name='created on')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated on')),
|
||||
('role', models.CharField(choices=[('member', 'Member'), ('administrator', 'Administrator'), ('owner', 'Owner')], default='member', max_length=20)),
|
||||
('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accesses', to='core.resource')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accesses', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Resource access',
|
||||
'verbose_name_plural': 'Resource accesses',
|
||||
'db_table': 'impress_resource_access',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='resource',
|
||||
name='users',
|
||||
field=models.ManyToManyField(related_name='resources', through='core.ResourceAccess', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Document',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='DocumentAccess',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Invitation',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Template',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='TemplateAccess',
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='resourceaccess',
|
||||
constraint=models.UniqueConstraint(fields=('user', 'resource'), name='resource_access_unique_user_resource', violation_error_message='Resource access with this user and resource already exists.'),
|
||||
),
|
||||
]
|
||||
@@ -8,8 +8,10 @@ from django.conf import settings
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.core import mail, validators
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.db import models
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.text import capfirst, slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from timezone_field import TimeZoneField
|
||||
@@ -17,6 +19,24 @@ from timezone_field import TimeZoneField
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class RoleChoices(models.TextChoices):
|
||||
"""Role choices."""
|
||||
|
||||
MEMBER = "member", _("Member")
|
||||
ADMIN = "administrator", _("Administrator")
|
||||
OWNER = "owner", _("Owner")
|
||||
|
||||
@classmethod
|
||||
def check_administrator_role(cls, role):
|
||||
"""Check if a role is administrator."""
|
||||
return role in [cls.ADMIN, cls.OWNER]
|
||||
|
||||
@classmethod
|
||||
def check_owner_role(cls, role):
|
||||
"""Check if a role is owner."""
|
||||
return role == cls.OWNER
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
"""
|
||||
Serves as an abstract base model for other models, ensuring that records are validated
|
||||
@@ -141,3 +161,159 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
||||
Must be cached if retrieved remotely.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class Resource(BaseModel):
|
||||
"""Model to define access control"""
|
||||
|
||||
is_public = models.BooleanField(default=settings.DEFAULT_ROOM_IS_PUBLIC)
|
||||
users = models.ManyToManyField(
|
||||
User,
|
||||
through="ResourceAccess",
|
||||
through_fields=("resource", "user"),
|
||||
related_name="resources",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "impress_resource"
|
||||
verbose_name = _("Resource")
|
||||
verbose_name_plural = _("Resources")
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
return self.name
|
||||
except AttributeError:
|
||||
return f"Resource {self.id!s}"
|
||||
|
||||
def get_role(self, user):
|
||||
"""
|
||||
Determine the role of a given user in this resource.
|
||||
"""
|
||||
if not user or not user.is_authenticated:
|
||||
return None
|
||||
|
||||
role = None
|
||||
for access in self.accesses.filter(user=user):
|
||||
if access.role == RoleChoices.OWNER:
|
||||
return RoleChoices.OWNER
|
||||
if access.role == RoleChoices.ADMIN:
|
||||
role = RoleChoices.ADMIN
|
||||
if access.role == RoleChoices.MEMBER and role != RoleChoices.ADMIN:
|
||||
role = RoleChoices.MEMBER
|
||||
return role
|
||||
|
||||
def is_administrator(self, user):
|
||||
"""
|
||||
Check if a user is administrator of the resource.
|
||||
|
||||
Users carrying the "owner" role are considered as administrators a fortiori.
|
||||
"""
|
||||
return RoleChoices.check_administrator_role(self.get_role(user))
|
||||
|
||||
def is_owner(self, user):
|
||||
"""Check if a user is owner of the resource."""
|
||||
return RoleChoices.check_owner_role(self.get_role(user))
|
||||
|
||||
|
||||
class ResourceAccess(BaseModel):
|
||||
"""Link table between resources and users"""
|
||||
|
||||
resource = models.ForeignKey(
|
||||
Resource,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="accesses",
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="accesses")
|
||||
role = models.CharField(
|
||||
max_length=20, choices=RoleChoices.choices, default=RoleChoices.MEMBER
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "impress_resource_access"
|
||||
verbose_name = _("Resource access")
|
||||
verbose_name_plural = _("Resource accesses")
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["user", "resource"],
|
||||
name="resource_access_unique_user_resource",
|
||||
violation_error_message=_(
|
||||
"Resource access with this User and Resource already exists."
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
role = capfirst(self.get_role_display())
|
||||
try:
|
||||
resource = self.resource.name
|
||||
except AttributeError:
|
||||
resource = f"resource {self.resource_id!s}"
|
||||
|
||||
return f"{role:s} role for {self.user!s} on {resource:s}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Make sure we keep at least one owner for the resource."""
|
||||
if self.pk and self.role != RoleChoices.OWNER:
|
||||
accesses = self._meta.model.objects.filter(
|
||||
resource=self.resource, role=RoleChoices.OWNER
|
||||
).only("pk")
|
||||
if len(accesses) == 1 and accesses[0].pk == self.pk:
|
||||
raise PermissionDenied("A resource should keep at least one owner.")
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Disallow deleting the last of the Mohicans."""
|
||||
if (
|
||||
self.role == RoleChoices.OWNER
|
||||
and self._meta.model.objects.filter(
|
||||
resource=self.resource, role=RoleChoices.OWNER
|
||||
).count()
|
||||
== 1
|
||||
):
|
||||
raise PermissionDenied("A resource should keep at least one owner.")
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
class Room(Resource):
|
||||
"""Model for one room"""
|
||||
|
||||
name = models.CharField(max_length=500)
|
||||
resource = models.OneToOneField(
|
||||
Resource,
|
||||
on_delete=models.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
)
|
||||
slug = models.SlugField(max_length=100, blank=True, null=True, unique=True)
|
||||
|
||||
configuration = models.JSONField(
|
||||
blank=True,
|
||||
default={},
|
||||
verbose_name=_("Visio room configuration"),
|
||||
help_text=_("Values for Visio parameters to configure the room."),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = "impress_room"
|
||||
ordering = ("name",)
|
||||
verbose_name = _("Room")
|
||||
verbose_name_plural = _("Rooms")
|
||||
|
||||
def __str__(self):
|
||||
return capfirst(self.name)
|
||||
|
||||
def clean_fields(self, exclude=None):
|
||||
"""
|
||||
Automatically generate the slug from the name and make sure it does not look like a UUID.
|
||||
|
||||
We don't want any overlapping between the `slug` and the `id` fields because they can
|
||||
both be used to get a room detail view on the API.
|
||||
"""
|
||||
self.slug = slugify(self.name)
|
||||
try:
|
||||
uuid.UUID(self.slug)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
raise ValidationError({"name": f'Room name "{self.name:s}" is reserved.'})
|
||||
super().clean_fields(exclude=exclude)
|
||||
|
||||
63
src/backend/core/tests/test_models_resource_accesses.py
Normal file
63
src/backend/core/tests/test_models_resource_accesses.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Unit tests for the ResourceAccess model with user
|
||||
"""
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
import pytest
|
||||
|
||||
from core.factories import RoomFactory, UserResourceAccessFactory
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_models_resource_accesses_user_str_member_room():
|
||||
"""The str representation should consist in the room and usernames."""
|
||||
room = RoomFactory(name="my room")
|
||||
access = UserResourceAccessFactory(
|
||||
resource=room, user__email="john.doe@impress.com", role="member"
|
||||
)
|
||||
assert str(access) == "Member role for john.doe@impress.com on my room"
|
||||
|
||||
|
||||
def test_models_resource_accesses_user_str_member_resource():
|
||||
"""The str representation should consist in the resource id and username."""
|
||||
access = UserResourceAccessFactory(
|
||||
user__email="john.doe@impress.com", role="member"
|
||||
)
|
||||
assert (
|
||||
str(access)
|
||||
== f"Member role for john.doe@impress.com on resource {access.resource_id!s}"
|
||||
)
|
||||
|
||||
|
||||
def test_models_resource_accesses_user_str_admin():
|
||||
"""The str representation for an admin user should include the role."""
|
||||
access = UserResourceAccessFactory(
|
||||
user__email="john.doe@impress.com", role="administrator"
|
||||
)
|
||||
|
||||
assert (
|
||||
str(access)
|
||||
== f"Administrator role for john.doe@impress.com on resource {access.resource_id!s}"
|
||||
)
|
||||
|
||||
|
||||
def test_models_resource_accesses_user_str_owner():
|
||||
"""The str representation for an admin user should include the role."""
|
||||
access = UserResourceAccessFactory(user__email="john.doe@impress.com", role="owner")
|
||||
assert (
|
||||
str(access)
|
||||
== f"Owner role for john.doe@impress.com on resource {access.resource_id!s}"
|
||||
)
|
||||
|
||||
|
||||
def test_models_resource_accesses_user_unique():
|
||||
"""Room user accesses should be unique."""
|
||||
access = UserResourceAccessFactory()
|
||||
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
UserResourceAccessFactory(user=access.user, resource=access.resource)
|
||||
|
||||
assert "Resource access with this User and Resource already exists." in str(
|
||||
excinfo.value
|
||||
)
|
||||
165
src/backend/core/tests/test_models_rooms.py
Normal file
165
src/backend/core/tests/test_models_rooms.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Unit tests for the Room model
|
||||
"""
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
import pytest
|
||||
|
||||
from core.factories import RoomFactory, UserFactory
|
||||
from core.models import Room
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_models_rooms_str():
|
||||
"""The str representation should be the name."""
|
||||
room = RoomFactory()
|
||||
assert str(room) == room.name
|
||||
|
||||
|
||||
def test_models_rooms_ordering():
|
||||
"""Rooms should be returned ordered by name."""
|
||||
RoomFactory.create_batch(3)
|
||||
rooms = Room.objects.all()
|
||||
# Remove hyphens because postgresql is ignoring them when they sort
|
||||
assert rooms[1].name.replace("-", "") >= rooms[0].name.replace("-", "")
|
||||
assert rooms[2].name.replace("-", "") >= rooms[1].name.replace("-", "")
|
||||
|
||||
|
||||
def test_models_rooms_name_maxlength():
|
||||
"""The name field should be less than 100 characters."""
|
||||
RoomFactory(name="a" * 100)
|
||||
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
RoomFactory(name="a" * 101)
|
||||
|
||||
assert "Ensure this value has at most 100 characters (it has 101)." in str(
|
||||
excinfo.value
|
||||
)
|
||||
|
||||
|
||||
def test_models_rooms_slug_unique():
|
||||
"""Room slugs should be unique."""
|
||||
RoomFactory(name="a room!")
|
||||
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
RoomFactory(name="A Room!")
|
||||
|
||||
assert "Room with this Slug already exists." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_models_rooms_name_slug_like_uuid():
|
||||
"""
|
||||
It should raise an error if the value of the name field leads to a slug looking
|
||||
like a UUID . We need unicity on the union of the `id` and `slug` fields.
|
||||
"""
|
||||
with pytest.raises(ValidationError) as excinfo:
|
||||
RoomFactory(name="918689fb-038e 4e81-bf09 efd5902c5f0b")
|
||||
|
||||
assert 'Room name "918689fb-038e 4e81-bf09 efd5902c5f0b" is reserved.' in str(
|
||||
excinfo.value
|
||||
)
|
||||
|
||||
|
||||
def test_models_rooms_slug_automatic():
|
||||
"""Room slugs should be automatically populated upon saving."""
|
||||
room = Room(name="Eléphant in the room")
|
||||
room.save()
|
||||
assert room.slug == "elephant-in-the-room"
|
||||
|
||||
|
||||
def test_models_rooms_users():
|
||||
"""It should be possible to attach users to a room."""
|
||||
room = RoomFactory()
|
||||
user = UserFactory()
|
||||
room.users.add(user)
|
||||
room.refresh_from_db()
|
||||
|
||||
assert list(room.users.all()) == [user]
|
||||
|
||||
|
||||
def test_models_rooms_is_public_default():
|
||||
"""A room should be public by default."""
|
||||
room = Room.objects.create(name="room")
|
||||
assert room.is_public is True
|
||||
|
||||
|
||||
# Access rights methods
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_none(django_assert_num_queries):
|
||||
"""Calling access rights methods with None should return None."""
|
||||
room = RoomFactory()
|
||||
|
||||
with django_assert_num_queries(0):
|
||||
assert room.get_role(None) is None
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_administrator(None) is False
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_owner(None) is False
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_anonymous(django_assert_num_queries):
|
||||
"""Check access rights methods on the room object for an anonymous user."""
|
||||
user = AnonymousUser()
|
||||
room = RoomFactory()
|
||||
|
||||
with django_assert_num_queries(0):
|
||||
assert room.get_role(user) is None
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(0):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_authenticated(django_assert_num_queries):
|
||||
"""Check access rights methods on the room object for an unrelated user."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory()
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) is None
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_member_direct(django_assert_num_queries):
|
||||
"""Check access rights methods on the room object for a direct member."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "member")])
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "member"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator(user) is False
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_administrator_direct(django_assert_num_queries):
|
||||
"""The is_administrator method should return True for a direct administrator."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "administrator")])
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "administrator"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator(user) is True
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is False
|
||||
|
||||
|
||||
def test_models_rooms_access_rights_owner_direct(django_assert_num_queries):
|
||||
"""Check access rights methods on the room object for an owner."""
|
||||
user = UserFactory()
|
||||
room = RoomFactory(users=[(user, "owner")])
|
||||
|
||||
with django_assert_num_queries(1):
|
||||
assert room.get_role(user) == "owner"
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_administrator(user) is True
|
||||
with django_assert_num_queries(1):
|
||||
assert room.is_owner(user) is True
|
||||
@@ -368,6 +368,11 @@ class Base(Configuration):
|
||||
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
|
||||
)
|
||||
|
||||
# Video conference configuration
|
||||
DEFAULT_ROOM_IS_PUBLIC = values.BooleanValue(
|
||||
True, environ_name="MAGNIFY_DEFAULT_ROOM_IS_PUBLIC", environ_prefix=None
|
||||
)
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@property
|
||||
def ENVIRONMENT(self):
|
||||
|
||||
Reference in New Issue
Block a user