diff --git a/TODO.md b/TODO.md index 9ef5e175..5e39f70b 100644 --- a/TODO.md +++ b/TODO.md @@ -154,3 +154,7 @@ textwrap.dedent( \\) * parmiko write to a channel instead of transfering files? http://sysadmin.circularvale.com/programming/paramiko-channel-hangs/ + +* strip leading and trailing whitre spaces of most input fields +* Examples of service.match and service.metric + diff --git a/orchestra/admin/decorators.py b/orchestra/admin/decorators.py index 029884a7..5531c495 100644 --- a/orchestra/admin/decorators.py +++ b/orchestra/admin/decorators.py @@ -16,6 +16,7 @@ def admin_field(method): kwargs['field'] = args[0] if args else '' kwargs['order'] = kwargs.get('order', kwargs['field']) kwargs['popup'] = kwargs.get('popup', False) + # TODO get field verbose name kwargs['short_description'] = kwargs.get('short_description', kwargs['field'].split('__')[-1].replace('_', ' ').capitalize()) admin_method = partial(method, **kwargs) diff --git a/orchestra/apps/accounts/forms.py b/orchestra/apps/accounts/forms.py index 076d68eb..c9d19a8f 100644 --- a/orchestra/apps/accounts/forms.py +++ b/orchestra/apps/accounts/forms.py @@ -13,7 +13,7 @@ def create_account_creation_form(): for model, key, kwargs, help_text in settings.ACCOUNTS_CREATE_RELATED: model = get_model(model) field_name = 'create_%s' % model._meta.model_name - label = _("Create related %s") % model._meta.verbose_name + label = _("Create %s") % model._meta.verbose_name fields[field_name] = forms.BooleanField(initial=True, required=False, label=label, help_text=help_text) diff --git a/orchestra/apps/bills/actions.py b/orchestra/apps/bills/actions.py index 0c1bebeb..11371e12 100644 --- a/orchestra/apps/bills/actions.py +++ b/orchestra/apps/bills/actions.py @@ -4,6 +4,7 @@ import zipfile from django.contrib import messages from django.contrib.admin import helpers from django.core.urlresolvers import reverse +from django.db import transaction from django.http import HttpResponse from django.shortcuts import render from django.utils.safestring import mark_safe @@ -45,6 +46,7 @@ view_bill.verbose_name = _("View") view_bill.url_name = 'view' +@transaction.atomic def close_bills(modeladmin, request, queryset): queryset = queryset.filter(is_open=True) if not queryset: diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py index 3b39c66f..88821a98 100644 --- a/orchestra/apps/orders/models.py +++ b/orchestra/apps/orders/models.py @@ -59,6 +59,7 @@ class OrderQuerySet(models.QuerySet): return self.filter(**qs) def get_related(self, **options): + """ returns related orders that could have a pricing effect """ Service = get_model(settings.ORDERS_SERVICE_MODEL) conflictive = self.filter(service__metric='') conflictive = conflictive.exclude(service__billing_period=Service.NEVER) @@ -66,6 +67,8 @@ class OrderQuerySet(models.QuerySet): qs = Q() for account_id, services in conflictive.iteritems(): for service, orders in services.iteritems(): + if not service.rates.exists(): + continue end = datetime.date.min bp = None for order in orders: @@ -107,7 +110,7 @@ class Order(models.Model): related_name='orders') registered_on = models.DateField(_("registered"), default=lambda: timezone.now()) cancelled_on = models.DateField(_("cancelled"), null=True, blank=True) - billed_on = models.DateField(_("billed on"), null=True, blank=True) + billed_on = models.DateField(_("billed"), null=True, blank=True) billed_until = models.DateField(_("billed until"), null=True, blank=True) ignore = models.BooleanField(_("ignore"), default=False) description = models.TextField(_("description"), blank=True) @@ -122,7 +125,8 @@ class Order(models.Model): return str(self.service) @classmethod - def update_orders(cls, instance, service=None): + def update_orders(cls, instance, service=None, commit=True): + updates = [] if service is None: Service = get_model(settings.ORDERS_SERVICE_MODEL) services = Service.get_services(instance) @@ -140,14 +144,23 @@ class Order(models.Model): account = getattr(instance, 'account', instance) if account.is_superuser: ignore = service.ignore_superusers - order = cls.objects.create(content_object=instance, service=service, + order = cls(content_object=instance, service=service, account_id=account_id, ignore=ignore) + if commit: + order.save() + updates.append((order, 'created')) logger.info("CREATED new order id: {id}".format(id=order.id)) else: order = orders.get() - order.update() + updates.append((order, 'updated')) + if commit: + order.update() elif orders: - orders.get().cancel() + order = orders.get() + if commit: + order.cancel() + updates.append((order, 'cancelled')) + return updates @classmethod def get_bill_backend(cls): diff --git a/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html b/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html index 0fe70639..4fa34595 100644 --- a/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html +++ b/orchestra/apps/orders/templates/admin/orders/order/bill_selected_options.html @@ -41,7 +41,7 @@ {% for line in lines %} - {{ line.order.description }} + {{ line.order.description }} {% for discount in line.discounts %}
      Discount per {{ discount.type }} {% endfor %} diff --git a/orchestra/apps/services/actions.py b/orchestra/apps/services/actions.py index 33186f31..9fb27ec8 100644 --- a/orchestra/apps/services/actions.py +++ b/orchestra/apps/services/actions.py @@ -1,15 +1,50 @@ +from django.contrib.admin import helpers +from django.core.urlresolvers import reverse from django.db import transaction +from django.shortcuts import render from django.template.response import TemplateResponse +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from orchestra.admin.utils import get_object_from_url @transaction.atomic -def update_orders(modeladmin, request, queryset): +def update_orders(modeladmin, request, queryset, extra_context=None): + if not queryset: + return + if request.POST.get('post') == 'confirmation': + num = 0 + services = [] + for service in queryset: + updates = service.update_orders() + num += len(updates) + services.append(str(service.pk)) + modeladmin.log_change(request, service, _("Orders updated")) + if num == 1: + url = reverse('admin:orders_order_change', args=(updates[0][0].pk,)) + msg = _('One related order has benn updated') % url + else: + url = reverse('admin:orders_order_changelist') + url += '?service__in=%s' % ','.join(services) + msg = _('%s related orders have been updated') % (url, num) + modeladmin.message_user(request, mark_safe(msg)) + return + updates = [] for service in queryset: - service.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) + updates += service.update_orders(commit=False) + opts = modeladmin.model._meta + context = { + 'title': _("Update orders will cause the following."), + 'action_name': 'Update orders', + 'action_value': 'update_orders', + 'updates': updates, + 'queryset': queryset, + 'opts': opts, + 'app_label': opts.app_label, + 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME, + 'obj': get_object_from_url(modeladmin, request), + } + return render(request, 'admin/services/service/update_orders.html', context) update_orders.url_name = 'update-orders' update_orders.verbose_name = _("Update orders") diff --git a/orchestra/apps/services/models.py b/orchestra/apps/services/models.py index 510166f9..aa8e3351 100644 --- a/orchestra/apps/services/models.py +++ b/orchestra/apps/services/models.py @@ -272,11 +272,13 @@ class Service(models.Model): def rate_method(self): return self.RATE_METHODS[self.rate_algorithm] - def update_orders(self): + def update_orders(self, commit=True): order_model = get_model(settings.SERVICES_ORDER_MODEL) related_model = self.content_type.model_class() + updates = [] for instance in related_model.objects.all().select_related('account'): - order_model.update_orders(instance, service=self) + updates += order_model.update_orders(instance, service=self, commit=commit) + return updates accounts.register(ContractedPlan) diff --git a/orchestra/apps/services/templates/admin/services/service/update_orders.html b/orchestra/apps/services/templates/admin/services/service/update_orders.html new file mode 100644 index 00000000..5c00cd06 --- /dev/null +++ b/orchestra/apps/services/templates/admin/services/service/update_orders.html @@ -0,0 +1,52 @@ +{% extends "admin/orchestra/generic_confirmation.html" %} +{% load i18n l10n %} +{% load url from future %} +{% load admin_urls static utils %} + + +{% block content %} +
+ +
+ +
{% csrf_token %} + {% for obj in queryset %} + + {% endfor %} + + + +
+{% endblock %} + diff --git a/orchestra/templatetags/utils.py b/orchestra/templatetags/utils.py index cda4559d..909b1497 100644 --- a/orchestra/templatetags/utils.py +++ b/orchestra/templatetags/utils.py @@ -54,5 +54,5 @@ def is_checkbox(field): @register.filter -def admin_link(obj): +def admin_url(obj): return change_url(obj)