gva/gnuviechadmin/managemails/models.py
Jan Dittberner 6cebd80c89 Started port to Django 2.1, Python 3, Docker
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.
2018-11-19 23:28:40 +01:00

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')