Major cleanup

This commit is contained in:
Marc 2014-09-26 15:05:20 +00:00
parent af323ebe25
commit 99b14f9c9f
111 changed files with 1279 additions and 328 deletions

19
TODO.md
View file

@ -59,12 +59,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
dependency collector with max_recursion that matches the number of dots on service.match and service.metric
* Be consistent with dates:
* created_on date
* created_at datetime
at + clock time, midnight, noon- At 3:30 p.m., At 4:01, At noon
* backend logs with hal logo
* Use logs for storing monitored values
@ -82,13 +76,6 @@ at + clock time, midnight, noon- At 3:30 p.m., At 4:01, At noon
* help_text on readonly_fields specialy Bill.state. (eg. A bill is in OPEN state when bla bla )
* Create ProForma from orders orders.bill(proforma=True)
* generic confirmation breadcrumbs for single objects
* DirectDebit due date = bill.due_date
* settings.ENABLED_PLUGINS = ('path.module.ClassPlugin',)
* Transaction states: CREATED, PROCESSED, EXECUTED, COMMITED, ABORTED (SECURED, REJECTED?)
* bill.send() -> transacction.EXECUTED when source=None
@ -108,5 +95,7 @@ at + clock time, midnight, noon- At 3:30 p.m., At 4:01, At noon
return order.register_at.date()
* latest by 'id' *always*
* replace add_now by default=lambda: timezone.now()
* mail backend related_models = ('resources__content_type') ??
* ignore orders
* Redmine, BSCW and other applications management

View file

