diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py index 2565a19cd..343fa61fc 100644 --- a/modoboa/admin/api/v2/serializers.py +++ b/modoboa/admin/api/v2/serializers.py @@ -544,10 +544,10 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer): if aliases: for alias in aliases: models.modify_or_create_alias( - "{}@{}".format(alias["localpart"], alias["domain"]), - [user.username], - creator, - alias["domain"] + address="{}@{}".format(alias["localpart"], alias["domain"]), + recipients=[user.username], + creator=creator, + domain=alias["domain"] ) return user @@ -572,14 +572,24 @@ class WritableAccountSerializer(v1_serializers.WritableAccountSerializer): mailbox_data["full_address"] = validated_data["username"] instance.email = validated_data["username"] self._create_mailbox(creator, instance, mailbox_data) - if aliases and "username" in validated_data: + if aliases is not None and "username" in validated_data: + # We create a list to keep track of alias we need to delete + alias_recipients = instance.mailbox.alias_addresses for alias in aliases: + address="{}@{}".format(alias["localpart"], alias["domain"]) models.modify_or_create_alias( - "{}@{}".format(alias["localpart"], alias["domain"]), - [validated_data["username"]], - creator, - alias["domain"] + address=address, + recipients=[validated_data["username"]], + creator=creator, + domain=alias["domain"] ) + try: + alias_recipients.remove(address) + except ValueError: + continue + for alias in alias_recipients: + models.remove_recipient_from_alias( + alias, validated_data["username"]) instance.save() resources = validated_data.get("resources") if resources: diff --git a/modoboa/admin/api/v2/tests.py b/modoboa/admin/api/v2/tests.py index 14c4328bf..0f09ae935 100644 --- a/modoboa/admin/api/v2/tests.py +++ b/modoboa/admin/api/v2/tests.py @@ -253,7 +253,10 @@ class AccountViewSetTestCase(ModoAPITestCase): "password": "Toto12345", "mailbox": { "quota": 10 - } + }, + "aliases": [ + "aliasupdate1@test.com" + ] } resp = self.client.patch(url, data, format="json") self.assertEqual(resp.status_code, 200) @@ -304,6 +307,80 @@ class AccountViewSetTestCase(ModoAPITestCase): self.assertEqual(account.mailbox.message_limit, None) self.assertEqual(account.mailbox.is_send_only, False) + def test_update_aliases(self): + account = core_models.User.objects.get(username="user@test.com") + url = reverse("v2:account-detail", args=[account.pk]) + data = { + "username": "user@test.com", + "role": "SimpleUsers", + "password": "Toto12345", + "mailbox": { + "quota": 10 + }, + "aliases": [ + "aliasupdate1@test.com" + ] + } + resp = self.client.patch(url, data, format="json") + self.assertEqual(resp.status_code, 200) + self.assertTrue( + models.Alias.objects.filter(address="aliasupdate1@test.com").exists()) + + # Create an alias for another user + url = reverse("v2:account-list") + data = { + "username": "toto@test.com", + "role": "SimpleUsers", + "mailbox": { + "use_domain_quota": True + }, + "password": "Toto12345", + "language": "fr", + "aliases": ["totoalias@test.com"] + } + resp = self.client.post(url, data, format="json") + self.assertEqual(resp.status_code, 201) + + # Try updating existing account with this newly created alias + url = reverse("v2:account-detail", args=[account.pk]) + data = { + "username": "user@test.com", + "role": "SimpleUsers", + "password": "Toto12345", + "mailbox": { + "quota": 10 + }, + "aliases": [ + "totoalias@test.com", + "aliasupdate1@test.com", + ] + } + resp = self.client.patch(url, data, format="json") + self.assertEqual(resp.status_code, 200) + alias = models.Alias.objects.filter(address="totoalias@test.com") + self.assertTrue(alias.exists()) + alias_recipients = list(alias.first().recipients) + self.assertIn("toto@test.com", alias_recipients) + self.assertIn("user@test.com", alias_recipients) + + # Try deleting the aliases + data = { + "username": "user@test.com", + "role": "SimpleUsers", + "mailbox": { + "quota": 10 + }, + "aliases": [] + } + resp = self.client.patch(url, data, format="json") + self.assertEqual(resp.status_code, 200) + self.assertFalse( + models.Alias.objects.filter(address="aliasupdate1@test.com").exists()) + alias_recipients = list(models.Alias.objects.filter( + address="totoalias@test.com").first().recipients + ) + self.assertEqual(alias_recipients, ["toto@test.com"]) + def test_update_admin(self): account = core_models.User.objects.get(username="admin") url = reverse("v2:account-detail", args=[account.pk]) diff --git a/modoboa/admin/management/commands/subcommands/_import.py b/modoboa/admin/management/commands/subcommands/_import.py index 4834dfb2a..cd70af320 100644 --- a/modoboa/admin/management/commands/subcommands/_import.py +++ b/modoboa/admin/management/commands/subcommands/_import.py @@ -68,7 +68,8 @@ class ImportCommand(BaseCommand): except ValidationError as e: raise CommandError( _("It seems that your CSV is badly formatted at row: ") + - options["sepchar"].join(row) + options["sepchar"].join(row) + + _(f"Details: {e.message}") ) if not fct: continue diff --git a/modoboa/admin/models/__init__.py b/modoboa/admin/models/__init__.py index 25f713197..6dcfc1c40 100644 --- a/modoboa/admin/models/__init__.py +++ b/modoboa/admin/models/__init__.py @@ -2,7 +2,11 @@ from .alarm import Alarm from .alias import ( - Alias, AliasRecipient, validate_alias_address, modify_or_create_alias) + Alias, + AliasRecipient, + validate_alias_address, + modify_or_create_alias, + remove_recipient_from_alias) from .base import AdminObject from .domain import Domain from .domain_alias import DomainAlias @@ -24,4 +28,5 @@ __all__ = [ "SenderAddress", "validate_alias_address", "modify_or_create_alias", + "remove_recipient_from_alias", ] diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py index b07495dff..f6497c01e 100644 --- a/modoboa/admin/models/alias.py +++ b/modoboa/admin/models/alias.py @@ -56,7 +56,7 @@ def validate_alias_address( return local_part, domain -def modify_or_create_alias(address, recipients, creator=None, domain=None): +def modify_or_create_alias(address, recipients, creator, domain): """Add recipient if the alias already exists or create it.""" alias = Alias.objects.filter(address=address, internal=False) @@ -71,6 +71,24 @@ def modify_or_create_alias(address, recipients, creator=None, domain=None): ) +def remove_recipient_from_alias(address, recipient_to_delete): + """Delete the selected recipient from an alias + or delete the whole alias if only one is left.""" + alias = Alias.objects.filter( + address=address, internal=False) + if alias.exists(): + alias_recipients = list(alias.first().recipients) + if recipient_to_delete in alias_recipients: + if len(alias_recipients) == 1: + # Only recipient, we delete the AliasExists + alias.delete() + else: + alias_recipients.remove(recipient_to_delete) + alias = alias.first() + alias.set_recipients(alias_recipients) + alias.save() + + class AliasManager(models.Manager): """Custom manager for Alias."""