We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
forward : allow local destination
hi

forwarding an account to a local destination isn't working, even when set as admin:
Permission denied: You can't define a forward to a local destination. Please ask your administrator to create an alias instead.

maybe i miss something?
there is
- account1@foo.bar
- account2@foo.bar

during holyday of account1 the emails should be forwarded to account2@foo.bar (with/without keeping local copy as account1 likes).
how to solve this?

thanks a lot



--- 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/admin/forms/forward.py`
Content:
```
1 """Forms related to forwards management."""
2 
3 from django import forms
4 from django.utils.translation import ugettext as _, ugettext_lazy
5 
6 from modoboa.lib.email_utils import split_mailbox
7 from modoboa.lib.exceptions import BadRequest, PermDeniedException
8 
9 from ..models import Domain
10 
11 
12 class ForwardForm(forms.Form):
13     dest = forms.CharField(
14         label=ugettext_lazy("Recipient(s)"),
15         widget=forms.Textarea(attrs={"class": "form-control"}),
16         required=False,
17         help_text=ugettext_lazy(
18             "Indicate one or more recipients separated by a ','")
19     )
20     keepcopies = forms.BooleanField(
21         label=ugettext_lazy("Keep local copies"),
22         required=False,
23         help_text=ugettext_lazy(
24             "Forward messages and store copies into your local mailbox")
25     )
26 
27     def get_recipients(self):
28         recipients = []
29         rawdata = self.cleaned_data["dest"].strip()
30         if not rawdata:
31             return recipients
32         for rcpt in rawdata.split(","):
33             local_part, domname = split_mailbox(rcpt)
34             if not local_part or not domname:
35                 raise BadRequest("Invalid mailbox syntax for %s" % rcpt)
36             try:
37                 Domain.objects.get(name=domname)
38             except Domain.DoesNotExist:
39                 recipients += [rcpt]
40             else:
41                 raise PermDeniedException(
42                     _("You can't define a forward to a local destination. "
43                       "Please ask your administrator to create an alias "
44                       "instead.")
45                 )
46         return recipients
47
```

Path: `modoboa/admin/models/alias.py`
Content:
```
1 """Models related to aliases management."""
2 
3 import hashlib
4 import random
5 
6 from django.core.urlresolvers import reverse
7 from django.db import models
8 from django.utils.encoding import python_2_unicode_compatible, smart_text
9 from django.utils.translation import ugettext as _, ugettext_lazy
10 
11 from reversion import revisions as reversion
12 
13 from modoboa.core import signals as core_signals
14 from modoboa.lib.email_utils import split_mailbox
15 from modoboa.lib.exceptions import (
16     PermDeniedException, BadRequest, Conflict, NotFound
17 )
18 
19 from .base import AdminObject
20 from .domain import Domain
21 from .mailbox import Mailbox
22 from .. import signals
23 
24 
25 @python_2_unicode_compatible
26 class Alias(AdminObject):
27 
28     """Mailbox alias."""
29 
30     address = models.CharField(
31         ugettext_lazy("address"), max_length=254,
32         help_text=ugettext_lazy(
33             "The alias address."
34         )
35     )
36     domain = models.ForeignKey(Domain, null=True)
37     enabled = models.BooleanField(
38         ugettext_lazy("enabled"),
39         help_text=ugettext_lazy("Check to activate this alias"),
40         default=True
41     )
42     internal = models.BooleanField(default=False)
43     description = models.TextField(
44         ugettext_lazy("Description"), blank=True)
45     expire_at = models.DateTimeField(
46         ugettext_lazy("Expire at"), blank=True, null=True)
47     _objectname = 'MailboxAlias'
48 
49     class Meta:
50         permissions = (
51             ("view_aliases", "View aliases"),
52         )
53         ordering = ["address"]
54         unique_together = (("address", "internal"), )
55         app_label = "admin"
56 
57     def __str__(self):
58         return smart_text(self.address)
59 
60     @classmethod
61     def generate_random_address(cls):
62         """Generate a random address (local part)."""
63         m = hashlib.md5()
64         for x in random.sample(xrange(10000000), 60):
65             m.update(str(x))
66         return m.hexdigest()[:20]
67 
68     @property
69     def identity(self):
70         return self.address
71 
72     @property
73     def name_or_rcpt(self):
74         rcpts_count = self.recipients_count
75         if not rcpts_count:
76             return "---"
77         rcpts = self.recipients
78         if rcpts_count > 1:
79             return "%s, ..." % rcpts[0]
80         return rcpts[0]
81 
82     @property
83     def type(self):
84         """FIXME: deprecated."""
85         return "alias"
86 
87     @property
88     def tags(self):
89         return [{"name": "alias", "label": _("alias"), "type": "idt"}]
90 
91     def get_absolute_url(self):
92         """Return detail url for this alias."""
93         return reverse("admin:alias_detail", args=[self.pk])
94 
95     def post_create(self, creator):
96         from modoboa.lib.permissions import grant_access_to_object
97         super(Alias, self).post_create(creator)
98         if creator.is_superuser:
99             for admin in self.domain.admins:
100                 grant_access_to_object(admin, self)
101 
102     def set_recipients(self, address_list):
103         """Set recipients for this alias. Special recipients:
104 
105         * local mailbox + extension: r_mailbox will be set to local mailbox
106         * alias address == recipient address: valid only to keep local copies
107           (when a forward is defined) and to create exceptions when a catchall
108           is defined on the associated domain
109 
110         """
111         to_create = []
112         for address in set(address_list):
113             if not address:
114                 continue
115             if self.aliasrecipient_set.filter(address=address).exists():
116                 continue
117             local_part, domname, extension = (
118                 split_mailbox(address, return_extension=True))
119             if domname is None:
120                 raise BadRequest(
121                     u"%s %s" % (_("Invalid address"), address)
122                 )
123             domain = Domain.objects.filter(name=domname).first()
124             kwargs = {"address": address, "alias": self}
125             if (
126                 (domain is not None) and
127                 (
128                     any(
129                         r[1] for r in signals.use_external_recipients.send(
130                             self, recipients=address)
131                     ) is False
132                 )
133             ):
134                 rcpt = Mailbox.objects.filter(
135                     domain=domain, address=local_part).first()
136                 if rcpt is None:
137                     rcpt = Alias.objects.filter(
138                         address='%s@%s' % (local_part, domname)
139                     ).first()
140                     if rcpt is None:
141                         raise NotFound(
142                             _("Local recipient {}@{} not found")
143                             .format(local_part, domname)
144                         )
145                     if rcpt.address == self.address:
146                         raise Conflict
147                     kwargs["r_alias"] = rcpt
148                 else:
149                     kwargs["r_mailbox"] = rcpt
150             to_create.append(AliasRecipient(**kwargs))
151         AliasRecipient.objects.bulk_create(to_create)
152         # Remove old recipients
153         self.aliasrecipient_set.exclude(
154             address__in=address_list).delete()
155 
156     @property
157     def recipients(self):
158         """Return the recipient list."""
159         return (
160             self.aliasrecipient_set.order_by("address")
161             .values_list("address", flat=True)
162         )
163 
164     @property
165     def recipients_count(self):
166         """Return the number of recipients of this alias."""
167         return self.aliasrecipient_set.count()
168 
169     def from_csv(self, user, row, expected_elements=5):
170         """Create a new alias from a CSV file entry."""
171         if len(row) < expected_elements:
172             raise BadRequest(_("Invalid line: %s" % row))
173         address = row[1].strip()
174         localpart, domname = split_mailbox(address)
175         try:
176             domain = Domain.objects.get(name=domname)
177         except Domain.DoesNotExist:
178             raise BadRequest(_("Domain '%s' does not exist" % domname))
179         if not user.can_access(domain):
180             raise PermDeniedException
181         core_signals.can_create_object.send(
182             sender="import", context=user, object_type="mailbox_aliases")
183         core_signals.can_create_object.send(
184             sender="import", context=domain, object_type="mailbox_aliases")
185         if Alias.objects.filter(address=address).exists():
186             raise Conflict
187         self.address = address
188         self.domain = domain
189         self.enabled = (row[2].strip() in ["True", "1", "yes", "y"])
190         self.save()
191         self.set_recipients([raddress.strip() for raddress in row[3:]])
192         self.post_create(user)
193 
194     def to_csv(self, csvwriter):
195         row = ["alias", self.address.encode("utf-8"), self.enabled]
196         row += self.recipients
197         csvwriter.writerow(row)
198 
199 reversion.register(Alias)
200 
201 
202 @python_2_unicode_compatible
203 class AliasRecipient(models.Model):
204 
205     """An alias recipient."""
206 
207     address = models.EmailField()
208     alias = models.ForeignKey(Alias)
209 
210     # if recipient is a local mailbox
211     r_mailbox = models.ForeignKey(Mailbox, blank=True, null=True)
212     # if recipient is a local alias
213     r_alias = models.ForeignKey(
214         Alias, related_name="alias_recipient_aliases", blank=True, null=True)
215 
216     class Meta:
217         app_label = "admin"
218         db_table = "modoboa_admin_aliasrecipient"
219         unique_together = [
220             ("alias", "r_mailbox"),
221             ("alias", "r_alias")
222         ]
223 
224     def __str__(self):
225         """Return alias and recipient."""
226         return smart_text(
227             "{} -> {}".format(self.alias.address, self.address)
228         )
229
```

Path: `modoboa/admin/views/user.py`
Content:
```
1 """SimpleUsers views."""
2 
3 from django.contrib.auth.decorators import login_required
4 from django.utils.translation import ugettext as _
5 
6 from reversion import revisions as reversion
7 
8 from modoboa.lib.web_utils import (
9     render_to_json_response, _render_to_string
10 )
11 
12 from ..forms import ForwardForm
13 from ..lib import needs_mailbox
14 from ..models import Alias
15 
16 
17 @login_required
18 @needs_mailbox()
19 @reversion.create_revision()
20 def forward(request, tplname="admin/forward.html"):
21     mb = request.user.mailbox
22     al = Alias.objects.filter(address=mb.full_address, internal=False).first()
23     if request.method == "POST":
24         form = ForwardForm(request.POST)
25         if form.is_valid():
26             if al is None:
27                 al = Alias.objects.create(
28                     address=mb.full_address, domain=mb.domain,
29                     enabled=mb.user.is_active)
30             recipients = form.get_recipients()
31             if form.cleaned_data["keepcopies"]:
32                 recipients.append(mb.full_address)
33             al.set_recipients(recipients)
34             al.post_create(request.user)
35             return render_to_json_response(_("Forward updated"))
36 
37         return render_to_json_response(
38             {'form_errors': form.errors}, status=400
39         )
40 
41     form = ForwardForm()
42     if al is not None and al.recipients:
43         recipients = list(al.recipients)
44         if al.aliasrecipient_set.filter(r_mailbox=mb).exists():
45             form.fields["keepcopies"].initial = True
46             recipients.remove(mb.full_address)
47         form.fields["dest"].initial = "\n".join(recipients)
48     return render_to_json_response({
49         "content": _render_to_string(request, tplname, {
50             "form": form
51         })
52     })
53
```
--- 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()

```