@ -1,6 +1,5 @@
from functools import wraps, partial
from django.contrib import messages
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
from django.utils.decorators import available_attrs
@ -61,8 +60,10 @@ def action_with_confirmation(action_name=None, extra_context={},
if len(queryset) == 1:
objects_name = force_text(opts.verbose_name)
obj = queryset.get()
else:
objects_name = force_text(opts.verbose_name_plural)
obj = None
if not action_name:
action_name = func.__name__
context = {
@ -73,6 +74,7 @@ def action_with_confirmation(action_name=None, extra_context={},
'action_value': action_value,
'queryset': queryset,
'opts': opts,
'obj': obj,
'app_label': app_label,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}

View file

@ -9,7 +9,6 @@ from django.shortcuts import redirect
from django.utils import importlib
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.models.utils import get_field_value
from orchestra.utils import humanize

View file

@ -6,7 +6,6 @@ from orchestra.utils.apps import autodiscover as module_autodiscover
from orchestra.utils.python import import_class
from .helpers import insert_links, replace_collectionmethodname
from .root import APIRoot
def collectionlink(**kwargs):

View file

@ -2,7 +2,6 @@ from django import forms
from django.conf.urls import patterns, url
from django.contrib import admin, messages
from django.contrib.admin.util import unquote
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.utils.safestring import mark_safe
from django.utils.six.moves.urllib.parse import parse_qsl
@ -115,7 +114,6 @@ class AccountListAdmin(AccountAdmin):
select_account.order_admin_field = 'user__username'
def changelist_view(self, request, extra_context=None):
opts = self.model._meta
original_app_label = request.META['PATH_INFO'].split('/')[-5]
original_model = request.META['PATH_INFO'].split('/')[-4]
context = {
@ -182,7 +180,6 @@ class AccountAdminMixin(object):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
account_id = self.get_account_from_preserve_filters(request)
verb = 'change' if object_id else 'add'
if not object_id:
if account_id:
# Preselect account

View file

@ -1,5 +1,4 @@
from django.contrib.admin import SimpleListFilter
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _

View file

@ -1,10 +1,9 @@
from optparse import make_option
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from django.db import transaction
from orchestra.apps.accounts.models import Account
from orchestra.apps.users.models import User
class Command(BaseCommand):
@ -29,5 +28,4 @@ class Command(BaseCommand):
username = options.get('username')
password = options.get('password')
account = Account.objects.create()
user = User.objects.create_superuser(username, email, password,
account=account, is_main=True)
account.users.create_superuser(username, email, password, is_main=True)

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20140909_1850'),
]
operations = [
migrations.RemoveField(
model_name='account',
name='register_date',
),
migrations.AddField(
model_name='account',
name='registered_on',
field=models.DateField(default=datetime.datetime(2014, 9, 26, 13, 25, 49, 42008), verbose_name='registered', auto_now_add=True),
preserve_default=False,
),
]

View file

@ -1,6 +1,5 @@
from django.conf import settings as djsettings
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
@ -18,7 +17,7 @@ class Account(models.Model):
language = models.CharField(_("language"), max_length=2,
choices=settings.ACCOUNTS_LANGUAGES,
default=settings.ACCOUNTS_DEFAULT_LANGUAGE)
register_date = models.DateTimeField(_("register date"), auto_now_add=True)
registered_on = models.DateField(_("registered"), auto_now_add=True)
comments = models.TextField(_("comments"), max_length=256, blank=True)
is_active = models.BooleanField(default=True)

View file

@ -5,7 +5,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_link, admin_date
from orchestra.admin.utils import admin_date
from orchestra.apps.accounts.admin import AccountAdminMixin
from . import settings
@ -53,8 +53,8 @@ class BillLineInline(admin.TabularInline):
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = (
'number', 'is_open', 'type_link', 'account_link', 'created_on_display',
'num_lines', 'display_total', 'display_payment_state'
'number', 'type_link', 'account_link', 'created_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_open'
)
list_filter = (BillTypeListFilter, 'is_open',)
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments')

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0007_auto_20140918_1454'),
]
operations = [
migrations.AlterModelOptions(
name='bill',
options={'get_latest_by': 'id'},
),
]

View file

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('orders', '__first__'),
('bills', '0008_auto_20140926_1218'),
]
operations = [
migrations.AddField(
model_name='billline',
name='created_on',
field=models.DateField(default=datetime.datetime(2014, 9, 26, 12, 20, 24, 908200), verbose_name='created', auto_now_add=True),
preserve_default=False,
),
migrations.AddField(
model_name='billline',
name='order_billed_on',
field=models.DateField(null=True, verbose_name='order billed', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='billline',
name='order_billed_until',
field=models.DateField(null=True, verbose_name='order billed until', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='billline',
name='order_id',
field=models.ForeignKey(blank=True, to='orders.Order', help_text='Informative link back to the order', null=True),
preserve_default=True,
),
migrations.AddField(
model_name='billsubline',
name='type',
field=models.CharField(default=b'OTHER', max_length=16, verbose_name='type', choices=[(b'VOLUME', 'Volume'), (b'COMPENSATION', 'Compensation'), (b'OTHER', 'Other')]),
preserve_default=True,
),
]

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0009_auto_20140926_1220'),
]
operations = [
migrations.AlterField(
model_name='bill',
name='closed_on',
field=models.DateField(null=True, verbose_name='closed on', blank=True),
),
migrations.AlterField(
model_name='bill',
name='created_on',
field=models.DateField(auto_now_add=True, verbose_name='created on'),
),
migrations.AlterField(
model_name='bill',
name='last_modified_on',
field=models.DateField(auto_now=True, verbose_name='last modified on'),
),
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('bills', '0010_auto_20140926_1326'),
]
operations = [
migrations.RemoveField(
model_name='bill',
name='last_modified_on',
),
migrations.AddField(
model_name='bill',
name='updated_on',
field=models.DateField(default=datetime.date(2014, 9, 26), verbose_name='updated on', auto_now=True),
preserve_default=False,
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('bills', '0011_auto_20140926_1334'),
]
operations = [
migrations.RenameField(
model_name='billline',
old_name='order_id',
new_name='order',
),
]

View file

@ -1,6 +1,6 @@
import inspect
from dateutil.relativedelta import relativedelta
from django.core.validators import ValidationError
from django.db import models
from django.template import loader, Context
from django.utils import timezone
@ -9,8 +9,8 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.accounts.models import Account
from orchestra.apps.contacts.models import Contact
from orchestra.core import accounts
from orchestra.utils.functional import cached
from orchestra.utils.html import html_to_pdf
from . import settings
@ -53,13 +53,12 @@ class Bill(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='%(class)s')
type = models.CharField(_("type"), max_length=16, choices=TYPES)
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
closed_on = models.DateTimeField(_("closed on"), blank=True, null=True)
# TODO rename to is_closed
created_on = models.DateField(_("created on"), auto_now_add=True)
closed_on = models.DateField(_("closed on"), blank=True, null=True)
is_open = models.BooleanField(_("is open"), default=True)
is_sent = models.BooleanField(_("is sent"), default=False)
due_on = models.DateField(_("due on"), null=True, blank=True)
last_modified_on = models.DateTimeField(_("last modified on"), auto_now=True)
updated_on = models.DateField(_("updated on"), auto_now=True)
total = models.DecimalField(max_digits=12, decimal_places=2, default=0)
comments = models.TextField(_("comments"), blank=True)
html = models.TextField(_("HTML"), blank=True)
@ -145,7 +144,6 @@ class Bill(models.Model):
self.save()
def send(self):
from orchestra.apps.contacts.models import Contact
self.account.send_email(
template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE,
context={
@ -241,9 +239,13 @@ class BillLine(models.Model):
quantity = models.DecimalField(_("quantity"), max_digits=12, decimal_places=2)
subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2)
tax = models.PositiveIntegerField(_("tax"))
# TODO
# order_id = models.ForeignKey('orders.Order', null=True, blank=True,
# help_text=_("Informative link back to the order"))
# Undo
order = models.ForeignKey(settings.BILLS_ORDER_MODEL, null=True, blank=True,
help_text=_("Informative link back to the order"))
order_billed_on = models.DateField(_("order billed"), null=True, blank=True)
order_billed_until = models.DateField(_("order billed until"), null=True, blank=True)
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)
@ -262,6 +264,17 @@ class BillLine(models.Model):
total += subline.total
return total
def undo(self):
# TODO warn user that undoing bills with compensations lead to compensation lost
for attr in ['order_id', 'order_billed_on', 'order_billed_until']:
if not getattr(self, attr):
raise ValidationError(_("Not enough information stored for undoing"))
if self.created_on != self.order.billed_on:
raise ValidationError(_("Dates don't match"))
self.order.billed_until = self.order_billed_until
self.order.billed_on = self.order_billed_on
self.delete()
def save(self, *args, **kwargs):
# TODO cost and consistency of this shit
super(BillLine, self).save(*args, **kwargs)
@ -272,10 +285,20 @@ class BillLine(models.Model):
class BillSubline(models.Model):
""" Subline used for describing an item discount """
VOLUME = 'VOLUME'
COMPENSATION = 'COMPENSATION'
OTHER = 'OTHER'
TYPES = (
(VOLUME, _("Volume")),
(COMPENSATION, _("Compensation")),
(OTHER, _("Other")),
)
# TODO: order info for undoing
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines')
description = models.CharField(_("description"), max_length=256)
total = models.DecimalField(max_digits=12, decimal_places=2)
# TODO type ? Volume and Compensation
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
def save(self, *args, **kwargs):
# TODO cost of this shit

View file

@ -36,3 +36,6 @@ BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT', '0000
BILLS_EMAIL_NOTIFICATION_TEMPLATE = getattr(settings, 'BILLS_EMAIL_NOTIFICATION_TEMPLATE',
'bills/bill-notification.email')
BILLS_ORDER_MODEL = getattr(settings, 'BILLS_ORDER_MODEL', 'orders.Order')

View file

@ -78,9 +78,9 @@
{% with sublines=line.sublines.all %}
<span class="{% if not sublines %}last {% endif %}column-id">{{ line.id }}</span>
<span class="{% if not sublines %}last {% endif %}column-description">{{ line.description }}</span>
<span class="{% if not sublines %}last {% endif %}column-quantity">{{ line.amount|default:"&nbsp;" }}</span>
<span class="{% if not sublines %}last {% endif %}column-quantity">{{ line.quantity|default:"&nbsp;" }}</span>
<span class="{% if not sublines %}last {% endif %}column-rate">{% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %}&nbsp;{% endif %}</span>
<span class="{% if not sublines %}last {% endif %}column-subtotal">{{ line.total }} &{{ currency.lower }};</span>
<span class="{% if not sublines %}last {% endif %}column-subtotal">{{ line.subtotal }} &{{ currency.lower }};</span>
<br>
{% for subline in sublines %}
<span class="{% if forloop.last %}last {% endif %}subline column-id">&nbsp;</span>

View file

@ -1,5 +1,4 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES',

View file

@ -1,8 +1,6 @@
from django.db import models
from django.conf.urls import patterns
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _

View file

@ -1,7 +1,4 @@
from rest_framework import viewsets
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from orchestra.api import router, SetPasswordApiMixin
from orchestra.apps.accounts.api import AccountApiMixin

View file

@ -77,21 +77,21 @@ class MysqlDisk(ServiceMonitor):
verbose_name = _("MySQL disk")
def exceeded(self, db):
context = self.get_context(obj)
context = self.get_context(db)
self.append("mysql -e '"
"UPDATE db SET Insert_priv=\"N\", Create_priv=\"N\""
" WHERE Db=\"%(db_name)s\";'" % context
)
def recovery(self, db):
context = self.get_context(obj)
context = self.get_context(db)
self.append("mysql -e '"
"UPDATE db SET Insert_priv=\"Y\", Create_priv=\"Y\""
" WHERE Db=\"%(db_name)s\";'" % context
)
def monitor(self, db):
context = self.get_context(obj)
context = self.get_context(db)
self.append(
"echo %(db_id)s $(mysql -B -e '"
" SELECT sum( data_length + index_length ) \"Size\"\n"

View file

@ -1,5 +1,5 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm, ReadOnlyPasswordHashField
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
@ -109,7 +109,6 @@ class ReadOnlySQLPasswordHashField(ReadOnlyPasswordHashField):
if 'Invalid' not in original:
return original
encoded = value
final_attrs = self.build_attrs(attrs)
if not encoded:
summary = mark_safe("<strong>%s</strong>" % _("No password set."))
else:

View file

@ -0,0 +1,14 @@
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
def view_zone(modeladmin, request, queryset):
zone = queryset.get()
context = {
'opts': modeladmin.model._meta,
'object': zone,
'title': _("%s zone content") % zone.origin.name
}
return TemplateResponse(request, 'admin/domains/domain/view_zone.html', context)
view_zone.url_name = 'view-zone'
view_zone.verbose_name = _("View zone")

View file

@ -1,17 +1,13 @@
from django import forms
from django.conf.urls import patterns, url
from django.contrib import admin
from django.contrib.admin.util import unquote
from django.core.urlresolvers import reverse
from django.db.models import F
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeListDefaultFilter, ExtendedModelAdmin
from orchestra.admin.utils import wrap_admin_view, admin_link, change_url
from orchestra.admin.utils import admin_link, change_url
from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.utils import apps
from .actions import view_zone
from .forms import RecordInlineFormSet, DomainAdminForm
from .filters import TopDomainListFilter
from .models import Domain, Record
@ -61,6 +57,7 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
('top_domain', 'True'),
)
form = DomainAdminForm
change_view_actions = [view_zone]
def structured_name(self, domain):
if not domain.is_top:
@ -89,35 +86,12 @@ class DomainAdmin(ChangeListDefaultFilter, AccountAdminMixin, ExtendedModelAdmin
websites.short_description = _("Websites")
websites.allow_tags = True
def get_urls(self):
""" Returns the additional urls for the change view links """
urls = super(DomainAdmin, self).get_urls()
admin_site = self.admin_site
opts = self.model._meta
urls = patterns("",
url('^(\d+)/view-zone/$',
wrap_admin_view(self, self.view_zone_view),
name='domains_domain_view_zone')
) + urls
return urls
def view_zone_view(self, request, object_id):
zone = self.get_object(request, unquote(object_id))
context = {
'opts': self.model._meta,
'object': zone,
'title': _("%s zone content") % zone.origin.name
}
return TemplateResponse(request, 'admin/domains/domain/view_zone.html',
context)
def get_queryset(self, request):
""" Order by structured name and imporve performance """
qs = super(DomainAdmin, self).get_queryset(request)
qs = qs.select_related('top')
# qs = qs.select_related('top')
# For some reason if we do this we know for sure that join table will be called T4
__ = str(qs.query)
str(qs.query)
qs = qs.extra(
select={'structured_name': 'CONCAT(T4.name, domains_domain.name)'},
).order_by('structured_name')

View file

@ -1,5 +1,3 @@
import os
from django.utils.translation import ugettext_lazy as _
from . import settings

View file

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.core.validators
import orchestra.apps.domains.validators
import orchestra.apps.domains.utils
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20140909_1850'),
]
operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(unique=True, max_length=256, verbose_name='name', validators=[orchestra.core.validators.validate_hostname, orchestra.apps.domains.validators.validate_allowed_domain])),
('serial', models.IntegerField(default=orchestra.apps.domains.utils.generate_zone_serial, help_text='Serial number', verbose_name='serial')),
('account', models.ForeignKey(related_name=b'domains', verbose_name='Account', blank=True, to='accounts.Account')),
('top', models.ForeignKey(related_name=b'subdomains', to='domains.Domain', null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Record',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('type', models.CharField(max_length=32, verbose_name='type', choices=[(b'MX', b'MX'), (b'NS', b'NS'), (b'CNAME', b'CNAME'), (b'A', 'A (IPv4 address)'), (b'AAAA', 'AAAA (IPv6 address)'), (b'SRV', b'SRV'), (b'TXT', b'TXT'), (b'SOA', b'SOA')])),
('value', models.CharField(max_length=256, verbose_name='value')),
('domain', models.ForeignKey(related_name=b'records', verbose_name='domain', to='domains.Domain')),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.apps.domains.validators
class Migration(migrations.Migration):
dependencies = [
('domains', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='record',
name='ttl',
field=models.CharField(default='', validators=[orchestra.apps.domains.validators.validate_zone_interval], max_length=8, blank=True, help_text='Record TTL, defaults to 1h', verbose_name='TTL'),
preserve_default=False,
),
]

View file

@ -1,11 +1,11 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import services
from orchestra.core.validators import (validate_ipv4_address, validate_ipv6_address,
validate_hostname, validate_ascii)
validate_hostname, validate_ascii)
from orchestra.utils.python import AttrDict
from . import settings, validators, utils
@ -69,13 +69,17 @@ class Domain(models.Model):
# Update serial and insert at 0
value = record.value.split()
value[2] = str(self.serial)
records.insert(0, (record.SOA, ' '.join(value)))
records.insert(0,
AttrDict(type=record.SOA, ttl=record.get_ttl(), value=' '.join(value))
)
else:
records.append((record.type, record.value))
records.append(
AttrDict(type=record.type, ttl=record.get_ttl(), value=record.value)
)
if not self.top:
if Record.NS not in types:
for ns in settings.DOMAINS_DEFAULT_NS:
records.append((Record.NS, ns))
records.append(AttrDict(type=Record.NS, value=ns))
if Record.SOA not in types:
soa = [
"%s." % settings.DOMAINS_DEFAULT_NAME_SERVER,
@ -86,18 +90,28 @@ class Domain(models.Model):
settings.DOMAINS_DEFAULT_EXPIRATION,
settings.DOMAINS_DEFAULT_MIN_CACHING_TIME
]
records.insert(0, (Record.SOA, ' '.join(soa)))
records.insert(0, AttrDict(type=Record.SOA, value=' '.join(soa)))
no_cname = Record.CNAME not in types
if Record.MX not in types and no_cname:
for mx in settings.DOMAINS_DEFAULT_MX:
records.append((Record.MX, mx))
records.append(AttrDict(type=Record.MX, value=mx))
if (Record.A not in types and Record.AAAA not in types) and no_cname:
records.append((Record.A, settings.DOMAINS_DEFAULT_A))
records.append(AttrDict(type=Record.A, value=settings.DOMAINS_DEFAULT_A))
result = ''
for type, value in records:
name = '%s.%s' % (self.name, ' '*(37-len(self.name)))
type = '%s %s' % (type, ' '*(7-len(type)))
result += '%s IN %s %s\n' % (name, type, value)
for record in records:
name = '{name}.{spaces}'.format(
name=self.name, spaces=' ' * (37-len(self.name))
)
ttl = record.get('ttl', settings.DOMAINS_DEFAULT_TTL)
ttl = '{spaces}{ttl}'.format(
spaces=' ' * (7-len(ttl)), ttl=ttl
)
type = '{type} {spaces}'.format(
type=record.type, spaces=' ' * (7-len(record.type))
)
result += '{name} {ttl} IN {type} {value}\n'.format(
name=name, ttl=ttl, type=type, value=record.value
)
return result
def save(self, *args, **kwargs):
@ -150,13 +164,15 @@ class Record(models.Model):
(SOA, "SOA"),
)
# TODO TTL
domain = models.ForeignKey(Domain, verbose_name=_("domain"), related_name='records')
type = models.CharField(max_length=32, choices=TYPE_CHOICES)
value = models.CharField(max_length=256)
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])
type = models.CharField(_("type"), max_length=32, choices=TYPE_CHOICES)
value = models.CharField(_("value"), max_length=256)
def __unicode__(self):
return "%s IN %s %s" % (self.domain, self.type, self.value)
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 """
@ -172,6 +188,8 @@ class Record(models.Model):
self.SOA: validators.validate_soa_record,
}
mapp[self.type](self.value)
def get_ttl(self):
return self.ttl or settings.DOMAINS_DEFAULT_TTL
services.register(Domain)

View file

@ -124,7 +124,7 @@ class DomainTestMixin(object):
self.assertNotEqual(hostmaster, soa[5])
def validate_update(self, server_addr, domain_name):
domain = Domain.objects.get(name=domain_name)
Domain.objects.get(name=domain_name)
context = {
'domain_name': domain_name,
'server_addr': server_addr

View file

@ -1,4 +1,3 @@
from django.db import IntegrityError, transaction
from django.test import TestCase
from ..models import Domain

View file

@ -56,7 +56,7 @@ class MessageReadOnlyInline(admin.TabularInline):
def content_html(self, msg):
context = {
'number': msg.number,
'time': admin_date('created_on')(msg),
'time': admin_date('created_at')(msg),
'author': admin_link('author')(msg) if msg.author else msg.author_name,
}
summary = _("#%(number)i Updated by %(author)s about %(time)s") % context
@ -98,11 +98,11 @@ class MessageInline(admin.TabularInline):
class TicketInline(admin.TabularInline):
fields = [
'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state',
'colored_priority', 'created', 'last_modified'
'colored_priority', 'created', 'updated'
]
readonly_fields = [
'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state',
'colored_priority', 'created', 'last_modified'
'colored_priority', 'created', 'updated'
]
model = Ticket
extra = 0
@ -110,8 +110,8 @@ class TicketInline(admin.TabularInline):
creator_link = admin_link('creator')
owner_link = admin_link('owner')
created = admin_link('created_on')
last_modified = admin_link('last_modified_on')
created = admin_link('created_at')
updated = admin_link('updated_at')
colored_state = admin_colored('state', colors=STATE_COLORS, bold=False)
colored_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
@ -121,10 +121,10 @@ class TicketInline(admin.TabularInline):
ticket_id.allow_tags = True
class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeViewActions,
class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin):
list_display = [
'unbold_id', 'bold_subject', 'display_creator', 'display_owner',
'display_queue', 'display_priority', 'display_state', 'last_modified'
'display_queue', 'display_priority', 'display_state', 'updated'
]
list_display_links = ('unbold_id', 'bold_subject')
list_filter = [
@ -134,7 +134,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
('my_tickets', lambda r: 'True' if not r.user.is_superuser else 'False'),
('state', 'OPEN')
)
date_hierarchy = 'created_on'
date_hierarchy = 'created_at'
search_fields = [
'id', 'subject', 'creator__username', 'creator__email', 'queue__name',
'owner__username'
@ -192,20 +192,20 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
display_creator = admin_link('creator')
display_queue = admin_link('queue')
display_owner = admin_link('owner')
last_modified = admin_date('last_modified_on')
updated = admin_date('updated')
display_state = admin_colored('state', colors=STATE_COLORS, bold=False)
display_priority = admin_colored('priority', colors=PRIORITY_COLORS, bold=False)
def display_summary(self, ticket):
context = {
'creator': admin_link('creator')(self, ticket) if ticket.creator else ticket.creator_name,
'created': admin_date('created_on')(ticket),
'created': admin_date('created_at')(ticket),
'updated': '',
}
msg = ticket.messages.last()
if msg:
context.update({
'updated': admin_date('created_on')(msg),
'updated': admin_date('created_at')(msg),
'updater': admin_link('author')(self, msg) if msg.author else msg.author_name,
})
context['updated'] = '. Updated by %(updater)s about %(updated)s' % context
@ -283,7 +283,7 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): #TODO ChangeView
def message_preview_view(self, request):
""" markdown preview render via ajax """
data = request.POST.get("data")
data_formated = markdowt_tn(strip_tags(data))
data_formated = markdown(strip_tags(data))
return HttpResponse(data_formated)
def get_queryset(self, request):

View file

@ -1,11 +1,9 @@
from django import forms
from django.core.urlresolvers import reverse
from django.utils.html import strip_tags
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from markdown import markdown
from orchestra.admin.utils import change_url
from orchestra.apps.users.models import User
from orchestra.forms.widgets import ReadOnlyWidget
@ -42,7 +40,6 @@ class MessageInlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MessageInlineForm, self).__init__(*args, **kwargs)
admin_link = change_url(self.user)
self.fields['created_on'].widget = ReadOnlyWidget('')
def clean_content(self):

View file

@ -1,6 +1,5 @@
from django.conf import settings as djsettings
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.contacts import settings as contacts_settings
@ -68,13 +67,13 @@ class Ticket(models.Model):
priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES,
default=MEDIUM)
state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW)
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
last_modified_on = models.DateTimeField(_("last modified on"), auto_now=True)
created_at = models.DateTimeField(_("created"), auto_now_add=True)
updated_at = models.DateTimeField(_("modified"), auto_now=True)
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"),
blank=True)
class Meta:
ordering = ["-last_modified_on"]
ordering = ['-updated_at']
def __unicode__(self):
return unicode(self.pk)

View file

@ -1,8 +1,6 @@
import textwrap
from django.template import Template, Context
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor

View file

@ -1,15 +1,12 @@
from django import forms
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import insertattr, admin_link, change_url
from orchestra.admin.utils import admin_link, change_url
from orchestra.apps.accounts.admin import SelectAccountAdminMixin, AccountAdminMixin
from orchestra.apps.domains.forms import DomainIterator
from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter
from .models import Mailbox, Address, Autoresponse

View file

@ -7,6 +7,7 @@ from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor
from . import settings
from .models import Address
class MailSystemUserBackend(ServiceController):
@ -152,7 +153,7 @@ class MaildirDisk(ServiceMonitor):
)
def get_context(self, mailbox):
context = MailSystemUserBackend().get_context(site)
context = MailSystemUserBackend().get_context(mailbox)
context['home'] = settings.EMAILS_HOME % context
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['object_id'] = mailbox.pk

View file

@ -1,6 +1,3 @@
import re
from django.contrib.auth.hashers import check_password, make_password
from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import ugettext_lazy as _

View file

@ -44,7 +44,6 @@ def validate_forward(value):
def validate_sieve(value):
from .models import Mailbox
sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest()
path = os.path.join(settings.EMAILS_SIEVETEST_PATH, sieve_name)
with open(path, 'wb') as f:

View file

@ -1,5 +1,4 @@
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
@ -89,18 +88,18 @@ class BackendLogAdmin(admin.ModelAdmin):
)
list_display_links = ('id', 'backend')
list_filter = ('state', 'backend')
date_hierarchy = 'last_update'
date_hierarchy = 'updated_at'
inlines = [BackendOperationInline]
fields = [
'backend', 'server_link', 'state', 'mono_script', 'mono_stdout',
'mono_stderr', 'mono_traceback', 'exit_code', 'task_id', 'display_created',
'display_last_update', 'execution_time'
'display_updated', 'execution_time'
]
readonly_fields = fields
server_link = admin_link('server')
display_last_update = admin_date('last_update')
display_created = admin_date('created')
display_updated = admin_date('updated_at')
display_created = admin_date('created_at')
display_state = admin_colored('state', colors=STATE_COLORS)
mono_script = display_mono('script')
mono_stdout = display_mono('stdout')
@ -111,6 +110,9 @@ class BackendLogAdmin(admin.ModelAdmin):
""" Order by structured name and imporve performance """
qs = super(BackendLogAdmin, self).get_queryset(request)
return qs.select_related('server').defer('script', 'stdout')
def has_add_permission(self, *args, **kwargs):
return False
class ServerAdmin(admin.ModelAdmin):

View file

@ -1,7 +1,6 @@
from functools import partial
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins

View file

@ -1,7 +1,5 @@
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -62,8 +60,8 @@ class BackendLog(models.Model):
exit_code = models.IntegerField(_("exit code"), null=True)
task_id = models.CharField(_("task ID"), max_length=36, unique=True, null=True,
help_text="Celery task ID when used as execution backend")
created = models.DateTimeField(_("created"), auto_now_add=True)
last_update = models.DateTimeField(_("last update"), auto_now=True)
created_at = models.DateTimeField(_("created"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated"), auto_now=True)
class Meta:
get_latest_by = 'id'
@ -89,7 +87,7 @@ class BackendOperation(models.Model):
action = models.CharField(_("action"), max_length=64)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
# TODO rename to content_object
instance = generic.GenericForeignKey('content_type', 'object_id')
class Meta:

View file

@ -1,5 +1,3 @@
from django.db import IntegrityError, transaction
from orchestra.utils.tests import BaseTestCase
from .. import operations, backends

View file

@ -78,7 +78,7 @@ class BillSelectedOrders(object):
if int(request.POST.get('step')) >= 3:
bills = self.queryset.bill(commit=True, **self.options)
for order in self.queryset:
modeladmin.log_change(request, order, 'Billed')
self.modeladmin.log_change(request, order, 'Billed')
if not bills:
msg = _("Selected orders do not have pending billing")
self.modeladmin.message_user(request, msg, messages.WARNING)

View file

@ -3,7 +3,6 @@ from django.utils import timezone
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeListDefaultFilter
from orchestra.admin.utils import admin_link, admin_date
from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.utils.humanize import naturaldate
@ -13,7 +12,7 @@ from .filters import ActiveOrderListFilter, BilledOrderListFilter
from .models import Order, MetricStorage
class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
class OrderAdmin(AccountAdminMixin, admin.ModelAdmin):
list_display = (
'id', 'service', 'account_link', 'content_object_link',
'display_registered_on', 'display_billed_until', 'display_cancelled_on'
@ -22,9 +21,6 @@ class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
list_filter = (ActiveOrderListFilter, BilledOrderListFilter, 'service',)
actions = (BillSelectedOrders(),)
date_hierarchy = 'registered_on'
default_changelist_filters = (
('is_active', 'True'),
)
content_object_link = admin_link('content_object', order=False)
display_registered_on = admin_date('registered_on')

View file

@ -2,7 +2,7 @@ import datetime
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.bills.models import Invoice, Fee, ProForma, BillLine, BillSubline
from orchestra.apps.bills.models import Invoice, Fee, ProForma
class BillsBackend(object):
@ -39,6 +39,10 @@ class BillsBackend(object):
subtotal=line.subtotal,
tax=service.tax,
description=self.get_line_description(line),
order=line.order,
order_billed_on=line.order.old_billed_on,
order_billed_until=line.order.old_billed_until
)
self.create_sublines(billine, line.discounts)
return bills
@ -46,7 +50,6 @@ class BillsBackend(object):
def format_period(self, ini, end):
ini = ini.strftime("%b, %Y")
end = (end-datetime.timedelta(seconds=1)).strftime("%b, %Y")
# TODO if diff is less than a month: write the month only
if ini == end:
return ini
return _("{ini} to {end}").format(ini=ini, end=end)
@ -67,6 +70,7 @@ class BillsBackend(object):
def create_sublines(self, line, discounts):
for discount in discounts:
line.sublines.create(
description=_("Discount per %s") % discount.type,
description=_("Discount per %s") % discount.type.lower(),
total=discount.total,
type=discount.type,
)

View file

@ -13,7 +13,6 @@ class ActiveOrderListFilter(SimpleListFilter):
return (
('True', _("Active")),
('False', _("Inactive")),
('None', _("All")),
)
def queryset(self, request, queryset):
@ -23,12 +22,6 @@ class ActiveOrderListFilter(SimpleListFilter):
return queryset.inactive()
return queryset
def choices(self, cl):
""" Remove default All """
choices = iter(super(ActiveOrderListFilter, self).choices(cl))
choices.next()
return choices
class BilledOrderListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """

