2014-11-18 17:47:26 +00:00
|
|
|
import os
|
2015-10-07 13:15:16 +00:00
|
|
|
import re
|
2015-10-07 11:44:30 +00:00
|
|
|
from collections import defaultdict
|
2014-11-18 17:47:26 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.contrib.auth.hashers import make_password
|
2014-11-13 15:34:00 +00:00
|
|
|
from django.core.validators import RegexValidator, ValidationError
|
2014-08-22 11:28:46 +00:00
|
|
|
from django.db import models
|
2014-09-30 14:46:29 +00:00
|
|
|
from django.utils.functional import cached_property
|
2023-10-24 16:59:02 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2014-08-22 11:28:46 +00:00
|
|
|
|
|
|
|
from . import validators, settings
|
|
|
|
|
|
|
|
|
|
|
|
class Mailbox(models.Model):
|
2014-10-09 17:04:12 +00:00
|
|
|
CUSTOM = 'CUSTOM'
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2016-10-03 08:59:47 +00:00
|
|
|
name = models.CharField(_("name"), unique=True, db_index=True,
|
|
|
|
max_length=settings.MAILBOXES_NAME_MAX_LENGTH,
|
2016-02-09 12:17:42 +00:00
|
|
|
help_text=_("Required. %s characters or fewer. Letters, digits and ./-/_ only.") %
|
|
|
|
settings.MAILBOXES_NAME_MAX_LENGTH,
|
2015-04-05 10:46:24 +00:00
|
|
|
validators=[
|
2015-09-22 10:24:04 +00:00
|
|
|
RegexValidator(r'^[\w.-]+$', _("Enter a valid mailbox name.")),
|
2015-04-05 10:46:24 +00:00
|
|
|
])
|
2014-09-29 12:22:45 +00:00
|
|
|
password = models.CharField(_("password"), max_length=128)
|
2014-08-22 15:31:44 +00:00
|
|
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
2021-04-22 08:28:00 +00:00
|
|
|
related_name='mailboxes', on_delete=models.CASCADE)
|
2014-10-09 17:04:12 +00:00
|
|
|
filtering = models.CharField(max_length=16,
|
2015-04-05 10:46:24 +00:00
|
|
|
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
|
2015-05-14 13:28:54 +00:00
|
|
|
choices=[(k, v[0]) for k,v in sorted(settings.MAILBOXES_MAILBOX_FILTERINGS.items())])
|
2014-08-22 11:28:46 +00:00
|
|
|
custom_filtering = models.TextField(_("filtering"), blank=True,
|
2015-04-05 10:46:24 +00:00
|
|
|
validators=[validators.validate_sieve],
|
2016-05-03 12:14:59 +00:00
|
|
|
help_text=_("Arbitrary email filtering in "
|
|
|
|
"<a href='https://tty1.net/blog/2011/sieve-tutorial_en.html'>sieve language</a>. "
|
2015-04-05 10:46:24 +00:00
|
|
|
"This overrides any automatic junk email filtering"))
|
2014-09-30 10:20:11 +00:00
|
|
|
is_active = models.BooleanField(_("active"), default=True)
|
2024-07-08 16:30:00 +00:00
|
|
|
ratelimit = models.CharField(_("ratelimit"),
|
|
|
|
max_length=100, null=True, blank=True,
|
|
|
|
choices=settings.MAILBOXES_RATELIMIT_GROUP,
|
|
|
|
default=settings.MAILBOXES_RATELIMIT_GROUP_DEFAULT,)
|
|
|
|
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-08-22 11:28:46 +00:00
|
|
|
class Meta:
|
|
|
|
verbose_name_plural = _("mailboxes")
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
2014-08-22 15:31:44 +00:00
|
|
|
return self.name
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-09-30 14:46:29 +00:00
|
|
|
@cached_property
|
|
|
|
def active(self):
|
2014-10-07 13:08:59 +00:00
|
|
|
try:
|
|
|
|
return self.is_active and self.account.is_active
|
2021-05-12 12:38:17 +00:00
|
|
|
except type(self).account.field.related_model.DoesNotExist:
|
2014-10-07 13:08:59 +00:00
|
|
|
return self.is_active
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-05-09 17:08:45 +00:00
|
|
|
def disable(self):
|
|
|
|
self.is_active = False
|
|
|
|
self.save(update_fields=('is_active',))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2016-03-31 16:18:38 +00:00
|
|
|
def enable(self):
|
|
|
|
self.is_active = False
|
|
|
|
self.save(update_fields=('is_active',))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
def set_password(self, raw_password):
|
|
|
|
self.password = make_password(raw_password)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
def get_home(self):
|
|
|
|
context = {
|
|
|
|
'name': self.name,
|
|
|
|
'username': self.name,
|
|
|
|
}
|
2014-11-18 17:47:26 +00:00
|
|
|
return os.path.normpath(settings.MAILBOXES_HOME % context)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def clean(self):
|
2016-02-19 11:51:26 +00:00
|
|
|
if self.filtering == self.CUSTOM and not self.custom_filtering:
|
2015-10-07 11:44:30 +00:00
|
|
|
raise ValidationError({
|
|
|
|
'custom_filtering': _("Custom filtering is selected but not provided.")
|
|
|
|
})
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def get_filtering(self):
|
2015-04-28 15:23:57 +00:00
|
|
|
name, content = settings.MAILBOXES_MAILBOX_FILTERINGS[self.filtering]
|
|
|
|
if callable(content):
|
2016-02-19 11:51:26 +00:00
|
|
|
# Custom filtering
|
2015-04-29 10:51:30 +00:00
|
|
|
content = content(self)
|
2015-04-28 15:23:57 +00:00
|
|
|
return (name, content)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-11-27 19:17:26 +00:00
|
|
|
def get_local_address(self):
|
2015-10-07 13:15:16 +00:00
|
|
|
if not settings.MAILBOXES_LOCAL_DOMAIN:
|
2015-04-07 15:14:49 +00:00
|
|
|
raise AttributeError("Mailboxes do not have a defined local address domain.")
|
2015-10-07 13:15:16 +00:00
|
|
|
return '@'.join((self.name, settings.MAILBOXES_LOCAL_DOMAIN))
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2016-05-04 13:00:03 +00:00
|
|
|
def get_forwards(self):
|
|
|
|
return Address.objects.filter(forward__regex=r'(^|.*\s)%s(\s.*|$)' % self.name)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2016-05-04 13:00:03 +00:00
|
|
|
def get_addresses(self):
|
|
|
|
mboxes = self.addresses.all()
|
|
|
|
forwards = self.get_forwards()
|
|
|
|
return set(mboxes).union(set(forwards))
|
2014-08-22 11:28:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Address(models.Model):
|
2014-11-02 14:33:55 +00:00
|
|
|
name = models.CharField(_("name"), max_length=64, blank=True,
|
2015-04-05 10:46:24 +00:00
|
|
|
validators=[validators.validate_emailname],
|
|
|
|
help_text=_("Address name, left blank for a <i>catch-all</i> address"))
|
2014-10-17 10:04:47 +00:00
|
|
|
domain = models.ForeignKey(settings.MAILBOXES_DOMAIN_MODEL,
|
2021-04-22 08:28:00 +00:00
|
|
|
verbose_name=_("domain"), related_name='addresses', on_delete=models.CASCADE)
|
2015-04-05 10:46:24 +00:00
|
|
|
mailboxes = models.ManyToManyField(Mailbox, verbose_name=_("mailboxes"),
|
|
|
|
related_name='addresses', blank=True)
|
2014-08-22 11:28:46 +00:00
|
|
|
forward = models.CharField(_("forward"), max_length=256, blank=True,
|
2015-04-05 10:46:24 +00:00
|
|
|
validators=[validators.validate_forward],
|
|
|
|
help_text=_("Space separated email addresses or mailboxes"))
|
2014-08-22 11:28:46 +00:00
|
|
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
2021-04-22 08:28:00 +00:00
|
|
|
related_name='addresses', on_delete=models.CASCADE)
|
|
|
|
|
2014-08-22 11:28:46 +00:00
|
|
|
class Meta:
|
|
|
|
verbose_name_plural = _("addresses")
|
|
|
|
unique_together = ('name', 'domain')
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
2014-08-22 11:28:46 +00:00
|
|
|
return self.email
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-08-22 11:28:46 +00:00
|
|
|
@property
|
|
|
|
def email(self):
|
|
|
|
return "%s@%s" % (self.name, self.domain)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-02-25 17:29:39 +00:00
|
|
|
@cached_property
|
|
|
|
def destination(self):
|
|
|
|
destinations = list(self.mailboxes.values_list('name', flat=True))
|
|
|
|
if self.forward:
|
2015-02-27 16:57:39 +00:00
|
|
|
destinations += self.forward.split()
|
2015-02-25 17:29:39 +00:00
|
|
|
return ' '.join(destinations)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-11-13 15:34:00 +00:00
|
|
|
def clean(self):
|
2015-10-07 11:44:30 +00:00
|
|
|
errors = defaultdict(list)
|
|
|
|
local_domain = settings.MAILBOXES_LOCAL_DOMAIN
|
|
|
|
if local_domain:
|
|
|
|
forwards = self.forward.split()
|
|
|
|
for ix, forward in enumerate(forwards):
|
|
|
|
if forward.endswith('@%s' % local_domain):
|
|
|
|
name = forward.split('@')[0]
|
|
|
|
if Mailbox.objects.filter(name=name).exists():
|
|
|
|
forwards[ix] = name
|
|
|
|
self.forward = ' '.join(forwards)
|
2014-11-14 23:06:14 +00:00
|
|
|
if self.account_id:
|
2014-11-13 15:34:00 +00:00
|
|
|
for mailbox in self.get_forward_mailboxes():
|
2014-11-14 23:06:14 +00:00
|
|
|
if mailbox.account_id == self.account_id:
|
2015-10-07 11:44:30 +00:00
|
|
|
errors['forward'].append(
|
2014-11-13 15:34:00 +00:00
|
|
|
_("Please use mailboxes field for '%s' mailbox.") % mailbox
|
2015-10-07 11:44:30 +00:00
|
|
|
)
|
|
|
|
if self.domain:
|
|
|
|
for forward in self.forward.split():
|
|
|
|
if self.email == forward:
|
|
|
|
errors['forward'].append(
|
|
|
|
_("'%s' forwards to itself.") % forward
|
|
|
|
)
|
|
|
|
if errors:
|
|
|
|
raise ValidationError(errors)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def get_forward_mailboxes(self):
|
2015-10-07 13:15:16 +00:00
|
|
|
rm_local_domain = re.compile(r'@%s$' % settings.MAILBOXES_LOCAL_DOMAIN)
|
2016-05-05 11:58:35 +00:00
|
|
|
mailboxes = []
|
2014-10-09 17:04:12 +00:00
|
|
|
for forward in self.forward.split():
|
2015-10-07 13:15:16 +00:00
|
|
|
forward = rm_local_domain.sub('', forward)
|
2014-10-09 17:04:12 +00:00
|
|
|
if '@' not in forward:
|
2016-05-05 11:58:35 +00:00
|
|
|
mailboxes.append(forward)
|
|
|
|
return Mailbox.objects.filter(name__in=mailboxes)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2014-10-09 17:04:12 +00:00
|
|
|
def get_mailboxes(self):
|
|
|
|
for mailbox in self.mailboxes.all():
|
|
|
|
yield mailbox
|
|
|
|
for mailbox in self.get_forward_mailboxes():
|
|
|
|
yield mailbox
|
2014-08-22 11:28:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Autoresponse(models.Model):
|
|
|
|
address = models.OneToOneField(Address, verbose_name=_("address"),
|
2021-04-22 08:28:00 +00:00
|
|
|
related_name='autoresponse', on_delete=models.CASCADE)
|
2014-08-22 11:28:46 +00:00
|
|
|
# TODO initial_date
|
|
|
|
subject = models.CharField(_("subject"), max_length=256)
|
|
|
|
message = models.TextField(_("message"))
|
|
|
|
enabled = models.BooleanField(_("enabled"), default=False)
|
2021-04-22 08:28:00 +00:00
|
|
|
|
2015-04-02 16:14:55 +00:00
|
|
|
def __str__(self):
|
2014-08-22 11:28:46 +00:00
|
|
|
return self.address
|