Email: office@yourdomain.com
Phone:: +44 20 7240 9319
back to top

Blog

Django заметки. Часть первая.

Django прекрасный фреймворк до того момента, пока не начинают говорить о его недостатках. Обычно тут на передний план выходят ORM и стандартная система шаблонов. Но, при правильно подходе, можно получать хороший результат и со стандартными решениями. Главное, правильно писать код.

Следить за производительностью во время разработки достаточно просто – достаточно установить Django Debug Toolbar и следить за показателями. Время генерации страницы больше секунд – все очень плохо. SQL запросов больше 10 – тоже не хорошо. Ну и дальше в таком духе. В производительности – чем меньше, тем лучше.

Для решения вопросов производительности с шаблонной системой рекомендуют сразу разрабатывать с использованием Jinja2. При использовании Django Templates рекомендуется уменьшить количество импортирования в коде. Вместо этого предлагается решение – inclusion_tag. Это отдельный тэг, который вставляет свое содержимое в генерируемый шаблон. Содержимое – какой-либо html файл, которому отдельно передаются параметры. Т.е. происходит генерация мини шаблона и результат вставляется генерируемый основной шаблон. Например, происходит операция по генерированию блоков из шаблона catalog/product_block.html и, вместо того, чтобы использовать

{% include 'catalog/product_block.html' %}

Создается отдельный тэг:

from django import template

register = template.Library()


@register.inclusion_tag('catalog/product_block.html')
def product_table_layout(product):
    return {'product': product}

И в шаблоне используется так:

{% product_table_layout product %}

Производительность в этом моменте выигрывается за счет отсутствия большого набора данных контекста запроса, который изначально передается в основной шаблон, а потом и в подшаблоны при операциях include.

С ORM воевать можно очень долго. Тут много нюансов и тонкостей. Начиная от уменьшения числа запросов, то написания этих запросов отдельно с помощью sql. Самое простое и, часто, эффективное решение по уменьшению запросов к базе данных, это использование метода select_related(). Эффективно это в случае, если используется несколько моделей, где поля одной модели связаны с другой посредством, например, ForeignKey и требуется получить еще поле из связанной модели. Каждый раз при обращении к этому полю, будет происходить дополнительный запрос, но, если сделать заранее select_related(), то данные уже будут получены.

Для случая с двумя моделями, пускай это будет Author и Book. Структура у них, примерно, следующая:

class Author(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    name = models.CharField(max_length=255)

class Book(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=255)

Если сделать запрос вида:

books = Book.objects.filter()

И итерировать объект books, получая у каждого author.name, будем создавать дополнительные запросы к модели Author. Но, добавив select_related(), решим все одним запросом.

books = Book.objects.filter().select_related()

Также, при разработке приложения, стоит учитывать, что запрос выполняется не в момент его описания и присваивания какой-либо переменной. Запрос выполняется в момент обращения к переменной. То есть, если в views.py вы опишите десяток запросов, а в шаблоне обратитесь только к одному из них – он и выполнится.

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

class Product(models.Model):
    title = models.CharField(max_length=255)

    def __unicode__(self):
        return self.title

    def latest_point(self):
        if not hasattr(self, '_latest_point'):
            try:
                self._latest_point = self.state_point.latest('id')
            except ObjectDoesNotExist, e:
                return None
            except Exception, e:
                raven_client = get_client()
                raven_client.captureException()
                return None
        return self._latest_point

class StatePoint(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    product = models.ForeignKey(Product, related_name="state_point")

В модели Product я описываю метод latest_point() для получения последнего связанного состояния для объекта Product. В дальнейшем, в шаблоне, я обращаюсь несколько раз к методу latest_point() и получаю его последнее состояние только при первом запросе. При последующих, если все корректно и без ошибок выполнится в первый раз, я буду получать результат из временно созданного поля _latest_point. Способ простой, но крайне эффективный.