View file

@ -1,28 +1,24 @@
import datetime
import decimal
import logging
import sys
from django.db import models
from django.db.migrations.recorder import MigrationRecorder
from django.db.models import F, Q
from django.db.models.loading import get_model
from django.db.models.signals import pre_delete, post_delete, post_save
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import caches, services, accounts
from orchestra.core import accounts
from orchestra.models import queryset
from orchestra.utils.apps import autodiscover
from orchestra.utils.python import import_class
from . import helpers, settings
from .handlers import ServiceHandler
logger = logging.getLogger(__name__)
@ -39,8 +35,13 @@ class OrderQuerySet(models.QuerySet):
for account, services in qs.group_by('account', 'service').iteritems():
bill_lines = []
for service, orders in services.iteritems():
for order in orders:
# Saved for undoing support
order.old_billed_on = order.billed_on
order.old_billed_until = order.billed_until
lines = service.handler.generate_bill_lines(orders, account, **options)
bill_lines.extend(lines)
# TODO make this consistent always returning the same fucking objects
if commit:
bills += bill_backend.create_bills(account, bill_lines, **options)
else:
@ -73,7 +74,7 @@ class OrderQuerySet(models.QuerySet):
def inactive(self, **kwargs):
""" return inactive orders """
return self.filter(cancelled_on__lt=timezone.now(), **kwargs)
return self.filter(cancelled_on__lte=timezone.now(), **kwargs)
class Order(models.Model):

