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.
491 lines
15 KiB
Python
491 lines
15 KiB
Python
"""
|
|
This module contains the hosting package models.
|
|
|
|
"""
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
from django.conf import settings
|
|
from django.db import transaction
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
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 domains.models import HostingDomain
|
|
from managemails.models import Mailbox
|
|
from osusers.models import (
|
|
AdditionalGroup,
|
|
Group,
|
|
User as OsUser,
|
|
)
|
|
from userdbs.models import (
|
|
DB_TYPES,
|
|
UserDatabase,
|
|
)
|
|
|
|
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']
|
|
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.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
unique_together = ['diskspace', 'diskspace_unit']
|
|
|
|
|
|
@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']
|
|
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.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
unique_together = ['number', 'db_type']
|
|
|
|
|
|
@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'),
|
|
on_delete=models.CASCADE)
|
|
template = models.ForeignKey(
|
|
HostingPackageTemplate, verbose_name=_('hosting package template'),
|
|
help_text=_(
|
|
'The hosting package template that this hosting package is based'
|
|
' on'
|
|
),
|
|
on_delete=models.CASCADE)
|
|
name = models.CharField(_('name'), max_length=128)
|
|
osuser = models.OneToOneField(
|
|
OsUser, verbose_name=_('Operating system user'),
|
|
blank=True, null=True, on_delete=models.CASCADE)
|
|
|
|
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_hostingoptions(self):
|
|
opts = []
|
|
for opt_type in [
|
|
CustomerDiskSpaceOption,
|
|
CustomerMailboxOption,
|
|
CustomerUserDatabaseOption
|
|
]:
|
|
opts.extend(opt_type.objects.filter(hosting_package=self))
|
|
return opts
|
|
|
|
hostingoptions = property(get_hostingoptions)
|
|
|
|
def get_disk_space(self, unit=None):
|
|
"""
|
|
Get the total disk space reserved for this hosting package and all its
|
|
additional disk space options.
|
|
|
|
:param unit: value from :py:data:`DISK_SPACE_UNITS` or :py:const:`None`
|
|
:return: disk space in unit or bytes (if parameter unit is
|
|
:py:const:`None`)
|
|
: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.diskspace
|
|
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_package_space(self, unit=None):
|
|
"""
|
|
Get the total disk space reserved for this package without looking at
|
|
any additional dis space options.
|
|
|
|
:param unit: value from :py:data:`DISK_SPACE_UNITS` or :py:const:`None`
|
|
:return: disk space in unit or bytes (if parameter unit is
|
|
:py:const:`None`)
|
|
:rtype: int
|
|
|
|
"""
|
|
if unit is None:
|
|
return (DISK_SPACE_FACTORS[self.diskspace_unit][0] *
|
|
self.diskspace * 1024 ** 2)
|
|
if unit > self.diskspace_unit:
|
|
return (DISK_SPACE_FACTORS[unit][self.diskspace_unit] *
|
|
self.diskspace)
|
|
return DISK_SPACE_FACTORS[self.diskspace_unit][unit] * self.diskspace
|
|
|
|
def get_quota(self):
|
|
soft = 1024 * self.get_disk_space(DISK_SPACE_UNITS.M)
|
|
hard = soft * 105 / 100
|
|
return (soft, hard)
|
|
|
|
def get_mailboxes(self):
|
|
if self.osuser:
|
|
return Mailbox.objects.filter(osuser=self.osuser).all()
|
|
|
|
mailboxes = property(get_mailboxes)
|
|
|
|
def get_used_mailbox_count(self):
|
|
"""
|
|
Get the number of used mailboxes for this hosting package.
|
|
|
|
"""
|
|
if self.osuser:
|
|
return Mailbox.objects.filter(osuser=self.osuser).count()
|
|
return 0
|
|
|
|
used_mailbox_count = property(get_used_mailbox_count)
|
|
|
|
def get_mailbox_count(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)
|
|
|
|
mailbox_count = property(get_mailbox_count)
|
|
|
|
def may_add_mailbox(self):
|
|
return self.used_mailbox_count < self.mailbox_count
|
|
|
|
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()
|
|
|
|
def get_databases_flat(self):
|
|
if self.osuser:
|
|
return UserDatabase.objects.filter(
|
|
db_user__osuser=self.osuser).all()
|
|
|
|
databases = property(get_databases_flat)
|
|
|
|
def may_add_database(self):
|
|
return (
|
|
CustomerUserDatabaseOption.objects.filter(
|
|
hosting_package=self).count() >
|
|
UserDatabase.objects.filter(
|
|
db_user__osuser=self.osuser).count()
|
|
)
|
|
|
|
@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)
|
|
|
|
|
|
@python_2_unicode_compatible
|
|
class CustomerHostingPackageDomain(TimeStampedModel):
|
|
"""
|
|
This class defines the relationship from a hosting package to a hosting
|
|
domain.
|
|
|
|
"""
|
|
hosting_package = models.ForeignKey(
|
|
CustomerHostingPackage, verbose_name=_('hosting package'),
|
|
related_name='domains', on_delete=models.CASCADE)
|
|
domain = models.OneToOneField(
|
|
HostingDomain, verbose_name=_('hosting domain'),
|
|
on_delete=models.CASCADE)
|
|
|
|
def __str__(self):
|
|
return self.domain.domain
|
|
|
|
def is_usable_for_email(self):
|
|
"""
|
|
Tells wether the related domain is usable for email addresses.
|
|
|
|
"""
|
|
return self.domain.maildomain is not None
|
|
|
|
|
|
class CustomerHostingPackageOption(TimeStampedModel):
|
|
"""
|
|
This class defines options for customer hosting packages.
|
|
|
|
"""
|
|
hosting_package = models.ForeignKey(
|
|
CustomerHostingPackage, verbose_name=_('hosting package'),
|
|
on_delete=models.CASCADE)
|
|
|
|
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'
|
|
),
|
|
on_delete=models.CASCADE)
|
|
|
|
|
|
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'
|
|
),
|
|
on_delete=models.CASCADE)
|
|
|
|
|
|
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'
|
|
),
|
|
on_delete=models.CASCADE)
|