prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Create alias with tag (+) in recipient address with internal domain
  It's impossible to create new alias with tag in recipient address.
  
  Example : 
  - I've contact@example.com mailbox
  - I would like to create foobar@example.com alias with contact+test@example.com recipient
  
  I've this error « Local recipient contact+test@example.com not found ».
  
  Solution proposition : 
  - use Alias.extmboxes to record this email address with tag
  
  Do you see other solution ?
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `modoboa/extensions/admin/forms/alias.py`
  Content:
  ```
  1 from django import forms
  2 from django.utils.translation import ugettext as _, ugettext_lazy
  3 from django.http import QueryDict
  4 from modoboa.lib.exceptions import BadRequest, NotFound, Conflict
  5 from modoboa.lib.emailutils import split_mailbox
  6 from modoboa.lib.formutils import (
  7     DynamicForm
  8 )
  9 from modoboa.extensions.admin.models import (
  10     Domain, Mailbox, Alias
  11 )
  12 
  13 
  14 class AliasForm(forms.ModelForm, DynamicForm):
  15     email = forms.EmailField(
  16         label=ugettext_lazy("Email address"),
  17         help_text=ugettext_lazy(
  18             "The distribution list address. Use the '*' character to create a "
  19             "'catchall' address (ex: *@domain.tld)."
  20         ),
  21         widget=forms.TextInput(attrs={"class": "form-control"})
  22     )
  23     recipients = forms.EmailField(
  24         label=ugettext_lazy("Recipients"), required=False,
  25         help_text=ugettext_lazy(
  26             "Mailbox(es) this alias will point to. Indicate only one address "
  27             "per input, press ENTER to add a new input."
  28         ),
  29         widget=forms.TextInput(attrs={"class": "form-control"})
  30     )
  31 
  32     class Meta:
  33         model = Alias
  34         fields = ("enabled",)
  35 
  36     def __init__(self, user, *args, **kwargs):
  37         self.user = user
  38         super(AliasForm, self).__init__(*args, **kwargs)
  39         self.fields.keyOrder = ['email', 'recipients', 'enabled']
  40 
  41         if len(args) and isinstance(args[0], QueryDict):
  42             if "instance" in kwargs:
  43                 if not kwargs["instance"].domain.enabled:
  44                     del self.fields["enabled"]
  45             self._load_from_qdict(args[0], "recipients", forms.EmailField)
  46         elif "instance" in kwargs:
  47             dlist = kwargs["instance"]
  48             self.fields["email"].initial = dlist.full_address
  49             if not dlist.domain.enabled:
  50                 self.fields["enabled"].widget.attrs["disabled"] = "disabled"
  51             cpt = 1
  52             for al in dlist.aliases.all():
  53                 name = "recipients_%d" % cpt
  54                 self._create_field(forms.EmailField, name, al.full_address, 2)
  55                 cpt += 1
  56             for mb in dlist.mboxes.all():
  57                 name = "recipients_%d" % (cpt)
  58                 self._create_field(forms.EmailField, name, mb.full_address, 2)
  59                 cpt += 1
  60             for addr in dlist.extmboxes.split(','):
  61                 if addr == "":
  62                     continue
  63                 name = "recipients_%d" % (cpt)
  64                 self._create_field(forms.EmailField, name, addr, 2)
  65                 cpt += 1
  66 
  67     def clean_email(self):
  68         localpart, domname = split_mailbox(self.cleaned_data["email"])
  69         try:
  70             domain = Domain.objects.get(name=domname)
  71         except Domain.DoesNotExist:
  72             raise forms.ValidationError(_("Domain does not exist"))
  73         if not self.user.can_access(domain):
  74             raise forms.ValidationError(
  75                 _("You don't have access to this domain")
  76             )
  77         return self.cleaned_data["email"].lower()
  78 
  79     def set_recipients(self):
  80         """Recipients dispatching
  81 
  82         We make a difference between 'local' recipients (the ones hosted
  83         by Modoboa) and 'external' recipients.
  84         """
  85         self.ext_rcpts = []
  86         self.int_rcpts = []
  87         total = 0
  88 
  89         for k, v in self.cleaned_data.items():
  90             if not k.startswith("recipients"):
  91                 continue
  92             if v == "":
  93                 continue
  94             local_part, domname = split_mailbox(v)
  95             if domname is None:
  96                 raise BadRequest(
  97                     u"%s %s" % (_("Invalid mailbox"), v)
  98                 )
  99             try:
  100                 domain = Domain.objects.get(name=domname)
  101             except Domain.DoesNotExist:
  102                 domain = None
  103             if domain is not None:
  104                 try:
  105                     rcpt = Alias.objects.get(domain=domain, address=local_part)
  106                     if rcpt.full_address == self.cleaned_data["email"]:
  107                         rcpt = None
  108                 except Alias.DoesNotExist:
  109                     rcpt = None
  110                 if rcpt is None:
  111                     try:
  112                         rcpt = Mailbox.objects.get(domain=domain, address=local_part)
  113                     except Mailbox.DoesNotExist:
  114                         raise NotFound(
  115                             _("Local recipient %s not found" % v)
  116                         )
  117                 if rcpt in self.int_rcpts:
  118                     raise Conflict(
  119                         _("Recipient %s already present" % v)
  120                     )
  121                 self.int_rcpts += [rcpt]
  122                 total += 1
  123                 continue
  124 
  125             if v in self.ext_rcpts:
  126                 raise Conflict(
  127                     _("Recipient %s already present" % v)
  128                 )
  129             self.ext_rcpts += [v]
  130             total += 1
  131 
  132         if total == 0:
  133             raise BadRequest(_("No recipient defined"))
  134 
  135     def save(self, commit=True):
  136         alias = super(AliasForm, self).save(commit=False)
  137         localpart, domname = split_mailbox(self.cleaned_data["email"])
  138         alias.address = localpart
  139         alias.domain = Domain.objects.get(name=domname)
  140         if commit:
  141             alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)
  142             self.save_m2m()
  143         return alias
  144
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/modoboa/extensions/admin/forms/alias.py b/modoboa/extensions/admin/forms/alias.py
  --- a/modoboa/extensions/admin/forms/alias.py
  +++ b/modoboa/extensions/admin/forms/alias.py
  @@ -96,31 +96,36 @@
                   raise BadRequest(
                       u"%s %s" % (_("Invalid mailbox"), v)
                   )
  -            try:
  -                domain = Domain.objects.get(name=domname)
  -            except Domain.DoesNotExist:
  -                domain = None
  +
  +            # Support tag in recipient, see https://github.com/tonioo/modoboa/issues/713
  +            local_part_with_tag = None
  +            if '+' in local_part:
  +                local_part_with_tag = local_part
  +                local_part = local_part[0:local_part.find('+')]
  +
  +            domain = Domain.objects.filter(name=domname).first()
  +
               if domain is not None:
  -                try:
  -                    rcpt = Alias.objects.get(domain=domain, address=local_part)
  -                    if rcpt.full_address == self.cleaned_data["email"]:
  -                        rcpt = None
  -                except Alias.DoesNotExist:
  +                rcpt = Alias.objects.filter(domain=domain, address=local_part).first()
  +                if rcpt and (rcpt.full_address == self.cleaned_data["email"]):
                       rcpt = None
  +
                   if rcpt is None:
                       try:
                           rcpt = Mailbox.objects.get(domain=domain, address=local_part)
                       except Mailbox.DoesNotExist:
                           raise NotFound(
  -                            _("Local recipient %s not found" % v)
  +                            _("Local recipient %s@%s not found" % (local_part, domname))
                           )
  -                if rcpt in self.int_rcpts:
  -                    raise Conflict(
  -                        _("Recipient %s already present" % v)
  -                    )
  -                self.int_rcpts += [rcpt]
  -                total += 1
  -                continue
  +
  +                if local_part_with_tag is None:
  +                    if rcpt in self.int_rcpts:
  +                        raise Conflict(
  +                            _("Recipient %s already present" % v)
  +                        )
  +                    self.int_rcpts += [rcpt]
  +                    total += 1
  +                    continue
   
               if v in self.ext_rcpts:
                   raise Conflict(
  @@ -134,8 +139,8 @@
   
       def save(self, commit=True):
           alias = super(AliasForm, self).save(commit=False)
  -        localpart, domname = split_mailbox(self.cleaned_data["email"])
  -        alias.address = localpart
  +        local_part, domname = split_mailbox(self.cleaned_data["email"])
  +        alias.address = local_part
           alias.domain = Domain.objects.get(name=domname)
           if commit:
               alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)
  
verification_info: {"golden_diff": "diff --git a/modoboa/extensions/admin/forms/alias.py b/modoboa/extensions/admin/forms/alias.py\n--- a/modoboa/extensions/admin/forms/alias.py\n+++ b/modoboa/extensions/admin/forms/alias.py\n@@ -96,31 +96,36 @@\n                 raise BadRequest(\n                     u\"%s %s\" % (_(\"Invalid mailbox\"), v)\n                 )\n-            try:\n-                domain = Domain.objects.get(name=domname)\n-            except Domain.DoesNotExist:\n-                domain = None\n+\n+            # Support tag in recipient, see https://github.com/tonioo/modoboa/issues/713\n+            local_part_with_tag = None\n+            if '+' in local_part:\n+                local_part_with_tag = local_part\n+                local_part = local_part[0:local_part.find('+')]\n+\n+            domain = Domain.objects.filter(name=domname).first()\n+\n             if domain is not None:\n-                try:\n-                    rcpt = Alias.objects.get(domain=domain, address=local_part)\n-                    if rcpt.full_address == self.cleaned_data[\"email\"]:\n-                        rcpt = None\n-                except Alias.DoesNotExist:\n+                rcpt = Alias.objects.filter(domain=domain, address=local_part).first()\n+                if rcpt and (rcpt.full_address == self.cleaned_data[\"email\"]):\n                     rcpt = None\n+\n                 if rcpt is None:\n                     try:\n                         rcpt = Mailbox.objects.get(domain=domain, address=local_part)\n                     except Mailbox.DoesNotExist:\n                         raise NotFound(\n-                            _(\"Local recipient %s not found\" % v)\n+                            _(\"Local recipient %s@%s not found\" % (local_part, domname))\n                         )\n-                if rcpt in self.int_rcpts:\n-                    raise Conflict(\n-                        _(\"Recipient %s already present\" % v)\n-                    )\n-                self.int_rcpts += [rcpt]\n-                total += 1\n-                continue\n+\n+                if local_part_with_tag is None:\n+                    if rcpt in self.int_rcpts:\n+                        raise Conflict(\n+                            _(\"Recipient %s already present\" % v)\n+                        )\n+                    self.int_rcpts += [rcpt]\n+                    total += 1\n+                    continue\n \n             if v in self.ext_rcpts:\n                 raise Conflict(\n@@ -134,8 +139,8 @@\n \n     def save(self, commit=True):\n         alias = super(AliasForm, self).save(commit=False)\n-        localpart, domname = split_mailbox(self.cleaned_data[\"email\"])\n-        alias.address = localpart\n+        local_part, domname = split_mailbox(self.cleaned_data[\"email\"])\n+        alias.address = local_part\n         alias.domain = Domain.objects.get(name=domname)\n         if commit:\n             alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)\n", "issue": "Create alias with tag (+) in recipient address with internal domain\nIt's impossible to create new alias with tag in recipient address.\n\nExample : \n- I've contact@example.com mailbox\n- I would like to create foobar@example.com alias with contact+test@example.com recipient\n\nI've this error \u00ab Local recipient contact+test@example.com not found \u00bb.\n\nSolution proposition : \n- use Alias.extmboxes to record this email address with tag\n\nDo you see other solution ?\n\n", "before_files": [{"content": "from django import forms\nfrom django.utils.translation import ugettext as _, ugettext_lazy\nfrom django.http import QueryDict\nfrom modoboa.lib.exceptions import BadRequest, NotFound, Conflict\nfrom modoboa.lib.emailutils import split_mailbox\nfrom modoboa.lib.formutils import (\n    DynamicForm\n)\nfrom modoboa.extensions.admin.models import (\n    Domain, Mailbox, Alias\n)\n\n\nclass AliasForm(forms.ModelForm, DynamicForm):\n    email = forms.EmailField(\n        label=ugettext_lazy(\"Email address\"),\n        help_text=ugettext_lazy(\n            \"The distribution list address. Use the '*' character to create a \"\n            \"'catchall' address (ex: *@domain.tld).\"\n        ),\n        widget=forms.TextInput(attrs={\"class\": \"form-control\"})\n    )\n    recipients = forms.EmailField(\n        label=ugettext_lazy(\"Recipients\"), required=False,\n        help_text=ugettext_lazy(\n            \"Mailbox(es) this alias will point to. Indicate only one address \"\n            \"per input, press ENTER to add a new input.\"\n        ),\n        widget=forms.TextInput(attrs={\"class\": \"form-control\"})\n    )\n\n    class Meta:\n        model = Alias\n        fields = (\"enabled\",)\n\n    def __init__(self, user, *args, **kwargs):\n        self.user = user\n        super(AliasForm, self).__init__(*args, **kwargs)\n        self.fields.keyOrder = ['email', 'recipients', 'enabled']\n\n        if len(args) and isinstance(args[0], QueryDict):\n            if \"instance\" in kwargs:\n                if not kwargs[\"instance\"].domain.enabled:\n                    del self.fields[\"enabled\"]\n            self._load_from_qdict(args[0], \"recipients\", forms.EmailField)\n        elif \"instance\" in kwargs:\n            dlist = kwargs[\"instance\"]\n            self.fields[\"email\"].initial = dlist.full_address\n            if not dlist.domain.enabled:\n                self.fields[\"enabled\"].widget.attrs[\"disabled\"] = \"disabled\"\n            cpt = 1\n            for al in dlist.aliases.all():\n                name = \"recipients_%d\" % cpt\n                self._create_field(forms.EmailField, name, al.full_address, 2)\n                cpt += 1\n            for mb in dlist.mboxes.all():\n                name = \"recipients_%d\" % (cpt)\n                self._create_field(forms.EmailField, name, mb.full_address, 2)\n                cpt += 1\n            for addr in dlist.extmboxes.split(','):\n                if addr == \"\":\n                    continue\n                name = \"recipients_%d\" % (cpt)\n                self._create_field(forms.EmailField, name, addr, 2)\n                cpt += 1\n\n    def clean_email(self):\n        localpart, domname = split_mailbox(self.cleaned_data[\"email\"])\n        try:\n            domain = Domain.objects.get(name=domname)\n        except Domain.DoesNotExist:\n            raise forms.ValidationError(_(\"Domain does not exist\"))\n        if not self.user.can_access(domain):\n            raise forms.ValidationError(\n                _(\"You don't have access to this domain\")\n            )\n        return self.cleaned_data[\"email\"].lower()\n\n    def set_recipients(self):\n        \"\"\"Recipients dispatching\n\n        We make a difference between 'local' recipients (the ones hosted\n        by Modoboa) and 'external' recipients.\n        \"\"\"\n        self.ext_rcpts = []\n        self.int_rcpts = []\n        total = 0\n\n        for k, v in self.cleaned_data.items():\n            if not k.startswith(\"recipients\"):\n                continue\n            if v == \"\":\n                continue\n            local_part, domname = split_mailbox(v)\n            if domname is None:\n                raise BadRequest(\n                    u\"%s %s\" % (_(\"Invalid mailbox\"), v)\n                )\n            try:\n                domain = Domain.objects.get(name=domname)\n            except Domain.DoesNotExist:\n                domain = None\n            if domain is not None:\n                try:\n                    rcpt = Alias.objects.get(domain=domain, address=local_part)\n                    if rcpt.full_address == self.cleaned_data[\"email\"]:\n                        rcpt = None\n                except Alias.DoesNotExist:\n                    rcpt = None\n                if rcpt is None:\n                    try:\n                        rcpt = Mailbox.objects.get(domain=domain, address=local_part)\n                    except Mailbox.DoesNotExist:\n                        raise NotFound(\n                            _(\"Local recipient %s not found\" % v)\n                        )\n                if rcpt in self.int_rcpts:\n                    raise Conflict(\n                        _(\"Recipient %s already present\" % v)\n                    )\n                self.int_rcpts += [rcpt]\n                total += 1\n                continue\n\n            if v in self.ext_rcpts:\n                raise Conflict(\n                    _(\"Recipient %s already present\" % v)\n                )\n            self.ext_rcpts += [v]\n            total += 1\n\n        if total == 0:\n            raise BadRequest(_(\"No recipient defined\"))\n\n    def save(self, commit=True):\n        alias = super(AliasForm, self).save(commit=False)\n        localpart, domname = split_mailbox(self.cleaned_data[\"email\"])\n        alias.address = localpart\n        alias.domain = Domain.objects.get(name=domname)\n        if commit:\n            alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)\n            self.save_m2m()\n        return alias\n", "path": "modoboa/extensions/admin/forms/alias.py"}], "after_files": [{"content": "from django import forms\nfrom django.utils.translation import ugettext as _, ugettext_lazy\nfrom django.http import QueryDict\nfrom modoboa.lib.exceptions import BadRequest, NotFound, Conflict\nfrom modoboa.lib.emailutils import split_mailbox\nfrom modoboa.lib.formutils import (\n    DynamicForm\n)\nfrom modoboa.extensions.admin.models import (\n    Domain, Mailbox, Alias\n)\n\n\nclass AliasForm(forms.ModelForm, DynamicForm):\n    email = forms.EmailField(\n        label=ugettext_lazy(\"Email address\"),\n        help_text=ugettext_lazy(\n            \"The distribution list address. Use the '*' character to create a \"\n            \"'catchall' address (ex: *@domain.tld).\"\n        ),\n        widget=forms.TextInput(attrs={\"class\": \"form-control\"})\n    )\n    recipients = forms.EmailField(\n        label=ugettext_lazy(\"Recipients\"), required=False,\n        help_text=ugettext_lazy(\n            \"Mailbox(es) this alias will point to. Indicate only one address \"\n            \"per input, press ENTER to add a new input.\"\n        ),\n        widget=forms.TextInput(attrs={\"class\": \"form-control\"})\n    )\n\n    class Meta:\n        model = Alias\n        fields = (\"enabled\",)\n\n    def __init__(self, user, *args, **kwargs):\n        self.user = user\n        super(AliasForm, self).__init__(*args, **kwargs)\n        self.fields.keyOrder = ['email', 'recipients', 'enabled']\n\n        if len(args) and isinstance(args[0], QueryDict):\n            if \"instance\" in kwargs:\n                if not kwargs[\"instance\"].domain.enabled:\n                    del self.fields[\"enabled\"]\n            self._load_from_qdict(args[0], \"recipients\", forms.EmailField)\n        elif \"instance\" in kwargs:\n            dlist = kwargs[\"instance\"]\n            self.fields[\"email\"].initial = dlist.full_address\n            if not dlist.domain.enabled:\n                self.fields[\"enabled\"].widget.attrs[\"disabled\"] = \"disabled\"\n            cpt = 1\n            for al in dlist.aliases.all():\n                name = \"recipients_%d\" % cpt\n                self._create_field(forms.EmailField, name, al.full_address, 2)\n                cpt += 1\n            for mb in dlist.mboxes.all():\n                name = \"recipients_%d\" % (cpt)\n                self._create_field(forms.EmailField, name, mb.full_address, 2)\n                cpt += 1\n            for addr in dlist.extmboxes.split(','):\n                if addr == \"\":\n                    continue\n                name = \"recipients_%d\" % (cpt)\n                self._create_field(forms.EmailField, name, addr, 2)\n                cpt += 1\n\n    def clean_email(self):\n        localpart, domname = split_mailbox(self.cleaned_data[\"email\"])\n        try:\n            domain = Domain.objects.get(name=domname)\n        except Domain.DoesNotExist:\n            raise forms.ValidationError(_(\"Domain does not exist\"))\n        if not self.user.can_access(domain):\n            raise forms.ValidationError(\n                _(\"You don't have access to this domain\")\n            )\n        return self.cleaned_data[\"email\"].lower()\n\n    def set_recipients(self):\n        \"\"\"Recipients dispatching\n\n        We make a difference between 'local' recipients (the ones hosted\n        by Modoboa) and 'external' recipients.\n        \"\"\"\n        self.ext_rcpts = []\n        self.int_rcpts = []\n        total = 0\n\n        for k, v in self.cleaned_data.items():\n            if not k.startswith(\"recipients\"):\n                continue\n            if v == \"\":\n                continue\n            local_part, domname = split_mailbox(v)\n            if domname is None:\n                raise BadRequest(\n                    u\"%s %s\" % (_(\"Invalid mailbox\"), v)\n                )\n\n            # Support tag in recipient, see https://github.com/tonioo/modoboa/issues/713\n            local_part_with_tag = None\n            if '+' in local_part:\n                local_part_with_tag = local_part\n                local_part = local_part[0:local_part.find('+')]\n\n            domain = Domain.objects.filter(name=domname).first()\n\n            if domain is not None:\n                rcpt = Alias.objects.filter(domain=domain, address=local_part).first()\n                if rcpt and (rcpt.full_address == self.cleaned_data[\"email\"]):\n                    rcpt = None\n\n                if rcpt is None:\n                    try:\n                        rcpt = Mailbox.objects.get(domain=domain, address=local_part)\n                    except Mailbox.DoesNotExist:\n                        raise NotFound(\n                            _(\"Local recipient %s@%s not found\" % (local_part, domname))\n                        )\n\n                if local_part_with_tag is None:\n                    if rcpt in self.int_rcpts:\n                        raise Conflict(\n                            _(\"Recipient %s already present\" % v)\n                        )\n                    self.int_rcpts += [rcpt]\n                    total += 1\n                    continue\n\n            if v in self.ext_rcpts:\n                raise Conflict(\n                    _(\"Recipient %s already present\" % v)\n                )\n            self.ext_rcpts += [v]\n            total += 1\n\n        if total == 0:\n            raise BadRequest(_(\"No recipient defined\"))\n\n    def save(self, commit=True):\n        alias = super(AliasForm, self).save(commit=False)\n        local_part, domname = split_mailbox(self.cleaned_data[\"email\"])\n        alias.address = local_part\n        alias.domain = Domain.objects.get(name=domname)\n        if commit:\n            alias.save(int_rcpts=self.int_rcpts, ext_rcpts=self.ext_rcpts)\n            self.save_m2m()\n        return alias\n", "path": "modoboa/extensions/admin/forms/alias.py"}]}