Django_-_podrobnoe_rukovodstvo
.pdf330 |
Глава 15. Кэширование |
Промежуточные кэши
До сих пор мы рассматривали кэширование ваших собственных данных. Но в веб-разработке встречается и другой вид кэширования: сис темами, расположенными вдоль тракта прохождения запроса от клиента к серверу. Они кэшируют страницы еще до того, как запрос поступит на ваш сайт.
Приведем несколько примеров промежуточных кэшей:
•• Некоторые страницы может кэшировать ваш интернет-провайдер, так что при запросе страницы сайта http://example.com/ провайдер вернет ее, не обращаясь к самому сайту. Лица, сопровождающие example.com, понятия не имеют о таком кэшировании; провайдер, находящийся между сайтом и броузером, работает прозрачно для сайта.
•• Ваш Django-сайт может находиться за кэширующим прокси-сервером, например, Squid Web Proxy Cache (http://www.squid-cache.org/), который кэширует страницы для повышения производительности. В этом случае каждый запрос сначала обрабатывается прокси-сервером и передается вашему приложению только при необходимости.
•• Веб-броузер тоже кэширует страницы. Если полученный от сервера ответ содержит специальные заголовки, то последующие запросы к той же странице броузер будет удовлетворять из локального кэша, даже не спрашивая у сервера, изменилась ли она.
Промежуточное кэширование действительно способствует повышению производительности, но таит в себе опасность: содержимое многих вебстраниц зависит от результатов аутентификации и множества других факторов, поэтому система кэширования, слепо сохраняющая у себя страницы на основе одного лишь URL, может вернуть последующим посетителям неверные или конфиденциальные данные.
Предположим, к примеру, что вы эксплуатируете систему электронной почты с веб-интерфейсом. Очевидно, содержимое страницы «Входящие» зависит от пользователя. Если бы провайдер слепо кэшировал весь сайт, то страница входящей почты первого посетителя, зашедшего на сайт через этого провайдера, отображалась бы всем последующим посетителям сайта. Так не годится.
К счастью, в протоколе HTTP предусмотрено решение этой проблемы. Существуют заголовки, сообщающие промежуточным кэшам о том, что содержимое должно кэшироваться в зависимости от указанных переменных или не должно кэшироваться вовсе. В следующих разделах мы рассмотрим эти заголовки.
Заголовки Vary
Заголовок Vary определяет, какие заголовки запроса должен принимать во внимание механизм кэширования при построении ключа кэша. На-
Заголовки Vary |
331 |
пример, если содержимое страницы зависит от заданной пользователем языковой настройки, то говорят, что страница «варьируется по языку».
По умолчанию система кэширования в Django формирует ключи, используя путь, указанный в запросе пути (например, “/stories/2005/ jun/23/bank_robbed/”). Это означает, что в ответ на любой запрос к этому URL будет возвращена одна и та же кэшированная версия страницы вне зависимости от таких различий между броузерами пользователей, как cookie или языковые настройки. Но если содержимое страницы зависит от некоторых заголовков в запросе (cookie, язык, тип броузера), то об этом нужно сообщить механизмам кэширования с помощью заголовка Vary.
Для этого в Django применяется декоратор представления vary_on_ headers:
from django.views.decorators.vary import vary_on_headers
# Синтаксис Python 2.3. def my_view(request):
# ...
my_view = vary_on_headers(my_view, ‘User-Agent’)
# Синтаксис декоратора Python 2.4+. @vary_on_headers(‘User-Agent’)
def my_view(request):
#...
Вэтом случае механизм кэширования (в частности встроенные в Django дополнительные процессоры) будет сохранять в кэше отдельные версии страницы для каждого типа броузера.
Преимущество декоратора vary_on_headers по сравнению с манипулированием заголовком Vary вручную (например, response[‘Vary’] = ‘useragent’) состоит в том, что декоратор добавляет информацию к заголовку Vary (а он может уже существовать), а не перезаписывает имеющиеся в нем данные.
Декоратору vary_on_headers() можно передать несколько заголовков:
@vary_on_headers(‘User-Agent’, ‘Cookie’) def my_view(request):
# ...
Тем самым промежуточные кэши уведомляются о том, что для каждой комбинации типа броузера и значения cookie в кэше должна быть создана отдельная версия страницы. Например, запрос, отправленный с помощью броузера Mozilla и со значением foo=bar в cookie будет считаться отличным от запроса, отправленного с помощью броузера Mozilla и со значением foo=ham в cookie.
Поскольку варьирование по cookie встречается очень часто, существует специальный декоратор vary_on_cookie. Следующие два представления эквивалентны:
332 |
Глава 15. Кэширование |
@vary_on_cookie
def my_view(request):
# ...
@vary_on_headers(‘Cookie’) def my_view(request):
# ...
Регистр символов в названиях заголовков, передаваемых декоратору vary_on_headers, не имеет значения: “User-Agent” и “user-agent” эквивалентны.
Можно также напрямую использовать вспомогательную функцию django.utils.cache.patch_vary_headers. Она устанавливает или добавляет заголовок Vary , например:
from django.utils.cache import patch_vary_headers
def my_view(request):
# ...
response = render_to_response(‘template_name’, context) patch_vary_headers(response, [‘Cookie’])
return response
Функция patch_vary_headers принимает в качестве первого аргумента объект HttpResponse, а в качестве второго – список или кортеж названий заголовков, имена которых нечувствительны к регистру символов.
Управление кэшем: другие заголовки
Среди других проблем следует упомянуть конфиденциальность данных и вопрос о том, в каком месте каскада кэшей следует хранить данные.
Обычно пользователь сталкивается с двумя видами кэшей: кэш своего броузера (частный кэш) и кэш своего провайдера (общий кэш). Общий кэш используется всеми пользователями и находится под контролем третьего лица. В связи с этим возникает проблема конфиденциальности данных – вы же не хотите, чтобы номер вашего банковского счета хранился в общем кэше. Поэтому у веб-приложения должен быть способ сообщить кэшам, какие данные являются частными, а какие – нет.
Решение состоит в том, чтобы пометить страницу как «частную». В Django для этого применяется декоратор представления cache_control, например:
from django.views.decorators.cache import cache_control
@cache_control(private=True) def my_view(request):
#...
Взадачу этого декоратора входит отправка нужного HTTP-заголовка. Существует несколько способов управления параметрами кэширования. Например, спецификация HTTP позволяет:
Управление кэшем: другие заголовки |
333 |
•• Определить максимальное время нахождения страницы в кэше.
•• Указать, должен ли кэш всегда проверять наличие новых версий и доставлять кэшированное содержимое, только когда не было изменений. (Некоторые кэши возвращают кэшированное содержимое, даже когда страница на сервере изменилась, – просто потому, что срок хранения кэшированной копии еще не истек.)
Декоратор cache_control в Django позволяет задать эти параметры. В примере ниже cache_control сообщает кэшу, что наличие изменений следует проверять при каждом обращении и хранить кэшированную версию не более 3600 секунд:
from django.views.decorators.cache import cache_control
@cache_control(must_revalidate=True, max_age=3600) def my_view(request):
#...
Вкачестве параметра декоратору cache_control() можно передать любую допустимую протоколом HTTP директиву заголовка Cache-Control. Вот их полный перечень:
•• public=True
•• private=True
•• no_cache=True
•• no_transform=True
•• must_revalidate=True
•• proxy_revalidate=True
•• max_age=num_seconds
•• s_maxage=num_seconds
(Отметим, что дополнительные процессоры всегда добавляют директиву max-age со значением, взятым из параметра настройки CACHE_ MIDDLEWARE_SETTINGS. Если декоратору cache_control передать другое значение max-age, то оно будет иметь приоритет.)
Чтобы полностью отменить кэширование, воспользуйтесь декоратором представления never_cache, который добавляет HTTP-заголовки, необходимые, чтобы ответ не кэшировался ни броузером, ни другими кэшами. Например:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
# ...
334 |
Глава 15. Кэширование |
Другие оптимизации
В Django есть и другие дополнительные процессоры, призванные оптимизировать производительность приложения:
•• django.middleware.http.ConditionalGetMiddleware реализует поддержку условных ответов на GET-запросы с помощью заголовков ETag и LastModified, реализованную в современных броузерах.
•• django.middleware.gzip.GZipMiddleware сжимает ответы (этот режим поддерживается всеми современными броузерами) для экономии пропускной способности сети и сокращения времени передачи.
Порядок строк в MIDDLEWARE_CLASSES
При использовании дополнительных процессоров кэширования важно перечислять их в параметре MIDDLEWARE_CLASSES в правильном порядке. Дело в том, что этим процессорам необходимо знать, какие заголовки использовать для управления кэшированием содержимого. Они стараются по возможности включать в ответ заголовок Vary.
Процессор UpdateCacheMiddleware работает на этапе формирования ответа. На этой стадии дополнительные процессоры вызываются в порядке, обратном перечислению, то есть процессор, находящийся в начале списка, получит управление последним. Поэтому UpdateCacheMiddleware должен быть указан до всех процессоров, которые могут добавлять чтото в заголовок Vary. Это касается следующих процессоров:
•• SessionMiddleware добавляет заголовок Cookie
•• GZipMiddleware добавляет заголовок Accept-Encoding
•• LocaleMiddleware добавляет заголовок Accept-Language
С другой стороны, процессор FetchFromCacheMiddleware работает на этапе обработки запроса. На этой стадии дополнительные процессоры вызываются в порядке перечисления, то есть процессор, находящийся в начале списка,получитуправление первым.Поскольку FetchFromCacheMiddleware также должен работать после всех процессоров, обновляющих заголовок Vary, то он должен находиться в списке после них.
Что дальше?
В комплект поставки Django входит ряд пакетов, написанных сторонними разработчиками («contrib»), которые содержат ряд полезных возможностей. Некоторые из них мы уже рассматривали: административный интерфейс (см. главу 6) и подсистему управления сеансами и пользователями (см. главу 14). В следующей главе мы рассмотрим другие подсистемы такого рода.
16
django.contrib
Одной из сильных сторон языка Python является его идеология «батарейки входят в комплект»: в состав дистрибутива Python входит большая стандартная библиотека пакетов, которыми можно начинать пользоваться сразу же, не загружая что-то еще. Фреймворк Django также придерживается этой идеологии и поставляется с собственной стандартной библиотекой дополнительных модулей, полезных для решения типичных задач веб-разработки. Их-то мы в этой главе и рассмотрим.
Стандартная библиотека Django
Стандартная библиотека Django находится в пакете django.contrib. В каждом его подпакете представлена какая-то отдельная часть функциональности. Эти части необязательно взаимосвязаны, но некоторые подпакеты зависят от других.
Нет никаких жестких требований, диктующих, какую функциональность можно включать в django.contrib. Некоторые пакеты содержат модели (и потому добавляют в вашу базу данных необходимые им таблицы), другие состоят исключительно из дополнительных процессоров или шаблонных тегов.
Единственное свойство, общее для всех пакетов в django.contrib, таково: при удалении целиком пакета django.contrib базовая функциональность Django остается работоспособной. При расширении функциональности фреймворка разработчики Django следуют этому правилу, решая, куда поместить новые функции: в django.contrib или в какое-то другое место.
Пакет django.contrib включает следующие подпакеты:
•• admin: административный интерфейс Django. См. главу 6.
•• admindocs: автоматически сгенерированная документация по административному интерфейсу. В этой книге не рассматривается, см. официальную документацию по Django.
336 |
Глава 16. django.contrib |
•• auth: подсистема аутентификации Django. См. главу 14.
•• comments: приложение для управления комментариями. В этой книге не рассматривается, см. официальную документацию по Django.
•• contenttypes: подсистема подключения к типам содержимого, в которой каждая установленная модель Django является отдельным типом. Используется другими приложениями из пакета contrib и предназначена главным образом для очень опытных разработчиков. А они почерпнут гораздо больше, изучив исходный код, который находится в каталоге django/contrib/contenttypes.
•• csrf: защита от техники подделки HTTP-запросов CSRF (cross-site request forgery). См. ниже раздел «Защита от CSRF».
•• databrowse: приложение Django, позволяющее просматривать данные. В этой книге не рассматривается, см. официальную документацию по Django.
•• flatpages: подсистема для хранения простых «плоских» HTML-стра ниц в базе данных.
•• formtools: ряд полезных высокоуровневых библиотек для решения типичных задач, возникающих при работе с формами. В этой книге не рассматривается, см. официальную документацию по Django.
•• gis: расширение Django для поддержки геоинформационных сис тем (ГИС). С его помощью в моделях можно хранить географические данные и выполнять географические запросы. Это большая и сложная библиотека, которая в настоящей книге не рассматривается. Документация приводится на сайте http://geodjango.org/.
•• humanize: набор шаблонных фильтров, полезных для придания данным «человеческого облика». См. раздел «Удобочитаемость данных».
•• localflavor: разнообразный код, настроенный для разных стран и культур. Например, сюда включены средства для проверки почтовых индексов США (ZIP-кодов) и персональных идентификационных номеров в Исландии.
•• markup: набор шаблонных фильтров для реализации распространенных языков разметки. См. раздел «Фильтры разметки».
•• redirects: подсистема управления переадресацией. См. раздел «Переадресация».
•• sessions: подсистема сеансов. См. главу 14.
•• sitemaps: подсистема создания карт сайтов в формате XML. См. главу 13.
•• sites: подсистема, позволяющая эксплуатировать несколько вебсайтов при наличии всего одной базы данных и инсталляции Django. См. раздел «Сайты».
•• syndication: подсистема создания каналов синдицирования в форматах RSS и Atom. См. главу 13.
Сайты |
337 |
•• webdesign: дополнительные модули, предназначенные прежде всего для веб-дизайнеров (а не разработчиков). На момент написания этой книги включали единственный шаблонный тег {% lorem %}. Дополнительные сведения см. в документации по Django.
Остальная часть этой главы посвящена детальному обсуждению некоторых пакетов, входящих в состав библиотеки django.contrib, которые ранее в этой книге не рассматривались.
Сайты
Подсистема сайтов позволяет эксплуатировать несколько веб-сайтов при наличии всего одной базы данных и одного проекта Django. Это довольно абстрактная концепция, непростая для понимания, поэтому начнем с рассмотрения нескольких сценариев, в которых она может оказаться полезной.
Сценарий 1: повторное использование данных в нескольких сайтах
В главе 1 мы рассказали о том, что созданные на основе Django сайты LJWorld.com и Lawrence.com эксплуатируются одной и той же организацией: газетой Lawrence Journal-World в городе Лоуренс, штат Канзас. Сайт LJWorld.com специализируется на новостях, а сайт Lawrence. com – на местных развлечениях. Но иногда возникает необходимость опубликовать некую статью на обоих сайтах.
Эту задачу можно решить в лоб, то есть завести для каждого сайта отдельную базу данных и потребовать, чтобы персонал сайта опубликовал требуемый материал дважды. Но этот метод повышает трудозатраты работников и требует дублирования одних и тех же данных в базе.
Есть ли решение получше? Да, пусть оба сайта пользуются одной и той же базой статей, и каждая статья связана с одним или несколькими сайтами отношением многие-ко-многим. Подсистема сайтов в Django предоставляет таблицу базы данных, с которой можно связать статьи. Это общая точка, позволяющая ассоциировать данные с одной или несколькими «площадками».
Сценарий 2: хранение имени и домена сайта в одном месте
На сайтах LJWorld.com и Lawrence.com реализована система оповещения по электронной почте, с помощью которой читатели могут получать уведомления о новостях. Устроена она довольно просто: читатель заполняет веб-форму и сразу же получает по почте сообщение «Благодарим за оформление подписки».
Было бы неэффективно и избыточно реализовывать код оформления подписки дважды, поэтому в обоих сайтах используется один и тот же
338 |
Глава 16. django.contrib |
код. Однако текст сообщения «Благодарим за оформление подписки» для каждого сайта должен быть различен. Объекты Site позволяют абстрагировать эти различия, воспользовавшись значениями атрибутов name (например, LJWorld.com) и domain (например, www.ljworld.com) текущего сайта.
Подсистема сайтов предоставляет место для хранения атрибутов name и domain каждого сайта в проекте Django, то есть позволяет использовать их повторно.
Как пользоваться подсистемой сайтов
Подсистема сайтов представляет собой, скорее, набор соглашений. В ее основе лежат две простые концепции:
•• Модель Site из пакета django.contrib.sites обладает полями domain и name.
•• Параметр SITE_ID задает идентификатор в объекте Site, ассоциированном с данным файлом параметров.
Применение этих концепций зависит от ваших потребностей, но Django в нескольких местах пользуется ими, следуя простым соглашениям.
Чтобы установить приложение sites, выполните следующие действия:
1.Добавьте в параметр INSTALLED_APPS строку ‘django.contrib.sites’.
2.Выполните команду manage.py syncdb, которая добавит в вашу базу данных таблицу django_site. Кроме этого, она добавит объект Site по умолчанию с доменным именем example.com.
3.Измените имя example.com, записав вместо него доменное имя своего сайта, и добавьте другие объекты Site, воспользовавшись либо административным интерфейсом, либо Python API. Создайте по одному объекту Site для каждого сайта, которым будет управлять данный проект Django.
4.Определите параметр SITE_ID в каждом файле параметров. Он должен быть равен идентификатору объекта Site, управляемому этим файлом параметров.
Возможности подсистемы сайтов
В следующих разделах мы расскажем о том, что позволяет делать эта подсистема.
Повторное использование данных в нескольких сайтах
Чтобы повторно использовать одни и те же данные в нескольких сайтах, как описано в первом сценарии, создайте в своих моделях поле типа ManyToManyField, ассоциированное с объектом Site:
from django.db import models
from django.contrib.sites.models import Site
Сайты |
339 |
|
class Article(models.Model): |
|
headline = models.CharField(max_length=200) |
|
# ... |
|
sites = models.ManyToManyField(Site) |
Это как раз та инфраструктура, которая необходима для связывания статей с несколькими сайтами. Теперь в нескольких сайтах можно будет использовать одно и то же представление. В примере с моделью Article представление article_detail могло бы выглядеть так:
from django.conf import settings
from django.shortcuts import get_object_or_404 from mysite.articles.models import Article
def article_detail(request, article_id):
a = get_object_or_404(Article, id=article_id, sites__id=settings.SITE_
ID)
# ...
Эту функцию представления можно использовать повторно, так как она динамически проверяет сайт, с которым ассоциирована данная статья, пользуясь параметром SITE_ID.
Предположим, к примеру, что для сайта LJWorld.com параметр SITE_ID равен 1, а для сайта Lawrence.com – 2. Если вызвать это представление, когда активным является файл параметров LJWorld.com, то будут отобраны только статьи, для которых список сайтов ссылается на LJWorld.com.
Ассоциирование содержимого с одним сайтом
Точно так же можно ассоциировать модель с одной моделью Site с помощью отношения многие-к-одному, воспользовавшись полем типа
ForeignKey.
Например, если каждая статья ассоциирована только с одним сайтом, модель можно использовать следующим образом:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site)
Это обеспечит те же преимущества, что и в предыдущем разделе.
Изменение логики представления в зависимости от текущего сайта
На более низком уровне подсистему сайтов можно использовать в представлениях, чтобы выполнять различные действия в зависимости от того, для какого сайта вызвано представление. Например:
