""" This module contains the hosting package models. """ from __future__ import absolute_import, unicode_literals from django.conf import settings from django.core.urlresolvers import reverse from django.db import transaction from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _, ungettext from model_utils import Choices from model_utils.models import TimeStampedModel from managemails.models import Mailbox from osusers.models import ( AdditionalGroup, Group, User as OsUser, ) from userdbs.models import DB_TYPES DISK_SPACE_UNITS = Choices( (0, 'M', _('MiB')), (1, 'G', _('GiB')), (2, 'T', _('TiB')), ) DISK_SPACE_FACTORS = ( (1, None, None), (1024, 1, None), (1024 * 1024, 1024, 1), ) @python_2_unicode_compatible class HostingPackageBase(TimeStampedModel): description = models.TextField(_('description'), blank=True) mailboxcount = models.PositiveIntegerField(_('mailbox count')) diskspace = models.PositiveIntegerField( _('disk space'), help_text=_('disk space for the hosting package')) diskspace_unit = models.PositiveSmallIntegerField( _('unit of disk space'), choices=DISK_SPACE_UNITS) class Meta: abstract = True def __str__(self): return self.name class HostingPackageTemplate(HostingPackageBase): name = models.CharField(_('name'), max_length=128, unique=True) class Meta: verbose_name = _('Hosting package') verbose_name_plural = _('Hosting packages') class HostingOption(TimeStampedModel): """ This is the base class for several types of hosting options. """ @python_2_unicode_compatible class DiskSpaceOptionBase(models.Model): diskspace = models.PositiveIntegerField(_('disk space')) diskspace_unit = models.PositiveSmallIntegerField( _('unit of disk space'), choices=DISK_SPACE_UNITS) class Meta: abstract = True ordering = ['diskspace_unit', 'diskspace'] unique_together = ['diskspace', 'diskspace_unit'] verbose_name = _('Disk space option') verbose_name_plural = _('Disk space options') def __str__(self): return _("Additional disk space {space} {unit}").format( space=self.diskspace, unit=self.get_diskspace_unit_display()) class DiskSpaceOption(DiskSpaceOptionBase, HostingOption): """ This is a class for hosting options adding additional disk space to existing hosting packages. """ @python_2_unicode_compatible class UserDatabaseOptionBase(models.Model): number = models.PositiveIntegerField( _('number of databases'), default=1) db_type = models.PositiveSmallIntegerField( _('database type'), choices=DB_TYPES) class Meta: abstract = True ordering = ['db_type', 'number'] unique_together = ['number', 'db_type'] verbose_name = _('Database option') verbose_name_plural = _('Database options') def __str__(self): return ungettext( '{type} database', '{count} {type} databases', self.number ).format( type=self.get_db_type_display(), count=self.number ) class UserDatabaseOption(UserDatabaseOptionBase, HostingOption): """ This is a class for hosting options adding user databases to existing hosting packages. """ @python_2_unicode_compatible class MailboxOptionBase(models.Model): """ Base class for mailbox options. """ number = models.PositiveIntegerField( _('number of mailboxes'), unique=True) class Meta: abstract = True ordering = ['number'] verbose_name = _('Mailbox option') verbose_name_plural = _('Mailbox options') def __str__(self): return ungettext( '{count} additional mailbox', '{count} additional mailboxes', self.number ).format( count=self.number ) class MailboxOption(MailboxOptionBase, HostingOption): """ This is a class for hosting options adding more mailboxes to existing hosting packages. """ class CustomerHostingPackageManager(models.Manager): """ This is the default manager implementation for :py:class:`CustomerHostingPackage`. """ def create_from_template(self, customer, template, name, **kwargs): """ Use this method to create a new :py:class:`CustomerHostingPackage` from a :py:class:`HostingPackageTemplate`. The method copies data from the template to the new :py:class:`CustomerHostingPackage` instance. :param customer: a Django user representing a customer :param template: a :py:class:`HostingPackageTemplate` :param str name: the name of the hosting package there must only be one hosting package of this name for each customer :return: customer hosting package :rtype: :py:class:`CustomerHostingPackage` """ package = CustomerHostingPackage( customer=customer, template=template, name=name) package.description = template.description package.copy_template_attributes() if 'commit' in kwargs and kwargs['commit'] is True: package.save(**kwargs) return package @python_2_unicode_compatible class CustomerHostingPackage(HostingPackageBase): """ This class defines customer specific hosting packages. """ customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer')) template = models.ForeignKey( HostingPackageTemplate, verbose_name=_('hosting package template'), help_text=_( 'The hosting package template that this hosting package is based' ' on' )) name = models.CharField(_('name'), max_length=128) osuser = models.OneToOneField( OsUser, verbose_name=_('Operating system user'), blank=True, null=True) objects = CustomerHostingPackageManager() class Meta: unique_together = ['customer', 'name'] verbose_name = _('customer hosting package') verbose_name_plural = _('customer hosting packages') def __str__(self): return _("{name} for {customer}").format( name=self.name, customer=self.customer ) def get_absolute_url(self): return reverse('hosting_package_details', kwargs={ 'user': self.customer.username, 'pk': self.id, }) def copy_template_attributes(self): """ Copy the attributes of the hosting package's template to the package. """ for attrname in ('diskspace', 'diskspace_unit', 'mailboxcount'): setattr(self, attrname, getattr(self.template, attrname)) def get_disk_space(self, unit=None): """ Get the total disk space reserved for this hosting package and all its additional disk space options. :return: disk space :rtype: int """ diskspace = self.diskspace min_unit = self.diskspace_unit options = CustomerDiskSpaceOption.objects.filter(hosting_package=self) for option in options: if option.diskspace_unit == min_unit: diskspace += option.disk_space elif option.diskspace_unit > min_unit: diskspace += ( DISK_SPACE_FACTORS[option.diskspace_unit][min_unit] * option.diskspace) else: diskspace = ( DISK_SPACE_FACTORS[min_unit][option.diskspace_unit] * diskspace) + option.diskspace min_unit = option.diskspace_unit if unit is None: return DISK_SPACE_FACTORS[min_unit][0] * diskspace * 1024**2 if unit > min_unit: return DISK_SPACE_FACTORS[unit][min_unit] * diskspace return DISK_SPACE_FACTORS[min_unit][unit] * diskspace def get_quota(self): soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M) hard = soft * 105 / 100 return (soft, hard) def get_used_mailboxes(self): """ Get the number of used mailboxes for this hosting package. """ if self.osuser: return Mailbox.objects.filter(osuser=self.osuser).count() return 0 def get_mailboxes(self): """ Get the number of mailboxes provided by this hosting package and all of its mailbox options. """ result = CustomerMailboxOption.objects.filter( hosting_package=self ).aggregate( mailbox_sum=models.Sum('number') ) return self.mailboxcount + (result['mailbox_sum'] or 0) def get_databases(self): """ Get the number of user databases provided by user database hosting options for this hosting package. """ return CustomerUserDatabaseOption.objects.values( 'db_type' ).filter(hosting_package=self).annotate( number=models.Sum('number') ).all() @transaction.atomic def save(self, *args, **kwargs): """ Save the hosting package to the database. If this is a new hosting package a new operating system user is created and assigned to the hosting package. :param args: positional arguments to be passed on to :py:meth:`django.db.Model.save` :param kwargs: keyword arguments to be passed on to :py:meth:`django.db.Model.save` :return: self :rtype: :py:class:`CustomerHostingPackage` """ if self.pk is None: self.copy_template_attributes() self.osuser = OsUser.objects.create_user(self.customer) for group in settings.OSUSER_DEFAULT_GROUPS: AdditionalGroup.objects.create( user=self.osuser, group=Group.objects.get(groupname=group) ) return super(CustomerHostingPackage, self).save(*args, **kwargs) class CustomerHostingPackageOption(TimeStampedModel): """ This class defines options for customer hosting packages. """ hosting_package = models.ForeignKey( CustomerHostingPackage, verbose_name=_('hosting package')) class Meta: verbose_name = _('customer hosting option') verbose_name_plural = _('customer hosting options') class CustomerDiskSpaceOption(DiskSpaceOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding additional disk space to existing customer hosting package. """ template = models.ForeignKey( DiskSpaceOption, verbose_name=_('disk space option template'), help_text=_( 'The disk space option template that this disk space option is' ' based on' )) class CustomerUserDatabaseOption(UserDatabaseOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding user databases to existing customer hosting packages. """ template = models.ForeignKey( UserDatabaseOption, verbose_name=_('user database option template'), help_text=_( 'The user database option template that this database option is' ' based on' )) class CustomerMailboxOption(MailboxOptionBase, CustomerHostingPackageOption): """ This is a class for customer hosting package options adding additional mailboxes to existing customer hosting packages. """ template = models.ForeignKey( MailboxOption, verbose_name=_('mailbox option template'), help_text=_( 'The mailbox option template that this mailbox option is based on' ))