Агрегатные функции в Django ORM — крутые. Это обстоятельство послужило поводом добавить еще одну =)
Далее речь пойдет о mysql-специфичной функции
GROUP_CONCAT
и Интересующий нас функционал находится в двух модулях,
django.db.models.aggregates
и django.db.models.sql.aggregates
. Итак, место действия — models.py:from django.db import models ## агрегатные функции ## from django.db.models.aggregates import Aggregate # определение from django.db.models.sql.aggregates import Aggregate as SQLAggregate # реализация
Начнем с самого интересного — собственно реализации.
class SQLConcat(SQLAggregate): sql_function = "GROUP_CONCAT" def __init__(self, col, separator=',', **extra): self.sql_template = "%%(function)s(%%(field)s SEPARATOR '%s')" % separator super(SQLConcat, self).__init__(col, source=models.DecimalField(), **extra)
Здесь есть (как минимум) одна некрасивая штука — передача
models.DecimalField()
в конструктор родителя; это необходимо для того, чтобы впоследствии результат работы нашей функции не был ошибочно приведен к числовому типу (!). Сломается этот код при очередном обновлении транка Django или нет, мы увидим после очередного обновления транка Django. You have been warned.Реализации готова, перейдем к скучному определению функции:
class Concat(Aggregate): name = "Concat" def add_to_query(self, query, alias, col, source, is_summary): aggregate = SQLConcat(col, is_summary=is_summary, **self.extra) query.aggregates[alias] = aggregate
Осталось удостовериться, что наше произведение работает:
>>> Post.threads.aggregate(Concat('id')) {'id__concat': '1,2,3,4'} >>> from django.db import connection >>> connection.queries [{'sql': u"SELECT GROUP_CONCAT(`main_post`.`id` SEPARATOR ',') AS `id__concat` FROM `main_post`", 'time': '0.000'}]
Первый пост на Хабре, уиии!