diff --git a/modoboa/admin/forms/forward.py b/modoboa/admin/forms/forward.py
--- a/modoboa/admin/forms/forward.py
+++ b/modoboa/admin/forms/forward.py
@@ -1,15 +1,13 @@
 """Forms related to forwards management."""
 
 from django import forms
-from django.utils.translation import ugettext as _, ugettext_lazy
-
-from modoboa.lib.email_utils import split_mailbox
-from modoboa.lib.exceptions import BadRequest, PermDeniedException
-
-from ..models import Domain
+from django.core import validators
+from django.utils.translation import ugettext_lazy
 
 
 class ForwardForm(forms.Form):
+    """Forward definition form."""
+
     dest = forms.CharField(
         label=ugettext_lazy("Recipient(s)"),
         widget=forms.Textarea(attrs={"class": "form-control"}),
@@ -24,23 +22,13 @@
             "Forward messages and store copies into your local mailbox")
     )
 
-    def get_recipients(self):
+    def clean_dest(self):
+        """Check recipients validity."""
+        rawdata = self.cleaned_data.get("dest", "").strip()
         recipients = []
-        rawdata = self.cleaned_data["dest"].strip()
         if not rawdata:
             return recipients
         for rcpt in rawdata.split(","):
-            local_part, domname = split_mailbox(rcpt)
-            if not local_part or not domname:
-                raise BadRequest("Invalid mailbox syntax for %s" % rcpt)
-            try:
-                Domain.objects.get(name=domname)
-            except Domain.DoesNotExist:
-                recipients += [rcpt]
-            else:
-                raise PermDeniedException(
-                    _("You can't define a forward to a local destination. "
-                      "Please ask your administrator to create an alias "
-                      "instead.")
-                )
+            validators.validate_email(rcpt)
+            recipients += [rcpt]
         return recipients
diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py
--- a/modoboa/admin/models/alias.py
+++ b/modoboa/admin/models/alias.py
@@ -100,8 +100,9 @@
                 grant_access_to_object(admin, self)
 
     def set_recipients(self, address_list):
-        """Set recipients for this alias. Special recipients:
+        """Set recipients for this alias.
 
+        Special recipients:
         * local mailbox + extension: r_mailbox will be set to local mailbox
         * alias address == recipient address: valid only to keep local copies
           (when a forward is defined) and to create exceptions when a catchall
diff --git a/modoboa/admin/views/user.py b/modoboa/admin/views/user.py
--- a/modoboa/admin/views/user.py
+++ b/modoboa/admin/views/user.py
@@ -27,11 +27,14 @@
                 al = Alias.objects.create(
                     address=mb.full_address, domain=mb.domain,
                     enabled=mb.user.is_active)
-            recipients = form.get_recipients()
+            recipients = form.cleaned_data["dest"]
             if form.cleaned_data["keepcopies"]:
                 recipients.append(mb.full_address)
             al.set_recipients(recipients)
-            al.post_create(request.user)
+            if len(recipients) == 0:
+                al.delete()
+            else:
+                al.post_create(request.user)
             return render_to_json_response(_("Forward updated"))
 
         return render_to_json_response(

{"golden_diff": "diff --git a/modoboa/admin/forms/forward.py b/modoboa/admin/forms/forward.py\n--- a/modoboa/admin/forms/forward.py\n+++ b/modoboa/admin/forms/forward.py\n@@ -1,15 +1,13 @@\n \"\"\"Forms related to forwards management.\"\"\"\n \n from django import forms\n-from django.utils.translation import ugettext as _, ugettext_lazy\n-\n-from modoboa.lib.email_utils import split_mailbox\n-from modoboa.lib.exceptions import BadRequest, PermDeniedException\n-\n-from ..models import Domain\n+from django.core import validators\n+from django.utils.translation import ugettext_lazy\n \n \n class ForwardForm(forms.Form):\n+    \"\"\"Forward definition form.\"\"\"\n+\n     dest = forms.CharField(\n         label=ugettext_lazy(\"Recipient(s)\"),\n         widget=forms.Textarea(attrs={\"class\": \"form-control\"}),\n@@ -24,23 +22,13 @@\n             \"Forward messages and store copies into your local mailbox\")\n     )\n \n-    def get_recipients(self):\n+    def clean_dest(self):\n+        \"\"\"Check recipients validity.\"\"\"\n+        rawdata = self.cleaned_data.get(\"dest\", \"\").strip()\n         recipients = []\n-        rawdata = self.cleaned_data[\"dest\"].strip()\n         if not rawdata:\n             return recipients\n         for rcpt in rawdata.split(\",\"):\n-            local_part, domname = split_mailbox(rcpt)\n-            if not local_part or not domname:\n-                raise BadRequest(\"Invalid mailbox syntax for %s\" % rcpt)\n-            try:\n-                Domain.objects.get(name=domname)\n-            except Domain.DoesNotExist:\n-                recipients += [rcpt]\n-            else:\n-                raise PermDeniedException(\n-                    _(\"You can't define a forward to a local destination. \"\n-                      \"Please ask your administrator to create an alias \"\n-                      \"instead.\")\n-                )\n+            validators.validate_email(rcpt)\n+            recipients += [rcpt]\n         return recipients\ndiff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py\n--- a/modoboa/admin/models/alias.py\n+++ b/modoboa/admin/models/alias.py\n@@ -100,8 +100,9 @@\n                 grant_access_to_object(admin, self)\n \n     def set_recipients(self, address_list):\n-        \"\"\"Set recipients for this alias. Special recipients:\n+        \"\"\"Set recipients for this alias.\n \n+        Special recipients:\n         * local mailbox + extension: r_mailbox will be set to local mailbox\n         * alias address == recipient address: valid only to keep local copies\n           (when a forward is defined) and to create exceptions when a catchall\ndiff --git a/modoboa/admin/views/user.py b/modoboa/admin/views/user.py\n--- a/modoboa/admin/views/user.py\n+++ b/modoboa/admin/views/user.py\n@@ -27,11 +27,14 @@\n                 al = Alias.objects.create(\n                     address=mb.full_address, domain=mb.domain,\n                     enabled=mb.user.is_active)\n-            recipients = form.get_recipients()\n+            recipients = form.cleaned_data[\"dest\"]\n             if form.cleaned_data[\"keepcopies\"]:\n                 recipients.append(mb.full_address)\n             al.set_recipients(recipients)\n-            al.post_create(request.user)\n+            if len(recipients) == 0:\n+                al.delete()\n+            else:\n+                al.post_create(request.user)\n             return render_to_json_response(_(\"Forward updated\"))\n \n         return render_to_json_response(\n", "issue": "forward : allow local destination\nhi\r\n\r\nforwarding an account to a local destination isn't working, even when set as admin:\r\nPermission denied: You can't define a forward to a local destination. Please ask your administrator to create an alias instead.\r\n\r\nmaybe i miss something?\r\nthere is\r\n- account1@foo.bar\r\n- account2@foo.bar\r\n\r\nduring holyday of account1 the emails should be forwarded to account2@foo.bar (with/without keeping local copy as account1 likes).\r\nhow to solve this?\r\n\r\nthanks a lot\r\n\r\n\n", "before_files": [{"content": "\"\"\"Forms related to forwards management.\"\"\"\n\nfrom django import forms\nfrom django.utils.translation import ugettext as _, ugettext_lazy\n\nfrom modoboa.lib.email_utils import split_mailbox\nfrom modoboa.lib.exceptions import BadRequest, PermDeniedException\n\nfrom ..models import Domain\n\n\nclass ForwardForm(forms.Form):\n    dest = forms.CharField(\n        label=ugettext_lazy(\"Recipient(s)\"),\n        widget=forms.Textarea(attrs={\"class\": \"form-control\"}),\n        required=False,\n        help_text=ugettext_lazy(\n            \"Indicate one or more recipients separated by a ','\")\n    )\n    keepcopies = forms.BooleanField(\n        label=ugettext_lazy(\"Keep local copies\"),\n        required=False,\n        help_text=ugettext_lazy(\n            \"Forward messages and store copies into your local mailbox\")\n    )\n\n    def get_recipients(self):\n        recipients = []\n        rawdata = self.cleaned_data[\"dest\"].strip()\n        if not rawdata:\n            return recipients\n        for rcpt in rawdata.split(\",\"):\n            local_part, domname = split_mailbox(rcpt)\n            if not local_part or not domname:\n                raise BadRequest(\"Invalid mailbox syntax for %s\" % rcpt)\n            try:\n                Domain.objects.get(name=domname)\n            except Domain.DoesNotExist:\n                recipients += [rcpt]\n            else:\n                raise PermDeniedException(\n                    _(\"You can't define a forward to a local destination. \"\n                      \"Please ask your administrator to create an alias \"\n                      \"instead.\")\n                )\n        return recipients\n", "path": "modoboa/admin/forms/forward.py"}, {"content": "\"\"\"Models related to aliases management.\"\"\"\n\nimport hashlib\nimport random\n\nfrom django.core.urlresolvers import reverse\nfrom django.db import models\nfrom django.utils.encoding import python_2_unicode_compatible, smart_text\nfrom django.utils.translation import ugettext as _, ugettext_lazy\n\nfrom reversion import revisions as reversion\n\nfrom modoboa.core import signals as core_signals\nfrom modoboa.lib.email_utils import split_mailbox\nfrom modoboa.lib.exceptions import (\n    PermDeniedException, BadRequest, Conflict, NotFound\n)\n\nfrom .base import AdminObject\nfrom .domain import Domain\nfrom .mailbox import Mailbox\nfrom .. import signals\n\n\n@python_2_unicode_compatible\nclass Alias(AdminObject):\n\n    \"\"\"Mailbox alias.\"\"\"\n\n    address = models.CharField(\n        ugettext_lazy(\"address\"), max_length=254,\n        help_text=ugettext_lazy(\n            \"The alias address.\"\n        )\n    )\n    domain = models.ForeignKey(Domain, null=True)\n    enabled = models.BooleanField(\n        ugettext_lazy(\"enabled\"),\n        help_text=ugettext_lazy(\"Check to activate this alias\"),\n        default=True\n    )\n    internal = models.BooleanField(default=False)\n    description = models.TextField(\n        ugettext_lazy(\"Description\"), blank=True)\n    expire_at = models.DateTimeField(\n        ugettext_lazy(\"Expire at\"), blank=True, null=True)\n    _objectname = 'MailboxAlias'\n\n    class Meta:\n        permissions = (\n            (\"view_aliases\", \"View aliases\"),\n        )\n        ordering = [\"address\"]\n        unique_together = ((\"address\", \"internal\"), )\n        app_label = \"admin\"\n\n    def __str__(self):\n        return smart_text(self.address)\n\n    @classmethod\n    def generate_random_address(cls):\n        \"\"\"Generate a random address (local part).\"\"\"\n        m = hashlib.md5()\n        for x in random.sample(xrange(10000000), 60):\n            m.update(str(x))\n        return m.hexdigest()[:20]\n\n    @property\n    def identity(self):\n        return self.address\n\n    @property\n    def name_or_rcpt(self):\n        rcpts_count = self.recipients_count\n        if not rcpts_count:\n            return \"---\"\n        rcpts = self.recipients\n        if rcpts_count > 1:\n            return \"%s, ...\" % rcpts[0]\n        return rcpts[0]\n\n    @property\n    def type(self):\n        \"\"\"FIXME: deprecated.\"\"\"\n        return \"alias\"\n\n    @property\n    def tags(self):\n        return [{\"name\": \"alias\", \"label\": _(\"alias\"), \"type\": \"idt\"}]\n\n    def get_absolute_url(self):\n        \"\"\"Return detail url for this alias.\"\"\"\n        return reverse(\"admin:alias_detail\", args=[self.pk])\n\n    def post_create(self, creator):\n        from modoboa.lib.permissions import grant_access_to_object\n        super(Alias, self).post_create(creator)\n        if creator.is_superuser:\n            for admin in self.domain.admins:\n                grant_access_to_object(admin, self)\n\n    def set_recipients(self, address_list):\n        \"\"\"Set recipients for this alias. Special recipients:\n\n        * local mailbox + extension: r_mailbox will be set to local mailbox\n        * alias address == recipient address: valid only to keep local copies\n          (when a forward is defined) and to create exceptions when a catchall\n          is defined on the associated domain\n\n        \"\"\"\n        to_create = []\n        for address in set(address_list):\n            if not address:\n                continue\n            if self.aliasrecipient_set.filter(address=address).exists():\n                continue\n            local_part, domname, extension = (\n                split_mailbox(address, return_extension=True))\n            if domname is None:\n                raise BadRequest(\n                    u\"%s %s\" % (_(\"Invalid address\"), address)\n                )\n            domain = Domain.objects.filter(name=domname).first()\n            kwargs = {\"address\": address, \"alias\": self}\n            if (\n                (domain is not None) and\n                (\n                    any(\n                        r[1] for r in signals.use_external_recipients.send(\n                            self, recipients=address)\n                    ) is False\n                )\n            ):\n                rcpt = Mailbox.objects.filter(\n                    domain=domain, address=local_part).first()\n                if rcpt is None:\n                    rcpt = Alias.objects.filter(\n                        address='%s@%s' % (local_part, domname)\n                    ).first()\n                    if rcpt is None:\n                        raise NotFound(\n                            _(\"Local recipient {}@{} not found\")\n                            .format(local_part, domname)\n                        )\n                    if rcpt.address == self.address:\n                        raise Conflict\n                    kwargs[\"r_alias\"] = rcpt\n                else:\n                    kwargs[\"r_mailbox\"] = rcpt\n            to_create.append(AliasRecipient(**kwargs))\n        AliasRecipient.objects.bulk_create(to_create)\n        # Remove old recipients\n        self.aliasrecipient_set.exclude(\n            address__in=address_list).delete()\n\n    @property\n    def recipients(self):\n        \"\"\"Return the recipient list.\"\"\"\n        return (\n            self.aliasrecipient_set.order_by(\"address\")\n            .values_list(\"address\", flat=True)\n        )\n\n    @property\n    def recipients_count(self):\n        \"\"\"Return the number of recipients of this alias.\"\"\"\n        return self.aliasrecipient_set.count()\n\n    def from_csv(self, user, row, expected_elements=5):\n        \"\"\"Create a new alias from a CSV file entry.\"\"\"\n        if len(row) < expected_elements:\n            raise BadRequest(_(\"Invalid line: %s\" % row))\n        address = row[1].strip()\n        localpart, domname = split_mailbox(address)\n        try:\n            domain = Domain.objects.get(name=domname)\n        except Domain.DoesNotExist:\n            raise BadRequest(_(\"Domain '%s' does not exist\" % domname))\n        if not user.can_access(domain):\n            raise PermDeniedException\n        core_signals.can_create_object.send(\n            sender=\"import\", context=user, object_type=\"mailbox_aliases\")\n        core_signals.can_create_object.send(\n            sender=\"import\", context=domain, object_type=\"mailbox_aliases\")\n        if Alias.objects.filter(address=address).exists():\n            raise Conflict\n        self.address = address\n        self.domain = domain\n        self.enabled = (row[2].strip() in [\"True\", \"1\", \"yes\", \"y\"])\n        self.save()\n        self.set_recipients([raddress.strip() for raddress in row[3:]])\n        self.post_create(user)\n\n    def to_csv(self, csvwriter):\n        row = [\"alias\", self.address.encode(\"utf-8\"), self.enabled]\n        row += self.recipients\n        csvwriter.writerow(row)\n\nreversion.register(Alias)\n\n\n@python_2_unicode_compatible\nclass AliasRecipient(models.Model):\n\n    \"\"\"An alias recipient.\"\"\"\n\n    address = models.EmailField()\n    alias = models.ForeignKey(Alias)\n\n    # if recipient is a local mailbox\n    r_mailbox = models.ForeignKey(Mailbox, blank=True, null=True)\n    # if recipient is a local alias\n    r_alias = models.ForeignKey(\n        Alias, related_name=\"alias_recipient_aliases\", blank=True, null=True)\n\n    class Meta:\n        app_label = \"admin\"\n        db_table = \"modoboa_admin_aliasrecipient\"\n        unique_together = [\n            (\"alias\", \"r_mailbox\"),\n            (\"alias\", \"r_alias\")\n        ]\n\n    def __str__(self):\n        \"\"\"Return alias and recipient.\"\"\"\n        return smart_text(\n            \"{} -> {}\".format(self.alias.address, self.address)\n        )\n", "path": "modoboa/admin/models/alias.py"}, {"content": "\"\"\"SimpleUsers views.\"\"\"\n\nfrom django.contrib.auth.decorators import login_required\nfrom django.utils.translation import ugettext as _\n\nfrom reversion import revisions as reversion\n\nfrom modoboa.lib.web_utils import (\n    render_to_json_response, _render_to_string\n)\n\nfrom ..forms import ForwardForm\nfrom ..lib import needs_mailbox\nfrom ..models import Alias\n\n\n@login_required\n@needs_mailbox()\n@reversion.create_revision()\ndef forward(request, tplname=\"admin/forward.html\"):\n    mb = request.user.mailbox\n    al = Alias.objects.filter(address=mb.full_address, internal=False).first()\n    if request.method == \"POST\":\n        form = ForwardForm(request.POST)\n        if form.is_valid():\n            if al is None:\n                al = Alias.objects.create(\n                    address=mb.full_address, domain=mb.domain,\n                    enabled=mb.user.is_active)\n            recipients = form.get_recipients()\n            if form.cleaned_data[\"keepcopies\"]:\n                recipients.append(mb.full_address)\n            al.set_recipients(recipients)\n            al.post_create(request.user)\n            return render_to_json_response(_(\"Forward updated\"))\n\n        return render_to_json_response(\n            {'form_errors': form.errors}, status=400\n        )\n\n    form = ForwardForm()\n    if al is not None and al.recipients:\n        recipients = list(al.recipients)\n        if al.aliasrecipient_set.filter(r_mailbox=mb).exists():\n            form.fields[\"keepcopies\"].initial = True\n            recipients.remove(mb.full_address)\n        form.fields[\"dest\"].initial = \"\\n\".join(recipients)\n    return render_to_json_response({\n        \"content\": _render_to_string(request, tplname, {\n            \"form\": form\n        })\n    })\n", "path": "modoboa/admin/views/user.py"}], "after_files": [{"content": "\"\"\"Forms related to forwards management.\"\"\"\n\nfrom django import forms\nfrom django.core import validators\nfrom django.utils.translation import ugettext_lazy\n\n\nclass ForwardForm(forms.Form):\n    \"\"\"Forward definition form.\"\"\"\n\n    dest = forms.CharField(\n        label=ugettext_lazy(\"Recipient(s)\"),\n        widget=forms.Textarea(attrs={\"class\": \"form-control\"}),\n        required=False,\n        help_text=ugettext_lazy(\n            \"Indicate one or more recipients separated by a ','\")\n    )\n    keepcopies = forms.BooleanField(\n        label=ugettext_lazy(\"Keep local copies\"),\n        required=False,\n        help_text=ugettext_lazy(\n            \"Forward messages and store copies into your local mailbox\")\n    )\n\n    def clean_dest(self):\n        \"\"\"Check recipients validity.\"\"\"\n        rawdata = self.cleaned_data.get(\"dest\", \"\").strip()\n        recipients = []\n        if not rawdata:\n            return recipients\n        for rcpt in rawdata.split(\",\"):\n            validators.validate_email(rcpt)\n            recipients += [rcpt]\n        return recipients\n", "path": "modoboa/admin/forms/forward.py"}, {"content": "\"\"\"Models related to aliases management.\"\"\"\n\nimport hashlib\nimport random\n\nfrom django.core.urlresolvers import reverse\nfrom django.db import models\nfrom django.utils.encoding import python_2_unicode_compatible, smart_text\nfrom django.utils.translation import ugettext as _, ugettext_lazy\n\nfrom reversion import revisions as reversion\n\nfrom modoboa.core import signals as core_signals\nfrom modoboa.lib.email_utils import split_mailbox\nfrom modoboa.lib.exceptions import (\n    PermDeniedException, BadRequest, Conflict, NotFound\n)\n\nfrom .base import AdminObject\nfrom .domain import Domain\nfrom .mailbox import Mailbox\nfrom .. import signals\n\n\n@python_2_unicode_compatible\nclass Alias(AdminObject):\n\n    \"\"\"Mailbox alias.\"\"\"\n\n    address = models.CharField(\n        ugettext_lazy(\"address\"), max_length=254,\n        help_text=ugettext_lazy(\n            \"The alias address.\"\n        )\n    )\n    domain = models.ForeignKey(Domain, null=True)\n    enabled = models.BooleanField(\n        ugettext_lazy(\"enabled\"),\n        help_text=ugettext_lazy(\"Check to activate this alias\"),\n        default=True\n    )\n    internal = models.BooleanField(default=False)\n    description = models.TextField(\n        ugettext_lazy(\"Description\"), blank=True)\n    expire_at = models.DateTimeField(\n        ugettext_lazy(\"Expire at\"), blank=True, null=True)\n    _objectname = 'MailboxAlias'\n\n    class Meta:\n        permissions = (\n            (\"view_aliases\", \"View aliases\"),\n        )\n        ordering = [\"address\"]\n        unique_together = ((\"address\", \"internal\"), )\n        app_label = \"admin\"\n\n    def __str__(self):\n        return smart_text(self.address)\n\n    @classmethod\n    def generate_random_address(cls):\n        \"\"\"Generate a random address (local part).\"\"\"\n        m = hashlib.md5()\n        for x in random.sample(xrange(10000000), 60):\n            m.update(str(x))\n        return m.hexdigest()[:20]\n\n    @property\n    def identity(self):\n        return self.address\n\n    @property\n    def name_or_rcpt(self):\n        rcpts_count = self.recipients_count\n        if not rcpts_count:\n            return \"---\"\n        rcpts = self.recipients\n        if rcpts_count > 1:\n            return \"%s, ...\" % rcpts[0]\n        return rcpts[0]\n\n    @property\n    def type(self):\n        \"\"\"FIXME: deprecated.\"\"\"\n        return \"alias\"\n\n    @property\n    def tags(self):\n        return [{\"name\": \"alias\", \"label\": _(\"alias\"), \"type\": \"idt\"}]\n\n    def get_absolute_url(self):\n        \"\"\"Return detail url for this alias.\"\"\"\n        return reverse(\"admin:alias_detail\", args=[self.pk])\n\n    def post_create(self, creator):\n        from modoboa.lib.permissions import grant_access_to_object\n        super(Alias, self).post_create(creator)\n        if creator.is_superuser:\n            for admin in self.domain.admins:\n                grant_access_to_object(admin, self)\n\n    def set_recipients(self, address_list):\n        \"\"\"Set recipients for this alias.\n\n        Special recipients:\n        * local mailbox + extension: r_mailbox will be set to local mailbox\n        * alias address == recipient address: valid only to keep local copies\n          (when a forward is defined) and to create exceptions when a catchall\n          is defined on the associated domain\n\n        \"\"\"\n        to_create = []\n        for address in set(address_list):\n            if not address:\n                continue\n            if self.aliasrecipient_set.filter(address=address).exists():\n                continue\n            local_part, domname, extension = (\n                split_mailbox(address, return_extension=True))\n            if domname is None:\n                raise BadRequest(\n                    u\"%s %s\" % (_(\"Invalid address\"), address)\n                )\n            domain = Domain.objects.filter(name=domname).first()\n            kwargs = {\"address\": address, \"alias\": self}\n            if (\n                (domain is not None) and\n                (\n                    any(\n                        r[1] for r in signals.use_external_recipients.send(\n                            self, recipients=address)\n                    ) is False\n                )\n            ):\n                rcpt = Mailbox.objects.filter(\n                    domain=domain, address=local_part).first()\n                if rcpt is None:\n                    rcpt = Alias.objects.filter(\n                        address='%s@%s' % (local_part, domname)\n                    ).first()\n                    if rcpt is None:\n                        raise NotFound(\n                            _(\"Local recipient {}@{} not found\")\n                            .format(local_part, domname)\n                        )\n                    if rcpt.address == self.address:\n                        raise Conflict\n                    kwargs[\"r_alias\"] = rcpt\n                else:\n                    kwargs[\"r_mailbox\"] = rcpt\n            to_create.append(AliasRecipient(**kwargs))\n        AliasRecipient.objects.bulk_create(to_create)\n        # Remove old recipients\n        self.aliasrecipient_set.exclude(\n            address__in=address_list).delete()\n\n    @property\n    def recipients(self):\n        \"\"\"Return the recipient list.\"\"\"\n        return (\n            self.aliasrecipient_set.order_by(\"address\")\n            .values_list(\"address\", flat=True)\n        )\n\n    @property\n    def recipients_count(self):\n        \"\"\"Return the number of recipients of this alias.\"\"\"\n        return self.aliasrecipient_set.count()\n\n    def from_csv(self, user, row, expected_elements=5):\n        \"\"\"Create a new alias from a CSV file entry.\"\"\"\n        if len(row) < expected_elements:\n            raise BadRequest(_(\"Invalid line: %s\" % row))\n        address = row[1].strip()\n        localpart, domname = split_mailbox(address)\n        try:\n            domain = Domain.objects.get(name=domname)\n        except Domain.DoesNotExist:\n            raise BadRequest(_(\"Domain '%s' does not exist\" % domname))\n        if not user.can_access(domain):\n            raise PermDeniedException\n        core_signals.can_create_object.send(\n            sender=\"import\", context=user, object_type=\"mailbox_aliases\")\n        core_signals.can_create_object.send(\n            sender=\"import\", context=domain, object_type=\"mailbox_aliases\")\n        if Alias.objects.filter(address=address).exists():\n            raise Conflict\n        self.address = address\n        self.domain = domain\n        self.enabled = (row[2].strip() in [\"True\", \"1\", \"yes\", \"y\"])\n        self.save()\n        self.set_recipients([raddress.strip() for raddress in row[3:]])\n        self.post_create(user)\n\n    def to_csv(self, csvwriter):\n        row = [\"alias\", self.address.encode(\"utf-8\"), self.enabled]\n        row += self.recipients\n        csvwriter.writerow(row)\n\nreversion.register(Alias)\n\n\n@python_2_unicode_compatible\nclass AliasRecipient(models.Model):\n\n    \"\"\"An alias recipient.\"\"\"\n\n    address = models.EmailField()\n    alias = models.ForeignKey(Alias)\n\n    # if recipient is a local mailbox\n    r_mailbox = models.ForeignKey(Mailbox, blank=True, null=True)\n    # if recipient is a local alias\n    r_alias = models.ForeignKey(\n        Alias, related_name=\"alias_recipient_aliases\", blank=True, null=True)\n\n    class Meta:\n        app_label = \"admin\"\n        db_table = \"modoboa_admin_aliasrecipient\"\n        unique_together = [\n            (\"alias\", \"r_mailbox\"),\n            (\"alias\", \"r_alias\")\n        ]\n\n    def __str__(self):\n        \"\"\"Return alias and recipient.\"\"\"\n        return smart_text(\n            \"{} -> {}\".format(self.alias.address, self.address)\n        )\n", "path": "modoboa/admin/models/alias.py"}, {"content": "\"\"\"SimpleUsers views.\"\"\"\n\nfrom django.contrib.auth.decorators import login_required\nfrom django.utils.translation import ugettext as _\n\nfrom reversion import revisions as reversion\n\nfrom modoboa.lib.web_utils import (\n    render_to_json_response, _render_to_string\n)\n\nfrom ..forms import ForwardForm\nfrom ..lib import needs_mailbox\nfrom ..models import Alias\n\n\n@login_required\n@needs_mailbox()\n@reversion.create_revision()\ndef forward(request, tplname=\"admin/forward.html\"):\n    mb = request.user.mailbox\n    al = Alias.objects.filter(address=mb.full_address, internal=False).first()\n    if request.method == \"POST\":\n        form = ForwardForm(request.POST)\n        if form.is_valid():\n            if al is None:\n                al = Alias.objects.create(\n                    address=mb.full_address, domain=mb.domain,\n                    enabled=mb.user.is_active)\n            recipients = form.cleaned_data[\"dest\"]\n            if form.cleaned_data[\"keepcopies\"]:\n                recipients.append(mb.full_address)\n            al.set_recipients(recipients)\n            if len(recipients) == 0:\n                al.delete()\n            else:\n                al.post_create(request.user)\n            return render_to_json_response(_(\"Forward updated\"))\n\n        return render_to_json_response(\n            {'form_errors': form.errors}, status=400\n        )\n\n    form = ForwardForm()\n    if al is not None and al.recipients:\n        recipients = list(al.recipients)\n        if al.aliasrecipient_set.filter(r_mailbox=mb).exists():\n            form.fields[\"keepcopies\"].initial = True\n            recipients.remove(mb.full_address)\n        form.fields[\"dest\"].initial = \"\\n\".join(recipients)\n    return render_to_json_response({\n        \"content\": _render_to_string(request, tplname, {\n            \"form\": form\n        })\n    })\n", "path": "modoboa/admin/views/user.py"}]}