""" 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 ` 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, ) @python_2_unicode_compatible class Mailbox(ActivateAbleMixin, TimeStampedModel): """ This is the model class for a mailbox. """ osuser = models.ForeignKey(OsUser) 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): create_file_mailbox.delay(self.osuser.username, self.username).get() super(Mailbox, self).save(*args, **kwargs) def delete(self, *args, **kwargs): 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')) 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: mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) if commit: mabox.save() 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 not item.target in addresses: item.delete() else: retval.append(item) for target in addresses: if not target in [item.target for item in forwards]: mafwd = MailAddressForward(mailaddress=self, target=target) if commit: mafwd.save() retval.append(mafwd) else: 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) mailbox = models.ForeignKey(Mailbox, verbose_name=_('mailbox')) 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) target = models.EmailField(max_length=254) class Meta: unique_together = ('mailaddress', 'target')