Added support for order ignoring

This commit is contained in:
Marc 2014-10-16 17:14:21 +00:00
parent 0417d18961
commit 0dcdb4ba79
9 changed files with 110 additions and 78 deletions

10
TODO.md
View file

@ -42,8 +42,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* passlib; nano /usr/local/lib/python2.7/dist-packages/passlib/ext/django/utils.py SortedDict -> collections.OrderedDict
* pip install pyinotify
* create custom field that returns backend python objects
* Timezone awareness on monitoring system (reading server-side logs with different TZ than orchestra) maybe a settings value? (use UTC internally, timezone.localtime() when interacting with servers)
* EMAIL backend operations which contain stderr messages (because under certain failures status code is still 0)
@ -90,8 +88,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* mail backend related_models = ('resources__content_type') ??
* ignore orders (mark orders as ignored), ignore orchestra related orders by default (or do not generate them on the first place) ignore superuser orders?
* Domain backend PowerDNS Bind validation support?
* Maildir billing tests/ webdisk billing tests (avg metric)
@ -121,12 +117,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* DOC: Complitely decouples scripts execution, billing, service definition
* Create SystemUser on account creation. username=username, is_main=True,
* Exclude is_main=True from queryset filter default is_main=False
* self referencing group.
* Unify all users
* delete main user -> delete account or prevent delete main user
* https://blog.flameeyes.eu/2011/01/mostly-unknown-openssh-tricks

View file

@ -2,7 +2,7 @@ import sys
from django.contrib import messages
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.admin.decorators import action_with_confirmation
@ -99,7 +99,11 @@ def mark_as_unread(modeladmin, request, queryset):
for ticket in queryset:
ticket.mark_as_unread_by(request.user)
modeladmin.log_change(request, ticket, 'Marked as unread')
msg = _("%s selected tickets have been marked as unread.") % queryset.count()
num = len(queryset)
msg = ungettext(
_("Selected ticket has been marked as unread."),
_("%i selected tickets have been marked as unread.") % num,
num)
modeladmin.message_user(request, msg)
@ -109,7 +113,11 @@ def mark_as_read(modeladmin, request, queryset):
for ticket in queryset:
ticket.mark_as_read_by(request.user)
modeladmin.log_change(request, ticket, 'Marked as read')
msg = _("%s selected tickets have been marked as read.") % queryset.count()
num = len(queryset)
msg = ungettext(
_("Selected ticket has been marked as read."),
_("%i selected tickets have been marked as read.") % num,
num)
modeladmin.message_user(request, msg)

View file

@ -107,3 +107,32 @@ class BillSelectedOrders(object):
'bills': bills,
})
return render(request, self.template, self.context)
@transaction.atomic
def mark_as_ignored(modeladmin, request, queryset):
""" Mark orders as ignored """
for order in queryset:
order.mark_as_ignored()
modeladmin.log_change(request, order, 'Marked as ignored')
num = len(queryset)
msg = ungettext(
_("Selected order has been marked as ignored."),
_("%i selected orders have been marked as ignored.") % num,
num)
modeladmin.message_user(request, msg)
@transaction.atomic
def mark_as_not_ignored(modeladmin, request, queryset):
""" Mark orders as ignored """
for order in queryset:
order.mark_as_not_ignored()
modeladmin.log_change(request, order, 'Marked as not ignored')
num = len(queryset)
msg = ungettext(
_("Selected order has been marked as not ignored."),
_("%i selected orders have been marked as not ignored.") % num,
num)
modeladmin.message_user(request, msg)

View file

