diff --git a/orchestra/apps/orders/helpers.py b/orchestra/apps/orders/helpers.py index 087c1c4e..f22cc5a3 100644 --- a/orchestra/apps/orders/helpers.py +++ b/orchestra/apps/orders/helpers.py @@ -3,7 +3,7 @@ import inspect from orchestra.apps.accounts.models import Account -def search_for_related(origin, max_depth=2): +def get_related_objects(origin, max_depth=2): """ Introspects origin object and return the first related service object diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py index 817835cd..369f0f9f 100644 --- a/orchestra/apps/orders/models.py +++ b/orchestra/apps/orders/models.py @@ -11,11 +11,11 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from orchestra.core import caches, services +from orchestra.models import queryset from orchestra.utils.apps import autodiscover -from . import settings +from . import settings, helpers from .handlers import ServiceHandler -from .helpers import search_for_related autodiscover('handlers') @@ -175,7 +175,7 @@ class Service(models.Model): return services # FIXME some times caching is nasty, do we really have to? make get_plugin more efficient? - @cached_property + # @cached_property def handler(self): """ Accessor of this service handler instance """ if self.handler_type: @@ -214,6 +214,19 @@ class Service(models.Model): class OrderQuerySet(models.QuerySet): + group_by = queryset.group_by + + def bill(self, **options): + for account, services in self.group_by('account_id', 'service_id'): + bill_lines = [] + for service, orders in services: + lines = helpers.create_bill_lines(service, orders, **options) + bill_lines.extend(lines) + helpers.create_bills(account, bill_lines) + + def get_related(self): + pass + def by_object(self, obj, **kwargs): ct = ContentType.objects.get_for_model(obj) return self.filter(object_id=obj.pk, content_type=ct, **kwargs) @@ -245,7 +258,7 @@ class Order(models.Model): content_object = generic.GenericForeignKey() objects = OrderQuerySet.as_manager() - + def __unicode__(self): return str(self.service) @@ -322,6 +335,6 @@ def update_orders(sender, **kwargs): if instance.pk: # post_save Order.update_orders(instance) - related = search_for_related(instance) + related = helpers.get_related_objects(instance) if related: Order.update_orders(related) diff --git a/orchestra/apps/resources/admin.py b/orchestra/apps/resources/admin.py index 023dccda..8da3f17b 100644 --- a/orchestra/apps/resources/admin.py +++ b/orchestra/apps/resources/admin.py @@ -129,7 +129,7 @@ def resource_inline_factory(resources): if not running_syncdb(): # not run during syncdb - for resources in Resource.group_by_content_type(): + for ct, resources in Resource.objects.group_by('content_type'): inline = resource_inline_factory(resources) - model = resources[0].content_type.model_class() + model = ct.model_class() insertattr(model, 'inlines', inline) diff --git a/orchestra/apps/resources/models.py b/orchestra/apps/resources/models.py index dde199c3..a91d37ae 100644 --- a/orchestra/apps/resources/models.py +++ b/orchestra/apps/resources/models.py @@ -5,13 +5,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from djcelery.models import PeriodicTask, CrontabSchedule -from orchestra.models.fields import MultiSelectField +from orchestra.models import queryset, fields from orchestra.utils.functional import cached from . import helpers from .backends import ServiceMonitor +class ResourceQuerySet(models.QuerySet): + group_by = queryset.group_by + + class Resource(models.Model): """ Defines a resource, a resource is basically an interpretation of data @@ -58,11 +62,13 @@ class Resource(models.Model): null=True, blank=True, help_text=_("Crontab for periodic execution. " "Leave it empty to disable periodic monitoring")) - monitors = MultiSelectField(_("monitors"), max_length=256, blank=True, + monitors = fields.MultiSelectField(_("monitors"), max_length=256, blank=True, choices=ServiceMonitor.get_plugin_choices(), help_text=_("Monitor backends used for monitoring this resource.")) is_active = models.BooleanField(_("is active"), default=True) + objects = ResourceQuerySet.as_manager() + class Meta: unique_together = ( ('name', 'content_type'), @@ -80,8 +86,12 @@ class Resource(models.Model): task = PeriodicTask.objects.get(name=name) except PeriodicTask.DoesNotExist: if self.is_active: - PeriodicTask.objects.create(name=name, task='resources.Monitor', - args=[self.pk], crontab=self.crontab) + PeriodicTask.objects.create( + name=name, + task='resources.Monitor', + args=[self.pk], + crontab=self.crontab + ) else: if not self.is_active: task.delete() @@ -92,25 +102,11 @@ class Resource(models.Model): def delete(self, *args, **kwargs): super(Resource, self).delete(*args, **kwargs) name = 'monitor.%s' % str(self) - PeriodicTask.objects.filter(name=name, task='resources.Monitor', - args=[self.pk]).delete() - - @classmethod - def group_by_content_type(cls): - prev = None - group = [] - resources = cls.objects.filter(is_active=True).order_by('content_type') - for resource in resources: - ct = resource.content_type - if prev != ct: - if group: - yield group - group = [resource] - else: - group.append(resource) - prev = ct - if group: - yield group + PeriodicTask.objects.filter( + name=name, + task='resources.Monitor', + args=[self.pk] + ).delete() class ResourceData(models.Model): @@ -181,7 +177,7 @@ def create_resource_relation(): return self relation = GenericRelation('resources.ResourceData') - for resources in Resource.group_by_content_type(): - model = resources[0].content_type.model_class() + for ct, resources in Resource.objects.group_by('content_type'): + model = ct.model_class() model.add_to_class('resource_set', relation) model.resources = ResourceHandler() diff --git a/orchestra/models/queryset.py b/orchestra/models/queryset.py new file mode 100644 index 00000000..75561b5f --- /dev/null +++ b/orchestra/models/queryset.py @@ -0,0 +1,38 @@ +def group_by(qset, *fields): + """ group_by iterator with support for multiple nested fields """ + def nest(objects, ix): + objs = [] + result = [] + first = True + for obj in objects: + current = getattr(obj, fields[ix]) + if first or current == previous: + objs.append(obj) + else: + if ix < len(fields)-1: + objs = nest(list(objs), ix+1) + result.append((previous, objs)) + objs = [obj] + previous = current + first = False + if ix < len(fields)-1: + objs = nest(list(objs), ix+1) + result.append((current, objs)) + return result + + objs = [] + first = True + for obj in qset.order_by(*fields): + current = getattr(obj, fields[0]) + if first or current == previous: + objs.append(obj) + else: + if len(fields) > 1: + objs = nest(objs, 1) + yield previous, objs + objs = [obj] + previous = current + first = False + if len(fields) > 1: + objs = nest(objs, 1) + yield current, objs