Added services templates

This commit is contained in:
Marc Aymerich 2015-04-24 14:03:42 +00:00
parent c2b0186034
commit e8759578b5
7 changed files with 179 additions and 27 deletions

View File

@ -112,9 +112,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
fields = ('account_link', 'email_link', 'mailboxes', 'forward') fields = ('account_link', 'email_link', 'mailboxes', 'forward')
add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward') add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
inlines = [AutoresponseInline] inlines = [AutoresponseInline]
search_fields = ( search_fields = ('forward', 'mailboxes__name', 'account__username', 'computed_email')
'name', 'domain__name', 'forward', 'mailboxes__name', 'account__username', 'computed_email'
)
readonly_fields = ('account_link', 'domain_link', 'email_link') readonly_fields = ('account_link', 'domain_link', 'email_link')
filter_by_account_fields = ('domain', 'mailboxes') filter_by_account_fields = ('domain', 'mailboxes')
filter_horizontal = ['mailboxes'] filter_horizontal = ['mailboxes']
@ -148,6 +146,7 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
return '<br>'.join(values) return '<br>'.join(values)
display_forward.short_description = _("Forward") display_forward.short_description = _("Forward")
display_forward.allow_tags = True display_forward.allow_tags = True
display_forward.admin_order_field = 'forward'
def formfield_for_dbfield(self, db_field, **kwargs): def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name == 'forward': if db_field.name == 'forward':

View File

@ -78,7 +78,8 @@ class Rate(models.Model):
service = models.ForeignKey('services.Service', verbose_name=_("service"), service = models.ForeignKey('services.Service', verbose_name=_("service"),
related_name='rates') related_name='rates')
plan = models.ForeignKey(Plan, verbose_name=_("plan"), related_name='rates') plan = models.ForeignKey(Plan, 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."))
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()

View File

@ -120,7 +120,8 @@ def step_price(rates, metric):
minimal = min(minimal, (value, result), key=lambda v: v[0]) minimal = min(minimal, (value, result), key=lambda v: v[0])
return minimal[1] return minimal[1]
step_price.verbose_name = _("Step price") step_price.verbose_name = _("Step price")
step_price.help_text = _("All price rates with a lower metric are applied.") step_price.help_text = _("All rates with a quantity lower than the metric are applied. "
"Nominal price will be used when initial block is missing.")
def match_price(rates, metric): def match_price(rates, metric):
@ -149,4 +150,5 @@ def match_price(rates, metric):
})] })]
return None return None
match_price.verbose_name = _("Match price") match_price.verbose_name = _("Match price")
match_price.help_text = _("Only the rate with inmediate inferior metric is applied.") match_price.help_text = _("Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. "
"Nominal price will be used when initial block is missing.")

View File

