Added service metric and match descriptions and fixed domain slaves without master

This commit is contained in:
Marc 2014-10-21 09:27:31 +00:00
parent e669dcf926
commit 8f27720c69
7 changed files with 80 additions and 9 deletions

View file

@ -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

View file

@ -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,

View file

@ -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 """

View file

@ -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"))

View file

@ -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
@ -34,6 +35,13 @@ 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)

View file

@ -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,

View file

@ -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>&nbsp;databaseuser.type == 'MYSQL'</tt><br>"
"<tt>&nbsp;miscellaneous.active and miscellaneous.service.name.lower() == 'domain .es'</tt><br>"
"<tt>&nbsp;contractedplan.plan.name == 'association_fee''</tt><br>"
"<tt>&nbsp;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>&nbsp;max((mailbox.resources.disk.allocated or 0) -1, 0)</tt><br>"
"<tt>&nbsp;miscellaneous.amount</tt><br>"
"<tt>&nbsp;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,