Может ли Django .annotate() возвращать объекты?

#python #django #annotate

#python #django #аннотировать

Вопрос:

У меня есть набор записей, который выглядит следующим образом (посторонние данные опущены, чтобы защитить легко вводимые в заблуждение):

  id | user_id |            created            | units
---- --------- ------------------------------- -------
  1 |       1 | 2011-04-18 15:43:02.737063 00 |    20
  2 |       1 | 2011-04-18 15:43:02.737063 00 |     4
  3 |       1 | 2011-04-18 15:46:48.592999 00 |    -1
  4 |       1 | 2011-04-19 12:02:10.687587 00 |    -1
  5 |       1 | 2011-04-19 12:09:20.039543 00 |    -1
  6 |       1 | 2011-04-19 12:11:21.948494 00 |    -1
  7 |       1 | 2011-04-19 12:15:51.544394 00 |    -1
  8 |       1 | 2011-04-19 12:16:44.623655 00 |    -1
  

И я хотел бы получить результат, который выглядит следующим образом:

  id | user_id |            created            | units
---- --------- ------------------------------- -------
  8 |       1 | 2011-04-19 12:16:44.623655 00 |    14
  2 |       1 | 2011-04-18 15:43:02.737063 00 |     4
  

Поэтому я, естественно, смотрю на .annotate() :

 u = User.objects.get(pk=1)
(MyModel.objects.filter(user=u)
        .values("id","user","created")
        .annotate(stuff=Sum("units"))
)
  

Проблема в том, что мне нужны объекты, а не единый список словарей. Мне нужно, чтобы методы, прикрепленные к этим объектам, были доступны. Есть идеи относительно того, как это сделать?

Редактировать: мне следовало указать, что я пытался использовать .values(), потому что без этого я бы получил кучу аннотированных объектов, но их было бы 8 (как в первом запросе выше), а не 2 (как во втором результате выше). Я предполагаю, что это не объединение строк, потому что там есть временная метка, делающая строки разными:

 MyModel.objects.filter(user=u).annotate(Sum("units"))
# Returns 8 records, not 2 as expected
  

Комментарии:

1. Что касается второй части проблемы, аннотации, я не совсем понимаю, как первый набор данных приводит ко второму… Почему идентификаторы 8 и 2 остаются с суммой 14 и 4? Аннотирование не уменьшает количество возвращаемых строк. Возможно, если бы вы добавили SQL для того, чего вы пытались достичь

2. Какой обычный sql-запрос вы использовали для создания результирующей таблицы (2 строки), отображаемой выше

Ответ №1:

Похоже, что при первоначальном задании вопроса возникла некоторая путаница, и в результате мне было предписано не использовать .values() , когда на самом деле это не было проблемой. Проблема в том, что функция Django .annotate() не допускает перезаписи свойств столбца вычисляемыми значениями и только аннотирует объекты дополнительными данными. Это действительно имеет смысл, поскольку, если вы хотите вернуть объект, вы хотите иметь возможность предположить, что рассматриваемый объект действительно представляет строку в базе данных, а не мешанину вычисленных значений, перезаписывающих значения столбцов.

Однако у меня это не сработало, потому что вышеупомянутая функциональность означает, что с такими столбцами, как created (временная метка), вы не сможете получить вычисленные значения, которые я хотел, без использования .values() … и это не дает мне объектов.

Итак, я выбрал следующую лучшую вещь: .raw() :

 query = """
    SELECT
        ep.product_id              AS product_id,
        ep.user_id                 AS user_id,
        MAX(ep.created)            AS created,
        SUM(eup.units)             AS units
    FROM
        ecom_unitpermission AS eup
    WHERE
        ep.user_id = 1
    GROUP BY
        ep.product_id,
        ep.user_id
"""

for perm in MyModel.objects.raw(query):
    print "s %3s %s" % (perm.product.name, perm.units, perm.product.somemethod())
  

Использование .defer() могло бы сделать это за меня, хотя похоже, что в Django 1.3 есть ошибка при объединении этого с .annotate()

Ответ №2:

Проблема не в аннотации, а в вызове значений. Документация довольно четко показывает, что annotate работает с наборами запросов. Именно вызов values возвращает словарь значений и лишает вас возможности доступа к моделям.

Отредактируйте, чтобы добавить: Если вы хотите вернуть только определенные поля, но у вас все еще есть модели, тогда используйте defer вместо значений

Комментарии:

1. На самом деле, я добавил .values(), потому что без нее я не смог бы объединить результат в две строки, которые я ожидал. Я думаю, это связано с тем фактом, что у меня там есть временная метка, поэтому все строки разные.

Ответ №3:

Annotate возвращает набор запросов, но вы также вызываете .values(), который всегда будет возвращать словарь.