Alex has correctly pointed you at the right bit of code for the new
aggregate implementation; however, I would like to add some extra
detail.
The new implementation is split across 2 classes to allow the
definition of aggregates to be independent of their eventual
implementation. This is useful for Django, because it leaves the door
open for supporting non-SQL data stores which don't implement literal
SQL, but may provide aggregate functionality.
However, just because Django splits aggregates over two classes, this
doesn't mean you have to do the same. There are essentially just two
interfaces - there's no reason a single class couldn't implement both.
The 'generic' Aggregate is little more than a placeholder that can
provide a default alias name on the default_alias property, and can
install itself into a query using the add_to_query method. The real
lifting is in the backend specific Aggregate, which keeps track of
column aliases, etc.
You should be able to write an site-specific SQL aggregate by
subclassing the backend-specific Aggregate class
(django.db.models.sql.aggregates.Aggregate), and adding to your
subclass an implementation of default_alias and add_to_query,
following the lead of django.db.models.aggregates.Aggregate. A
straight mixin probably won't work, because your implementation of
add_to_query needs to be slightly different to the version in the
generic Aggregate class, but you should be able to crib off the
generic version to see what needs to be done.
What you will end up with is a backend-specific aggregate class that
appears to be a generic aggregate for the purposes of annotate() and
aggregate() calls.
Yours,
Russ Magee %-)
Curiosity got the better of me... so here's a worked (and documented)
example that reimplements the Max() aggregate:
from django.db.models.sql.aggregates import Aggregate
class MyMax(Aggregate):
sql_function = 'MAX'
"""A base class to make it easy for end users to define their own
custom SQL aggregates.
The subclass should define the following two class properties:
* sql_function - the name of the SQL function to invoke
Optionally, you can define
* sql_template - a format string that is used to compose the
SQL that will be sent to the database. The template will be
provided with the following substitution variables:
- ``function``, the sql fuction that will be invoked
- ``field``, the resolved name of the column to be
operated on.
The template will also be provided with any keyword argument
provided to the aggregate when it was defined.
The default template is '%(function)s(%(field)s)'
* is_ordinal - a boolean, True if the result of the aggregate
will always be a count, regardless of the field on which the
aggregate is applied. False by default.
* is_computed - a boolean, True if the result of the aggregate
will always be a float, regardless of the field on which the
aggregate is applied. False by default.
"""
def __init__(self, lookup, **extra):
self.lookup = lookup
self.extra = extra
def _default_alias(self):
return '%s__%s' % (self.lookup, self.__class__.__name__.lower())
default_alias = property(_default_alias)
def add_to_query(self, query, alias, col, source, is_summary):
super(MyMax, self).__init__(col, source, is_summary, **self.extra)
query.aggregate_select[alias] = self
Usage:
Book.objects.annotate(MyMax('pubdate'))
Yours,
Russ Magee %-)