diff --git a/src/backend/mailbox_manager/api/client/serializers.py b/src/backend/mailbox_manager/api/client/serializers.py index 43034d4..2f12ec6 100644 --- a/src/backend/mailbox_manager/api/client/serializers.py +++ b/src/backend/mailbox_manager/api/client/serializers.py @@ -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"] diff --git a/src/backend/mailbox_manager/api/client/viewsets.py b/src/backend/mailbox_manager/api/client/viewsets.py index 30b07f2..b376b27 100644 --- a/src/backend/mailbox_manager/api/client/viewsets.py +++ b/src/backend/mailbox_manager/api/client/viewsets.py @@ -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.""" diff --git a/src/backend/mailbox_manager/factories.py b/src/backend/mailbox_manager/factories.py index fa443ba..21f52e8 100644 --- a/src/backend/mailbox_manager/factories.py +++ b/src/backend/mailbox_manager/factories.py @@ -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") diff --git a/src/backend/mailbox_manager/migrations/0024_domaininvitation.py b/src/backend/mailbox_manager/migrations/0024_domaininvitation.py index 5933133..231954f 100644 --- a/src/backend/mailbox_manager/migrations/0024_domaininvitation.py +++ b/src/backend/mailbox_manager/migrations/0024_domaininvitation.py @@ -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')], }, ), diff --git a/src/backend/mailbox_manager/models.py b/src/backend/mailbox_manager/models.py index d2494e0..0c8b233 100644 --- a/src/backend/mailbox_manager/models.py +++ b/src/backend/mailbox_manager/models.py @@ -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" diff --git a/src/backend/mailbox_manager/signals.py b/src/backend/mailbox_manager/signals.py index cefba00..c74c2a1 100644 --- a/src/backend/mailbox_manager/signals.py +++ b/src/backend/mailbox_manager/signals.py @@ -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() diff --git a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_create.py b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_create.py index a57a238..fd5db23 100644 --- a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_create.py +++ b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_create.py @@ -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." ] diff --git a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_list.py b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_list.py index 63869da..6cc18f4 100644 --- a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_list.py +++ b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_list.py @@ -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 ) diff --git a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_retrieve.py b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_retrieve.py index 2b0349d..93bf457 100644 --- a/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_retrieve.py +++ b/src/backend/mailbox_manager/tests/api/invitations/test_api_domain_invitations_retrieve.py @@ -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 ) diff --git a/src/backend/mailbox_manager/tests/models/test_invitations.py b/src/backend/mailbox_manager/tests/models/test_invitations.py index c15abd7..3a3d50d 100644 --- a/src/backend/mailbox_manager/tests/models/test_invitations.py +++ b/src/backend/mailbox_manager/tests/models/test_invitations.py @@ -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 diff --git a/src/backend/mailbox_manager/urls.py b/src/backend/mailbox_manager/urls.py index dc67cfa..42f4991 100644 --- a/src/backend/mailbox_manager/urls.py +++ b/src/backend/mailbox_manager/urls.py @@ -26,7 +26,7 @@ maildomain_related_router.register( maildomain_related_router.register( "invitations", - viewsets.DomainInvitationViewset, + viewsets.MailDomainInvitationViewset, basename="invitations", )