Added service metric and match descriptions and fixed domain slaves without master
This commit is contained in:
parent
e669dcf926
commit
8f27720c69
4
TODO.md
4
TODO.md
|
@ -119,7 +119,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* delete main user -> delete account or prevent delete main user
|
* delete main user -> delete account or prevent delete main user
|
||||||
|
|
||||||
* Ansible orchestration *method* (methods.py)
|
* Ansible orchestration *method* (methods.py)
|
||||||
* pip upgrade or install
|
|
||||||
* multiple domains creation; line separated domains
|
* multiple domains creation; line separated domains
|
||||||
* Move MU webapps to SaaS?
|
* Move MU webapps to SaaS?
|
||||||
|
|
||||||
|
@ -156,5 +155,6 @@ textwrap.dedent( \\)
|
||||||
* parmiko write to a channel instead of transfering files? http://sysadmin.circularvale.com/programming/paramiko-channel-hangs/
|
* parmiko write to a channel instead of transfering files? http://sysadmin.circularvale.com/programming/paramiko-channel-hangs/
|
||||||
|
|
||||||
* strip leading and trailing whitre spaces of most input fields
|
* strip leading and trailing whitre spaces of most input fields
|
||||||
* Examples of service.match and service.metric
|
|
||||||
|
|
||||||
|
* better modeling of the interdependency between webapps and websites (settings)
|
||||||
|
* webapp options cfig agnostic
|
||||||
|
|
|
@ -133,7 +133,7 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
|
||||||
'name': domain.name,
|
'name': domain.name,
|
||||||
'banner': self.get_banner(),
|
'banner': self.get_banner(),
|
||||||
'subdomains': domain.subdomains.all(),
|
'subdomains': domain.subdomains.all(),
|
||||||
'masters': '; '.join(self.get_masters(domain)) or 'none',
|
'masters': '; '.join(self.get_masters(domain)) or '"none"',
|
||||||
}
|
}
|
||||||
context.update({
|
context.update({
|
||||||
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
'conf_path': settings.DOMAINS_SLAVES_PATH,
|
||||||
|
|
|
@ -24,6 +24,46 @@ class DomainAdminForm(forms.ModelForm):
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
#class BatchDomainCreationAdminForm(DomainAdminForm):
|
||||||
|
# # TODO
|
||||||
|
# name = forms.CharField(widget=forms.Textarea, label=_("Names"),
|
||||||
|
# help_text=_("Domain per line. All domains will share the same attributes."))
|
||||||
|
#
|
||||||
|
# def clean_name(self):
|
||||||
|
# self.names = []
|
||||||
|
# target = None
|
||||||
|
# for name in self.cleaned_data['name'].splitlines():
|
||||||
|
# name = name.strip()
|
||||||
|
# if target is None:
|
||||||
|
# target = name
|
||||||
|
# else:
|
||||||
|
# domain = Domain(name=name)
|
||||||
|
# try:
|
||||||
|
# domain.full_clean(exclude=['top'])
|
||||||
|
# except ValidationError as e:
|
||||||
|
# raise ValidationError(e.error_dict['name'])
|
||||||
|
# self.names.append(name)
|
||||||
|
# return target
|
||||||
|
#
|
||||||
|
# def save_model(self, request, obj, form, change):
|
||||||
|
# # TODO thsi is modeladmin
|
||||||
|
# """ batch domain creation support """
|
||||||
|
# super(DomainAdmin, self).save_model(request, obj, form, change)
|
||||||
|
# if not change:
|
||||||
|
# for name in form.names:
|
||||||
|
# domain = Domain.objects.create(name=name, account_id=obj.account_id)
|
||||||
|
#
|
||||||
|
# def save_related(self, request, form, formsets, change):
|
||||||
|
# # TODO thsi is modeladmin
|
||||||
|
# """ batch domain creation support """
|
||||||
|
# super(DomainAdmin, self).save_related(request, form, formsets, change)
|
||||||
|
# if not change:
|
||||||
|
# for name in form.names:
|
||||||
|
# for formset in formsets:
|
||||||
|
# formset.instance = form.instance
|
||||||
|
# self.save_formset(request, form, formset, change=change)
|
||||||
|
|
||||||
|
|
||||||
class RecordInlineFormSet(forms.models.BaseInlineFormSet):
|
class RecordInlineFormSet(forms.models.BaseInlineFormSet):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" Checks if everything is consistent """
|
""" Checks if everything is consistent """
|
||||||
|
|
|
@ -11,9 +11,10 @@ from . import settings, validators, utils
|
||||||
|
|
||||||
class Domain(models.Model):
|
class Domain(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=256, unique=True,
|
name = models.CharField(_("name"), max_length=256, unique=True,
|
||||||
validators=[validate_hostname, validators.validate_allowed_domain])
|
validators=[validate_hostname, validators.validate_allowed_domain],
|
||||||
|
help_text=_("Domain or subdomain name."))
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='domains', blank=True, help_text=_("Automatically selected for subdomains"))
|
related_name='domains', blank=True, help_text=_("Automatically selected for subdomains."))
|
||||||
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomains')
|
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomains')
|
||||||
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial,
|
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial,
|
||||||
help_text=_("Serial number"))
|
help_text=_("Serial number"))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
@ -35,5 +36,12 @@ class Miscellaneous(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "{0}-{1}".format(str(self.service), str(self.account))
|
return "{0}-{1}".format(str(self.service), str(self.account))
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
|
||||||
services.register(Miscellaneous)
|
services.register(Miscellaneous)
|
||||||
|
|
|
@ -45,6 +45,9 @@ class ServiceHandler(plugins.Plugin):
|
||||||
return ContentType.objects.get_by_natural_key(app_label, model.lower())
|
return ContentType.objects.get_by_natural_key(app_label, model.lower())
|
||||||
|
|
||||||
def matches(self, instance):
|
def matches(self, instance):
|
||||||
|
if not self.match:
|
||||||
|
# Blank expressions always evaluate True
|
||||||
|
return True
|
||||||
safe_locals = {
|
safe_locals = {
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
'obj': instance,
|
'obj': instance,
|
||||||
|
|
|
@ -100,8 +100,19 @@ class Service(models.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
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, verbose_name=_("content type"),
|
||||||
match = models.CharField(_("match"), max_length=256, blank=True)
|
help_text=_("Content type of the related service objects."))
|
||||||
|
match = models.CharField(_("match"), max_length=256, blank=True,
|
||||||
|
help_text=_(
|
||||||
|
"Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> "
|
||||||
|
"that designates wheter a <tt>content_type</tt> object is related to this service "
|
||||||
|
"or not, always evaluates <tt>True</tt> when left blank. "
|
||||||
|
"Related instance can be instantiated with <tt>instance</tt> keyword or "
|
||||||
|
"<tt>content_type.model_name</tt>.</br>"
|
||||||
|
"<tt> databaseuser.type == 'MYSQL'</tt><br>"
|
||||||
|
"<tt> miscellaneous.active and miscellaneous.service.name.lower() == 'domain .es'</tt><br>"
|
||||||
|
"<tt> contractedplan.plan.name == 'association_fee''</tt><br>"
|
||||||
|
"<tt> instance.active</tt>"))
|
||||||
handler_type = models.CharField(_("handler"), max_length=256, blank=True,
|
handler_type = models.CharField(_("handler"), max_length=256, blank=True,
|
||||||
help_text=_("Handler used for processing this Service. A handler "
|
help_text=_("Handler used for processing this Service. A handler "
|
||||||
"enables customized behaviour far beyond what options "
|
"enables customized behaviour far beyond what options "
|
||||||
|
@ -132,8 +143,16 @@ class Service(models.Model):
|
||||||
" membership fee or not"))
|
" membership fee or not"))
|
||||||
# Pricing
|
# Pricing
|
||||||
metric = models.CharField(_("metric"), max_length=256, blank=True,
|
metric = models.CharField(_("metric"), max_length=256, blank=True,
|
||||||
help_text=_("Metric used to compute the pricing rate. "
|
help_text=_(
|
||||||
"Number of orders is used when left blank."))
|
"Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> "
|
||||||
|
"used for obtinging the <i>metric value</i> for the pricing rate computation. "
|
||||||
|
"Number of orders is used when left blank. Related instance can be instantiated "
|
||||||
|
"with <tt>instance</tt> keyword or <tt>content_type.model_name</tt>.<br>"
|
||||||
|
"<tt> max((mailbox.resources.disk.allocated or 0) -1, 0)</tt><br>"
|
||||||
|
"<tt> miscellaneous.amount</tt><br>"
|
||||||
|
"<tt> max((account.resources.traffic.used or 0) -"
|
||||||
|
" getattr(account.miscellaneous.filter(is_active=True,"
|
||||||
|
" service__name='traffic prepay').last(), 'amount', 0), 0)</tt>"))
|
||||||
nominal_price = models.DecimalField(_("nominal price"), max_digits=12,
|
nominal_price = models.DecimalField(_("nominal price"), max_digits=12,
|
||||||
decimal_places=2)
|
decimal_places=2)
|
||||||
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
|
tax = models.PositiveIntegerField(_("tax"), choices=settings.SERVICES_SERVICE_TAXES,
|
||||||
|
|
Loading…
Reference in New Issue