diff --git a/orchestra/contrib/orders/admin.py b/orchestra/contrib/orders/admin.py index a0224d0a..171aa60d 100644 --- a/orchestra/contrib/orders/admin.py +++ b/orchestra/contrib/orders/admin.py @@ -75,8 +75,11 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin): def display_billed_until(self, order): billed_until = order.billed_until red = False + human = escape(naturaldate(billed_until)) if billed_until: - if order.service.payment_style == order.service.POSTPAY: + if order.service.billing_period == order.service.NEVER: + human = _("Forever") + elif order.service.payment_style == order.service.POSTPAY: boundary = order.service.handler.get_billing_point(order) if billed_until < boundary: red = True @@ -84,7 +87,7 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin): red = True color = 'style="color:red;"' if red else '' return '{human}'.format( - raw=escape(str(billed_until)), color=color, human=escape(naturaldate(billed_until)), + raw=escape(str(billed_until)), color=color, human=human, ) display_billed_until.short_description = _("billed until") display_billed_until.allow_tags = True diff --git a/orchestra/contrib/orders/filters.py b/orchestra/contrib/orders/filters.py index 1e05166d..fa768d46 100644 --- a/orchestra/contrib/orders/filters.py +++ b/orchestra/contrib/orders/filters.py @@ -1,11 +1,13 @@ from datetime import timedelta +from django.apps import apps from django.contrib.admin import SimpleListFilter from django.db.models import Q, Prefetch, F from django.utils import timezone from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ +from . import settings from .models import MetricStorage @@ -56,8 +58,13 @@ class BilledOrderListFilter(SimpleListFilter): metric_queryset = queryset.exclude(service__metric='').exclude(billed_on__isnull=True) for order in metric_queryset.prefetch_related(prefetch_valid_metrics, prefetch_billed_metric): if len(order.billed_metric) != 1: - raise ValueError("Data inconsistency #metrics %i != 1." % len(order.billed_metric)) - billed_metric = order.billed_metric[0].value + # corner case of prefetch_billed_metric: Does not always work with latests metrics + latest = order.metrics.latest() + if not latest: + raise ValueError("Data inconsistency #metrics %i != 1." % len(order.billed_metric)) + billed_metric = latest.value + else: + billed_metric = order.billed_metric[0].value for metric in order.valid_metrics: if metric.created_on <= order.billed_on: raise ValueError("This value should already be filtered on the prefetch query.") @@ -72,15 +79,18 @@ class BilledOrderListFilter(SimpleListFilter): elif self.value() == 'no': return queryset.exclude(billed_until__isnull=False, billed_until__gte=timezone.now()) elif self.value() == 'pending': + Service = apps.get_model(settings.ORDERS_SERVICE_MODEL) return queryset.filter( Q(pk__in=self.get_pending_metric_pks(queryset)) | Q( - Q(billed_until__isnull=True) | Q(billed_until__lt=timezone.now()) + Q(billed_until__isnull=True) | Q(~Q(service__billing_period=Service.NEVER) & + Q(billed_until__lt=timezone.now())) ) ) elif self.value() == 'not_pending': return queryset.exclude( Q(pk__in=self.get_pending_metric_pks(queryset)) | Q( - Q(billed_until__isnull=True) | Q(billed_until__lt=timezone.now()) + Q(billed_until__isnull=True) | Q(~Q(service__billing_period=Service.NEVER) & + Q(billed_until__lt=timezone.now())) ) ) return queryset diff --git a/orchestra/contrib/resources/actions.py b/orchestra/contrib/resources/actions.py index 8ace0996..70917489 100644 --- a/orchestra/contrib/resources/actions.py +++ b/orchestra/contrib/resources/actions.py @@ -12,7 +12,7 @@ def run_monitor(modeladmin, request, queryset): for resource in queryset: rlogs = resource.monitor() if not async: - logs = logs.union(set(map(str, rlogs))) + logs = logs.union(set([str(log.pk) for log in rlogs])) modeladmin.log_change(request, resource, _("Run monitors")) if async: num = len(queryset) diff --git a/orchestra/contrib/resources/admin.py b/orchestra/contrib/resources/admin.py index 4679b8b2..1623404b 100644 --- a/orchestra/contrib/resources/admin.py +++ b/orchestra/contrib/resources/admin.py @@ -1,3 +1,5 @@ +from urllib.parse import parse_qs + from django.conf.urls import url from django.contrib import admin, messages from django.contrib.contenttypes.admin import GenericTabularInline @@ -16,6 +18,7 @@ from orchestra.utils import db, sys from orchestra.utils.functional import cached from .actions import run_monitor +from .filters import ResourceDataListFilter from .forms import ResourceForm from .models import Resource, ResourceData, MonitorData @@ -147,25 +150,14 @@ class ResourceDataAdmin(ExtendedModelAdmin): return False def used_monitordata_view(self, request, object_id): - """ - Does the redirect on a separated view for performance reassons - (calculate this on a changelist is expensive) - """ - data = self.get_object(request, object_id) - ids = [] - for dataset in data.get_monitor_datasets(): - if isinstance(dataset, MonitorData): - ids.append(dataset.id) - else: - ids += dataset.values_list('id', flat=True) url = reverse('admin:resources_monitordata_changelist') - url += '?id__in=%s' % ','.join(map(str, ids)) + url += '?resource_data=%s' % object_id return redirect(url) class MonitorDataAdmin(ExtendedModelAdmin): list_display = ('id', 'monitor', 'display_created', 'value', 'content_object_link') - list_filter = ('monitor',) + list_filter = ('monitor', ResourceDataListFilter) add_fields = ('monitor', 'content_type', 'object_id', 'created_at', 'value') fields = ('monitor', 'content_type', 'content_object_link', 'display_created', 'value') change_readonly_fields = fields @@ -173,8 +165,23 @@ class MonitorDataAdmin(ExtendedModelAdmin): content_object_link = admin_link('content_object') display_created = admin_date('created_at', short_description=_("Created")) + def filter_used_monitordata(self, request, queryset): + query_string = parse_qs(request.META['QUERY_STRING']) + resource_data = query_string.get('resource_data') + if resource_data: + data = ResourceData.objects.get(pk=int(resource_data[0])) + ids = [] + for dataset in data.get_monitor_datasets(): + if isinstance(dataset, MonitorData): + ids.append(dataset.id) + else: + ids += dataset.values_list('id', flat=True) + return queryset.filter(id__in=ids) + return queryset + def get_queryset(self, request): queryset = super(MonitorDataAdmin, self).get_queryset(request) + queryset = self.filter_used_monitordata(request, queryset) return queryset.prefetch_related('content_object') diff --git a/orchestra/contrib/resources/filters.py b/orchestra/contrib/resources/filters.py new file mode 100644 index 00000000..91ea81a3 --- /dev/null +++ b/orchestra/contrib/resources/filters.py @@ -0,0 +1,17 @@ +from django.contrib.admin import SimpleListFilter +from django.utils.translation import ugettext_lazy as _ + + +class ResourceDataListFilter(SimpleListFilter): + """ Mock filter to avoid e=1 """ + title = _("Resource data") + parameter_name = 'resource_data' + + def lookups(self, request, model_admin): + return () + + def queryset(self, request, queryset): + return queryset + + def choices(self, cl): + return [] diff --git a/orchestra/contrib/resources/models.py b/orchestra/contrib/resources/models.py index 4d48aab7..bc4b2357 100644 --- a/orchestra/contrib/resources/models.py +++ b/orchestra/contrib/resources/models.py @@ -225,8 +225,8 @@ class ResourceData(models.Model): def monitor(self, async=False): ids = (self.object_id,) if async: - return tasks.monitor.delay(self.resource_id, ids=ids, async=async) - return tasks.monitor(self.resource_id, ids=ids, async=async) + return tasks.monitor.delay(self.resource_id, ids=ids) + return tasks.monitor(self.resource_id, ids=ids) def get_monitor_datasets(self): resource = self.resource