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