Jan Dittberner
6cebd80c89
This commit is a rough port to Django 2.1, Python 3 and a Docker based local development setup. Tests fail/error but migrations and the web frontend are already runnable. Task queue functionality is untested and translations seem to have trouble.
275 lines
8.5 KiB
Python
275 lines
8.5 KiB
Python
"""
|
|
This module defines the database models for mail handling.
|
|
|
|
"""
|
|
from __future__ import unicode_literals
|
|
|
|
from django.db import models
|
|
from django.utils.encoding import python_2_unicode_compatible
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from passlib.hash import sha512_crypt
|
|
from model_utils.models import TimeStampedModel
|
|
|
|
from domains.models import MailDomain
|
|
from osusers.models import User as OsUser
|
|
|
|
from fileservertasks.tasks import (
|
|
create_file_mailbox,
|
|
delete_file_mailbox,
|
|
)
|
|
|
|
|
|
class ActivateAbleMixin(models.Model):
|
|
"""
|
|
Mixin for model classes that can be active or inactive.
|
|
|
|
"""
|
|
active = models.BooleanField(default=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
|
|
class MailboxManager(models.Manager):
|
|
"""
|
|
This is the default manager class for :py:class:`Mailbox`.
|
|
"""
|
|
|
|
def get_next_mailbox_name(self, osuser):
|
|
"""
|
|
Get the next available mailbox name for the given operating system
|
|
user.
|
|
|
|
:param osuser: a :py:class:`operating system user <osuser.models.User>`
|
|
instance
|
|
:return: name of the mailbox
|
|
:rtype: str
|
|
|
|
"""
|
|
count = 1
|
|
mailboxformat = "{0}p{1:02d}"
|
|
mailboxname = mailboxformat.format(osuser.username, count)
|
|
|
|
for box in self.values('username').filter(osuser=osuser).order_by(
|
|
'username'
|
|
):
|
|
if box['username'] == mailboxname:
|
|
count += 1
|
|
mailboxname = mailboxformat.format(osuser.username, count)
|
|
else:
|
|
break
|
|
return mailboxname
|
|
|
|
def unused_or_own(self, mailaddress, osuser):
|
|
"""
|
|
Returns a queryset that fetches those mailboxes that belong to the
|
|
given operating system user and are either not used by any mail address
|
|
or used by the given mailaddress itself.
|
|
|
|
"""
|
|
return self.filter(
|
|
models.Q(mailaddressmailbox__isnull=True) |
|
|
models.Q(mailaddressmailbox__mailaddress=mailaddress),
|
|
active=True, osuser=osuser,
|
|
)
|
|
|
|
def unused(self, osuser):
|
|
"""
|
|
Returns a queryset that fetches unused mailboxes belonging to the
|
|
given operating system user.
|
|
|
|
"""
|
|
return self.filter(
|
|
mailaddressmailbox__isnull=True,
|
|
active=True, osuser=osuser,
|
|
)
|
|
|
|
def create_mailbox(self, osuser, password=None, commit=True):
|
|
"""
|
|
Create a new mailbox for the given operating system user.
|
|
|
|
:param osuser: a :py:class:`osuser.models.OsUser` instance
|
|
:param password: an optional password
|
|
:param commit: whether the mailbox should be commited to the database
|
|
:return: mailbox instance
|
|
:rtype: :py:class:`managemails.models.Mailbox`
|
|
|
|
"""
|
|
mailbox = self.create(
|
|
osuser=osuser, username=self.get_next_mailbox_name(osuser))
|
|
if password is not None:
|
|
mailbox.set_password(password)
|
|
return mailbox
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class Mailbox(ActivateAbleMixin, TimeStampedModel):
|
|
"""
|
|
This is the model class for a mailbox.
|
|
|
|
"""
|
|
osuser = models.ForeignKey(OsUser, on_delete=models.CASCADE)
|
|
username = models.CharField(max_length=128, unique=True)
|
|
password = models.CharField(max_length=255)
|
|
|
|
objects = MailboxManager()
|
|
|
|
class Meta:
|
|
ordering = ['osuser', 'username']
|
|
verbose_name = _('Mailbox')
|
|
verbose_name_plural = _('Mailboxes')
|
|
|
|
def set_password(self, password):
|
|
"""
|
|
Set the hashed password for the mailbox.
|
|
|
|
:param str password: the clear text password
|
|
|
|
"""
|
|
self.password = sha512_crypt.encrypt(password)
|
|
|
|
def save(self, *args, **kwargs):
|
|
# TODO: refactor to use signals
|
|
create_file_mailbox.delay(self.osuser.username, self.username).get()
|
|
super(Mailbox, self).save(*args, **kwargs)
|
|
|
|
def delete(self, *args, **kwargs):
|
|
# TODO: refactor to use signals
|
|
delete_file_mailbox.delay(self.osuser.username, self.username).get()
|
|
super(Mailbox, self).delete(*args, **kwargs)
|
|
|
|
def get_mailaddresses(self):
|
|
"""
|
|
Get a list of mail addresses assigned to this mailbox.
|
|
|
|
"""
|
|
addrs = [
|
|
mbadr.mailaddress for mbadr in
|
|
self.mailaddressmailbox_set.all()
|
|
]
|
|
return addrs
|
|
mailaddresses = property(get_mailaddresses)
|
|
|
|
def __str__(self):
|
|
return self.username
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
|
|
"""
|
|
This is the model class for a mail address.
|
|
|
|
"""
|
|
localpart = models.CharField(_('local part'), max_length=128)
|
|
domain = models.ForeignKey(
|
|
MailDomain, verbose_name=_('domain'), on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
ordering = ['domain', 'localpart']
|
|
unique_together = ('localpart', 'domain')
|
|
verbose_name = _('Mail address')
|
|
verbose_name_plural = _('Mail addresses')
|
|
|
|
def __str__(self):
|
|
return "{0}@{1}".format(self.localpart, self.domain)
|
|
|
|
def set_mailbox(self, mailbox, commit=True):
|
|
"""
|
|
Set the target for this mail address to the given mailbox. This method
|
|
takes care of removing forwarding addresses or changing existing
|
|
mailbox relations.
|
|
|
|
:param mailbox: :py:class:`Mailbox`
|
|
:param boolean commit: whether to persist changes or not
|
|
:return: :py:class:`MailAddressMailbox` instance
|
|
|
|
"""
|
|
if self.pk is not None:
|
|
if MailAddressMailbox.objects.filter(mailaddress=self).exists():
|
|
mabox = MailAddressMailbox.objects.get(mailaddress=self)
|
|
mabox.mailbox = mailbox
|
|
else:
|
|
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
|
if commit:
|
|
mabox.save()
|
|
for mafwd in MailAddressForward.objects.filter(mailaddress=self):
|
|
mafwd.delete()
|
|
else:
|
|
if commit:
|
|
self.save()
|
|
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
|
mabox.save()
|
|
else:
|
|
mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox)
|
|
return mabox
|
|
|
|
def set_forward_addresses(self, addresses, commit=True):
|
|
"""
|
|
Set the forwarding addresses for this mail address to the given
|
|
addresses. This method takes care of removing other mail forwards and
|
|
mailboxes.
|
|
|
|
:param addresses: list of email address strings
|
|
:param boolean commit: whether to persist changes or not
|
|
:return: list of :py:class:`MailAddressForward` instances
|
|
|
|
"""
|
|
retval = []
|
|
if self.pk is not None:
|
|
if MailAddressMailbox.objects.filter(mailaddress=self).exists():
|
|
mabox = MailAddressMailbox.objects.get(mailaddress=self)
|
|
mabox.delete()
|
|
forwards = MailAddressForward.objects.filter(
|
|
mailaddress=self).all()
|
|
for item in forwards:
|
|
if item.target not in addresses:
|
|
item.delete()
|
|
else:
|
|
retval.append(item)
|
|
for target in addresses:
|
|
if target not in [item.target for item in forwards]:
|
|
mafwd = MailAddressForward(mailaddress=self, target=target)
|
|
if commit:
|
|
mafwd.save()
|
|
retval.append(mafwd)
|
|
else:
|
|
if commit:
|
|
self.save()
|
|
for target in addresses:
|
|
mafwd = MailAddressForward(mailaddress=self, target=target)
|
|
if commit:
|
|
mafwd.save()
|
|
retval.append(mafwd)
|
|
return retval
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class MailAddressMailbox(TimeStampedModel, models.Model):
|
|
"""
|
|
This is the model class to assign a mail address to a mailbox.
|
|
|
|
"""
|
|
mailaddress = models.OneToOneField(
|
|
MailAddress, verbose_name=_('mailaddress'), primary_key=True,
|
|
on_delete=models.CASCADE)
|
|
mailbox = models.ForeignKey(
|
|
Mailbox, verbose_name=_('mailbox'), on_delete=models.CASCADE)
|
|
|
|
class Meta:
|
|
unique_together = ('mailaddress', 'mailbox')
|
|
|
|
def __str__(self):
|
|
return self.mailbox.username
|
|
|
|
|
|
class MailAddressForward(TimeStampedModel, models.Model):
|
|
"""
|
|
This is a model class to map mail addresses to forwarding addresses.
|
|
|
|
"""
|
|
mailaddress = models.ForeignKey(MailAddress, on_delete=models.CASCADE)
|
|
target = models.EmailField(max_length=254)
|
|
|
|
class Meta:
|
|
unique_together = ('mailaddress', 'target')
|