@ -1,5 +1,5 @@
{% extends "orchestra/admin/change_form.html" %} {% extends "orchestra/admin/change_form.html" %}
{% load i18n admin_urls admin_static admin_modify %} {% load i18n admin_urls admin_static admin_modify utils %}
{% block object-tools %} {% block object-tools %}
@ -7,8 +7,9 @@
<ul class="object-tools"> <ul class="object-tools">
<li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 0 0 0;"> <li><select name="forma" onchange="location = this.options[this.selectedIndex].value;" style="margin: -3px 0 0 0;">
<option selected disabled>{% trans "Templates" %}</option> <option selected disabled>{% trans "Templates" %}</option>
{% oneliner %}
<option value="./?description=Mailbox& <option value="./?description=Mailbox&
content_type=10& content_type={{ 'mailboxes.mailbox'|content_type_id }}&
match=mailbox.active& match=mailbox.active&
handler_type=& handler_type=&
is_active=True& is_active=True&
@ -22,9 +23,27 @@
nominal_price=28.10& nominal_price=28.10&
tax=21& tax=21&
pricing_period=BILLING_PERIOD& pricing_period=BILLING_PERIOD&
rate_algorithm=MATCH_PRICE& rate_algorithm=STEP_PRICE&
on_cancel=COMPENSATE& on_cancel=COMPENSATE&
payment_style=PREPAY">Mailbox</option> payment_style=PREPAY">Mailbox</option>
<option value="./?description=Mailbox%20allocated%20disk&
content_type=10&
match=mailbox.active&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=max(logsteps(mailbox.resources.disk.allocated%20or%200)%20-2,%200)&
nominal_price=20.00&
tax=21&
pricing_period=&
rate_algorithm=MATCH_PRICE&
on_cancel=DISCOUNT&
payment_style=PREPAY">Mailbox allocated disk</option>
<option value="./?description=Database& <option value="./?description=Database&
content_type=19& content_type=19&
match=database.account.is_active&handler_type=& match=database.account.is_active&handler_type=&
@ -42,6 +61,115 @@
rate_algorithm=STEP_PRICE& rate_algorithm=STEP_PRICE&
on_cancel=COMPENSATE& on_cancel=COMPENSATE&
payment_style=PREPAY">Database</option> payment_style=PREPAY">Database</option>
<option value="./?description=Basic%20domain&
content_type=34&
match=miscellaneous.active%20and%20miscellaneous.service.name%20==%20%27domain-registration%27&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_REGISTER&
is_fee=&
order_description=&
ignore_period=&
metric=&
nominal_price=19.01&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=STEP_PRICE&
on_cancel=NOTHING&
payment_style=PREPAY">Basic domain</option>
<option value="./?description=Domain%20.cat%20extra&
content_type=34&
match=miscellaneous.active%20and%20miscellaneous.service.name%20==%20%27domain-registration%27%20and%20miscellaneous.identifier.endswith(%27.cat%27)&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_REGISTER&
is_fee=&
order_description=&
ignore_period=&
metric=&
nominal_price=26.59&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=STEP_PRICE&
on_cancel=NOTHING&
payment_style=PREPAY">Domain .cat extra</option>
<option value="./?description=FTP%20account&
content_type=9&
match=systemuser.active%20and%20not%20systemuser.is_main&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=&
nominal_price=28.10&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=STEP_PRICE&
on_cancel=COMPENSATE&
payment_style=PREPAY">FTP account</option>
<option value="./?description=Traffic&
content_type=1&
match=True&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=MONTHLY&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=logsteps(max((account.resources.traffic.used%20or%200)%20-%20getattr(account.miscellaneous.filter(is_active=True,%20service__name=%27traffic-prepay%27).last(),%20%27amount%27,%200),%200),%20size=0.05)&
nominal_price=4.50&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=MATCH_PRICE&
on_cancel=NOTHING&
payment_style=POSTPAY">Traffic</option>
<option value="./?description=Traffic%20prepay&
content_type=34&
match=miscellaneous.active%20and%20miscellaneous.service.name%20==%20%27traffic-prepay%27&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=ANUAL&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=miscellaneous.amount&
nominal_price=3.00&
tax=21&
pricing_period=MONTHLY&
rate_algorithm=MATCH_PRICE&
on_cancel=REFUND&
payment_style=PREPAY">Traffic prepay</option>
<option value="./?description=Development&
content_type=34&
match=miscellaneous.active%20and%20miscellaneous.service.name%20==%20%27development%27&
handler_type=&
is_active=True&
ignore_superusers=True&
billing_period=&
billing_point=ON_FIXED_DATE&
is_fee=&
order_description=&
ignore_period=TEN_DAYS&
metric=miscellaneous.amount&
nominal_price=40.00&
tax=21&
pricing_period=BILLING_PERIOD&
rate_algorithm=STEP_PRICE&
on_cancel=NOTHING&
payment_style=PREPAY">Develompent</option>
{% endoneliner %}
</select></li> </select></li>
<li> <li>
<a href="./help/" class="historylink">{% trans "Help" %}</a> <a href="./help/" class="historylink">{% trans "Help" %}</a>

