2010-08-12 3 views
4

У меня есть модель Invoice, и я хочу сохранить total в актуальном состоянии. Поэтому я добавил свойство, как этотDjango: кэширование агрегированного запроса?

@property 
def item_total(self): 
    if not hasattr(self, 'item_total_cache'): 
     self.item_total_cache = self.items.aggregate(t=Sum('amount'))['t'] 
    return self.item_total_cache 

Поскольку item_total может вызываться несколько раз в течение одного запроса страницы, и я не хочу попасть в БД каждый раз. Это хороший способ сделать это? Будет ли мой объект Invoice существовать за пределами одного запроса страницы, и, таким образом, item_total может действительно устареть?

В идеале, я хотел бы вычислить item_total в то же время, когда запрашиваются счета ... Я знаю, что могу сделать это вручную, но, например, сайт администратора Django не знает, что он понадобится сумма. Есть ли какой-то способ «вставить» этот лишний лакомый кусочек информации, чтобы все запросы Invoice.objects.all/filter/get() запускали агрегат одновременно?

ответ

3

Это хорошее решение, которое я использовал и рекомендовал раньше. Объект Invoice не будет сохраняться за пределами одного запроса страницы, если вы не будете ссылаться на него в глобальной области действия - если он просто создан в представлении и передан шаблону, все будет в порядке.

Существует несколько способов получить сайт администратора для автоматического создания агрегата. Один из них заключается в определении пользовательского диспетчера и переопределении get_query_set для возврата self.annotate(Sum('amount')) - это гарантирует, что агрегат всегда создается для каждого отдельного запроса в модели счета-фактуры, который может быть не таким, каким вы хотите.

В качестве альтернативы можно определить отдельный метод на менеджера - with_aggregate, например, - который возвращает это, но затем переопределить ModelAdmin «S get_queryset метод возвращает результат этого метода. Это дает вам больше контроля, чтобы решить, следует ли использовать агрегат в своих собственных представлениях.

+0

Забыл я мог переопределить менеджера ... никогда не нужно было делать это раньше. Это должно хорошо работать!Рад, что мое текущее решение не так ужасно; может отложить это до тех пор, пока не будут выполнены более неотложные функции :) – mpen

4

Могу ли я предложить другое решение? Почему бы не сохранить счет и сделать его автоматическим приращением/уменьшением с помощью сигналов?

Это зависит от количества чтений/записей, которые вы имеете в виду, но кеширование чего-то вроде этого, вероятно, является более эффективным решением. И возможно совсем немного легче.

В любом случае, вы правильно выполняете кеширование счета. Объект Invoice не должен затухать, если вы не кэшируете его каким-либо другим способом. Этот кеш связан с экземпляром и с новым запросом вы можете предположить, что вы также получите новый экземпляр. Если вы не получаете свои предметы из memcached.

Что касается инъекции кеша при необходимости. Как это будет работать, если вы используете get() или filter()? В этом случае ваш счет будет меньше, а ваш кеш - неправильным. Это должно быть справедливо выполнимым для all(). Вы можете просто перезаписать диспетчер по умолчанию, чтобы автоматически добавлять эти данные во время извлечения. Но это будет означать, что за каждые Item, которые вы запросите, вы получите также Invoice.

+2

Я не доверяю сигналам ... если сигнал не получен, все приложение разваливается. Небольшая синтаксическая ошибка может выкинуть все из синхронизации, и ее трудно отладить. Впрочем, это возможно. – mpen

+3

@Mark: Если вы не доверяете сигналам, вы можете поместить их в метод 'save()' модели. Я согласен с тем, что отладить немного сложнее, но это не должно быть так сложно. И если вы считаете, что это не синхронизировано, просто перестройка кеша выполняется в нескольких строках. – Wolph

Смежные вопросы