Ноябрь 04, 2012
Уменьшение количества запросов к БД при работе с django-taggit

Для фреймворка Django есть удобное приложение django-taggit, при помощи которого можно без труда добавлять теги к моделям. Например, существует модель Post и необходимо обеспечить привязку экземпляров этой модели к тегам. С помощью django-taggit сделать это просто:

from django.db import models
from taggit.managers import TaggableManager


class Post(models.Model):
    #...
    tags = TaggableManager()

Теперь при создании объекта класса Post, можно указывать нужные теги, а затем получать их:

post = Post.objects.create(name="sample")
post.tags.add("tag1", "tag2", "tag3")
tags = post.tags.all()

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

{% for post in posts %}
    <div class="post ">
        <h2 class="post_name">{{ post.name }}</h2>        
        <div class="post_text">
            {{ post.text }}
        </div>
        <div class="post_tags">
            Теги:
            {% for tag in post.tags.all %}
                <b>{{ tag.name }} </b>
            {% endfor %}
        </div>
    </div>
{% endfor %}

Такое решение содержит в себе минус: при обходе постов на каждой итерации при вызове "post.tags.all" производится запрос к базе данных, в результате чего количество запросов растет с увеличением количества записей на странице.
Очевидно, что гораздо лучше выбирать все теги сразу для всех постов, а не для каждого отдельно. Для этой цели я решил использовать метод prefetch_related. Данный метод применим для отношений "многие–ко–многим" и позволяет при выборе списка записей сразу выбрать из базы данных нужные связанные записи. В конечном итоге, для удобства был создан управляющий класс модели (Manager), который переопределяет метод get_query_set таким образом, чтобы при его вызове использовался метод prefetch_related и выбирались связанные записи тегов. Данный управляющий класс можно прикрепить к любой модели, имеющей теги и уже при его помощи выбирать объекты данной модели:

#управляющий класс
class TaggedManager(models.Manager):
    def get_query_set(self):
        return super(TaggedManager, self).get_query_set().prefetch_related('tagged_items__tag')
    #...

#немного изменим класс Post
class Post(models.Model):
    #...
    tagged = TaggedManager()
    objects = models.Manager()

#...

#теперь получаем посты так
posts = Post.tagged.all()

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

...
<div class="post_tags">
    Теги:
    {% for item in post.tagged_items.all %}
    	{% with item.tag as tag %}
            <b>{{ tag.name }} </b>
        {% endwith %}
    {% endfor %}
</div>

В общем-то, все, оптимизация завершена. Может быть, это не самое красивое решение, но оно работает: при выборе постов выбираются сразу все связанные с ними теги, и затем, при их выводе обращение происходит к кешу, а не к базе данных.
Теги: ,
Добавить комментарий:
Комментариев: (0)
Опубликовать