♻️(backend) rename DomainInvitation

All models relacted to mail domain are prefixed
with "MailDomain". Do the same for mail domain invitations.
This commit is contained in:
Sabrina Demagny
2025-03-06 12:30:22 +01:00
parent c90a74b362
commit 45bafe04de
11 changed files with 57 additions and 55 deletions

View File

@@ -269,11 +269,11 @@ class MailDomainAccessReadOnlySerializer(MailDomainAccessSerializer):
]
class DomainInvitationSerializer(serializers.ModelSerializer):
class MailDomainInvitationSerializer(serializers.ModelSerializer):
"""Serialize invitations."""
class Meta:
model = models.DomainInvitation
model = models.MailDomainInvitation
fields = ["id", "created_at", "email", "domain", "role", "issuer", "is_expired"]
read_only_fields = ["id", "created_at", "domain", "issuer", "is_expired"]

View File

@@ -294,7 +294,7 @@ class MailBoxViewSet(
return Response(serializers.MailboxSerializer(mailbox).data)
class DomainInvitationViewset(
class MailDomainInvitationViewset(
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
@@ -320,11 +320,11 @@ class DomainInvitationViewset(
lookup_field = "id"
permission_classes = [permissions.AccessPermission]
queryset = (
models.DomainInvitation.objects.all()
models.MailDomainInvitation.objects.all()
.select_related("domain")
.order_by("-created_at")
)
serializer_class = serializers.DomainInvitationSerializer
serializer_class = serializers.MailDomainInvitationSerializer
def get_serializer_context(self):
"""Extra context provided to the serializer class."""

View File

@@ -86,11 +86,11 @@ class MailboxEnabledFactory(MailboxFactory):
status = enums.MailboxStatusChoices.ENABLED
class DomainInvitationFactory(factory.django.DjangoModelFactory):
class MailDomainInvitationFactory(factory.django.DjangoModelFactory):
"""A factory to create invitations for a user"""
class Meta:
model = models.DomainInvitation
model = models.MailDomainInvitation
domain = factory.SubFactory(MailDomainEnabledFactory)
email = factory.Faker("email")

View File

@@ -15,20 +15,20 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='DomainInvitation',
name='MailDomainInvitation',
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 at')),
('updated_at', models.DateTimeField(auto_now=True, help_text='date and time at which a record was last updated', verbose_name='updated at')),
('email', models.EmailField(max_length=254, verbose_name='email address')),
('role', models.CharField(choices=[('viewer', 'Viewer'), ('administrator', 'Administrator'), ('owner', 'Owner')], default='viewer', max_length=20)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domain_invitations', to='mailbox_manager.maildomain')),
('issuer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domain_invitations', to=settings.AUTH_USER_MODEL)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mail_domain_invitations', to='mailbox_manager.maildomain')),
('issuer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mail_domain_invitations', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Domain invitation',
'verbose_name_plural': 'Domain invitations',
'db_table': 'people_domain_invitation',
'verbose_name': 'Mail domain invitation',
'verbose_name_plural': 'Mail domain invitations',
'db_table': 'people_mail_domain_invitation',
'constraints': [models.UniqueConstraint(fields=('email', 'domain'), name='email_and_domain_unique_together')],
},
),

View File

@@ -275,18 +275,18 @@ class Mailbox(AbstractBaseUser, BaseModel):
return f"{self.local_part}@{self.domain.name}"
class DomainInvitation(BaseInvitation):
class MailDomainInvitation(BaseInvitation):
"""User invitation to teams."""
issuer = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="domain_invitations",
related_name="mail_domain_invitations",
)
domain = models.ForeignKey(
MailDomain,
on_delete=models.CASCADE,
related_name="domain_invitations",
related_name="mail_domain_invitations",
)
role = models.CharField(
max_length=20,
@@ -295,9 +295,9 @@ class DomainInvitation(BaseInvitation):
)
class Meta:
db_table = "people_domain_invitation"
verbose_name = _("Domain invitation")
verbose_name_plural = _("Domain invitations")
db_table = "people_mail_domain_invitation"
verbose_name = _("Mail domain invitation")
verbose_name_plural = _("Mail domain invitations")
constraints = [
models.UniqueConstraint(
fields=["email", "domain"], name="email_and_domain_unique_together"

View File

@@ -13,7 +13,7 @@ from django.utils import timezone
from core.models import User
from mailbox_manager import enums
from mailbox_manager.models import DomainInvitation, MailDomainAccess
from mailbox_manager.models import MailDomainAccess, MailDomainInvitation
from mailbox_manager.utils.dimail import DimailAPIClient
logger = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ def convert_domain_invitations(sender, created, instance, **kwargs): # pylint:
"""
logger.info("Convert domain invitations for user %s", instance)
if created:
valid_domain_invitations = DomainInvitation.objects.filter(
valid_domain_invitations = MailDomainInvitation.objects.filter(
email=instance.email,
created_at__gte=(
timezone.now()

View File

@@ -1,5 +1,6 @@
"""
Tests for DomainInvitations API endpoint in People's app mailbox_manager. Focus on "create" action.
Tests for MailDomainInvitations API endpoint in People's app mailbox_manager.
Focus on "create" action.
"""
import pytest
@@ -17,8 +18,8 @@ pytestmark = pytest.mark.django_db
def test_api_domain_invitations__create__anonymous():
"""Anonymous users should not be able to create invitations."""
domain = factories.MailDomainEnabledFactory()
invitation_values = serializers.DomainInvitationSerializer(
factories.DomainInvitationFactory.build()
invitation_values = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build()
).data
response = APIClient().post(
@@ -37,8 +38,8 @@ def test_api_domain_invitations__create__authenticated_outsider():
for a domain they don't manage."""
user = core_factories.UserFactory()
domain = factories.MailDomainEnabledFactory()
invitation_values = serializers.DomainInvitationSerializer(
factories.DomainInvitationFactory.build()
invitation_values = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build()
).data
client = APIClient()
@@ -62,8 +63,8 @@ def test_api_domain_invitations__admin_should_create_invites(role):
domain = factories.MailDomainEnabledFactory()
factories.MailDomainAccessFactory(domain=domain, user=user, role=role)
invitation_values = serializers.DomainInvitationSerializer(
factories.DomainInvitationFactory.build()
invitation_values = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build()
).data
client = APIClient()
@@ -87,8 +88,8 @@ def test_api_domain_invitations__viewers_should_not_invite_to_manage_domains():
domain=domain, user=user, role=enums.MailDomainRoleChoices.VIEWER
)
invitation_values = serializers.DomainInvitationSerializer(
factories.DomainInvitationFactory.build()
invitation_values = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build()
).data
client = APIClient()
@@ -107,7 +108,7 @@ def test_api_domain_invitations__viewers_should_not_invite_to_manage_domains():
def test_api_domain_invitations__should_not_create_duplicate_invitations():
"""An email should not be invited multiple times to the same domain."""
existing_invitation = factories.DomainInvitationFactory()
existing_invitation = factories.MailDomainInvitationFactory()
domain = existing_invitation.domain
# Grant privileged role on the domain to the user
@@ -117,8 +118,8 @@ def test_api_domain_invitations__should_not_create_duplicate_invitations():
)
# Create a new invitation to the same domain with the exact same email address
duplicated_invitation = serializers.DomainInvitationSerializer(
factories.DomainInvitationFactory.build(email=existing_invitation.email)
duplicated_invitation = serializers.MailDomainInvitationSerializer(
factories.MailDomainInvitationFactory.build(email=existing_invitation.email)
).data
client = APIClient()
@@ -130,5 +131,5 @@ def test_api_domain_invitations__should_not_create_duplicate_invitations():
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["__all__"] == [
"Domain invitation with this Email address and Domain already exists."
"Mail domain invitation with this Email address and Domain already exists."
]

View File

@@ -1,5 +1,6 @@
"""
Tests for DomainInvitations API endpoint in People's app mailbox_manager. Focus on "list" action.
Tests for MailDomainInvitations API endpoint in People's app mailbox_manager.
Focus on "list" action.
"""
import time
@@ -37,24 +38,24 @@ def test_api_domain_invitations__domain_managers_should_list_invitations():
factories.MailDomainAccessFactory(
domain=domain, user=other_user, role=enums.MailDomainRoleChoices.OWNER
)
invitation = factories.DomainInvitationFactory(
invitation = factories.MailDomainInvitationFactory(
domain=domain, role=enums.MailDomainRoleChoices.ADMIN, issuer=auth_user
)
other_invitations = factories.DomainInvitationFactory.create_batch(
other_invitations = factories.MailDomainInvitationFactory.create_batch(
2, domain=domain, role=enums.MailDomainRoleChoices.VIEWER, issuer=other_user
)
# expired invitations should be listed too
# override settings to accelerate validation expiration
settings.INVITATION_VALIDITY_DURATION = 1 # second
expired_invitation = factories.DomainInvitationFactory(
expired_invitation = factories.MailDomainInvitationFactory(
domain=domain, role=enums.MailDomainRoleChoices.VIEWER, issuer=auth_user
)
time.sleep(1)
# invitations from other teams should not be listed
other_domain = factories.MailDomainEnabledFactory()
factories.DomainInvitationFactory.create_batch(
factories.MailDomainInvitationFactory.create_batch(
2, domain=other_domain, role=enums.MailDomainRoleChoices.OWNER
)

View File

@@ -1,5 +1,5 @@
"""
Tests for DomainInvitations API endpoint. Focus on "retrieve" action.
Tests for MailDomainInvitations API endpoint. Focus on "retrieve" action.
"""
import pytest
@@ -18,7 +18,7 @@ def test_api_domain_invitations__anonymous_user_should_not_retrieve_invitations(
Anonymous user should not be able to retrieve invitations.
"""
invitation = factories.DomainInvitationFactory()
invitation = factories.MailDomainInvitationFactory()
response = APIClient().get(
f"/api/v1.0/mail-domains/{invitation.domain.slug}/invitations/",
)
@@ -31,7 +31,7 @@ def test_api_domain_invitations__unrelated_user_should_not_retrieve_invitations(
Authenticated unrelated users should not be able to retrieve invitations.
"""
auth_user = core_factories.UserFactory()
invitation = factories.DomainInvitationFactory()
invitation = factories.MailDomainInvitationFactory()
client = APIClient()
client.force_login(auth_user)
@@ -50,7 +50,7 @@ def test_api_domain_invitations__domain_managers_should_list_invitations():
whatever their role in the domain.
"""
auth_user = core_factories.UserFactory()
invitation = factories.DomainInvitationFactory()
invitation = factories.MailDomainInvitationFactory()
factories.MailDomainAccessFactory(
domain=invitation.domain, user=auth_user, role=enums.MailDomainRoleChoices.ADMIN
)

View File

@@ -1,5 +1,5 @@
"""
Unit tests for the Domain Invitation model
Unit tests for the Mail Domain Invitation model
"""
import re
@@ -23,7 +23,7 @@ pytestmark = pytest.mark.django_db
def test_models_domain_invitations_readonly_after_create():
"""Existing invitations should be readonly."""
invitation = factories.DomainInvitationFactory()
invitation = factories.MailDomainInvitationFactory()
with pytest.raises(exceptions.PermissionDenied):
invitation.save()
@@ -33,7 +33,7 @@ def test_models_domain_invitations__is_expired():
The 'is_expired' property should return False until validity duration
is exceeded and True afterwards.
"""
expired_invitation = factories.DomainInvitationFactory()
expired_invitation = factories.MailDomainInvitationFactory()
assert expired_invitation.is_expired is False
settings.INVITATION_VALIDITY_DURATION = 1
@@ -49,17 +49,17 @@ def test_models_domain_invitation__should_convert_invitations_to_accesses_upon_j
"""
# Two invitations to the same mail but to different domains
email = "future_admin@example.com"
invitation_to_domain1 = factories.DomainInvitationFactory(
invitation_to_domain1 = factories.MailDomainInvitationFactory(
email=email, role=enums.MailDomainRoleChoices.OWNER
)
invitation_to_domain2 = factories.DomainInvitationFactory(email=email)
invitation_to_domain2 = factories.MailDomainInvitationFactory(email=email)
# an expired invitation that should not be converted
with freeze_time("1985-10-30"):
expired_invitation = factories.DomainInvitationFactory(email=email)
expired_invitation = factories.MailDomainInvitationFactory(email=email)
# another person invited to domain2
other_invitation = factories.DomainInvitationFactory(
other_invitation = factories.MailDomainInvitationFactory(
domain=invitation_to_domain2.domain
)
@@ -89,15 +89,15 @@ def test_models_domain_invitation__should_convert_invitations_to_accesses_upon_j
assert models.MailDomainAccess.objects.filter(
domain=invitation_to_domain2.domain, user=new_user
).exists()
assert not models.DomainInvitation.objects.filter(
assert not models.MailDomainInvitation.objects.filter(
domain=invitation_to_domain1.domain, email=email
).exists() # invitation "consumed"
assert not models.DomainInvitation.objects.filter(
assert not models.MailDomainInvitation.objects.filter(
domain=invitation_to_domain2.domain, email=email
).exists() # invitation "consumed"
assert models.DomainInvitation.objects.filter(
assert models.MailDomainInvitation.objects.filter(
domain=expired_invitation.domain, email=email
).exists() # expired invitation remains
assert models.DomainInvitation.objects.filter(
assert models.MailDomainInvitation.objects.filter(
domain=invitation_to_domain2.domain, email=other_invitation.email
).exists() # the other invitation remains

View File

@@ -26,7 +26,7 @@ maildomain_related_router.register(
maildomain_related_router.register(
"invitations",
viewsets.DomainInvitationViewset,
viewsets.MailDomainInvitationViewset,
basename="invitations",
)