View File

@ -32,13 +32,8 @@ class WebAppViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet):
data = meta.get_serializer_info(app_type.serializer()) data = meta.get_serializer_info(app_type.serializer())
else: else:
data = {} data = {}
options = [] data['option_groups'] = app_type.option_groups
for group, option in app_type.get_options(): app_types[app_type.get_name()] = data
options += [opt.name for opt in option]
app_types[app_type.get_name()] = {
'data': data,
'options': options,
}
metadata.data['actions']['types'] = app_types metadata.data['actions']['types'] = app_types
# Options # Options
options = {} options = {}

View File

@ -74,29 +74,29 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
return instance return instance
def create(self, validated_data): def create(self, validated_data):
options_data = validated_data.pop('options') directives_data = validated_data.pop('directives')
webapp = super(WebsiteSerializer, self).create(validated_data) webapp = super(WebsiteSerializer, self).create(validated_data)
for key, value in options_data.items(): for key, value in directives_data.items():
WebAppOption.objects.create(webapp=webapp, name=key, value=value) WebsiteDirective.objects.create(webapp=webapp, name=key, value=value)
return webap return webap
def update(self, instance, validated_data): def update(self, instance, validated_data):
options_data = validated_data.pop('options') directives_data = validated_data.pop('directives')
instance = super(WebsiteSerializer, self).update(instance, validated_data) instance = super(WebsiteSerializer, self).update(instance, validated_data)
existing = {} existing = {}
for obj in instance.options.all(): for obj in instance.directives.all():
existing[obj.name] = obj existing[obj.name] = obj
posted = set() posted = set()
for key, value in options_data.items(): for key, value in directives_data.items():
posted.add(key) posted.add(key)
try: try:
option = existing[key] directive = existing[key]
except KeyError: except KeyError:
option = instance.options.create(name=key, value=value) directive = instance.directives.create(name=key, value=value)
else: else:
if option.value != value: if directive.value != value:
option.value = value directive.value = value
option.save(update_fields=('value',)) directive.save(update_fields=('value',))
for to_delete in set(existing.keys())-posted: for to_delete in set(existing.keys())-posted:
existing[to_delete].delete() existing[to_delete].delete()
return instance return instance

View File

@ -1,6 +1,10 @@
import re
from django import template from django import template
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from django.forms import CheckboxInput from django.forms import CheckboxInput
from django.template.base import Node
from orchestra import get_version from orchestra import get_version
from orchestra.admin.utils import change_url from orchestra.admin.utils import change_url
@ -43,6 +47,22 @@ def rest_to_admin_url(context):
return reverse('admin:index') return reverse('admin:index')
class OneLinerNode(Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
line = self.nodelist.render(context).replace('\n', ' ')
return re.sub(r'\s\s+', '', line)
@register.tag
def oneliner(parser, token):
nodelist = parser.parse(('endoneliner',))
parser.delete_first_token()
return OneLinerNode(nodelist)
@register.filter @register.filter
def size(value, length): def size(value, length):
value = str(value)[:int(length)] value = str(value)[:int(length)]
@ -55,6 +75,12 @@ def is_checkbox(field):
return isinstance(field.field.widget, CheckboxInput) return isinstance(field.field.widget, CheckboxInput)
@register.filter
def content_type_id(label):
app_label, model = label.split('.')
return ContentType.objects.filter(app_label=app_label, model=model).values_list('id', flat=True)[0]
@register.filter @register.filter
def admin_url(obj): def admin_url(obj):
return change_url(obj) return change_url(obj)
@ -63,3 +89,4 @@ def admin_url(obj):
@register.filter @register.filter
def isactive(obj): def isactive(obj):
return getattr(obj, 'is_active', True) return getattr(obj, 'is_active', True)