View file

@ -1,5 +1,4 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
ORDERS_BILLING_BACKEND = getattr(settings, 'ORDERS_BILLING_BACKEND',

View file

@ -26,8 +26,8 @@ def process_transactions(modeladmin, request, queryset):
method = PaymentMethod.get_plugin(method)
procs = method.process(transactions)
processes += procs
for transaction in transactions:
modeladmin.log_change(request, transaction, 'Processed')
for trans in transactions:
modeladmin.log_change(request, trans, 'Processed')
if not processes:
return
opts = modeladmin.model._meta
@ -44,9 +44,9 @@ def process_transactions(modeladmin, request, queryset):
@transaction.atomic
@action_with_confirmation()
def mark_as_executed(modeladmin, request, queryset, extra_context={}):
for transaction in queryset:
transaction.mark_as_executed()
modeladmin.log_change(request, transaction, 'Executed')
for trans in queryset:
trans.mark_as_executed()
modeladmin.log_change(request, trans, 'Executed')
msg = _("%s selected transactions have been marked as executed.") % queryset.count()
modeladmin.message_user(request, msg)
mark_as_executed.url_name = 'execute'
@ -56,9 +56,9 @@ mark_as_executed.verbose_name = _("Mark as executed")
@transaction.atomic
@action_with_confirmation()
def mark_as_secured(modeladmin, request, queryset):
for transaction in queryset:
transaction.mark_as_secured()
modeladmin.log_change(request, transaction, 'Secured')
for trans in queryset:
trans.mark_as_secured()
modeladmin.log_change(request, trans, 'Secured')
msg = _("%s selected transactions have been marked as secured.") % queryset.count()
modeladmin.message_user(request, msg)
mark_as_secured.url_name = 'secure'
@ -68,9 +68,9 @@ mark_as_secured.verbose_name = _("Mark as secured")
@transaction.atomic
@action_with_confirmation()
def mark_as_rejected(modeladmin, request, queryset):
for transaction in queryset:
transaction.mark_as_rejected()
modeladmin.log_change(request, transaction, 'Rejected')
for trans in queryset:
trans.mark_as_rejected()
modeladmin.log_change(request, trans, 'Rejected')
msg = _("%s selected transactions have been marked as rejected.") % queryset.count()
modeladmin.message_user(request, msg)
mark_as_rejected.url_name = 'reject'
@ -90,7 +90,7 @@ def _format_display_objects(modeladmin, request, queryset, related):
for related in getattr(obj.transactions, attr)():
subobjects.append(
mark_safe('{0}: <a href="{1}">{2}</a> will be marked as {3}'.format(
capfirst(subobj.get_type().lower()), change_url(subobj), subobj, verb))
capfirst(related.get_type().lower()), change_url(related), related, verb))
)
objects.append(subobjects)
return {'display_objects': objects}
@ -127,9 +127,9 @@ abort.verbose_name = _("Abort")
@transaction.atomic
@action_with_confirmation(extra_context=_format_commit)
def commit(modeladmin, request, queryset):
for transaction in queryset:
transaction.mark_as_rejected()
modeladmin.log_change(request, transaction, 'Rejected')
for trans in queryset:
trans.mark_as_rejected()
modeladmin.log_change(request, trans, 'Rejected')
msg = _("%s selected transactions have been marked as rejected.") % queryset.count()
modeladmin.message_user(request, msg)
commit.url_name = 'commit'

View file

@ -1,4 +1,3 @@
from django import forms
from django.conf.urls import patterns, url
from django.contrib import admin
from django.core.urlresolvers import reverse
@ -37,7 +36,6 @@ class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
def get_urls(self):
""" Hooks select account url """
urls = super(PaymentSourceAdmin, self).get_urls()
admin_site = self.admin_site
opts = self.model._meta
info = opts.app_label, opts.model_name
select_urls = patterns("",

View file

@ -1,6 +1,5 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
@ -99,8 +98,8 @@ class Transaction(models.Model):
default=WAITTING_PROCESSING)
amount = models.DecimalField(_("amount"), max_digits=12, decimal_places=2)
currency = models.CharField(max_length=10, default=settings.PAYMENT_CURRENCY)
created_on = models.DateTimeField(auto_now_add=True)
modified_on = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(_("created"), auto_now_add=True)
modified_at = models.DateTimeField(_("modified"), auto_now=True)
objects = TransactionQuerySet.as_manager()

View file

