henriquebastos / django-aggregate-if

Conditional aggregates for Django queries, just like the famous SumIf and CountIf in Excel.
MIT License
138 stars 17 forks source link

Doesn`t work in django 1.8 #17

Open sergei-iurchenko opened 8 years ago

henriquebastos commented 8 years ago

It won't work with 1.8+. Django 1.8 already has support for complex aggregate.

martsberger commented 8 years ago

When my company updated to Django 1.8, we decided that we liked the aggregate-if api and didn't want to change everywhere we were using it. So we created a file with the following:

from django.db.models import (Count as DjangoCount, Min as DjangoMin, Max as DjangoMax, Sum as DjangoSum,
                              Avg as DjangoAvg, Case, When, Q, FloatField)

def aggregate_if_to_case_when(*args, **kwargs):
    """
    We extend the Django versions of Avg, Count, Min, Max, Sum to use the api that we like from aggregate-if

    # TODO: we can probably get rid of this join fix in Django 1.8.2
    Join fix: A Q object will default to type AND making it get demoted to an INNER JOIN by
    django.db.models.sql.query.update_join_types(). So the line Q() | Q(...) changes
    the Q from an AND to an OR causing it to be promoted to a LEFT OUTER JOIN instead.
    """
    output_field = kwargs.pop('output_field', None)
    if kwargs.get('only'):
        only = Q() | Q(kwargs.pop('only'))
        if not args or not args[0]:
            raise Exception('Must pass a positional expression to use only')
        args = (Case(When(only, then=args[0]), output_field=output_field),) + args[1:]
    return args, kwargs

class Avg(DjangoAvg):
    def __init__(self, *args, **kwargs):
        kwargs['output_field'] = FloatField()
        args, kwargs = aggregate_if_to_case_when(*args, **kwargs)
        super(Avg, self).__init__(*args, **kwargs)

class Count(DjangoCount):
    def __init__(self, *args, **kwargs):
        args, kwargs = aggregate_if_to_case_when(*args, **kwargs)
        super(Count, self).__init__(*args, **kwargs)

class Max(DjangoMax):
    def __init__(self, *args, **kwargs):
        args, kwargs = aggregate_if_to_case_when(*args, **kwargs)
        super(Max, self).__init__(*args, **kwargs)

class Min(DjangoMin):
    def __init__(self, *args, **kwargs):
        args, kwargs = aggregate_if_to_case_when(*args, **kwargs)
        super(Min, self).__init__(*args, **kwargs)

class Sum(DjangoSum):
    def __init__(self, *args, **kwargs):
        args, kwargs = aggregate_if_to_case_when(*args, **kwargs)
        super(Sum, self).__init__(*args, **kwargs)

The Case and When objects introduced in Django 1.8 are very powerful and let you do even more than the aggregate-if package supports, but for the 95% of cases when we don't need the additional power, I find the only=Q API much more clear and easy to use.