@ -3,23 +3,27 @@ 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
from .actions import BillSelectedOrders
from .filters import ActiveOrderListFilter, BilledOrderListFilter
from .actions import BillSelectedOrders, mark_as_ignored, mark_as_not_ignored
from .filters import IgnoreOrderListFilter, ActiveOrderListFilter, BilledOrderListFilter
from .models import Order, MetricStorage
class OrderAdmin(AccountAdminMixin, admin.ModelAdmin):
class OrderAdmin(ChangeListDefaultFilter, AccountAdminMixin, admin.ModelAdmin):
list_display = (
'id', 'service', 'account_link', 'content_object_link',
'display_registered_on', 'display_billed_until', 'display_cancelled_on'
)
list_display_links = ('id', 'service')
list_filter = (ActiveOrderListFilter, BilledOrderListFilter, 'service',)
actions = (BillSelectedOrders(),)
list_filter = (ActiveOrderListFilter, BilledOrderListFilter, IgnoreOrderListFilter, 'service',)
default_changelist_filters = (
('ignore', '0'),
)
actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored)
date_hierarchy = 'registered_on'
content_object_link = admin_link('content_object', order=False)

View file

@ -1,6 +1,7 @@
from django.contrib.admin import SimpleListFilter
from django.db.models import Q
from django.utils import timezone
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
@ -47,3 +48,40 @@ class BilledOrderListFilter(SimpleListFilter):
Q(billed_until__lt=timezone.now())
)
return queryset
class IgnoreOrderListFilter(SimpleListFilter):
""" Filter Nodes by group according to request.user """
title = _("Ignore")
parameter_name = 'ignore'
def lookups(self, request, model_admin):
return (
('0', _("Not ignored")),
('1', _("Ignored")),
('2', _("All")),
)
def queryset(self, request, queryset):
if self.value() == '0':
return queryset.filter(ignore=False)
elif self.value() == '1':
return queryset.filter(ignore=True)
return queryset
def choices(self, cl):
""" Enable default selection different than All """
for lookup, title in self.lookup_choices:
title = title._proxy____args[0]
selected = self.value() == force_text(lookup)
if not selected and title == "Not ignored" and self.value() is None:
selected = True
# end of workaround
yield {
'selected': selected,
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}

View file