@ -15,16 +15,16 @@ from .models import Resource, ResourceData, MonitorData
class ResourceAdmin(ExtendedModelAdmin):
list_display = (
'id', 'verbose_name', 'content_type', 'period', 'ondemand',
'id', 'verbose_name', 'content_type', 'period', 'on_demand',
'default_allocation', 'unit', 'disable_trigger', 'crontab',
)
list_filter = (UsedContentTypeFilter, 'period', 'ondemand', 'disable_trigger')
list_filter = (UsedContentTypeFilter, 'period', 'on_demand', 'disable_trigger')
fieldsets = (
(None, {
'fields': ('name', 'content_type', 'period'),
}),
(_("Configuration"), {
'fields': ('verbose_name', 'unit', 'scale', 'ondemand',
'fields': ('verbose_name', 'unit', 'scale', 'on_demand',
'default_allocation', 'disable_trigger', 'is_active'),
}),
(_("Monitoring"), {
@ -64,7 +64,7 @@ class ResourceAdmin(ExtendedModelAdmin):
class ResourceDataAdmin(admin.ModelAdmin):
list_display = (
'id', 'resource', 'used', 'allocated', 'last_update', 'content_object_link'
'id', 'resource', 'used', 'allocated', 'updated_at', 'content_object_link'
)
list_filter = ('resource',)
readonly_fields = ('content_object_link',)
@ -77,7 +77,7 @@ class ResourceDataAdmin(admin.ModelAdmin):
class MonitorDataAdmin(admin.ModelAdmin):
list_display = ('id', 'monitor', 'date', 'value', 'content_object_link')
list_display = ('id', 'monitor', 'created_at', 'value', 'content_object_link')
list_filter = ('monitor',)
readonly_fields = ('content_object_link',)
@ -118,16 +118,16 @@ def resource_inline_factory(resources):
formset = ResourceInlineFormSet
can_delete = False
fields = (
'verbose_name', 'used', 'display_last_update', 'allocated', 'unit'
'verbose_name', 'used', 'display_updated', 'allocated', 'unit'
)
readonly_fields = ('used', 'display_last_update')
readonly_fields = ('used', 'display_updated')
class Media:
css = {
'all': ('orchestra/css/hide-inline-id.css',)
}
display_last_update = admin_date('last_update', default=_("Never"))
display_updated = admin_date('updated_at', default=_("Never"))
def has_add_permission(self, *args, **kwargs):
""" Hidde add another """

View file

@ -1,5 +1,4 @@
from django.apps import AppConfig
from django.contrib.contenttypes import generic
from orchestra.utils import running_syncdb

View file

@ -1,7 +1,5 @@
from django import forms
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from djcelery.humanize import naturaldate
from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
@ -21,7 +19,7 @@ class ResourceForm(forms.ModelForm):
if self.resource:
self.fields['verbose_name'].initial = self.resource.verbose_name
self.fields['unit'].initial = self.resource.unit
if self.resource.ondemand:
if self.resource.on_demand:
self.fields['allocated'].required = False
self.fields['allocated'].widget = ReadOnlyWidget(None, '')
else:

View file

@ -38,13 +38,15 @@ def compute_resource_usage(data):
continue
has_result = True
epoch = datetime(year=today.year, month=today.month, day=1, tzinfo=timezone.utc)
total = (epoch-last.date).total_seconds()
dataset = dataset.filter(date__year=today.year, date__month=today.month)
total = (last.created_at-epoch).total_seconds()
dataset = dataset.filter(created_at__year=today.year, created_at__month=today.month)
ini = epoch
for data in dataset:
slot = (previous-data.date).total_seconds()
slot = (data.created_at-ini).total_seconds()
result += data.value * slot/total
ini = data.created_at
elif resource.period == resource.MONTHLY_SUM:
dataset = dataset.filter(date__year=today.year, date__month=today.month)
dataset = dataset.filter(created_at__year=today.year, created_at__month=today.month)
# FIXME Aggregation of 0s returns None! django bug?
# value = dataset.aggregate(models.Sum('value'))['value__sum']
values = dataset.values_list('value', flat=True)

View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import orchestra.models.fields
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('djcelery', '__first__'),
('contenttypes', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='MonitorData',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('monitor', models.CharField(max_length=256, verbose_name='monitor', choices=[(b'Apache2Backend', 'Apache 2'), (b'Apache2Traffic', 'Apache 2 Traffic'), (b'AutoresponseBackend', 'Mail autoresponse'), (b'AwstatsBackend', 'Awstats'), (b'Bind9MasterDomainBackend', 'Bind9 master domain'), (b'Bind9SlaveDomainBackend', 'Bind9 slave domain'), (b'DokuWikiMuBackend', 'DokuWiki multisite'), (b'DrupalMuBackend', 'Drupal multisite'), (b'FTPTraffic', 'FTP traffic'), (b'MailSystemUserBackend', 'Mail system user'), (b'MaildirDisk', 'Maildir disk usage'), (b'MailmanBackend', b'Mailman'), (b'MailmanTraffic', b'MailmanTraffic'), (b'MySQLDBBackend', b'MySQL database'), (b'MySQLPermissionBackend', b'MySQL permission'), (b'MySQLUserBackend', b'MySQL user'), (b'MysqlDisk', 'MySQL disk'), (b'OpenVZTraffic', b'OpenVZTraffic'), (b'PHPFPMBackend', 'PHP-FPM'), (b'PHPFcgidBackend', 'PHP-Fcgid'), (b'PostfixAddressBackend', 'Postfix address'), (b'ServiceController', b'ServiceController'), (b'ServiceMonitor', b'ServiceMonitor'), (b'StaticBackend', 'Static'), (b'SystemUserBackend', 'System User'), (b'SystemUserDisk', 'System user disk'), (b'WebalizerBackend', 'Webalizer'), (b'WordpressMuBackend', 'Wordpress multisite')])),
('object_id', models.PositiveIntegerField()),
('date', models.DateTimeField(auto_now_add=True, verbose_name='date')),
('value', models.DecimalField(verbose_name='value', max_digits=16, decimal_places=2)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
],
options={
'get_latest_by': 'id',
'verbose_name_plural': 'monitor data',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Resource',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='Required. 32 characters or fewer. Lowercase letters, digits and hyphen only.', max_length=32, verbose_name='name', validators=[django.core.validators.RegexValidator(b'^[a-z0-9_\\-]+$', 'Enter a valid name.', b'invalid')])),
('verbose_name', models.CharField(max_length=256, verbose_name='verbose name')),
('period', models.CharField(default=b'LAST', help_text='Operation used for aggregating this resource monitoreddata.', max_length=16, verbose_name='period', choices=[(b'LAST', 'Last'), (b'MONTHLY_SUM', 'Monthly Sum'), (b'MONTHLY_AVG', 'Monthly Average')])),
('ondemand', models.BooleanField(default=False, help_text='If enabled the resource will not be pre-allocated, but allocated under the application demand', verbose_name='on demand')),
('default_allocation', models.PositiveIntegerField(help_text='Default allocation value used when this is not an on demand resource', null=True, verbose_name='default allocation', blank=True)),
('unit', models.CharField(help_text='The unit in which this resource is measured. For example GB, KB or subscribers', max_length=16, verbose_name='unit')),
('scale', models.PositiveIntegerField(help_text='Scale in which this resource monitoring resoults should be prorcessed to match with unit.', verbose_name='scale')),
('disable_trigger', models.BooleanField(default=False, help_text='Disables monitors exeeded and recovery triggers', verbose_name='disable trigger')),
('monitors', orchestra.models.fields.MultiSelectField(blank=True, help_text='Monitor backends used for monitoring this resource.', max_length=256, verbose_name='monitors', choices=[(b'Apache2Backend', 'Apache 2'), (b'Apache2Traffic', 'Apache 2 Traffic'), (b'AutoresponseBackend', 'Mail autoresponse'), (b'AwstatsBackend', 'Awstats'), (b'Bind9MasterDomainBackend', 'Bind9 master domain'), (b'Bind9SlaveDomainBackend', 'Bind9 slave domain'), (b'DokuWikiMuBackend', 'DokuWiki multisite'), (b'DrupalMuBackend', 'Drupal multisite'), (b'FTPTraffic', 'FTP traffic'), (b'MailSystemUserBackend', 'Mail system user'), (b'MaildirDisk', 'Maildir disk usage'), (b'MailmanBackend', b'Mailman'), (b'MailmanTraffic', b'MailmanTraffic'), (b'MySQLDBBackend', b'MySQL database'), (b'MySQLPermissionBackend', b'MySQL permission'), (b'MySQLUserBackend', b'MySQL user'), (b'MysqlDisk', 'MySQL disk'), (b'OpenVZTraffic', b'OpenVZTraffic'), (b'PHPFPMBackend', 'PHP-FPM'), (b'PHPFcgidBackend', 'PHP-Fcgid'), (b'PostfixAddressBackend', 'Postfix address'), (b'ServiceController', b'ServiceController'), (b'ServiceMonitor', b'ServiceMonitor'), (b'StaticBackend', 'Static'), (b'SystemUserBackend', 'System User'), (b'SystemUserDisk', 'System user disk'), (b'WebalizerBackend', 'Webalizer'), (b'WordpressMuBackend', 'Wordpress multisite')])),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('content_type', models.ForeignKey(help_text='Model where this resource will be hooked.', to='contenttypes.ContentType')),
('crontab', models.ForeignKey(blank=True, to='djcelery.CrontabSchedule', help_text='Crontab for periodic execution. Leave it empty to disable periodic monitoring', null=True, verbose_name='crontab')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ResourceData',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField()),
('used', models.PositiveIntegerField(null=True)),
('last_update', models.DateTimeField(null=True)),
('allocated', models.PositiveIntegerField(null=True, blank=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('resource', models.ForeignKey(related_name=b'dataset', to='resources.Resource')),
],
options={
'verbose_name_plural': 'resource data',
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='resourcedata',
unique_together=set([('resource', 'content_type', 'object_id')]),
),
migrations.AlterUniqueTogether(
name='resource',
unique_together=set([('name', 'content_type'), ('verbose_name', 'content_type')]),
),
]

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('resources', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='resource',
old_name='ondemand',
new_name='on_demand',
),
]

View file

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
class Migration(migrations.Migration):
dependencies = [
('resources', '0002_auto_20140926_1143'),
]
operations = [
migrations.RemoveField(
model_name='monitordata',
name='date',
),
migrations.RemoveField(
model_name='resourcedata',
name='last_update',
),
migrations.AddField(
model_name='monitordata',
name='created_at',
field=models.DateTimeField(default=datetime.datetime(2014, 9, 26, 13, 25, 33, 290000), verbose_name='created', auto_now_add=True),
preserve_default=False,
),
migrations.AddField(
model_name='resourcedata',
name='updated_at',
field=models.DateTimeField(null=True, verbose_name='updated'),
preserve_default=True,
),
migrations.AlterField(
model_name='monitordata',
name='content_type',
field=models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='monitordata',
name='object_id',
field=models.PositiveIntegerField(verbose_name='object id'),
),
migrations.AlterField(
model_name='resourcedata',
name='allocated',
field=models.PositiveIntegerField(null=True, verbose_name='allocated', blank=True),
),
migrations.AlterField(
model_name='resourcedata',
name='content_type',
field=models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='resourcedata',
name='object_id',
field=models.PositiveIntegerField(verbose_name='object id'),
),
migrations.AlterField(
model_name='resourcedata',
name='resource',
field=models.ForeignKey(related_name=b'dataset', verbose_name='resource', to='resources.Resource'),
),
migrations.AlterField(
model_name='resourcedata',
name='used',
field=models.PositiveIntegerField(null=True, verbose_name='used'),
),
]

View file

@ -43,8 +43,7 @@ class Resource(models.Model):
default=LAST,
help_text=_("Operation used for aggregating this resource monitored"
"data."))
# TODO rename to on_deman
ondemand = models.BooleanField(_("on demand"), default=False,
on_demand = models.BooleanField(_("on demand"), default=False,
help_text=_("If enabled the resource will not be pre-allocated, "
"but allocated under the application demand"))
default_allocation = models.PositiveIntegerField(_("default allocation"),
@ -116,12 +115,12 @@ class Resource(models.Model):
class ResourceData(models.Model):
""" Stores computed resource usage and allocation """
resource = models.ForeignKey(Resource, related_name='dataset')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
used = models.PositiveIntegerField(null=True)
last_update = models.DateTimeField(null=True)
allocated = models.PositiveIntegerField(null=True, blank=True)
resource = models.ForeignKey(Resource, related_name='dataset', verbose_name=_("resource"))
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
object_id = models.PositiveIntegerField(_("object id"))
used = models.PositiveIntegerField(_("used"), null=True)
updated_at = models.DateTimeField(_("updated"), null=True)
allocated = models.PositiveIntegerField(_("allocated"), null=True, blank=True)
content_object = GenericForeignKey()
@ -146,7 +145,7 @@ class ResourceData(models.Model):
if current is None:
current = self.get_used()
self.used = current or 0
self.last_update = timezone.now()
self.updated_at = timezone.now()
self.save()
@ -154,9 +153,9 @@ class MonitorData(models.Model):
""" Stores monitored data """
monitor = models.CharField(_("monitor"), max_length=256,
choices=ServiceMonitor.get_plugin_choices())
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
date = models.DateTimeField(_("date"), auto_now_add=True)
content_type = models.ForeignKey(ContentType, verbose_name=_("content type"))
object_id = models.PositiveIntegerField(_("object id"))
created_at = models.DateTimeField(_("created"), auto_now_add=True)
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
content_object = GenericForeignKey()

View file

@ -44,12 +44,12 @@ if not running_syncdb():
msg = "Unknown or duplicated resource '%s'." % resource
raise serializers.ValidationError(msg)
resources.remove(resource)
if not resource.ondemand and not data.allocated:
if not resource.on_demand and not data.allocated:
data.allocated = resource.default_allocation
result.append(data)
for resource in resources:
data = ResourceData(resource=resource)
if not resource.ondemand:
if not resource.on_demand:
data.allocated = resource.default_allocation
result.append(data)
attrs[source] = result
@ -64,7 +64,7 @@ if not running_syncdb():
ret['available_resources'] = [
{
'name': resource.name,
'ondemand': resource.ondemand,
'on_demand': resource.on_demand,
'default_allocation': resource.default_allocation
} for resource in resources
]

View file

@ -1,6 +1,5 @@
from celery import shared_task
from django.db.models.loading import get_model
from django.utils import timezone
from orchestra.apps.orchestration.models import BackendOperation as Operation
@ -34,7 +33,7 @@ def monitor(resource_id):
operations.append(op)
elif data.used < data.allocated:
op = Operation.create(backend, obj, Operation.RECOVERY)
operation.append(op)
operations.append(op)
# data = ResourceData.get_or_create(obj, resource)
# current = data.get_used()
# if not resource.disable_trigger:

View file

@ -1,5 +1,5 @@
from django.contrib import messages
from django.db import transaction
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
@ -7,8 +7,23 @@ from django.utils.translation import ugettext_lazy as _
def update_orders(modeladmin, request, queryset):
for service in queryset:
service.update_orders()
modeladmin.log_change(request, transaction, 'Update orders')
modeladmin.log_change(request, service, 'Update orders')
msg = _("Orders for %s selected services have been updated.") % queryset.count()
modeladmin.message_user(request, msg)
update_orders.url_name = 'update-orders'
update_orders.verbose_name = _("Update orders")
def view_help(modeladmin, request, queryset):
opts = modeladmin.model._meta
context = {
'title': _("Need some help?"),
'opts': opts,
'queryset': queryset,
'obj': queryset.get(),
'action_name': _("help"),
'app_label': opts.app_label,
}
return TemplateResponse(request, 'admin/services/service/help.html', context)
view_help.url_name = 'help'
view_help.verbose_name = _("Help")

View file

@ -9,7 +9,7 @@ from orchestra.admin.filters import UsedContentTypeFilter
from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.core import services
from .actions import update_orders
from .actions import update_orders, view_help
from .models import Plan, ContractedPlan, Rate, Service
@ -52,7 +52,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
)
inlines = [RateInline]
actions = [update_orders]
change_view_actions = actions
change_view_actions = actions + [view_help]
def formfield_for_dbfield(self, db_field, **kwargs):
""" Improve performance of account field and filter by account """

View file

@ -4,12 +4,11 @@ import decimal
from dateutil import relativedelta
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins
from orchestra.utils.python import AttributeDict
from orchestra.utils.python import AttrDict
from . import settings, helpers
@ -21,6 +20,8 @@ class ServiceHandler(plugins.Plugin):
Relax and enjoy the journey.
"""
_VOLUME = 'VOLUME'
_COMPENSATION = 'COMPENSATION'
model = None
@ -160,7 +161,7 @@ class ServiceHandler(plugins.Plugin):
return None
def generate_discount(self, line, dtype, price):
line.discounts.append(AttributeDict(**{
line.discounts.append(AttrDict(**{
'type': dtype,
'total': price,
}))
@ -182,7 +183,7 @@ class ServiceHandler(plugins.Plugin):
if not computed:
price = price * size
subtotal = self.nominal_price * size * metric
line = AttributeDict(**{
line = AttrDict(**{
'order': order,
'subtotal': subtotal,
'ini': ini,
@ -197,7 +198,7 @@ class ServiceHandler(plugins.Plugin):
discounted += dprice
subtotal += discounted
if subtotal > price:
self.generate_discount(line, 'volume', price-subtotal)
self.generate_discount(line, self._VOLUME, price-subtotal)
return line
def assign_compensations(self, givers, receivers, **options):
@ -225,7 +226,6 @@ class ServiceHandler(plugins.Plugin):
def apply_compensations(self, order, only_beyond=False):
dsize = 0
discounts = ()
ini = order.billed_until or order.registered_on
end = order.new_billed_until
beyond = end
@ -296,7 +296,7 @@ class ServiceHandler(plugins.Plugin):
cprice += dsize*price
if cprice:
discounts = (
('compensation', -cprice),
(self._COMPENSATION, -cprice),
)
if new_end:
size = self.get_price_size(order.new_billed_until, new_end)
@ -323,11 +323,12 @@ class ServiceHandler(plugins.Plugin):
discounts = ()
dsize, new_end = self.apply_compensations(order)
if dsize:
discounts=(('compensation', -dsize*price),)
discounts=(
(self._COMPENSATION, -dsize*price),
)
if new_end:
order.new_billed_until = new_end
end = new_end
size = self.get_price_size(ini, end)
line = self.generate_line(order, price, ini, end, discounts=discounts)
lines.append(line)
return lines
@ -395,7 +396,7 @@ class ServiceHandler(plugins.Plugin):
dsize, new_end = self.apply_compensations(order)
if dsize:
discounts=(
('compensation', -dsize*price),
(self._COMPENSATION, -dsize*price),
)
if new_end:
order.new_billed_until = new_end

View file

@ -1,24 +1,18 @@
import decimal
import sys
from django.db import models
from django.db.models import F, Q
from django.db.models import Q
from django.db.models.loading import get_model
from django.db.models.signals import pre_delete, post_delete, post_save
from django.dispatch import receiver
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from orchestra.core import caches, services, accounts
from orchestra.models import queryset
from orchestra.utils.apps import autodiscover
from orchestra.utils.python import import_class
from . import helpers, settings, rating
from . import settings, rating
from .handlers import ServiceHandler
@ -329,7 +323,7 @@ class Service(models.Model):
def update_orders(self):
order_model = get_model(settings.SERVICES_ORDER_MODEL)
related_model = self.content_type.model_class()
for instance in related_model.objects.all():
for instance in related_model.objects.all().select_related('account__user'):
order_model.update_orders(instance, service=self)

View file

@ -1,6 +1,6 @@
import sys
from orchestra.utils.python import AttributeDict
from orchestra.utils.python import AttrDict
def _compute(rates, metric):
@ -30,7 +30,7 @@ def _compute(rates, metric):
quantity = metric - accumulated
end = True
price = rates[ix].price
steps.append(AttributeDict(**{
steps.append(AttrDict(**{
'quantity': quantity,
'price': price,
'barrier': barrier,
@ -109,7 +109,7 @@ def step_price(rates, metric):
if result and result[-1].price == price:
result[-1].quantity += quantity
else:
result.append(AttributeDict(quantity=quantity, price=price))
result.append(AttrDict(quantity=quantity, price=price))
ix = 0
targets = []
else:
@ -139,7 +139,7 @@ def match_price(rates, metric):
candidates.append(prev)
candidates.sort(key=lambda r: r.price)
if candidates:
return [AttributeDict(**{
return [AttrDict(**{
'quantity': metric,
'price': candidates[0].price,
})]

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View file

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1052.3622"
height="744.09448"
id="svg2"
version="1.1"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="services.svg"
inkscape:export-filename="/home/glic3rinu/orchestra/django-orchestra/orchestra/apps/services/static/services/img/services.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.70710678"
inkscape:cx="559.86324"
inkscape:cy="278.12745"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1024"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-308.2677)">
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:FreeMono Bold"
x="132.85733"
y="526.45612"
id="text2985"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan2987"
x="132.85733"
y="526.45612">Orders</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="133.94112"
y="853.0473"
id="text2989"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan2991"
x="133.94112"
y="853.0473"
style="font-size:22px;text-align:center;line-height:94.99999880999999391%;text-anchor:middle">Metric</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="294.6925"
y="431.67795"
id="text2993"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan2995"
x="294.6925"
y="431.67795">Periodic</tspan><tspan
sodipodi:role="line"
x="294.6925"
y="453.67804"
id="tspan2997">billing</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="294.77344"
y="597.10419"
id="text2999"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3001"
x="294.77344"
y="597.10419">One-time</tspan><tspan
sodipodi:role="line"
x="294.77344"
y="619.10431"
id="tspan3003">service</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.67383"
y="472.50183"
id="text3005"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3007"
x="488.67383"
y="472.50183">Pricing</tspan><tspan
sodipodi:role="line"
x="488.67383"
y="494.50192"
id="tspan3009">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.91711"
y="390.854"
id="text3011"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3013"
x="488.91711"
y="390.854">No pricing</tspan><tspan
sodipodi:role="line"
x="488.91711"
y="412.8541"
id="tspan3015">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="294.6925"
y="758.26892"
id="text2993-8"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan2995-2"
x="294.6925"
y="758.26892">Periodic</tspan><tspan
sodipodi:role="line"
x="294.6925"
y="780.26904"
id="tspan2997-4">billing</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="294.77344"
y="923.69543"
id="text2999-4"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3001-2"
x="294.77344"
y="923.69543">One-time</tspan><tspan
sodipodi:role="line"
x="294.77344"
y="945.69556"
id="tspan3003-9">service</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.67383"
y="554.14972"
id="text3005-7"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3007-9"
x="488.67383"
y="554.14972">Pricing</tspan><tspan
sodipodi:role="line"
x="488.67383"
y="576.14984"
id="tspan3009-3">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.91711"
y="635.79749"
id="text3011-6"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3013-2"
x="488.91711"
y="635.79749">No pricing</tspan><tspan
sodipodi:role="line"
x="488.91711"
y="657.79761"
id="tspan3015-0">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.67383"
y="799.09296"
id="text3005-6"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3007-8"
x="488.67383"
y="799.09296">Pricing</tspan><tspan
sodipodi:role="line"
x="488.67383"
y="821.09308"
id="tspan3009-2">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.91711"
y="717.44519"
id="text3011-1"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3013-6"
x="488.91711"
y="717.44519">No pricing</tspan><tspan
sodipodi:role="line"
x="488.91711"
y="739.44531"
id="tspan3015-3">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.67383"
y="962.38898"
id="text3005-1"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3007-3"
x="488.67383"
y="962.38898">Pricing</tspan><tspan
sodipodi:role="line"
x="488.67383"
y="984.3891"
id="tspan3009-7">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans Bold"
x="488.91711"
y="880.74097"
id="text3011-64"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3013-1"
x="488.91711"
y="880.74097">No pricing</tspan><tspan
sodipodi:role="line"
x="488.91711"
y="902.74109"
id="tspan3015-08">period</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="583.38361"
y="379.86563"
id="text3898"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3900"
x="583.38361"
y="379.86563"
style="font-weight:bold;font-size:22px">Mail accounts</tspan><tspan
sodipodi:role="line"
x="583.38361"
y="401.86572"
id="tspan3912">Concurrent (changes)</tspan><tspan
sodipodi:role="line"
x="583.38361"
y="423.86581"
id="tspan3902">Compensate on <tspan
style="font-weight:bold;line-height:94.99999880999999391%;-inkscape-font-specification:FreeMono Bold;font-size:22px"
id="tspan3904">prepay</tspan></tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="586.6123"
y="461.51324"
id="text3906"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3908"
x="586.6123"
y="461.51324"
style="font-weight:bold;font-size:22px">Domains</tspan><tspan
sodipodi:role="line"
x="586.6123"
y="483.51334"
id="tspan3914">Register or renew events</tspan><tspan
sodipodi:role="line"
x="586.6123"
y="505.51343"
id="tspan3910">Compensate on <tspan
style="font-weight:bold;line-height:94.99999880999999391%;font-size:22px"
id="tspan3993">prepay</tspan></tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="590.04663"
y="554.09149"
id="text3916"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3918"
x="590.04663"
y="554.09149"
style="font-weight:bold;font-size:22px">Plans</tspan><tspan
sodipodi:role="line"
x="590.04663"
y="576.09161"
id="tspan3920">Always one order</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="590.58252"
y="635.97125"
id="text3922"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
x="590.58252"
y="635.97125"
id="tspan3926"
style="font-weight:bold;font-size:22px">CMS installation</tspan><tspan
sodipodi:role="line"
x="590.58252"
y="657.97137"
id="tspan3930">Register or renew events</tspan><tspan
sodipodi:role="line"
x="590.58252"
y="679.97144"
id="tspan3932" /></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="591.32349"
y="777.26685"
id="text3934"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3936"
x="591.32349"
y="777.26685"
style="font-weight:bold;font-size:22px">Traffic consumption</tspan><tspan
sodipodi:role="line"
x="591.32349"
y="799.26697"
id="tspan3938">Metric period lookup</tspan><tspan
sodipodi:role="line"
x="591.32349"
y="821.26703"
id="tspan3940">Prepay and != billing_period</tspan><tspan
sodipodi:role="line"
x="591.32349"
y="843.26715"
id="tspan3995"
style="font-style:italic;-inkscape-font-specification:FreeMono Italic;font-size:22px"> NotImplemented</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="591.32349"
y="717.61884"
id="text3942"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3944"
x="591.32349"
y="717.61884"
style="font-weight:bold;font-size:22px">Mailbox size</tspan><tspan
sodipodi:role="line"
x="591.32349"
y="739.61896"
id="tspan3946">Concurrent (changes)</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="590.1192"
y="882.65179"
id="text3942-8"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3944-2"
x="590.1192"
y="882.65179"
style="font-weight:bold;font-size:22px">Jobs</tspan><tspan
sodipodi:role="line"
x="590.1192"
y="904.65192"
id="tspan3946-6">Last known metric</tspan></text>
<text
xml:space="preserve"
style="font-size:22px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:94.99999880999999391%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeMono;-inkscape-font-specification:Sans"
x="591.06866"
y="973.33081"
id="text3972"
sodipodi:linespacing="94.999999%"><tspan
sodipodi:role="line"
id="tspan3974"
x="591.06866"
y="973.33081"
style="font-style:italic;-inkscape-font-specification:FreeMono Italic;font-size:22px">NotImplement</tspan></text>
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 228.73934,436.50836 -23.61913,0 0,164.75267 23.53877,0"
id="path4013"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 205.55487,521.14184 -23.98356,0"
id="path4019"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 228.73934,764.42561 -23.61913,0 0,164.7526 23.53877,0"
id="path4013-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 205.55487,849.05914 -23.98356,0"
id="path4019-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 407.81779,398.68928 -23.61908,0 0,80.25672 23.5387,0"
id="path4013-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 384.6333,438.12728 -23.98362,0"
id="path4019-18"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 407.81779,561.72165 -23.61908,0 0,80.25664 23.5387,0"
id="path4013-6-63"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 384.6333,601.15965 -23.98362,0"
id="path4019-18-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 407.81779,726.60658 -23.61908,0 0,80.25672 23.5387,0"
id="path4013-6-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 384.6333,766.04458 -23.98362,0"
id="path4019-18-3"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 407.81779,889.63886 -23.61908,0 0,80.25667 23.5387,0"
id="path4013-6-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.92632013999999996px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 384.6333,929.07689 -23.98362,0"
id="path4019-18-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,14 @@
{% extends "admin/orchestra/generic_confirmation.html" %}
{% load i18n l10n %}
{% load url from future %}
{% load admin_urls static utils %}
{% block content %}
<div>
<div style="margin:20px;">
Enjoy my friend.
<img src="{% static "services/img/services.png" %}"</img>
</div>
</div>
{% endblock %}

View file

@ -41,7 +41,7 @@ class DomainBillingTest(BaseBillingTest):
return account.miscellaneous.create(service=domain_service, description=domain_name)
def test_domain(self):
service = self.create_domain_service()
self.create_domain_service()
account = self.create_account()
self.create_domain(account=account)
bills = account.orders.bill()
@ -69,7 +69,7 @@ class DomainBillingTest(BaseBillingTest):
self.assertEqual(56, bills[0].get_total())
def test_domain_proforma(self):
service = self.create_domain_service()
self.create_domain_service()
account = self.create_account()
self.create_domain(account=account)
bills = account.orders.bill(proforma=True, new_open=True)
@ -97,7 +97,7 @@ class DomainBillingTest(BaseBillingTest):
self.assertEqual(56, bills[0].get_total())
def test_domain_cumulative(self):
service = self.create_domain_service()
self.create_domain_service()
account = self.create_account()
self.create_domain(account=account)
bills = account.orders.bill(proforma=True)
@ -110,7 +110,7 @@ class DomainBillingTest(BaseBillingTest):
self.assertEqual(30, bills[0].get_total())
def test_domain_new_open(self):
service = self.create_domain_service()
self.create_domain_service()
account = self.create_account()
self.create_domain(account=account)
bills = account.orders.bill(new_open=True)

View file

@ -43,14 +43,14 @@ class FTPBillingTest(BaseBillingTest):
def test_ftp_account_1_year_fiexed(self):
service = self.create_ftp_service()
user = self.create_ftp()
self.create_ftp()
bp = timezone.now().date() + relativedelta(years=1)
bills = service.orders.bill(billing_point=bp, fixed_point=True)
self.assertEqual(10, bills[0].get_total())
def test_ftp_account_2_year_fiexed(self):
service = self.create_ftp_service()
user = self.create_ftp()
self.create_ftp()
bp = timezone.now().date() + relativedelta(years=2)
bills = service.orders.bill(billing_point=bp, fixed_point=True)
self.assertEqual(20, bills[0].get_total())
@ -79,7 +79,7 @@ class FTPBillingTest(BaseBillingTest):
def test_ftp_account_with_compensation(self):
account = self.create_account()
service = self.create_ftp_service()
self.create_ftp_service()
user = self.create_ftp(account=account)
first_bp = timezone.now().date() + relativedelta(years=2)
bills = account.orders.bill(billing_point=first_bp, fixed_point=True)

View file

@ -39,13 +39,13 @@ class JobBillingTest(BaseBillingTest):
return account.miscellaneous.create(service=service, description=description, amount=amount)
def test_job(self):
service = self.create_job_service()
self.create_job_service()
account = self.create_account()
job = self.create_job(5, account=account)
self.create_job(5, account=account)
bill = account.orders.bill()[0]
self.assertEqual(5*20, bill.get_total())
job = self.create_job(100, account=account)
self.create_job(100, account=account)
bill = account.orders.bill(new_open=True)[0]
self.assertEqual(100*15, bill.get_total())

View file

@ -4,7 +4,7 @@ from django.utils import timezone
from freezegun import freeze_time
from orchestra.apps.mails.models import Mailbox
from orchestra.apps.resources.models import Resource, ResourceData, MonitorData
from orchestra.apps.resources.models import Resource, ResourceData
from orchestra.utils.tests import random_ascii
from ...models import Service, Plan
@ -62,7 +62,7 @@ class MailboxBillingTest(BaseBillingTest):
verbose_name='Mailbox disk',
unit='GB',
scale=10**9,
ondemand=False,
on_demand=False,
monitors='MaildirDisk',
)
return self.resource

View file

@ -42,7 +42,7 @@ class PlanBillingTest(BaseBillingTest):
def test_plan(self):
account = self.create_account()
service = self.create_plan_service()
self.create_plan_service()
self.create_plan(account=account)
bill = account.orders.bill().pop()
self.assertEqual(bill.FEE, bill.type)

View file

@ -1,5 +1,3 @@
import datetime
from dateutil.relativedelta import relativedelta
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
@ -46,26 +44,23 @@ class BaseTrafficBillingTest(BaseBillingTest):
verbose_name='Account Traffic',
unit='GB',
scale=10**9,
ondemand=True,
on_demand=True,
monitors='FTPTraffic',
)
return self.resource
def report_traffic(self, account, value):
ct = ContentType.objects.get_for_model(Account)
object_id = account.pk
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.user,
value=value, date=timezone.now())
MonitorData.objects.create(monitor='FTPTraffic', content_object=account.user, value=value)
data = ResourceData.get_or_create(account, self.resource)
data.update()
class TrafficBillingTest(BaseTrafficBillingTest):
def test_traffic(self):
service = self.create_traffic_service()
resource = self.create_traffic_resource()
self.create_traffic_service()
self.create_traffic_resource()
account = self.create_account()
now = timezone.now().date()
now = timezone.now()
self.report_traffic(account, 10**9)
bill = account.orders.bill(commit=False)[0]
@ -82,8 +77,8 @@ class TrafficBillingTest(BaseTrafficBillingTest):
self.assertEqual((90-10)*10, bill.get_total())
def test_multiple_traffics(self):
service = self.create_traffic_service()
resource = self.create_traffic_resource()
self.create_traffic_service()
self.create_traffic_resource()
account1 = self.create_account()
account2 = self.create_account()
self.report_traffic(account1, 10**10)
@ -129,13 +124,13 @@ class TrafficPrepayBillingTest(BaseTrafficBillingTest):
return account.miscellaneous.create(service=service, description=name, amount=amount)
def test_traffic_prepay(self):
service = self.create_traffic_service()
prepay_service = self.create_prepay_service()
self.create_traffic_service()
self.create_prepay_service()
account = self.create_account()
self.create_traffic_resource()
now = timezone.now()
prepay = self.create_prepay(10, account=account)
self.create_prepay(10, account=account)
bill = account.orders.bill(proforma=True)[0]
self.assertEqual(10*50, bill.get_total())

View file

@ -1,17 +1,15 @@
import datetime
import decimal
import sys
from dateutil import relativedelta
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from orchestra.apps.accounts.models import Account
from orchestra.apps.users.models import User
from orchestra.utils.tests import BaseTestCase, random_ascii
from orchestra.utils.tests import BaseTestCase
from .. import settings, helpers
from ..models import Service, Plan, Rate
from .. import helpers
from ..models import Service, Plan
class Order(object):

View file

@ -55,7 +55,6 @@ class UserAdmin(AccountAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
def get_urls(self):
""" Returns the additional urls for the change view links """
urls = super(UserAdmin, self).get_urls()
admin_site = self.admin_site
opts = self.model._meta
new_urls = patterns("")
for role in self.roles:

View file

@ -1,8 +1,5 @@
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from orchestra.api import router, SetPasswordApiMixin
from orchestra.apps.accounts.api import AccountApiMixin

View file

@ -1,5 +1,6 @@
from django.contrib.auth import models as auth
from django.core import validators
from django.core.mail import send_mail
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

View file

@ -1,5 +1,3 @@
from django.db import models
from ..models import User

View file

@ -1,7 +1,6 @@
from django.contrib import messages
from django.contrib.admin.util import unquote, get_deleted_objects
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.core.urlresolvers import reverse
from django.db import router
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse

View file

@ -1,6 +1,4 @@
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import insertattr
from orchestra.apps.users.roles.admin import RoleAdmin

View file

@ -1,15 +1,12 @@
from django import forms
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import insertattr, admin_link
from orchestra.apps.accounts.admin import SelectAccountAdminMixin
from orchestra.apps.domains.forms import DomainIterator
from orchestra.apps.users.roles.admin import RoleAdmin
from .forms import MailRoleAdminForm

View file

@ -7,6 +7,7 @@ from orchestra.apps.orchestration import ServiceController
from orchestra.apps.resources import ServiceMonitor
from . import settings
from .models import Address
class MailSystemUserBackend(ServiceController):
@ -152,7 +153,7 @@ class MaildirDisk(ServiceMonitor):
)
def get_context(self, mailbox):
context = MailSystemUserBackend().get_context(site)
context = MailSystemUserBackend().get_context(mailbox)
context['home'] = settings.EMAILS_HOME % context
context['maildir_path'] = os.path.join(context['home'], 'Maildir/maildirsize')
context['object_id'] = mailbox.pk

View file

@ -1,7 +1,3 @@
import re
from django.contrib.auth.hashers import check_password, make_password
from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import ugettext_lazy as _

View file

@ -44,7 +44,6 @@ def validate_forward(value):
def validate_sieve(value):
from .models import Mailbox
sieve_name = '%s.sieve' % hashlib.md5(value).hexdigest()
path = os.path.join(settings.EMAILS_SIEVETEST_PATH, sieve_name)
with open(path, 'wb') as f:

View file

@ -1,6 +1,4 @@
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import insertattr
from orchestra.apps.users.roles.admin import RoleAdmin

View file

@ -1,7 +1,6 @@
from django.conf.urls import patterns
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin

View file

@ -1,5 +1,3 @@
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.resources import ServiceMonitor

View file

@ -1,5 +1,5 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm, ReadOnlyPasswordHashField
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.utils.translation import ugettext_lazy as _

View file

@ -1,6 +1,5 @@
from django import forms
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin

View file

@ -1,5 +1,4 @@
from rest_framework import viewsets
from rest_framework.response import Response
from orchestra.api import router
from orchestra.apps.accounts.api import AccountApiMixin

View file

@ -1,3 +1,5 @@
import os
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
@ -21,7 +23,7 @@ class DokuWikiMuBackend(WebAppServiceMixin, ServiceController):
self.append("rm -fr %(app_path)s" % context)
def get_context(self, webapp):
context = super(DokuwikiMuBackend, self).get_context(webapp)
context = super(DokuWikiMuBackend, self).get_context(webapp)
context.update({
'template': settings.WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH,
'app_path': os.path.join(settings.WEBAPPS_DOKUWIKIMU_FARM_PATH, webapp.name)

View file

@ -1,5 +1,4 @@
import re
import sys
import requests
from django.utils.translation import ugettext_lazy as _
@ -55,6 +54,7 @@ class WordpressMuBackend(WebAppServiceMixin, ServiceController):
'blog[email]': email,
'_wpnonce_add-blog': wpnonce,
}
# TODO validate response
response = session.post(url, data=data)
def delete_blog(self, webapp, server):

View file

@ -1,13 +1,10 @@
from django import forms
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_link, change_url
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
from orchestra.apps.accounts.widgets import account_related_field_widget_factory
from .models import Content, Website, WebsiteOption

View file

@ -1,5 +1,4 @@
from rest_framework import viewsets
from rest_framework.response import Response
from orchestra.api import router
from orchestra.apps.accounts.api import AccountApiMixin

View file

@ -1,5 +1,6 @@
import textwrap
import os
import re
from django.template import Template, Context
from django.utils import timezone
@ -123,6 +124,7 @@ class Apache2Backend(ServiceController):
def get_protections(self, site):
protections = ""
__, regex = settings.WEBSITES_OPTIONS['directory_protection']
context = self.get_context(site)
for protection in site.options.filter(name='directory_protection'):
path, name, passwd = re.match(regex, protection.value).groups()
path = os.path.join(context['root'], path)

Some files were not shown because too many files have changed in this diff Show more