Define on_delete argument for ForeignKey and OneToOneField
Required since Django 2.0
This commit is contained in:
parent
eadc06d4c5
commit
d863598d81
|
@ -29,7 +29,7 @@ class Account(auth.AbstractBaseUser):
|
||||||
validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."), 'invalid')
|
validators.RegexValidator(r'^[\w.-]+$', _("Enter a valid username."), 'invalid')
|
||||||
])
|
])
|
||||||
main_systemuser = models.ForeignKey(settings.ACCOUNTS_SYSTEMUSER_MODEL, null=True,
|
main_systemuser = models.ForeignKey(settings.ACCOUNTS_SYSTEMUSER_MODEL, null=True,
|
||||||
related_name='accounts_main', editable=False)
|
related_name='accounts_main', editable=False, on_delete=models.SET_NULL)
|
||||||
short_name = models.CharField(_("short name"), max_length=64, blank=True)
|
short_name = models.CharField(_("short name"), max_length=64, blank=True)
|
||||||
full_name = models.CharField(_("full name"), max_length=256)
|
full_name = models.CharField(_("full name"), max_length=256)
|
||||||
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
||||||
|
|
|
@ -24,7 +24,7 @@ from . import settings
|
||||||
|
|
||||||
class BillContact(models.Model):
|
class BillContact(models.Model):
|
||||||
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
|
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='billcontact')
|
related_name='billcontact', on_delete=models.CASCADE)
|
||||||
name = models.CharField(_("name"), max_length=256, blank=True,
|
name = models.CharField(_("name"), max_length=256, blank=True,
|
||||||
help_text=_("Account full name will be used when left blank."))
|
help_text=_("Account full name will be used when left blank."))
|
||||||
address = models.TextField(_("address"))
|
address = models.TextField(_("address"))
|
||||||
|
@ -102,9 +102,9 @@ class Bill(models.Model):
|
||||||
|
|
||||||
number = models.CharField(_("number"), max_length=16, unique=True, blank=True)
|
number = models.CharField(_("number"), max_length=16, unique=True, blank=True)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='%(class)s')
|
related_name='%(class)s', on_delete=models.CASCADE)
|
||||||
amend_of = models.ForeignKey('self', null=True, blank=True, verbose_name=_("amend of"),
|
amend_of = models.ForeignKey('self', null=True, blank=True, verbose_name=_("amend of"),
|
||||||
related_name='amends')
|
related_name='amends', on_delete=models.SET_NULL)
|
||||||
type = models.CharField(_("type"), max_length=16, choices=TYPES)
|
type = models.CharField(_("type"), max_length=16, choices=TYPES)
|
||||||
created_on = models.DateField(_("created on"), auto_now_add=True)
|
created_on = models.DateField(_("created on"), auto_now_add=True)
|
||||||
closed_on = models.DateField(_("closed on"), blank=True, null=True, db_index=True)
|
closed_on = models.DateField(_("closed on"), blank=True, null=True, db_index=True)
|
||||||
|
@ -416,7 +416,7 @@ class ProForma(Bill):
|
||||||
|
|
||||||
class BillLine(models.Model):
|
class BillLine(models.Model):
|
||||||
""" Base model for bill item representation """
|
""" Base model for bill item representation """
|
||||||
bill = models.ForeignKey(Bill, verbose_name=_("bill"), related_name='lines')
|
bill = models.ForeignKey(Bill, verbose_name=_("bill"), related_name='lines', on_delete=models.CASCADE)
|
||||||
description = models.CharField(_("description"), max_length=256)
|
description = models.CharField(_("description"), max_length=256)
|
||||||
rate = models.DecimalField(_("rate"), blank=True, null=True, max_digits=12, decimal_places=2)
|
rate = models.DecimalField(_("rate"), blank=True, null=True, max_digits=12, decimal_places=2)
|
||||||
quantity = models.DecimalField(_("quantity"), blank=True, null=True, max_digits=12,
|
quantity = models.DecimalField(_("quantity"), blank=True, null=True, max_digits=12,
|
||||||
|
@ -434,7 +434,7 @@ class BillLine(models.Model):
|
||||||
created_on = models.DateField(_("created"), auto_now_add=True)
|
created_on = models.DateField(_("created"), auto_now_add=True)
|
||||||
# Amendment
|
# Amendment
|
||||||
amended_line = models.ForeignKey('self', verbose_name=_("amended line"),
|
amended_line = models.ForeignKey('self', verbose_name=_("amended line"),
|
||||||
related_name='amendment_lines', null=True, blank=True)
|
related_name='amendment_lines', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
get_latest_by = 'id'
|
get_latest_by = 'id'
|
||||||
|
@ -495,7 +495,7 @@ class BillSubline(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: order info for undoing
|
# TODO: order info for undoing
|
||||||
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines')
|
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines', on_delete=models.CASCADE)
|
||||||
description = models.CharField(_("description"), max_length=256)
|
description = models.CharField(_("description"), max_length=256)
|
||||||
total = models.DecimalField(max_digits=12, decimal_places=2)
|
total = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
|
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
|
||||||
|
|
|
@ -29,11 +29,11 @@ class Contact(models.Model):
|
||||||
('ADDS', _("Announcements")),
|
('ADDS', _("Announcements")),
|
||||||
('EMERGENCY', _("Emergency contact")),
|
('EMERGENCY', _("Emergency contact")),
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = ContactQuerySet.as_manager()
|
objects = ContactQuerySet.as_manager()
|
||||||
|
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='contacts', null=True)
|
related_name='contacts', null=True, on_delete=models.SET_NULL)
|
||||||
short_name = models.CharField(_("short name"), max_length=128)
|
short_name = models.CharField(_("short name"), max_length=128)
|
||||||
full_name = models.CharField(_("full name"), max_length=256, blank=True)
|
full_name = models.CharField(_("full name"), max_length=256, blank=True)
|
||||||
email = models.EmailField()
|
email = models.EmailField()
|
||||||
|
@ -54,10 +54,10 @@ class Contact(models.Model):
|
||||||
country = models.CharField(_("country"), max_length=20, blank=True,
|
country = models.CharField(_("country"), max_length=20, blank=True,
|
||||||
choices=settings.CONTACTS_COUNTRIES,
|
choices=settings.CONTACTS_COUNTRIES,
|
||||||
default=settings.CONTACTS_DEFAULT_COUNTRY)
|
default=settings.CONTACTS_DEFAULT_COUNTRY)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_name or self.short_name
|
return self.full_name or self.short_name
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.short_name = self.short_name.strip()
|
self.short_name = self.short_name.strip()
|
||||||
self.full_name = self.full_name.strip()
|
self.full_name = self.full_name.strip()
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Database(models.Model):
|
||||||
""" Represents a basic database for a web application """
|
""" Represents a basic database for a web application """
|
||||||
MYSQL = 'mysql'
|
MYSQL = 'mysql'
|
||||||
POSTGRESQL = 'postgresql'
|
POSTGRESQL = 'postgresql'
|
||||||
|
|
||||||
name = models.CharField(_("name"), max_length=64, # MySQL limit
|
name = models.CharField(_("name"), max_length=64, # MySQL limit
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
users = models.ManyToManyField('databases.DatabaseUser', blank=True,
|
users = models.ManyToManyField('databases.DatabaseUser', blank=True,
|
||||||
|
@ -20,16 +20,16 @@ class Database(models.Model):
|
||||||
type = models.CharField(_("type"), max_length=32,
|
type = models.CharField(_("type"), max_length=32,
|
||||||
choices=settings.DATABASES_TYPE_CHOICES,
|
choices=settings.DATABASES_TYPE_CHOICES,
|
||||||
default=settings.DATABASES_DEFAULT_TYPE)
|
default=settings.DATABASES_DEFAULT_TYPE)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='databases')
|
verbose_name=_("Account"), related_name='databases')
|
||||||
comments = models.TextField(default="", blank=True)
|
comments = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('name', 'type')
|
unique_together = ('name', 'type')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % self.name
|
return "%s" % self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner(self):
|
def owner(self):
|
||||||
""" database owner is the first user related to it """
|
""" database owner is the first user related to it """
|
||||||
|
@ -39,7 +39,7 @@ class Database(models.Model):
|
||||||
if user is not None:
|
if user is not None:
|
||||||
return user.databaseuser
|
return user.databaseuser
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.account.is_active
|
return self.account.is_active
|
||||||
|
@ -53,26 +53,26 @@ Database.users.through._meta.unique_together = (
|
||||||
class DatabaseUser(models.Model):
|
class DatabaseUser(models.Model):
|
||||||
MYSQL = Database.MYSQL
|
MYSQL = Database.MYSQL
|
||||||
POSTGRESQL = Database.POSTGRESQL
|
POSTGRESQL = Database.POSTGRESQL
|
||||||
|
|
||||||
username = models.CharField(_("username"), max_length=16, # MySQL usernames 16 char long
|
username = models.CharField(_("username"), max_length=16, # MySQL usernames 16 char long
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
password = models.CharField(_("password"), max_length=256)
|
password = models.CharField(_("password"), max_length=256)
|
||||||
type = models.CharField(_("type"), max_length=32,
|
type = models.CharField(_("type"), max_length=32,
|
||||||
choices=settings.DATABASES_TYPE_CHOICES,
|
choices=settings.DATABASES_TYPE_CHOICES,
|
||||||
default=settings.DATABASES_DEFAULT_TYPE)
|
default=settings.DATABASES_DEFAULT_TYPE)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='databaseusers')
|
verbose_name=_("Account"), related_name='databaseusers')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("DB users")
|
verbose_name_plural = _("DB users")
|
||||||
unique_together = ('username', 'type')
|
unique_together = ('username', 'type')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
if self.type == self.MYSQL:
|
if self.type == self.MYSQL:
|
||||||
# MySQL stores sha1(sha1(password).binary).hex
|
# MySQL stores sha1(sha1(password).binary).hex
|
||||||
|
|
|
@ -31,9 +31,9 @@ class Domain(models.Model):
|
||||||
validators.validate_allowed_domain
|
validators.validate_allowed_domain
|
||||||
])
|
])
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), blank=True,
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), blank=True,
|
||||||
related_name='domains', help_text=_("Automatically selected for subdomains."))
|
related_name='domains', on_delete=models.CASCADE, help_text=_("Automatically selected for subdomains."))
|
||||||
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomain_set',
|
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomain_set',
|
||||||
editable=False, verbose_name=_("top domain"))
|
editable=False, verbose_name=_("top domain"), on_delete=models.CASCADE)
|
||||||
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, editable=False,
|
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, editable=False,
|
||||||
help_text=_("A revision number that changes whenever this domain is updated."))
|
help_text=_("A revision number that changes whenever this domain is updated."))
|
||||||
refresh = models.CharField(_("refresh"), max_length=16, blank=True,
|
refresh = models.CharField(_("refresh"), max_length=16, blank=True,
|
||||||
|
@ -69,16 +69,16 @@ class Domain(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="A bind-9 'address_match_list' that will be granted permission to perform "
|
help_text="A bind-9 'address_match_list' that will be granted permission to perform "
|
||||||
"dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.")
|
"dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.")
|
||||||
|
|
||||||
objects = DomainQuerySet.as_manager()
|
objects = DomainQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def origin(self):
|
def origin(self):
|
||||||
return self.top or self
|
return self.top or self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_top(self):
|
def is_top(self):
|
||||||
# don't cache, don't replace by top_id
|
# don't cache, don't replace by top_id
|
||||||
|
@ -86,14 +86,14 @@ class Domain(models.Model):
|
||||||
return not bool(self.top)
|
return not bool(self.top)
|
||||||
except Domain.DoesNotExist:
|
except Domain.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subdomains(self):
|
def subdomains(self):
|
||||||
return Domain.objects.filter(name__regex='\.%s$' % self.name)
|
return Domain.objects.filter(name__regex='\.%s$' % self.name)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.name = self.name.lower()
|
self.name = self.name.lower()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" create top relation """
|
""" create top relation """
|
||||||
update = False
|
update = False
|
||||||
|
@ -110,7 +110,7 @@ class Domain(models.Model):
|
||||||
# queryset.update() is not used because we want to trigger backend to delete ex-topdomains
|
# queryset.update() is not used because we want to trigger backend to delete ex-topdomains
|
||||||
domain.top = self
|
domain.top = self
|
||||||
domain.save(update_fields=('top',))
|
domain.save(update_fields=('top',))
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
if self.is_top:
|
if self.is_top:
|
||||||
num = self.subdomains.count()
|
num = self.subdomains.count()
|
||||||
|
@ -119,21 +119,21 @@ class Domain(models.Model):
|
||||||
_("top domain with %d subdomains") % num,
|
_("top domain with %d subdomains") % num,
|
||||||
num)
|
num)
|
||||||
return _("subdomain")
|
return _("subdomain")
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return 'http://%s' % self.name
|
return 'http://%s' % self.name
|
||||||
|
|
||||||
def get_declared_records(self):
|
def get_declared_records(self):
|
||||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||||
return self.records.all()
|
return self.records.all()
|
||||||
|
|
||||||
def get_subdomains(self):
|
def get_subdomains(self):
|
||||||
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
""" proxy method, needed for input validation, see helpers.domain_for_validation """
|
||||||
return self.origin.subdomain_set.all().prefetch_related('records')
|
return self.origin.subdomain_set.all().prefetch_related('records')
|
||||||
|
|
||||||
def get_parent(self, top=False):
|
def get_parent(self, top=False):
|
||||||
return type(self).objects.get_parent(self.name, top=top)
|
return type(self).objects.get_parent(self.name, top=top)
|
||||||
|
|
||||||
def render_zone(self):
|
def render_zone(self):
|
||||||
origin = self.origin
|
origin = self.origin
|
||||||
zone = origin.render_records()
|
zone = origin.render_records()
|
||||||
|
@ -147,7 +147,7 @@ class Domain(models.Model):
|
||||||
for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True):
|
for subdomain in sorted(tail, key=lambda x: len(x.name), reverse=True):
|
||||||
zone += subdomain.render_records()
|
zone += subdomain.render_records()
|
||||||
return zone.strip()
|
return zone.strip()
|
||||||
|
|
||||||
def refresh_serial(self):
|
def refresh_serial(self):
|
||||||
""" Increases the domain serial number by one """
|
""" Increases the domain serial number by one """
|
||||||
serial = utils.generate_zone_serial()
|
serial = utils.generate_zone_serial()
|
||||||
|
@ -159,7 +159,7 @@ class Domain(models.Model):
|
||||||
serial = int(serial)
|
serial = int(serial)
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.save(update_fields=('serial',))
|
self.save(update_fields=('serial',))
|
||||||
|
|
||||||
def get_default_soa(self):
|
def get_default_soa(self):
|
||||||
return ' '.join([
|
return ' '.join([
|
||||||
"%s." % settings.DOMAINS_DEFAULT_NAME_SERVER,
|
"%s." % settings.DOMAINS_DEFAULT_NAME_SERVER,
|
||||||
|
@ -170,7 +170,7 @@ class Domain(models.Model):
|
||||||
self.expire or settings.DOMAINS_DEFAULT_EXPIRE,
|
self.expire or settings.DOMAINS_DEFAULT_EXPIRE,
|
||||||
self.min_ttl or settings.DOMAINS_DEFAULT_MIN_TTL,
|
self.min_ttl or settings.DOMAINS_DEFAULT_MIN_TTL,
|
||||||
])
|
])
|
||||||
|
|
||||||
def get_default_records(self):
|
def get_default_records(self):
|
||||||
defaults = []
|
defaults = []
|
||||||
if self.is_top:
|
if self.is_top:
|
||||||
|
@ -202,7 +202,7 @@ class Domain(models.Model):
|
||||||
value=default_aaaa
|
value=default_aaaa
|
||||||
))
|
))
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
def record_is_implicit(self, record, types):
|
def record_is_implicit(self, record, types):
|
||||||
if record.type not in types:
|
if record.type not in types:
|
||||||
if record.type is Record.NS:
|
if record.type is Record.NS:
|
||||||
|
@ -221,7 +221,7 @@ class Domain(models.Model):
|
||||||
elif not has_a and not has_aaaa:
|
elif not has_a and not has_aaaa:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_records(self):
|
def get_records(self):
|
||||||
types = set()
|
types = set()
|
||||||
records = utils.RecordStorage()
|
records = utils.RecordStorage()
|
||||||
|
@ -249,7 +249,7 @@ class Domain(models.Model):
|
||||||
else:
|
else:
|
||||||
records.append(record)
|
records.append(record)
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def render_records(self):
|
def render_records(self):
|
||||||
result = ''
|
result = ''
|
||||||
for record in self.get_records():
|
for record in self.get_records():
|
||||||
|
@ -273,7 +273,7 @@ class Domain(models.Model):
|
||||||
value=record.value
|
value=record.value
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def has_default_mx(self):
|
def has_default_mx(self):
|
||||||
records = self.get_records()
|
records = self.get_records()
|
||||||
for record in records.by_type('MX'):
|
for record in records.by_type('MX'):
|
||||||
|
@ -294,7 +294,7 @@ class Record(models.Model):
|
||||||
TXT = 'TXT'
|
TXT = 'TXT'
|
||||||
SPF = 'SPF'
|
SPF = 'SPF'
|
||||||
SOA = 'SOA'
|
SOA = 'SOA'
|
||||||
|
|
||||||
TYPE_CHOICES = (
|
TYPE_CHOICES = (
|
||||||
(MX, "MX"),
|
(MX, "MX"),
|
||||||
(NS, "NS"),
|
(NS, "NS"),
|
||||||
|
@ -305,7 +305,7 @@ class Record(models.Model):
|
||||||
(TXT, "TXT"),
|
(TXT, "TXT"),
|
||||||
(SPF, "SPF"),
|
(SPF, "SPF"),
|
||||||
)
|
)
|
||||||
|
|
||||||
VALIDATORS = {
|
VALIDATORS = {
|
||||||
MX: (validators.validate_mx_record,),
|
MX: (validators.validate_mx_record,),
|
||||||
NS: (validators.validate_zone_label,),
|
NS: (validators.validate_zone_label,),
|
||||||
|
@ -317,8 +317,8 @@ class Record(models.Model):
|
||||||
SRV: (validators.validate_srv_record,),
|
SRV: (validators.validate_srv_record,),
|
||||||
SOA: (validators.validate_soa_record,),
|
SOA: (validators.validate_soa_record,),
|
||||||
}
|
}
|
||||||
|
|
||||||
domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records')
|
domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records', on_delete=models.CASCADE)
|
||||||
ttl = models.CharField(_("TTL"), max_length=8, blank=True,
|
ttl = models.CharField(_("TTL"), max_length=8, blank=True,
|
||||||
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
|
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
|
||||||
validators=[validators.validate_zone_interval])
|
validators=[validators.validate_zone_interval])
|
||||||
|
@ -326,10 +326,10 @@ class Record(models.Model):
|
||||||
# max_length bumped from 256 to 1024 (arbitrary) on August 2019.
|
# max_length bumped from 256 to 1024 (arbitrary) on August 2019.
|
||||||
value = models.CharField(_("value"), max_length=1024,
|
value = models.CharField(_("value"), max_length=1024,
|
||||||
help_text=_("MX, NS and CNAME records sould end with a dot."))
|
help_text=_("MX, NS and CNAME records sould end with a dot."))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s %s IN %s %s" % (self.domain, self.get_ttl(), self.type, self.value)
|
return "%s %s IN %s %s" % (self.domain, self.get_ttl(), self.type, self.value)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" validates record value based on its type """
|
""" validates record value based on its type """
|
||||||
# validate value
|
# validate value
|
||||||
|
@ -343,6 +343,6 @@ class Record(models.Model):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'value': error,
|
'value': error,
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_ttl(self):
|
def get_ttl(self):
|
||||||
return self.ttl or settings.DOMAINS_DEFAULT_TTL
|
return self.ttl or settings.DOMAINS_DEFAULT_TTL
|
||||||
|
|
|
@ -19,10 +19,10 @@ class Queue(models.Model):
|
||||||
choices=Contact.EMAIL_USAGES,
|
choices=Contact.EMAIL_USAGES,
|
||||||
default=contacts_settings.CONTACTS_DEFAULT_EMAIL_USAGES,
|
default=contacts_settings.CONTACTS_DEFAULT_EMAIL_USAGES,
|
||||||
help_text=_("Contacts to notify by email"))
|
help_text=_("Contacts to notify by email"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.verbose_name or self.name
|
return self.verbose_name or self.name
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" mark as default queue if needed """
|
""" mark as default queue if needed """
|
||||||
existing_default = Queue.objects.filter(default=True)
|
existing_default = Queue.objects.filter(default=True)
|
||||||
|
@ -48,7 +48,7 @@ class Ticket(models.Model):
|
||||||
(MEDIUM, 'Medium'),
|
(MEDIUM, 'Medium'),
|
||||||
(LOW, 'Low'),
|
(LOW, 'Low'),
|
||||||
)
|
)
|
||||||
|
|
||||||
NEW = 'NEW'
|
NEW = 'NEW'
|
||||||
IN_PROGRESS = 'IN_PROGRESS'
|
IN_PROGRESS = 'IN_PROGRESS'
|
||||||
RESOLVED = 'RESOLVED'
|
RESOLVED = 'RESOLVED'
|
||||||
|
@ -63,7 +63,7 @@ class Ticket(models.Model):
|
||||||
(REJECTED, 'Rejected'),
|
(REJECTED, 'Rejected'),
|
||||||
(CLOSED, 'Closed'),
|
(CLOSED, 'Closed'),
|
||||||
)
|
)
|
||||||
|
|
||||||
creator = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("created by"),
|
creator = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("created by"),
|
||||||
related_name='tickets_created', null=True, on_delete=models.SET_NULL)
|
related_name='tickets_created', null=True, on_delete=models.SET_NULL)
|
||||||
creator_name = models.CharField(_("creator name"), max_length=256, blank=True)
|
creator_name = models.CharField(_("creator name"), max_length=256, blank=True)
|
||||||
|
@ -79,15 +79,15 @@ class Ticket(models.Model):
|
||||||
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
||||||
updated_at = models.DateTimeField(_("modified"), auto_now=True)
|
updated_at = models.DateTimeField(_("modified"), auto_now=True)
|
||||||
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), blank=True)
|
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), blank=True)
|
||||||
|
|
||||||
objects = TicketQuerySet.as_manager()
|
objects = TicketQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-updated_at']
|
ordering = ['-updated_at']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pk)
|
return str(self.pk)
|
||||||
|
|
||||||
def get_notification_emails(self):
|
def get_notification_emails(self):
|
||||||
""" Get emails of the users related to the ticket """
|
""" Get emails of the users related to the ticket """
|
||||||
emails = list(settings.ISSUES_SUPPORT_EMAILS)
|
emails = list(settings.ISSUES_SUPPORT_EMAILS)
|
||||||
|
@ -100,7 +100,7 @@ class Ticket(models.Model):
|
||||||
for message in self.messages.distinct('author'):
|
for message in self.messages.distinct('author'):
|
||||||
emails.append(message.author.email)
|
emails.append(message.author.email)
|
||||||
return set(emails + self.get_cc_emails())
|
return set(emails + self.get_cc_emails())
|
||||||
|
|
||||||
def notify(self, message=None, content=None):
|
def notify(self, message=None, content=None):
|
||||||
""" Send an email to ticket stakeholders notifying an state update """
|
""" Send an email to ticket stakeholders notifying an state update """
|
||||||
emails = self.get_notification_emails()
|
emails = self.get_notification_emails()
|
||||||
|
@ -111,7 +111,7 @@ class Ticket(models.Model):
|
||||||
'ticket_message': message
|
'ticket_message': message
|
||||||
}
|
}
|
||||||
send_email_template(template, context, emails, html=html_template)
|
send_email_template(template, context, emails, html=html_template)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" notify stakeholders of new ticket """
|
""" notify stakeholders of new ticket """
|
||||||
new_issue = not self.pk
|
new_issue = not self.pk
|
||||||
|
@ -121,60 +121,60 @@ class Ticket(models.Model):
|
||||||
if new_issue:
|
if new_issue:
|
||||||
# PK should be available for rendering the template
|
# PK should be available for rendering the template
|
||||||
self.notify()
|
self.notify()
|
||||||
|
|
||||||
def is_involved_by(self, user):
|
def is_involved_by(self, user):
|
||||||
""" returns whether user has participated or is referenced on the ticket
|
""" returns whether user has participated or is referenced on the ticket
|
||||||
as owner or member of the group
|
as owner or member of the group
|
||||||
"""
|
"""
|
||||||
return Ticket.objects.filter(pk=self.pk).involved_by(user).exists()
|
return Ticket.objects.filter(pk=self.pk).involved_by(user).exists()
|
||||||
|
|
||||||
def get_cc_emails(self):
|
def get_cc_emails(self):
|
||||||
return self.cc.split(',') if self.cc else []
|
return self.cc.split(',') if self.cc else []
|
||||||
|
|
||||||
def mark_as_read_by(self, user):
|
def mark_as_read_by(self, user):
|
||||||
self.trackers.get_or_create(user=user)
|
self.trackers.get_or_create(user=user)
|
||||||
|
|
||||||
def mark_as_unread_by(self, user):
|
def mark_as_unread_by(self, user):
|
||||||
self.trackers.filter(user=user).delete()
|
self.trackers.filter(user=user).delete()
|
||||||
|
|
||||||
def mark_as_unread(self):
|
def mark_as_unread(self):
|
||||||
self.trackers.all().delete()
|
self.trackers.all().delete()
|
||||||
|
|
||||||
def is_read_by(self, user):
|
def is_read_by(self, user):
|
||||||
return self.trackers.filter(user=user).exists()
|
return self.trackers.filter(user=user).exists()
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
self.state = Ticket.REJECTED
|
self.state = Ticket.REJECTED
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
def resolve(self):
|
def resolve(self):
|
||||||
self.state = Ticket.RESOLVED
|
self.state = Ticket.RESOLVED
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.state = Ticket.CLOSED
|
self.state = Ticket.CLOSED
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
def take(self, user):
|
def take(self, user):
|
||||||
self.owner = user
|
self.owner = user
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
ticket = models.ForeignKey('issues.Ticket', verbose_name=_("ticket"),
|
ticket = models.ForeignKey('issues.Ticket', on_delete=models.CASCADE,
|
||||||
related_name='messages')
|
verbose_name=_("ticket"), related_name='messages')
|
||||||
author = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("author"),
|
author = models.ForeignKey(djsettings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||||
related_name='ticket_messages')
|
verbose_name=_("author"), related_name='ticket_messages')
|
||||||
author_name = models.CharField(_("author name"), max_length=256, blank=True)
|
author_name = models.CharField(_("author name"), max_length=256, blank=True)
|
||||||
content = models.TextField(_("content"))
|
content = models.TextField(_("content"))
|
||||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
get_latest_by = 'id'
|
get_latest_by = 'id'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "#%i" % self.id
|
return "#%i" % self.id
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
""" notify stakeholders of ticket update """
|
""" notify stakeholders of ticket update """
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
|
@ -183,7 +183,7 @@ class Message(models.Model):
|
||||||
self.ticket.notify(message=self)
|
self.ticket.notify(message=self)
|
||||||
self.author_name = self.author.get_full_name()
|
self.author_name = self.author.get_full_name()
|
||||||
super(Message, self).save(*args, **kwargs)
|
super(Message, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number(self):
|
def number(self):
|
||||||
return self.ticket.messages.filter(id__lte=self.id).count()
|
return self.ticket.messages.filter(id__lte=self.id).count()
|
||||||
|
@ -191,10 +191,11 @@ class Message(models.Model):
|
||||||
|
|
||||||
class TicketTracker(models.Model):
|
class TicketTracker(models.Model):
|
||||||
""" Keeps track of user read tickets """
|
""" Keeps track of user read tickets """
|
||||||
ticket = models.ForeignKey(Ticket, verbose_name=_("ticket"), related_name='trackers')
|
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE,
|
||||||
user = models.ForeignKey(djsettings.AUTH_USER_MODEL, verbose_name=_("user"),
|
verbose_name=_("ticket"), related_name='trackers')
|
||||||
related_name='ticket_trackers')
|
user = models.ForeignKey(djsettings.AUTH_USER_MODEL, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("user"), related_name='ticket_trackers')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = (
|
unique_together = (
|
||||||
('ticket', 'user'),
|
('ticket', 'user'),
|
||||||
|
|
|
@ -30,54 +30,54 @@ class List(models.Model):
|
||||||
admin_email = models.EmailField(_("admin email"),
|
admin_email = models.EmailField(_("admin email"),
|
||||||
help_text=_("Administration email address"))
|
help_text=_("Administration email address"))
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='lists')
|
related_name='lists', on_delete=models.CASCADE)
|
||||||
# TODO also admin
|
# TODO also admin
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
objects = ListQuerySet.as_manager()
|
objects = ListQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('address_name', 'address_domain')
|
unique_together = ('address_name', 'address_domain')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address(self):
|
def address(self):
|
||||||
if self.address_name and self.address_domain:
|
if self.address_name and self.address_domain:
|
||||||
return "%s@%s" % (self.address_name, self.address_domain)
|
return "%s@%s" % (self.address_name, self.address_domain)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.address_name and not self.address_domain_id:
|
if self.address_name and not self.address_domain_id:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'address_domain': _("Domain should be selected for provided address name."),
|
'address_domain': _("Domain should be selected for provided address name."),
|
||||||
})
|
})
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def get_address_name(self):
|
def get_address_name(self):
|
||||||
return self.address_name or self.name
|
return self.address_name or self.name
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
context = {
|
context = {
|
||||||
'name': self.name
|
'name': self.name
|
||||||
|
|
|
@ -13,7 +13,7 @@ from . import validators, settings
|
||||||
|
|
||||||
class Mailbox(models.Model):
|
class Mailbox(models.Model):
|
||||||
CUSTOM = 'CUSTOM'
|
CUSTOM = 'CUSTOM'
|
||||||
|
|
||||||
name = models.CharField(_("name"), unique=True, db_index=True,
|
name = models.CharField(_("name"), unique=True, db_index=True,
|
||||||
max_length=settings.MAILBOXES_NAME_MAX_LENGTH,
|
max_length=settings.MAILBOXES_NAME_MAX_LENGTH,
|
||||||
help_text=_("Required. %s characters or fewer. Letters, digits and ./-/_ only.") %
|
help_text=_("Required. %s characters or fewer. Letters, digits and ./-/_ only.") %
|
||||||
|
@ -23,7 +23,7 @@ class Mailbox(models.Model):
|
||||||
])
|
])
|
||||||
password = models.CharField(_("password"), max_length=128)
|
password = models.CharField(_("password"), max_length=128)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='mailboxes')
|
related_name='mailboxes', on_delete=models.CASCADE)
|
||||||
filtering = models.CharField(max_length=16,
|
filtering = models.CharField(max_length=16,
|
||||||
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
|
default=settings.MAILBOXES_MAILBOX_DEFAULT_FILTERING,
|
||||||
choices=[(k, v[0]) for k,v in sorted(settings.MAILBOXES_MAILBOX_FILTERINGS.items())])
|
choices=[(k, v[0]) for k,v in sorted(settings.MAILBOXES_MAILBOX_FILTERINGS.items())])
|
||||||
|
@ -33,59 +33,59 @@ class Mailbox(models.Model):
|
||||||
"<a href='https://tty1.net/blog/2011/sieve-tutorial_en.html'>sieve language</a>. "
|
"<a href='https://tty1.net/blog/2011/sieve-tutorial_en.html'>sieve language</a>. "
|
||||||
"This overrides any automatic junk email filtering"))
|
"This overrides any automatic junk email filtering"))
|
||||||
is_active = models.BooleanField(_("active"), default=True)
|
is_active = models.BooleanField(_("active"), default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("mailboxes")
|
verbose_name_plural = _("mailboxes")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
try:
|
try:
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
except type(self).account.field.rel.to.DoesNotExist:
|
except type(self).account.field.rel.to.DoesNotExist:
|
||||||
return self.is_active
|
return self.is_active
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self.password = make_password(raw_password)
|
self.password = make_password(raw_password)
|
||||||
|
|
||||||
def get_home(self):
|
def get_home(self):
|
||||||
context = {
|
context = {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'username': self.name,
|
'username': self.name,
|
||||||
}
|
}
|
||||||
return os.path.normpath(settings.MAILBOXES_HOME % context)
|
return os.path.normpath(settings.MAILBOXES_HOME % context)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.filtering == self.CUSTOM and not self.custom_filtering:
|
if self.filtering == self.CUSTOM and not self.custom_filtering:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'custom_filtering': _("Custom filtering is selected but not provided.")
|
'custom_filtering': _("Custom filtering is selected but not provided.")
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_filtering(self):
|
def get_filtering(self):
|
||||||
name, content = settings.MAILBOXES_MAILBOX_FILTERINGS[self.filtering]
|
name, content = settings.MAILBOXES_MAILBOX_FILTERINGS[self.filtering]
|
||||||
if callable(content):
|
if callable(content):
|
||||||
# Custom filtering
|
# Custom filtering
|
||||||
content = content(self)
|
content = content(self)
|
||||||
return (name, content)
|
return (name, content)
|
||||||
|
|
||||||
def get_local_address(self):
|
def get_local_address(self):
|
||||||
if not settings.MAILBOXES_LOCAL_DOMAIN:
|
if not settings.MAILBOXES_LOCAL_DOMAIN:
|
||||||
raise AttributeError("Mailboxes do not have a defined local address domain.")
|
raise AttributeError("Mailboxes do not have a defined local address domain.")
|
||||||
return '@'.join((self.name, settings.MAILBOXES_LOCAL_DOMAIN))
|
return '@'.join((self.name, settings.MAILBOXES_LOCAL_DOMAIN))
|
||||||
|
|
||||||
def get_forwards(self):
|
def get_forwards(self):
|
||||||
return Address.objects.filter(forward__regex=r'(^|.*\s)%s(\s.*|$)' % self.name)
|
return Address.objects.filter(forward__regex=r'(^|.*\s)%s(\s.*|$)' % self.name)
|
||||||
|
|
||||||
def get_addresses(self):
|
def get_addresses(self):
|
||||||
mboxes = self.addresses.all()
|
mboxes = self.addresses.all()
|
||||||
forwards = self.get_forwards()
|
forwards = self.get_forwards()
|
||||||
|
@ -97,33 +97,33 @@ class Address(models.Model):
|
||||||
validators=[validators.validate_emailname],
|
validators=[validators.validate_emailname],
|
||||||
help_text=_("Address name, left blank for a <i>catch-all</i> address"))
|
help_text=_("Address name, left blank for a <i>catch-all</i> address"))
|
||||||
domain = models.ForeignKey(settings.MAILBOXES_DOMAIN_MODEL,
|
domain = models.ForeignKey(settings.MAILBOXES_DOMAIN_MODEL,
|
||||||
verbose_name=_("domain"), related_name='addresses')
|
verbose_name=_("domain"), related_name='addresses', on_delete=models.CASCADE)
|
||||||
mailboxes = models.ManyToManyField(Mailbox, verbose_name=_("mailboxes"),
|
mailboxes = models.ManyToManyField(Mailbox, verbose_name=_("mailboxes"),
|
||||||
related_name='addresses', blank=True)
|
related_name='addresses', blank=True)
|
||||||
forward = models.CharField(_("forward"), max_length=256, blank=True,
|
forward = models.CharField(_("forward"), max_length=256, blank=True,
|
||||||
validators=[validators.validate_forward],
|
validators=[validators.validate_forward],
|
||||||
help_text=_("Space separated email addresses or mailboxes"))
|
help_text=_("Space separated email addresses or mailboxes"))
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='addresses')
|
related_name='addresses', on_delete=models.CASCADE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("addresses")
|
verbose_name_plural = _("addresses")
|
||||||
unique_together = ('name', 'domain')
|
unique_together = ('name', 'domain')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.email
|
return self.email
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def email(self):
|
def email(self):
|
||||||
return "%s@%s" % (self.name, self.domain)
|
return "%s@%s" % (self.name, self.domain)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def destination(self):
|
def destination(self):
|
||||||
destinations = list(self.mailboxes.values_list('name', flat=True))
|
destinations = list(self.mailboxes.values_list('name', flat=True))
|
||||||
if self.forward:
|
if self.forward:
|
||||||
destinations += self.forward.split()
|
destinations += self.forward.split()
|
||||||
return ' '.join(destinations)
|
return ' '.join(destinations)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
errors = defaultdict(list)
|
errors = defaultdict(list)
|
||||||
local_domain = settings.MAILBOXES_LOCAL_DOMAIN
|
local_domain = settings.MAILBOXES_LOCAL_DOMAIN
|
||||||
|
@ -149,7 +149,7 @@ class Address(models.Model):
|
||||||
)
|
)
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
def get_forward_mailboxes(self):
|
def get_forward_mailboxes(self):
|
||||||
rm_local_domain = re.compile(r'@%s$' % settings.MAILBOXES_LOCAL_DOMAIN)
|
rm_local_domain = re.compile(r'@%s$' % settings.MAILBOXES_LOCAL_DOMAIN)
|
||||||
mailboxes = []
|
mailboxes = []
|
||||||
|
@ -158,7 +158,7 @@ class Address(models.Model):
|
||||||
if '@' not in forward:
|
if '@' not in forward:
|
||||||
mailboxes.append(forward)
|
mailboxes.append(forward)
|
||||||
return Mailbox.objects.filter(name__in=mailboxes)
|
return Mailbox.objects.filter(name__in=mailboxes)
|
||||||
|
|
||||||
def get_mailboxes(self):
|
def get_mailboxes(self):
|
||||||
for mailbox in self.mailboxes.all():
|
for mailbox in self.mailboxes.all():
|
||||||
yield mailbox
|
yield mailbox
|
||||||
|
@ -168,11 +168,11 @@ class Address(models.Model):
|
||||||
|
|
||||||
class Autoresponse(models.Model):
|
class Autoresponse(models.Model):
|
||||||
address = models.OneToOneField(Address, verbose_name=_("address"),
|
address = models.OneToOneField(Address, verbose_name=_("address"),
|
||||||
related_name='autoresponse')
|
related_name='autoresponse', on_delete=models.CASCADE)
|
||||||
# TODO initial_date
|
# TODO initial_date
|
||||||
subject = models.CharField(_("subject"), max_length=256)
|
subject = models.CharField(_("subject"), max_length=256)
|
||||||
message = models.TextField(_("message"))
|
message = models.TextField(_("message"))
|
||||||
enabled = models.BooleanField(_("enabled"), default=False)
|
enabled = models.BooleanField(_("enabled"), default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.address
|
return self.address
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Message(models.Model):
|
||||||
(DEFERRED, _("Deferred")),
|
(DEFERRED, _("Deferred")),
|
||||||
(FAILED, _("Failed")),
|
(FAILED, _("Failed")),
|
||||||
)
|
)
|
||||||
|
|
||||||
CRITICAL = 0
|
CRITICAL = 0
|
||||||
HIGH = 1
|
HIGH = 1
|
||||||
NORMAL = 2
|
NORMAL = 2
|
||||||
|
@ -26,7 +26,7 @@ class Message(models.Model):
|
||||||
(NORMAL, _("Normal")),
|
(NORMAL, _("Normal")),
|
||||||
(LOW, _("Low")),
|
(LOW, _("Low")),
|
||||||
)
|
)
|
||||||
|
|
||||||
state = models.CharField(_("State"), max_length=16, choices=STATES, default=QUEUED,
|
state = models.CharField(_("State"), max_length=16, choices=STATES, default=QUEUED,
|
||||||
db_index=True)
|
db_index=True)
|
||||||
priority = models.PositiveIntegerField(_("Priority"), choices=PRIORITIES, default=NORMAL,
|
priority = models.PositiveIntegerField(_("Priority"), choices=PRIORITIES, default=NORMAL,
|
||||||
|
@ -38,21 +38,21 @@ class Message(models.Model):
|
||||||
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
|
||||||
retries = models.PositiveIntegerField(_("retries"), default=0, db_index=True)
|
retries = models.PositiveIntegerField(_("retries"), default=0, db_index=True)
|
||||||
last_try = models.DateTimeField(_("last try"), null=True, db_index=True)
|
last_try = models.DateTimeField(_("last try"), null=True, db_index=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s to %s' % (self.subject, self.to_address)
|
return '%s to %s' % (self.subject, self.to_address)
|
||||||
|
|
||||||
def defer(self):
|
def defer(self):
|
||||||
self.state = self.DEFERRED
|
self.state = self.DEFERRED
|
||||||
# Max tries
|
# Max tries
|
||||||
if self.retries >= len(settings.MAILER_DEFERE_SECONDS):
|
if self.retries >= len(settings.MAILER_DEFERE_SECONDS):
|
||||||
self.state = self.FAILED
|
self.state = self.FAILED
|
||||||
self.save(update_fields=('state',))
|
self.save(update_fields=('state',))
|
||||||
|
|
||||||
def sent(self):
|
def sent(self):
|
||||||
self.state = self.SENT
|
self.state = self.SENT
|
||||||
self.save(update_fields=('state',))
|
self.save(update_fields=('state',))
|
||||||
|
|
||||||
def log(self, error):
|
def log(self, error):
|
||||||
result = SMTPLog.SUCCESS
|
result = SMTPLog.SUCCESS
|
||||||
if error:
|
if error:
|
||||||
|
@ -67,7 +67,7 @@ class SMTPLog(models.Model):
|
||||||
(SUCCESS, _("Success")),
|
(SUCCESS, _("Success")),
|
||||||
(FAILURE, _("Failure")),
|
(FAILURE, _("Failure")),
|
||||||
)
|
)
|
||||||
message = models.ForeignKey(Message, editable=False, related_name='logs')
|
message = models.ForeignKey(Message, editable=False, related_name='logs', on_delete=models.CASCADE)
|
||||||
result = models.CharField(max_length=16, choices=RESULTS, default=SUCCESS)
|
result = models.CharField(max_length=16, choices=RESULTS, default=SUCCESS)
|
||||||
date = models.DateTimeField(auto_now_add=True)
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
log_message = models.TextField()
|
log_message = models.TextField()
|
||||||
|
|
|
@ -22,30 +22,30 @@ class MiscService(models.Model):
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Whether new instances of this service can be created "
|
help_text=_("Whether new instances of this service can be created "
|
||||||
"or not. Unselect this instead of deleting services."))
|
"or not. Unselect this instead of deleting services."))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.verbose_name = self.verbose_name.strip()
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
|
||||||
def get_verbose_name(self):
|
def get_verbose_name(self):
|
||||||
return self.verbose_name or self.name
|
return self.verbose_name or self.name
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
|
|
||||||
class Miscellaneous(models.Model):
|
class Miscellaneous(models.Model):
|
||||||
service = models.ForeignKey(MiscService, verbose_name=_("service"),
|
service = models.ForeignKey(MiscService, on_delete=models.CASCADE,
|
||||||
related_name='instances')
|
verbose_name=_("service"), related_name='instances')
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='miscellaneous')
|
verbose_name=_("account"), related_name='miscellaneous')
|
||||||
identifier = NullableCharField(_("identifier"), max_length=256, null=True, unique=True,
|
identifier = NullableCharField(_("identifier"), max_length=256, null=True, unique=True,
|
||||||
db_index=True, help_text=_("A unique identifier for this service."))
|
db_index=True, help_text=_("A unique identifier for this service."))
|
||||||
description = models.TextField(_("description"), blank=True)
|
description = models.TextField(_("description"), blank=True)
|
||||||
|
@ -53,32 +53,32 @@ class Miscellaneous(models.Model):
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this service should be treated as "
|
help_text=_("Designates whether this service should be treated as "
|
||||||
"active. Unselect this instead of deleting services."))
|
"active. Unselect this instead of deleting services."))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("miscellaneous")
|
verbose_name_plural = _("miscellaneous")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.identifier or self.description[:32] or str(self.service)
|
return self.identifier or self.description[:32] or str(self.service)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.is_active and self.service.is_active and self.account.is_active
|
return self.is_active and self.service.is_active and self.account.is_active
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return ' '.join((str(self.amount), self.service.description or self.service.verbose_name))
|
return ' '.join((str(self.amount), self.service.description or self.service.verbose_name))
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_class(self):
|
def service_class(self):
|
||||||
return self.service
|
return self.service
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.identifier:
|
if self.identifier:
|
||||||
self.identifier = self.identifier.strip().lower()
|
self.identifier = self.identifier.strip().lower()
|
||||||
|
|
|
@ -90,7 +90,7 @@ class BackendLog(models.Model):
|
||||||
|
|
||||||
backend = models.CharField(_("backend"), max_length=256)
|
backend = models.CharField(_("backend"), max_length=256)
|
||||||
state = models.CharField(_("state"), max_length=16, choices=STATES, default=RECEIVED)
|
state = models.CharField(_("state"), max_length=16, choices=STATES, default=RECEIVED)
|
||||||
server = models.ForeignKey(Server, verbose_name=_("server"), related_name='execution_logs')
|
server = models.ForeignKey(Server, verbose_name=_("server"), related_name='execution_logs', on_delete=models.CASCADE)
|
||||||
script = models.TextField(_("script"))
|
script = models.TextField(_("script"))
|
||||||
stdout = models.TextField(_("stdout"))
|
stdout = models.TextField(_("stdout"))
|
||||||
stderr = models.TextField(_("stderr"))
|
stderr = models.TextField(_("stderr"))
|
||||||
|
@ -135,10 +135,10 @@ class BackendOperation(models.Model):
|
||||||
"""
|
"""
|
||||||
Encapsulates an operation, storing its related object, the action and the backend.
|
Encapsulates an operation, storing its related object, the action and the backend.
|
||||||
"""
|
"""
|
||||||
log = models.ForeignKey('orchestration.BackendLog', related_name='operations')
|
log = models.ForeignKey('orchestration.BackendLog', related_name='operations', on_delete=models.CASCADE)
|
||||||
backend = models.CharField(_("backend"), max_length=256)
|
backend = models.CharField(_("backend"), max_length=256)
|
||||||
action = models.CharField(_("action"), max_length=64)
|
action = models.CharField(_("action"), max_length=64)
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField(null=True)
|
object_id = models.PositiveIntegerField(null=True)
|
||||||
instance_repr = models.CharField(_("instance representation"), max_length=256)
|
instance_repr = models.CharField(_("instance representation"), max_length=256)
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ class Route(models.Model):
|
||||||
"""
|
"""
|
||||||
backend = models.CharField(_("backend"), max_length=256,
|
backend = models.CharField(_("backend"), max_length=256,
|
||||||
choices=ServiceBackend.get_choices())
|
choices=ServiceBackend.get_choices())
|
||||||
host = models.ForeignKey(Server, verbose_name=_("host"), related_name='routes')
|
host = models.ForeignKey(Server, verbose_name=_("host"), related_name='routes', on_delete=models.CASCADE)
|
||||||
match = models.CharField(_("match"), max_length=256, blank=True, default='True',
|
match = models.CharField(_("match"), max_length=256, blank=True, default='True',
|
||||||
help_text=_("Python expression used for selecting the targe host, "
|
help_text=_("Python expression used for selecting the targe host, "
|
||||||
"<em>instance</em> referes to the current object."))
|
"<em>instance</em> referes to the current object."))
|
||||||
|
|
|
@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class OrderQuerySet(models.QuerySet):
|
class OrderQuerySet(models.QuerySet):
|
||||||
group_by = queryset.group_by
|
group_by = queryset.group_by
|
||||||
|
|
||||||
def bill(self, **options):
|
def bill(self, **options):
|
||||||
bills = []
|
bills = []
|
||||||
bill_backend = Order.get_bill_backend()
|
bill_backend = Order.get_bill_backend()
|
||||||
|
@ -46,17 +46,17 @@ class OrderQuerySet(models.QuerySet):
|
||||||
if commit:
|
if commit:
|
||||||
return list(set(bills))
|
return list(set(bills))
|
||||||
return bills
|
return bills
|
||||||
|
|
||||||
def givers(self, ini, end):
|
def givers(self, ini, end):
|
||||||
return self.cancelled_and_billed().filter(billed_until__gt=ini, registered_on__lt=end)
|
return self.cancelled_and_billed().filter(billed_until__gt=ini, registered_on__lt=end)
|
||||||
|
|
||||||
def cancelled_and_billed(self, exclude=False):
|
def cancelled_and_billed(self, exclude=False):
|
||||||
qs = dict(cancelled_on__isnull=False, billed_until__isnull=False,
|
qs = dict(cancelled_on__isnull=False, billed_until__isnull=False,
|
||||||
cancelled_on__lte=F('billed_until'))
|
cancelled_on__lte=F('billed_until'))
|
||||||
if exclude:
|
if exclude:
|
||||||
return self.exclude(**qs)
|
return self.exclude(**qs)
|
||||||
return self.filter(**qs)
|
return self.filter(**qs)
|
||||||
|
|
||||||
def get_related(self, **options):
|
def get_related(self, **options):
|
||||||
""" returns related orders that could have a pricing effect """
|
""" returns related orders that could have a pricing effect """
|
||||||
Service = apps.get_model(settings.ORDERS_SERVICE_MODEL)
|
Service = apps.get_model(settings.ORDERS_SERVICE_MODEL)
|
||||||
|
@ -86,25 +86,25 @@ class OrderQuerySet(models.QuerySet):
|
||||||
return self.model.objects.none()
|
return self.model.objects.none()
|
||||||
ids = self.values_list('id', flat=True)
|
ids = self.values_list('id', flat=True)
|
||||||
return self.model.objects.filter(qs).exclude(id__in=ids)
|
return self.model.objects.filter(qs).exclude(id__in=ids)
|
||||||
|
|
||||||
def pricing_orders(self, ini, end):
|
def pricing_orders(self, ini, end):
|
||||||
return self.filter(billed_until__isnull=False, billed_until__gt=ini,
|
return self.filter(billed_until__isnull=False, billed_until__gt=ini,
|
||||||
registered_on__lt=end)
|
registered_on__lt=end)
|
||||||
|
|
||||||
def by_object(self, obj, **kwargs):
|
def by_object(self, obj, **kwargs):
|
||||||
ct = ContentType.objects.get_for_model(obj)
|
ct = ContentType.objects.get_for_model(obj)
|
||||||
return self.filter(object_id=obj.pk, content_type=ct, **kwargs)
|
return self.filter(object_id=obj.pk, content_type=ct, **kwargs)
|
||||||
|
|
||||||
def active(self, **kwargs):
|
def active(self, **kwargs):
|
||||||
""" return active orders """
|
""" return active orders """
|
||||||
return self.filter(
|
return self.filter(
|
||||||
Q(cancelled_on__isnull=True) | Q(cancelled_on__gt=timezone.now())
|
Q(cancelled_on__isnull=True) | Q(cancelled_on__gt=timezone.now())
|
||||||
).filter(**kwargs)
|
).filter(**kwargs)
|
||||||
|
|
||||||
def inactive(self, **kwargs):
|
def inactive(self, **kwargs):
|
||||||
""" return inactive orders """
|
""" return inactive orders """
|
||||||
return self.filter(cancelled_on__lte=timezone.now(), **kwargs)
|
return self.filter(cancelled_on__lte=timezone.now(), **kwargs)
|
||||||
|
|
||||||
def update_by_instance(self, instance, service=None, commit=True):
|
def update_by_instance(self, instance, service=None, commit=True):
|
||||||
updates = []
|
updates = []
|
||||||
if service is None:
|
if service is None:
|
||||||
|
@ -150,12 +150,12 @@ class OrderQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='orders')
|
verbose_name=_("account"), related_name='orders')
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
object_id = models.PositiveIntegerField(null=True)
|
object_id = models.PositiveIntegerField(null=True)
|
||||||
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"),
|
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, on_delete=models.PROTECT,
|
||||||
related_name='orders')
|
verbose_name=_("service"), related_name='orders')
|
||||||
registered_on = models.DateField(_("registered"), default=timezone.now, db_index=True)
|
registered_on = models.DateField(_("registered"), default=timezone.now, db_index=True)
|
||||||
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
|
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
|
||||||
billed_on = models.DateField(_("billed"), null=True, blank=True)
|
billed_on = models.DateField(_("billed"), null=True, blank=True)
|
||||||
|
@ -166,29 +166,29 @@ class Order(models.Model):
|
||||||
description = models.TextField(_("description"), blank=True)
|
description = models.TextField(_("description"), blank=True)
|
||||||
content_object_repr = models.CharField(_("content object representation"), max_length=256,
|
content_object_repr = models.CharField(_("content object representation"), max_length=256,
|
||||||
editable=False, help_text=_("Used for searches."))
|
editable=False, help_text=_("Used for searches."))
|
||||||
|
|
||||||
content_object = GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
objects = OrderQuerySet.as_manager()
|
objects = OrderQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
get_latest_by = 'id'
|
get_latest_by = 'id'
|
||||||
index_together = (
|
index_together = (
|
||||||
('content_type', 'object_id'),
|
('content_type', 'object_id'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.service)
|
return str(self.service)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_bill_backend(cls):
|
def get_bill_backend(cls):
|
||||||
return import_class(settings.ORDERS_BILLING_BACKEND)()
|
return import_class(settings.ORDERS_BILLING_BACKEND)()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.billed_on and self.billed_on < self.registered_on:
|
if self.billed_on and self.billed_on < self.registered_on:
|
||||||
raise ValidationError(_("Billed date can not be earlier than registered on."))
|
raise ValidationError(_("Billed date can not be earlier than registered on."))
|
||||||
if self.billed_until and not self.billed_on:
|
if self.billed_until and not self.billed_on:
|
||||||
raise ValidationError(_("Billed on is missing while billed until is being provided."))
|
raise ValidationError(_("Billed on is missing while billed until is being provided."))
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
instance = self.content_object
|
instance = self.content_object
|
||||||
if instance is None:
|
if instance is None:
|
||||||
|
@ -214,22 +214,22 @@ class Order(models.Model):
|
||||||
update_fields.append('content_object_repr')
|
update_fields.append('content_object_repr')
|
||||||
if update_fields:
|
if update_fields:
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
def cancel(self, commit=True):
|
def cancel(self, commit=True):
|
||||||
self.cancelled_on = timezone.now()
|
self.cancelled_on = timezone.now()
|
||||||
self.ignore = self.service.handler.get_order_ignore(self)
|
self.ignore = self.service.handler.get_order_ignore(self)
|
||||||
if commit:
|
if commit:
|
||||||
self.save(update_fields=['cancelled_on', 'ignore'])
|
self.save(update_fields=['cancelled_on', 'ignore'])
|
||||||
logger.info("CANCELLED order id: {id}".format(id=self.id))
|
logger.info("CANCELLED order id: {id}".format(id=self.id))
|
||||||
|
|
||||||
def mark_as_ignored(self):
|
def mark_as_ignored(self):
|
||||||
self.ignore = True
|
self.ignore = True
|
||||||
self.save(update_fields=['ignore'])
|
self.save(update_fields=['ignore'])
|
||||||
|
|
||||||
def mark_as_not_ignored(self):
|
def mark_as_not_ignored(self):
|
||||||
self.ignore = False
|
self.ignore = False
|
||||||
self.save(update_fields=['ignore'])
|
self.save(update_fields=['ignore'])
|
||||||
|
|
||||||
def get_metric(self, *args, **kwargs):
|
def get_metric(self, *args, **kwargs):
|
||||||
if kwargs.pop('changes', False):
|
if kwargs.pop('changes', False):
|
||||||
ini, end = args
|
ini, end = args
|
||||||
|
@ -294,16 +294,17 @@ class MetricStorageQuerySet(models.QuerySet):
|
||||||
|
|
||||||
class MetricStorage(models.Model):
|
class MetricStorage(models.Model):
|
||||||
""" Stores metric state for future billing """
|
""" Stores metric state for future billing """
|
||||||
order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics')
|
order = models.ForeignKey(Order, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("order"), related_name='metrics')
|
||||||
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
||||||
created_on = models.DateField(_("created"), auto_now_add=True, editable=True)
|
created_on = models.DateField(_("created"), auto_now_add=True, editable=True)
|
||||||
# TODO time field?
|
# TODO time field?
|
||||||
updated_on = models.DateTimeField(_("updated"))
|
updated_on = models.DateTimeField(_("updated"))
|
||||||
|
|
||||||
objects = MetricStorageQuerySet.as_manager()
|
objects = MetricStorageQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
get_latest_by = 'id'
|
get_latest_by = 'id'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.order)
|
return str(self.order)
|
||||||
|
|
|
@ -18,50 +18,50 @@ class PaymentSourcesQueryset(models.QuerySet):
|
||||||
|
|
||||||
class PaymentSource(models.Model):
|
class PaymentSource(models.Model):
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='paymentsources')
|
related_name='paymentsources', on_delete=models.CASCADE)
|
||||||
method = models.CharField(_("method"), max_length=32,
|
method = models.CharField(_("method"), max_length=32,
|
||||||
choices=PaymentMethod.get_choices())
|
choices=PaymentMethod.get_choices())
|
||||||
data = JSONField(_("data"), default={})
|
data = JSONField(_("data"), default={})
|
||||||
is_active = models.BooleanField(_("active"), default=True)
|
is_active = models.BooleanField(_("active"), default=True)
|
||||||
|
|
||||||
objects = PaymentSourcesQueryset.as_manager()
|
objects = PaymentSourcesQueryset.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s (%s)" % (self.label, self.method_class.verbose_name)
|
return "%s (%s)" % (self.label, self.method_class.verbose_name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def method_class(self):
|
def method_class(self):
|
||||||
return PaymentMethod.get(self.method)
|
return PaymentMethod.get(self.method)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def method_instance(self):
|
def method_instance(self):
|
||||||
""" Per request lived method_instance """
|
""" Per request lived method_instance """
|
||||||
return self.method_class(self)
|
return self.method_class(self)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def label(self):
|
def label(self):
|
||||||
return self.method_instance.get_label()
|
return self.method_instance.get_label()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def number(self):
|
def number(self):
|
||||||
return self.method_instance.get_number()
|
return self.method_instance.get_number()
|
||||||
|
|
||||||
def get_bill_context(self):
|
def get_bill_context(self):
|
||||||
method = self.method_instance
|
method = self.method_instance
|
||||||
return {
|
return {
|
||||||
'message': method.get_bill_message(),
|
'message': method.get_bill_message(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_due_delta(self):
|
def get_due_delta(self):
|
||||||
return self.method_instance.due_delta
|
return self.method_instance.due_delta
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.data = self.method_instance.clean_data()
|
self.data = self.method_instance.clean_data()
|
||||||
|
|
||||||
|
|
||||||
class TransactionQuerySet(models.QuerySet):
|
class TransactionQuerySet(models.QuerySet):
|
||||||
group_by = group_by
|
group_by = group_by
|
||||||
|
|
||||||
def create(self, **kwargs):
|
def create(self, **kwargs):
|
||||||
source = kwargs.get('source')
|
source = kwargs.get('source')
|
||||||
if source is None or not hasattr(source.method_class, 'process'):
|
if source is None or not hasattr(source.method_class, 'process'):
|
||||||
|
@ -71,16 +71,16 @@ class TransactionQuerySet(models.QuerySet):
|
||||||
if amount == 0:
|
if amount == 0:
|
||||||
kwargs['state'] = self.model.SECURED
|
kwargs['state'] = self.model.SECURED
|
||||||
return super(TransactionQuerySet, self).create(**kwargs)
|
return super(TransactionQuerySet, self).create(**kwargs)
|
||||||
|
|
||||||
def secured(self):
|
def secured(self):
|
||||||
return self.filter(state=Transaction.SECURED)
|
return self.filter(state=Transaction.SECURED)
|
||||||
|
|
||||||
def exclude_rejected(self):
|
def exclude_rejected(self):
|
||||||
return self.exclude(state=Transaction.REJECTED)
|
return self.exclude(state=Transaction.REJECTED)
|
||||||
|
|
||||||
def amount(self):
|
def amount(self):
|
||||||
return next(iter(self.aggregate(models.Sum('amount')).values())) or 0
|
return next(iter(self.aggregate(models.Sum('amount')).values())) or 0
|
||||||
|
|
||||||
def processing(self):
|
def processing(self):
|
||||||
return self.filter(state__in=[Transaction.EXECUTED, Transaction.WAITTING_EXECUTION])
|
return self.filter(state__in=[Transaction.EXECUTED, Transaction.WAITTING_EXECUTION])
|
||||||
|
|
||||||
|
@ -108,8 +108,8 @@ class Transaction(models.Model):
|
||||||
REJECTED: _("The transaction has failed and the ammount is lost, a new transaction "
|
REJECTED: _("The transaction has failed and the ammount is lost, a new transaction "
|
||||||
"should be created for recharging."),
|
"should be created for recharging."),
|
||||||
}
|
}
|
||||||
|
|
||||||
bill = models.ForeignKey('bills.bill', verbose_name=_("bill"),
|
bill = models.ForeignKey('bills.bill', on_delete=models.CASCADE, verbose_name=_("bill"),
|
||||||
related_name='transactions')
|
related_name='transactions')
|
||||||
source = models.ForeignKey(PaymentSource, null=True, blank=True, on_delete=models.SET_NULL,
|
source = models.ForeignKey(PaymentSource, null=True, blank=True, on_delete=models.SET_NULL,
|
||||||
verbose_name=_("source"), related_name='transactions')
|
verbose_name=_("source"), related_name='transactions')
|
||||||
|
@ -121,16 +121,16 @@ class Transaction(models.Model):
|
||||||
currency = models.CharField(max_length=10, default=settings.PAYMENT_CURRENCY)
|
currency = models.CharField(max_length=10, default=settings.PAYMENT_CURRENCY)
|
||||||
created_at = models.DateTimeField(_("created"), auto_now_add=True)
|
created_at = models.DateTimeField(_("created"), auto_now_add=True)
|
||||||
modified_at = models.DateTimeField(_("modified"), auto_now=True)
|
modified_at = models.DateTimeField(_("modified"), auto_now=True)
|
||||||
|
|
||||||
objects = TransactionQuerySet.as_manager()
|
objects = TransactionQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "#%i" % self.id
|
return "#%i" % self.id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def account(self):
|
def account(self):
|
||||||
return self.bill.account
|
return self.bill.account
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
amount = self.bill.transactions.exclude(state=self.REJECTED).amount()
|
amount = self.bill.transactions.exclude(state=self.REJECTED).amount()
|
||||||
|
@ -141,24 +141,24 @@ class Transaction(models.Model):
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_state_help(self):
|
def get_state_help(self):
|
||||||
if self.source:
|
if self.source:
|
||||||
return self.source.method_instance.state_help.get(self.state) or self.STATE_HELP.get(self.state)
|
return self.source.method_instance.state_help.get(self.state) or self.STATE_HELP.get(self.state)
|
||||||
return self.STATE_HELP.get(self.state)
|
return self.STATE_HELP.get(self.state)
|
||||||
|
|
||||||
def mark_as_processed(self):
|
def mark_as_processed(self):
|
||||||
self.state = self.WAITTING_EXECUTION
|
self.state = self.WAITTING_EXECUTION
|
||||||
self.save(update_fields=('state', 'modified_at'))
|
self.save(update_fields=('state', 'modified_at'))
|
||||||
|
|
||||||
def mark_as_executed(self):
|
def mark_as_executed(self):
|
||||||
self.state = self.EXECUTED
|
self.state = self.EXECUTED
|
||||||
self.save(update_fields=('state', 'modified_at'))
|
self.save(update_fields=('state', 'modified_at'))
|
||||||
|
|
||||||
def mark_as_secured(self):
|
def mark_as_secured(self):
|
||||||
self.state = self.SECURED
|
self.state = self.SECURED
|
||||||
self.save(update_fields=('state', 'modified_at'))
|
self.save(update_fields=('state', 'modified_at'))
|
||||||
|
|
||||||
def mark_as_rejected(self):
|
def mark_as_rejected(self):
|
||||||
self.state = self.REJECTED
|
self.state = self.REJECTED
|
||||||
self.save(update_fields=('state', 'modified_at'))
|
self.save(update_fields=('state', 'modified_at'))
|
||||||
|
@ -178,31 +178,31 @@ class TransactionProcess(models.Model):
|
||||||
(ABORTED, _("Aborted")),
|
(ABORTED, _("Aborted")),
|
||||||
(COMMITED, _("Commited")),
|
(COMMITED, _("Commited")),
|
||||||
)
|
)
|
||||||
|
|
||||||
data = JSONField(_("data"), blank=True)
|
data = JSONField(_("data"), blank=True)
|
||||||
file = PrivateFileField(_("file"), blank=True)
|
file = PrivateFileField(_("file"), blank=True)
|
||||||
state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED)
|
state = models.CharField(_("state"), max_length=16, choices=STATES, default=CREATED)
|
||||||
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
|
||||||
updated_at = models.DateTimeField(_("updated"), auto_now=True)
|
updated_at = models.DateTimeField(_("updated"), auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("Transaction processes")
|
verbose_name_plural = _("Transaction processes")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '#%i' % self.id
|
return '#%i' % self.id
|
||||||
|
|
||||||
def mark_as_executed(self):
|
def mark_as_executed(self):
|
||||||
self.state = self.EXECUTED
|
self.state = self.EXECUTED
|
||||||
for transaction in self.transactions.all():
|
for transaction in self.transactions.all():
|
||||||
transaction.mark_as_executed()
|
transaction.mark_as_executed()
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
self.state = self.ABORTED
|
self.state = self.ABORTED
|
||||||
for transaction in self.transactions.all():
|
for transaction in self.transactions.all():
|
||||||
transaction.mark_as_rejected()
|
transaction.mark_as_rejected()
|
||||||
self.save(update_fields=('state', 'updated_at'))
|
self.save(update_fields=('state', 'updated_at'))
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
self.state = self.COMMITED
|
self.state = self.COMMITED
|
||||||
for transaction in self.transactions.processing():
|
for transaction in self.transactions.processing():
|
||||||
|
|
|
@ -25,32 +25,33 @@ class Plan(models.Model):
|
||||||
help_text=_("Designates whether this plan can be combined with other plans or not."))
|
help_text=_("Designates whether this plan can be combined with other plans or not."))
|
||||||
allow_multiple = models.BooleanField(_("allow multiple"), default=False,
|
allow_multiple = models.BooleanField(_("allow multiple"), default=False,
|
||||||
help_text=_("Designates whether this plan allow for multiple contractions."))
|
help_text=_("Designates whether this plan allow for multiple contractions."))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_verbose_name()
|
return self.get_verbose_name()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.verbose_name = self.verbose_name.strip()
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
|
||||||
def get_verbose_name(self):
|
def get_verbose_name(self):
|
||||||
return self.verbose_name or self.name
|
return self.verbose_name or self.name
|
||||||
|
|
||||||
|
|
||||||
class ContractedPlan(models.Model):
|
class ContractedPlan(models.Model):
|
||||||
plan = models.ForeignKey(Plan, verbose_name=_("plan"), related_name='contracts')
|
plan = models.ForeignKey(Plan, on_delete=models.CASCADE,
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
verbose_name=_("plan"), related_name='contracts')
|
||||||
related_name='plans')
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("account"), related_name='plans')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("plans")
|
verbose_name_plural = _("plans")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.plan)
|
return str(self.plan)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.plan.is_active and self.account.is_active
|
return self.plan.is_active and self.account.is_active
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.pk and not self.plan.allow_multiple:
|
if not self.pk and not self.plan.allow_multiple:
|
||||||
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():
|
if ContractedPlan.objects.filter(plan=self.plan, account=self.account).exists():
|
||||||
|
@ -59,7 +60,7 @@ class ContractedPlan(models.Model):
|
||||||
|
|
||||||
class RateQuerySet(models.QuerySet):
|
class RateQuerySet(models.QuerySet):
|
||||||
group_by = queryset.group_by
|
group_by = queryset.group_by
|
||||||
|
|
||||||
def by_account(self, account):
|
def by_account(self, account):
|
||||||
# Default allways selected
|
# Default allways selected
|
||||||
return self.filter(
|
return self.filter(
|
||||||
|
@ -69,27 +70,27 @@ class RateQuerySet(models.QuerySet):
|
||||||
|
|
||||||
|
|
||||||
class Rate(models.Model):
|
class Rate(models.Model):
|
||||||
service = models.ForeignKey('services.Service', verbose_name=_("service"),
|
service = models.ForeignKey('services.Service', on_delete=models.CASCADE,
|
||||||
related_name='rates')
|
verbose_name=_("service"), related_name='rates')
|
||||||
plan = models.ForeignKey(Plan, verbose_name=_("plan"), related_name='rates', null=True,
|
plan = models.ForeignKey(Plan, on_delete=models.SET_NULL, null=True, blank=True,
|
||||||
blank=True)
|
verbose_name=_("plan"), related_name='rates')
|
||||||
quantity = models.PositiveIntegerField(_("quantity"), null=True, blank=True,
|
quantity = models.PositiveIntegerField(_("quantity"), null=True, blank=True,
|
||||||
help_text=_("See rate algorihm help text."))
|
help_text=_("See rate algorihm help text."))
|
||||||
price = models.DecimalField(_("price"), max_digits=12, decimal_places=2)
|
price = models.DecimalField(_("price"), max_digits=12, decimal_places=2)
|
||||||
|
|
||||||
objects = RateQuerySet.as_manager()
|
objects = RateQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('service', 'plan', 'quantity')
|
unique_together = ('service', 'plan', 'quantity')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}-{}".format(str(self.price), self.quantity)
|
return "{}-{}".format(str(self.price), self.quantity)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_methods(cls):
|
def get_methods(cls):
|
||||||
return dict((method, import_class(method)) for method in settings.PLANS_RATE_METHODS)
|
return dict((method, import_class(method)) for method in settings.PLANS_RATE_METHODS)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_choices(cls):
|
def get_choices(cls):
|
||||||
|
@ -97,7 +98,7 @@ class Rate(models.Model):
|
||||||
for name, method in cls.get_methods().items():
|
for name, method in cls.get_methods().items():
|
||||||
choices.append((name, method.verbose_name))
|
choices.append((name, method.verbose_name))
|
||||||
return choices
|
return choices
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default(cls):
|
def get_default(cls):
|
||||||
return settings.PLANS_DEFAULT_RATE_METHOD
|
return settings.PLANS_DEFAULT_RATE_METHOD
|
||||||
|
|
|
@ -42,7 +42,7 @@ class Resource(models.Model):
|
||||||
"digits and hyphen only."),
|
"digits and hyphen only."),
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
||||||
content_type = models.ForeignKey(ContentType,
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
|
||||||
help_text=_("Model where this resource will be hooked."))
|
help_text=_("Model where this resource will be hooked."))
|
||||||
aggregation = models.CharField(_("aggregation"), max_length=16,
|
aggregation = models.CharField(_("aggregation"), max_length=16,
|
||||||
choices=Aggregation.get_choices(), default=Aggregation.get_choices()[0][0],
|
choices=Aggregation.get_choices(), default=Aggregation.get_choices()[0][0],
|
||||||
|
@ -178,8 +178,8 @@ class ResourceDataQuerySet(models.QuerySet):
|
||||||
|
|
||||||
class ResourceData(models.Model):
|
class ResourceData(models.Model):
|
||||||
""" Stores computed resource usage and allocation """
|
""" Stores computed resource usage and allocation """
|
||||||
resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource"))
|
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='dataset', verbose_name=_("resource"))
|
||||||
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name=_("content type"))
|
||||||
object_id = models.PositiveIntegerField(_("object id"))
|
object_id = models.PositiveIntegerField(_("object id"))
|
||||||
used = models.DecimalField(_("used"), max_digits=16, decimal_places=3, null=True,
|
used = models.DecimalField(_("used"), max_digits=16, decimal_places=3, null=True,
|
||||||
editable=False)
|
editable=False)
|
||||||
|
@ -267,7 +267,7 @@ class MonitorData(models.Model):
|
||||||
""" Stores monitored data """
|
""" Stores monitored data """
|
||||||
monitor = models.CharField(_("monitor"), max_length=256, db_index=True,
|
monitor = models.CharField(_("monitor"), max_length=256, db_index=True,
|
||||||
choices=ServiceMonitor.get_choices())
|
choices=ServiceMonitor.get_choices())
|
||||||
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name=_("content type"))
|
||||||
object_id = models.PositiveIntegerField(_("object id"))
|
object_id = models.PositiveIntegerField(_("object id"))
|
||||||
created_at = models.DateTimeField(_("created"), default=timezone.now, db_index=True)
|
created_at = models.DateTimeField(_("created"), default=timezone.now, db_index=True)
|
||||||
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
|
||||||
|
|
|
@ -26,8 +26,8 @@ class SaaS(models.Model):
|
||||||
name = models.CharField(_("Name"), max_length=64,
|
name = models.CharField(_("Name"), max_length=64,
|
||||||
help_text=_("Required. 64 characters or fewer. Letters, digits and ./- only."),
|
help_text=_("Required. 64 characters or fewer. Letters, digits and ./- only."),
|
||||||
validators=[validators.validate_hostname])
|
validators=[validators.validate_hostname])
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='saas')
|
verbose_name=_("account"), related_name='saas')
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this service should be treated as active. "))
|
help_text=_("Designates whether this service should be treated as active. "))
|
||||||
data = JSONField(_("data"), default={},
|
data = JSONField(_("data"), default={},
|
||||||
|
@ -36,51 +36,52 @@ class SaaS(models.Model):
|
||||||
help_text=_("Optional and alternative URL for accessing this service instance. "
|
help_text=_("Optional and alternative URL for accessing this service instance. "
|
||||||
"i.e. <tt>https://wiki.mydomain/doku/</tt><br>"
|
"i.e. <tt>https://wiki.mydomain/doku/</tt><br>"
|
||||||
"A related website will be automatically configured if needed."))
|
"A related website will be automatically configured if needed."))
|
||||||
database = models.ForeignKey('databases.Database', null=True, blank=True)
|
database = models.ForeignKey('databases.Database',
|
||||||
|
on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
# Some SaaS sites may need a database, with this virtual field we tell the ORM to delete them
|
# Some SaaS sites may need a database, with this virtual field we tell the ORM to delete them
|
||||||
databases = VirtualDatabaseRelation('databases.Database')
|
databases = VirtualDatabaseRelation('databases.Database')
|
||||||
objects = SaaSQuerySet.as_manager()
|
objects = SaaSQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "SaaS"
|
verbose_name = "SaaS"
|
||||||
verbose_name_plural = "SaaS"
|
verbose_name_plural = "SaaS"
|
||||||
unique_together = (
|
unique_together = (
|
||||||
('name', 'service'),
|
('name', 'service'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s@%s" % (self.name, self.service)
|
return "%s@%s" % (self.name, self.service)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_class(self):
|
def service_class(self):
|
||||||
return SoftwareService.get(self.service)
|
return SoftwareService.get(self.service)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_instance(self):
|
def service_instance(self):
|
||||||
""" Per request lived service_instance """
|
""" Per request lived service_instance """
|
||||||
return self.service_class(self)
|
return self.service_class(self)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = True
|
self.is_active = True
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.pk:
|
if not self.pk:
|
||||||
self.name = self.name.lower()
|
self.name = self.name.lower()
|
||||||
self.service_instance.clean()
|
self.service_instance.clean()
|
||||||
self.data = self.service_instance.clean_data()
|
self.data = self.service_instance.clean_data()
|
||||||
|
|
||||||
def get_site_domain(self):
|
def get_site_domain(self):
|
||||||
return self.service_instance.get_site_domain()
|
return self.service_instance.get_site_domain()
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
|
@ -53,12 +53,13 @@ class Service(models.Model):
|
||||||
REFUND = 'REFUND'
|
REFUND = 'REFUND'
|
||||||
PREPAY = 'PREPAY'
|
PREPAY = 'PREPAY'
|
||||||
POSTPAY = 'POSTPAY'
|
POSTPAY = 'POSTPAY'
|
||||||
|
|
||||||
_ignore_types = ' and '.join(
|
_ignore_types = ' and '.join(
|
||||||
', '.join(settings.SERVICES_IGNORE_ACCOUNT_TYPE).rsplit(', ', 1)).lower()
|
', '.join(settings.SERVICES_IGNORE_ACCOUNT_TYPE).rsplit(', ', 1)).lower()
|
||||||
|
|
||||||
description = models.CharField(_("description"), max_length=256, unique=True)
|
description = models.CharField(_("description"), max_length=256, unique=True)
|
||||||
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"),
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("content type"),
|
||||||
help_text=_("Content type of the related service objects."))
|
help_text=_("Content type of the related service objects."))
|
||||||
match = models.CharField(_("match"), max_length=256, blank=True,
|
match = models.CharField(_("match"), max_length=256, blank=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
|
@ -168,19 +169,19 @@ class Service(models.Model):
|
||||||
(POSTPAY, _("Postpay (on demand)")),
|
(POSTPAY, _("Postpay (on demand)")),
|
||||||
),
|
),
|
||||||
default=PREPAY)
|
default=PREPAY)
|
||||||
|
|
||||||
objects = ServiceQuerySet.as_manager()
|
objects = ServiceQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.description
|
return self.description
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def handler(self):
|
def handler(self):
|
||||||
""" Accessor of this service handler instance """
|
""" Accessor of this service handler instance """
|
||||||
if self.handler_type:
|
if self.handler_type:
|
||||||
return ServiceHandler.get(self.handler_type)(self)
|
return ServiceHandler.get(self.handler_type)(self)
|
||||||
return ServiceHandler(self)
|
return ServiceHandler(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.description = self.description.strip()
|
self.description = self.description.strip()
|
||||||
if hasattr(self, 'content_type'):
|
if hasattr(self, 'content_type'):
|
||||||
|
@ -190,12 +191,12 @@ class Service(models.Model):
|
||||||
'metric': (self.handler.validate_metric, self),
|
'metric': (self.handler.validate_metric, self),
|
||||||
'order_description': (self.handler.validate_order_description, self),
|
'order_description': (self.handler.validate_order_description, self),
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_pricing_period(self):
|
def get_pricing_period(self):
|
||||||
if self.pricing_period == self.BILLING_PERIOD:
|
if self.pricing_period == self.BILLING_PERIOD:
|
||||||
return self.billing_period
|
return self.billing_period
|
||||||
return self.pricing_period
|
return self.pricing_period
|
||||||
|
|
||||||
def get_price(self, account, metric, rates=None, position=None):
|
def get_price(self, account, metric, rates=None, position=None):
|
||||||
"""
|
"""
|
||||||
if position is provided an specific price for that position is returned,
|
if position is provided an specific price for that position is returned,
|
||||||
|
@ -233,7 +234,7 @@ class Service(models.Model):
|
||||||
price = round(rate['price'], 2)
|
price = round(rate['price'], 2)
|
||||||
return decimal.Decimal(str(rate['price']))
|
return decimal.Decimal(str(rate['price']))
|
||||||
raise RuntimeError("Rating algorithm bad result")
|
raise RuntimeError("Rating algorithm bad result")
|
||||||
|
|
||||||
def get_rates(self, account, cache=True):
|
def get_rates(self, account, cache=True):
|
||||||
# rates are cached per account
|
# rates are cached per account
|
||||||
if not cache:
|
if not cache:
|
||||||
|
@ -246,11 +247,11 @@ class Service(models.Model):
|
||||||
rates = self.rates.by_account(account)
|
rates = self.rates.by_account(account)
|
||||||
self.__cached_rates[account.id] = rates
|
self.__cached_rates[account.id] = rates
|
||||||
return rates
|
return rates
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rate_method(self):
|
def rate_method(self):
|
||||||
return rate_class.get_methods()[self.rate_algorithm]
|
return rate_class.get_methods()[self.rate_algorithm]
|
||||||
|
|
||||||
def update_orders(self, commit=True):
|
def update_orders(self, commit=True):
|
||||||
order_model = apps.get_model(settings.SERVICES_ORDER_MODEL)
|
order_model = apps.get_model(settings.SERVICES_ORDER_MODEL)
|
||||||
manager = order_model.objects
|
manager = order_model.objects
|
||||||
|
|
|
@ -19,7 +19,7 @@ class SystemUserQuerySet(models.QuerySet):
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save(update_fields=['password'])
|
user.save(update_fields=['password'])
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def by_is_main(self, is_main=True, **kwargs):
|
def by_is_main(self, is_main=True, **kwargs):
|
||||||
if is_main:
|
if is_main:
|
||||||
return self.filter(account__main_systemuser_id=F('id'))
|
return self.filter(account__main_systemuser_id=F('id'))
|
||||||
|
@ -30,7 +30,7 @@ class SystemUserQuerySet(models.QuerySet):
|
||||||
class SystemUser(models.Model):
|
class SystemUser(models.Model):
|
||||||
"""
|
"""
|
||||||
System users
|
System users
|
||||||
|
|
||||||
Username max_length determined by LINUX system user/group lentgh: 32
|
Username max_length determined by LINUX system user/group lentgh: 32
|
||||||
"""
|
"""
|
||||||
username = models.CharField(_("username"), max_length=32, unique=True,
|
username = models.CharField(_("username"), max_length=32, unique=True,
|
||||||
|
@ -38,7 +38,7 @@ class SystemUser(models.Model):
|
||||||
validators=[validators.validate_username])
|
validators=[validators.validate_username])
|
||||||
password = models.CharField(_("password"), max_length=128)
|
password = models.CharField(_("password"), max_length=128)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='systemusers')
|
related_name='systemusers', on_delete=models.CASCADE)
|
||||||
home = models.CharField(_("home"), max_length=256, blank=True,
|
home = models.CharField(_("home"), max_length=256, blank=True,
|
||||||
help_text=_("Starting location when login with this no-shell user."))
|
help_text=_("Starting location when login with this no-shell user."))
|
||||||
directory = models.CharField(_("directory"), max_length=256, blank=True,
|
directory = models.CharField(_("directory"), max_length=256, blank=True,
|
||||||
|
@ -51,19 +51,19 @@ class SystemUser(models.Model):
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
|
|
||||||
objects = SystemUserQuerySet.as_manager()
|
objects = SystemUserQuerySet.as_manager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
try:
|
try:
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
except type(self).account.field.rel.to.DoesNotExist:
|
except type(self).account.field.rel.to.DoesNotExist:
|
||||||
return self.is_active
|
return self.is_active
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_main(self):
|
def is_main(self):
|
||||||
# TODO on account delete
|
# TODO on account delete
|
||||||
|
@ -71,34 +71,34 @@ class SystemUser(models.Model):
|
||||||
if self.account.main_systemuser_id:
|
if self.account.main_systemuser_id:
|
||||||
return self.account.main_systemuser_id == self.pk
|
return self.account.main_systemuser_id == self.pk
|
||||||
return self.account.username == self.username
|
return self.account.username == self.username
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def main(self):
|
def main(self):
|
||||||
# On account creation main_systemuser_id is still None
|
# On account creation main_systemuser_id is still None
|
||||||
if self.account.main_systemuser_id:
|
if self.account.main_systemuser_id:
|
||||||
return self.account.main_systemuser
|
return self.account.main_systemuser
|
||||||
return type(self).objects.get(username=self.account.username)
|
return type(self).objects.get(username=self.account.username)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_shell(self):
|
def has_shell(self):
|
||||||
return self.shell not in settings.SYSTEMUSERS_DISABLED_SHELLS
|
return self.shell not in settings.SYSTEMUSERS_DISABLED_SHELLS
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = True
|
self.is_active = True
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return self.get_shell_display()
|
return self.get_shell_display()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.home:
|
if not self.home:
|
||||||
self.home = self.get_base_home()
|
self.home = self.get_base_home()
|
||||||
super(SystemUser, self).save(*args, **kwargs)
|
super(SystemUser, self).save(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.directory = self.directory.lstrip('/')
|
self.directory = self.directory.lstrip('/')
|
||||||
if self.home:
|
if self.home:
|
||||||
|
@ -123,16 +123,16 @@ class SystemUser(models.Model):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'home': _("Shell users should use their own home."),
|
'home': _("Shell users should use their own home."),
|
||||||
})
|
})
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self.password = make_password(raw_password)
|
self.password = make_password(raw_password)
|
||||||
|
|
||||||
def get_base_home(self):
|
def get_base_home(self):
|
||||||
context = {
|
context = {
|
||||||
'user': self.username,
|
'user': self.username,
|
||||||
'username': self.username,
|
'username': self.username,
|
||||||
}
|
}
|
||||||
return os.path.normpath(settings.SYSTEMUSERS_HOME % context)
|
return os.path.normpath(settings.SYSTEMUSERS_HOME % context)
|
||||||
|
|
||||||
def get_home(self):
|
def get_home(self):
|
||||||
return os.path.normpath(os.path.join(self.home, self.directory))
|
return os.path.normpath(os.path.join(self.home, self.directory))
|
||||||
|
|
|
@ -15,31 +15,31 @@ class VPS(models.Model):
|
||||||
template = models.CharField(_("template"), max_length=64,
|
template = models.CharField(_("template"), max_length=64,
|
||||||
choices=settings.VPS_TEMPLATES, default=settings.VPS_DEFAULT_TEMPLATE,
|
choices=settings.VPS_TEMPLATES, default=settings.VPS_DEFAULT_TEMPLATE,
|
||||||
help_text=_("Initial template."))
|
help_text=_("Initial template."))
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='vpss')
|
verbose_name=_("Account"), related_name='vpss')
|
||||||
is_active = models.BooleanField(_("active"), default=True)
|
is_active = models.BooleanField(_("active"), default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "VPS"
|
verbose_name = "VPS"
|
||||||
verbose_name_plural = "VPSs"
|
verbose_name_plural = "VPSs"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.hostname
|
return self.hostname
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self.password = make_password(raw_password)
|
self.password = make_password(raw_password)
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.hostname
|
return self.hostname
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
|
|
|
@ -19,44 +19,44 @@ class WebApp(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name],
|
name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name],
|
||||||
help_text=_("The app will be installed in %s") % settings.WEBAPPS_BASE_DIR)
|
help_text=_("The app will be installed in %s") % settings.WEBAPPS_BASE_DIR)
|
||||||
type = models.CharField(_("type"), max_length=32, choices=AppType.get_choices())
|
type = models.CharField(_("type"), max_length=32, choices=AppType.get_choices())
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='webapps')
|
verbose_name=_("Account"), related_name='webapps')
|
||||||
data = JSONField(_("data"), blank=True, default={},
|
data = JSONField(_("data"), blank=True, default={},
|
||||||
help_text=_("Extra information dependent of each service."))
|
help_text=_("Extra information dependent of each service."))
|
||||||
target_server = models.ForeignKey('orchestration.Server', verbose_name=_("Target Server"),
|
target_server = models.ForeignKey('orchestration.Server', on_delete=models.CASCADE,
|
||||||
related_name='webapps')
|
verbose_name=_("Target Server"), related_name='webapps')
|
||||||
comments = models.TextField(default="", blank=True)
|
comments = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
# CMS webapps usually need a database and dbuser, with these virtual fields we tell the ORM to delete them
|
# CMS webapps usually need a database and dbuser, with these virtual fields we tell the ORM to delete them
|
||||||
databases = VirtualDatabaseRelation('databases.Database')
|
databases = VirtualDatabaseRelation('databases.Database')
|
||||||
databaseusers = VirtualDatabaseUserRelation('databases.DatabaseUser')
|
databaseusers = VirtualDatabaseUserRelation('databases.DatabaseUser')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('name', 'account')
|
unique_together = ('name', 'account')
|
||||||
verbose_name = _("Web App")
|
verbose_name = _("Web App")
|
||||||
verbose_name_plural = _("Web Apps")
|
verbose_name_plural = _("Web Apps")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return self.get_type_display()
|
return self.get_type_display()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def type_class(self):
|
def type_class(self):
|
||||||
return AppType.get(self.type)
|
return AppType.get(self.type)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def type_instance(self):
|
def type_instance(self):
|
||||||
""" Per request lived type_instance """
|
""" Per request lived type_instance """
|
||||||
return self.type_class(self)
|
return self.type_class(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
apptype = self.type_instance
|
apptype = self.type_instance
|
||||||
apptype.validate()
|
apptype.validate()
|
||||||
a = apptype.clean_data()
|
a = apptype.clean_data()
|
||||||
self.data = apptype.clean_data()
|
self.data = apptype.clean_data()
|
||||||
|
|
||||||
def get_options(self, **kwargs):
|
def get_options(self, **kwargs):
|
||||||
options = OrderedDict()
|
options = OrderedDict()
|
||||||
qs = WebAppOption.objects.filter(**kwargs)
|
qs = WebAppOption.objects.filter(**kwargs)
|
||||||
|
@ -69,57 +69,57 @@ class WebApp(models.Model):
|
||||||
else:
|
else:
|
||||||
options[name] = value
|
options[name] = value
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def get_directive(self):
|
def get_directive(self):
|
||||||
return self.type_instance.get_directive()
|
return self.type_instance.get_directive()
|
||||||
|
|
||||||
def get_base_path(self):
|
def get_base_path(self):
|
||||||
context = {
|
context = {
|
||||||
'home': self.get_user().get_home(),
|
'home': self.get_user().get_home(),
|
||||||
'app_name': self.name,
|
'app_name': self.name,
|
||||||
}
|
}
|
||||||
return settings.WEBAPPS_BASE_DIR % context
|
return settings.WEBAPPS_BASE_DIR % context
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
path = self.get_base_path()
|
path = self.get_base_path()
|
||||||
public_root = self.options.filter(name='public-root').first()
|
public_root = self.options.filter(name='public-root').first()
|
||||||
if public_root:
|
if public_root:
|
||||||
path = os.path.join(path, public_root.value)
|
path = os.path.join(path, public_root.value)
|
||||||
return os.path.normpath(path.replace('//', '/'))
|
return os.path.normpath(path.replace('//', '/'))
|
||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
return self.account.main_systemuser
|
return self.account.main_systemuser
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.get_user().username
|
return self.get_user().username
|
||||||
|
|
||||||
def get_groupname(self):
|
def get_groupname(self):
|
||||||
return self.get_username()
|
return self.get_username()
|
||||||
|
|
||||||
|
|
||||||
class WebAppOption(models.Model):
|
class WebAppOption(models.Model):
|
||||||
webapp = models.ForeignKey(WebApp, verbose_name=_("Web application"),
|
webapp = models.ForeignKey(WebApp, on_delete=models.CASCADE,
|
||||||
related_name='options')
|
verbose_name=_("Web application"), related_name='options')
|
||||||
name = models.CharField(_("name"), max_length=128,
|
name = models.CharField(_("name"), max_length=128,
|
||||||
choices=AppType.get_group_options_choices())
|
choices=AppType.get_group_options_choices())
|
||||||
value = models.CharField(_("value"), max_length=256)
|
value = models.CharField(_("value"), max_length=256)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('webapp', 'name')
|
unique_together = ('webapp', 'name')
|
||||||
verbose_name = _("option")
|
verbose_name = _("option")
|
||||||
verbose_name_plural = _("options")
|
verbose_name_plural = _("options")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def option_class(self):
|
def option_class(self):
|
||||||
return AppOption.get(self.name)
|
return AppOption.get(self.name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def option_instance(self):
|
def option_instance(self):
|
||||||
""" Per request lived option instance """
|
""" Per request lived option instance """
|
||||||
return self.option_class(self)
|
return self.option_class(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.option_instance.validate()
|
self.option_instance.validate()
|
||||||
|
|
|
@ -18,11 +18,11 @@ class Website(models.Model):
|
||||||
HTTPS = 'https'
|
HTTPS = 'https'
|
||||||
HTTP_AND_HTTPS = 'http/https'
|
HTTP_AND_HTTPS = 'http/https'
|
||||||
HTTPS_ONLY = 'https-only'
|
HTTPS_ONLY = 'https-only'
|
||||||
|
|
||||||
name = models.CharField(_("name"), max_length=128,
|
name = models.CharField(_("name"), max_length=128,
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
related_name='websites')
|
verbose_name=_("Account"), related_name='websites')
|
||||||
protocol = models.CharField(_("protocol"), max_length=16,
|
protocol = models.CharField(_("protocol"), max_length=16,
|
||||||
choices=settings.WEBSITES_PROTOCOL_CHOICES,
|
choices=settings.WEBSITES_PROTOCOL_CHOICES,
|
||||||
default=settings.WEBSITES_DEFAULT_PROTOCOL,
|
default=settings.WEBSITES_DEFAULT_PROTOCOL,
|
||||||
|
@ -34,34 +34,34 @@ class Website(models.Model):
|
||||||
domains = models.ManyToManyField(settings.WEBSITES_DOMAIN_MODEL, blank=True,
|
domains = models.ManyToManyField(settings.WEBSITES_DOMAIN_MODEL, blank=True,
|
||||||
related_name='websites', verbose_name=_("domains"))
|
related_name='websites', verbose_name=_("domains"))
|
||||||
contents = models.ManyToManyField('webapps.WebApp', through='websites.Content')
|
contents = models.ManyToManyField('webapps.WebApp', through='websites.Content')
|
||||||
target_server = models.ForeignKey('orchestration.Server', verbose_name=_("Target Server"),
|
target_server = models.ForeignKey('orchestration.Server', on_delete=models.CASCADE,
|
||||||
related_name='websites')
|
verbose_name=_("Target Server"), related_name='websites')
|
||||||
is_active = models.BooleanField(_("active"), default=True)
|
is_active = models.BooleanField(_("active"), default=True)
|
||||||
comments = models.TextField(default="", blank=True)
|
comments = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('name', 'account')
|
unique_together = ('name', 'account')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_name(self):
|
def unique_name(self):
|
||||||
context = self.get_settings_context()
|
context = self.get_settings_context()
|
||||||
return settings.WEBSITES_UNIQUE_NAME_FORMAT % context
|
return settings.WEBSITES_UNIQUE_NAME_FORMAT % context
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.is_active and self.account.is_active
|
return self.is_active and self.account.is_active
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def get_settings_context(self):
|
def get_settings_context(self):
|
||||||
""" format settings strings """
|
""" format settings strings """
|
||||||
return {
|
return {
|
||||||
|
@ -73,12 +73,12 @@ class Website(models.Model):
|
||||||
'site_name': self.name,
|
'site_name': self.name,
|
||||||
'protocol': self.protocol,
|
'protocol': self.protocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_protocol(self):
|
def get_protocol(self):
|
||||||
if self.protocol in (self.HTTP, self.HTTP_AND_HTTPS):
|
if self.protocol in (self.HTTP, self.HTTP_AND_HTTPS):
|
||||||
return self.HTTP
|
return self.HTTP
|
||||||
return self.HTTPS
|
return self.HTTPS
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def get_directives(self):
|
def get_directives(self):
|
||||||
directives = OrderedDict()
|
directives = OrderedDict()
|
||||||
|
@ -88,7 +88,7 @@ class Website(models.Model):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
directives[opt.name] = [opt.value]
|
directives[opt.name] = [opt.value]
|
||||||
return directives
|
return directives
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
try:
|
try:
|
||||||
domain = self.domains.all()[0]
|
domain = self.domains.all()[0]
|
||||||
|
@ -96,22 +96,22 @@ class Website(models.Model):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return '%s://%s' % (self.get_protocol(), domain)
|
return '%s://%s' % (self.get_protocol(), domain)
|
||||||
|
|
||||||
def get_user(self):
|
def get_user(self):
|
||||||
return self.account.main_systemuser
|
return self.account.main_systemuser
|
||||||
|
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.get_user().username
|
return self.get_user().username
|
||||||
|
|
||||||
def get_groupname(self):
|
def get_groupname(self):
|
||||||
return self.get_username()
|
return self.get_username()
|
||||||
|
|
||||||
def get_www_access_log_path(self):
|
def get_www_access_log_path(self):
|
||||||
context = self.get_settings_context()
|
context = self.get_settings_context()
|
||||||
context['unique_name'] = self.unique_name
|
context['unique_name'] = self.unique_name
|
||||||
path = settings.WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH % context
|
path = settings.WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH % context
|
||||||
return os.path.normpath(path)
|
return os.path.normpath(path)
|
||||||
|
|
||||||
def get_www_error_log_path(self):
|
def get_www_error_log_path(self):
|
||||||
context = self.get_settings_context()
|
context = self.get_settings_context()
|
||||||
context['unique_name'] = self.unique_name
|
context['unique_name'] = self.unique_name
|
||||||
|
@ -120,52 +120,54 @@ class Website(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class WebsiteDirective(models.Model):
|
class WebsiteDirective(models.Model):
|
||||||
website = models.ForeignKey(Website, verbose_name=_("web site"),
|
website = models.ForeignKey(Website, on_delete=models.CASCADE,
|
||||||
related_name='directives')
|
verbose_name=_("web site"), related_name='directives')
|
||||||
name = models.CharField(_("name"), max_length=128, db_index=True,
|
name = models.CharField(_("name"), max_length=128, db_index=True,
|
||||||
choices=SiteDirective.get_choices())
|
choices=SiteDirective.get_choices())
|
||||||
value = models.CharField(_("value"), max_length=256, blank=True)
|
value = models.CharField(_("value"), max_length=256, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def directive_class(self):
|
def directive_class(self):
|
||||||
return SiteDirective.get(self.name)
|
return SiteDirective.get(self.name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def directive_instance(self):
|
def directive_instance(self):
|
||||||
""" Per request lived directive instance """
|
""" Per request lived directive instance """
|
||||||
return self.directive_class()
|
return self.directive_class()
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.directive_instance.validate(self)
|
self.directive_instance.validate(self)
|
||||||
|
|
||||||
|
|
||||||
class Content(models.Model):
|
class Content(models.Model):
|
||||||
# related_name is content_set to differentiate between website.content -> webapp
|
# related_name is content_set to differentiate between website.content -> webapp
|
||||||
webapp = models.ForeignKey('webapps.WebApp', verbose_name=_("web application"))
|
webapp = models.ForeignKey('webapps.WebApp', on_delete=models.CASCADE,
|
||||||
website = models.ForeignKey('websites.Website', verbose_name=_("web site"))
|
verbose_name=_("web application"))
|
||||||
|
website = models.ForeignKey('websites.Website', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("web site"))
|
||||||
path = models.CharField(_("path"), max_length=256, blank=True,
|
path = models.CharField(_("path"), max_length=256, blank=True,
|
||||||
validators=[validators.validate_url_path])
|
validators=[validators.validate_url_path])
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('website', 'path')
|
unique_together = ('website', 'path')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
return self.website.name + self.path
|
return self.website.name + self.path
|
||||||
except Website.DoesNotExist:
|
except Website.DoesNotExist:
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
def clean_fields(self, *args, **kwargs):
|
def clean_fields(self, *args, **kwargs):
|
||||||
self.path = self.path.strip()
|
self.path = self.path.strip()
|
||||||
return super(Content, self).clean_fields(*args, **kwargs)
|
return super(Content, self).clean_fields(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not self.path:
|
if not self.path:
|
||||||
self.path = '/'
|
self.path = '/'
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
try:
|
try:
|
||||||
domain = self.website.domains.all()[0]
|
domain = self.website.domains.all()[0]
|
||||||
|
|
Loading…
Reference in New Issue