@ -77,7 +77,7 @@ class OrderQuerySet(models.QuerySet):
Q(Q(billed_until__isnull=True) | Q(billed_until__lt=end))
)
ids = self.values_list('id', flat=True)
return self.model.objects.filter(qs).exclude(id__in=ids)
return self.model.objects.filter(qs).exclude(id__in=ids, ignore=True)
def pricing_orders(self, ini, end):
return self.filter(billed_until__isnull=False, billed_until__gt=ini,
@ -136,8 +136,12 @@ class Order(models.Model):
if account_id is None:
# New account workaround -> user.account_id == None
continue
order = cls.objects.create(content_object=instance,
service=service, account_id=account_id)
ignore = False
account = getattr(instance, 'account', instance)
if account.is_superuser:
ignore = service.ignore_superusers
order = cls.objects.create(content_object=instance, service=service,
account_id=account_id, ignore=ignore)
logger.info("CREATED new order id: {id}".format(id=order.id))
else:
order = orders.get()
@ -170,6 +174,14 @@ class Order(models.Model):
self.save(update_fields=['cancelled_on'])
logger.info("CANCELLED order id: {id}".format(id=self.id))
def mark_as_ignored(self):
self.ignore = True
self.save(update_fields=['ignore'])
def mark_as_not_ignored(self):
self.ignore = False
self.save(update_fields=['ignore'])
def get_metric(self, *args, **kwargs):
if kwargs.pop('changes', False):
ini, end = args

View file

@ -38,7 +38,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
(None, {
'classes': ('wide',),
'fields': ('description', 'content_type', 'match', 'handler_type',
'is_active')
'ignore_superusers', 'is_active')
}),
(_("Billing options"), {
'classes': ('wide',),

View file

@ -109,6 +109,8 @@ class Service(models.Model):
"here allow to."),
choices=ServiceHandler.get_plugin_choices())
is_active = models.BooleanField(_("active"), default=True)
ignore_superusers = models.BooleanField(_("ignore superusers"), default=True,
help_text=_("Designates whether superuser orders are marked as ignored by default or not"))
# Billing
billing_period = models.CharField(_("billing period"), max_length=16,
help_text=_("Renewal period for recurring invoicing"),
@ -126,14 +128,6 @@ class Service(models.Model):
(FIXED_DATE, _("Fixed billing date")),
),
default=FIXED_DATE)
# delayed_billing = models.CharField(_("delayed billing"), max_length=16,
# help_text=_("Period in which this service will be ignored for billing"),
# choices=(
# (NEVER, _("No delay (inmediate billing)")),
# (TEN_DAYS, _("Ten days")),
# (ONE_MONTH, _("One month")),
# ),
# default=ONE_MONTH, blank=True)
is_fee = models.BooleanField(_("fee"), default=False,
help_text=_("Designates whether this service should be billed as "
" membership fee or not"))
@ -161,14 +155,6 @@ class Service(models.Model):
(MATCH_PRICE, _("Match price")),
),
default=STEP_PRICE)
# orders_effect = models.CharField(_("orders effect"), max_length=16,
# help_text=_("Defines the lookup behaviour when using orders for "
# "the pricing rate computation of this service."),
# choices=(
# (REGISTER_OR_RENEW, _("Register or renew events")),
# (CONCURRENT, _("Active at every given time")),
# ),
# default=CONCURRENT)
on_cancel = models.CharField(_("on cancel"), max_length=16,
help_text=_("Defines the cancellation behaviour of this service"),
choices=(
@ -178,24 +164,6 @@ class Service(models.Model):
(REFUND, _("Refund")),
),
default=DISCOUNT)
# on_broken_period = models.CharField(_("on broken period", max_length=16,
# help_text=_("Defines the billing behaviour when periods are incomplete on register and on cancel"),
# choices=(
# (NOTHING, _("Nothing, period is atomic")),
# (DISCOUNT, _("Bill partially")),
# (COMPENSATE, _("Compensate on cancel")),
# (REFUND, _("Refund on cancel")),
# ),
# default=DISCOUNT)
# granularity = models.CharField(_("granularity"), max_length=16,
# help_text=_("Defines the minimum size a period can be broken into"),
# choices=(
# (DAILY, _("One day")),
# (MONTHLY, _("One month")),
# (ANUAL, _("One year")),
# ),
# default=DAILY,
# )
payment_style = models.CharField(_("payment style"), max_length=16,
help_text=_("Designates whether this service should be paid after "
"consumtion (postpay/on demand) or prepaid"),
@ -204,24 +172,6 @@ class Service(models.Model):
(POSTPAY, _("Postpay (on demand)")),
),
default=PREPAY)
# trial_period = models.CharField(_("trial period"), max_length=16, blank=True,
# help_text=_("Period in which no charge will be issued"),
# choices=(
# (NEVER, _("No trial")),
# (TEN_DAYS, _("Ten days")),
# (ONE_MONTH, _("One month")),
# ),
# default=NEVER)
# refund_period = models.CharField(_("refund period"), max_length=16,
# help_text=_("Period in which automatic refund will be performed on "
# "service cancellation"),
# choices=(
# (NEVER, _("Never refund")),
# (TEN_DAYS, _("Ten days")),
# (ONE_MONTH, _("One month")),
# (ALWAYS, _("Always refund")),
# ),
# default=NEVER, blank=True)
def __unicode__(self):
return self.description

View file

@ -180,10 +180,6 @@ function install_requirements () {
update-locale LANG=en_US.UTF-8
fi
run apt-get update
run apt-get install -y $APT
run pip install $PIP
# Install ca certificates
if [[ ! -e /usr/local/share/ca-certificates/cacert.org ]]; then
mkdir -p /usr/local/share/ca-certificates/cacert.org
@ -192,6 +188,11 @@ function install_requirements () {
http://www.cacert.org/certs/class3.crt
update-ca-certificates
fi
run apt-get update
run apt-get install -y $APT
run pip install $PIP
# Some versions of rabbitmq-server will not start automatically by default unless ...
sed -i "s/# Default-Start:.*/# Default-Start: 2 3 4 5/" /etc/init.d/rabbitmq-server
sed -i "s/# Default-Stop:.*/# Default-Stop: 0 1 6/" /etc/init.d/rabbitmq-server