Django_-_podrobnoe_rukovodstvo
.pdf340 |
Глава 16. django.contrib |
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
#Сделать одно.
else:
#Сделать другое.
Разумеется, жестко определять идентификаторы сайтов в программном коде некрасиво. Чуть более элегантный способ решить эту задачу заключается в проверке доменного имени текущего сайта:
from django.conf import settings
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get(id=settings.SITE_ID) if current_site.domain == ‘foo.com’:
#Сделать одно.
else:
#Сделать другое.
Идиома получения объекта Site по значению параметра SITE_ID настолько распространена, что менеджер модели Site (Site.objects) содержит специальный метод get_current(). Следующий пример эквивалентен предыдущему:
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get_current() if current_site.domain == ‘foo.com’:
#Сделать одно.
else:
#Сделать другое.
Примечание
В последнем примере импортировать django.conf.settings необязательно.
Отображение текущего доменного имени
Чтобы не нарушать принцип DRY (Не повторяйся) при хранении имени и домена сайта (см. сценарий 2 «Хранение имени и домена сайта в одном месте»), можно просто сослаться на атрибуты name и domain текущего объекта Site. Например:
from django.contrib.sites.models import Site from django.core.mail import send_mail
def register_for_newsletter(request):
#Проверить значения в форме и т. д. и подписать пользователя.
#...
current_site = Site.objects.get_current()
Сайты |
341 |
send_mail(‘Благодарим за оформление подписки на уведомления от %s ‘ % \ current_site.name,
‘Спасибо за оформление подписки. С уважением,\n\nКоллектив %s.’ % \ current_site.name,
‘editor@%s’ % current_site.domain, [user_email])
# ...
Так, в теме письма от сайта Lawrence.com будет текст «Благодарим за оформление подписки на уведомления от Lawrence.com», а в письме от сайта LJWorld.com – «Благодарим за оформление подписки на уведомления от LJWorld.com». То же самое относится и к тексту в теле письма.
Более гибкий (и более сложный) способ реализации этой идеи состоит в том, чтобы воспользоваться системой шаблонов Django. Если предположить, что каталоги шаблонов для сайтов Lawrence.com и LJWorld. com определены по-разному (параметр TEMPLATE_DIRS), можно переложить ответственность на систему шаблонов:
from django.core.mail import send_mail from django.template import loader, Context
def register_for_newsletter(request):
#Проверить значения в форме и т.д. и подписать пользователя.
#...
subject = loader.get_template(‘alerts/subject.txt’).render(Context({})) message = loader.get_template(‘alerts/message.txt’).render(Context({})) send_mail(subject, message, ‘do-not-reply@example.com’, [user_email])
#...
Вэтом случае нужно создать файлы subject.txt и message.txt в каталогах шаблонов LJWorld.com и Lawrence.com. Как уже отмечалось, это более гибкое решение, которое, однако, требует дополнительных усилий.
Мы всячески рекомендуем использовать объекты Site всюду, где возможно, чтобы избавиться от ненужной сложности и дублирования.
Объект CurrentSiteManager
Если объекты Site играют в вашем приложении ключевую роль, подумайте о включении в определения моделей менеджера CurrentSiteManager (см. главу 10). Он автоматически фильтрует запросы, выбирая только объекты, ассоциированные с текущим объектом Site.
Менеджер CurrentSiteManager следует добавлять в модель явно, например:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to=’/home/photos’)
342 |
Глава 16. django.contrib |
photographer_name = models.CharField(max_length=100) pub_date = models.DateField()
site = models.ForeignKey(Site) objects = models.Manager() on_site = CurrentSiteManager()
Вэтой модели метод Photo.objects.all() возвращает все объекты Photo
вбазе данных, а метод Photo.on_site.all() – только объекты Photo, ассоциированные с текущим сайтом, идентификатор которого определяется параметром SITE_ID.
Иными словами, следующие два предложения эквивалентны:
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
Откуда менеджер CurrentSiteManager знает, какое поле объекта Photo содержит идентификатор сайта? По умолчанию он ищет поле с именем site. Если в вашей модели поле типа ForeignKey или ManyToManyField называется иначе, его имя необходимо явно передать менеджеру CurrentSiteManager. В следующей модели поле с идентификатором сайта называется publish_on:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to=’/home/photos’) photographer_name = models.CharField(max_length=100) pub_date = models.DateField()
publish_on = models.ForeignKey(Site) objects = models.Manager()
on_site = CurrentSiteManager(‘publish_on’)
Если вы воспользуетесь менеджером CurrentSiteManager, передав ему имя несуществующего поля, то будет возбуждено исключение ValueError.
Примечание
Надо полагать, что вы захотите сохранить в модели обычный (не привязанный к сайту) менеджер, даже если используете CurrentSiteManager. В приложении B поясняется, что в случае, когда менеджер задается вручную, Django не создает менеджер objects = models.Manager() автоматически.
Кроме того, в некоторых частях Django – точнее, в административном интерфейсе и в обобщенных представлениях – используется тот менеджер, который определен в модели первым, поэтому если необходимо, чтобы административный интерфейс получал доступ ко всем объектам (а не только относящимся к одному сайту), то предложение objects = models.Manager() следует поместить в модели раньше определения CurrentSiteManager.
Плоские страницы |
343 |
Как сам Django пользуется подсистемой сайтов
Включать подсистему сайтов в свое приложение необязательно, однако мы все же рекомендуем это сделать, потому что в некоторых местах Django может воспользоваться предоставляемыми ею преимуществами. Даже если инсталляция Django обслуживает только один сайт, все равно стоит потратить несколько секунд на создание объекта Site, записав в него имя и домен своего сайта и указав в параметре SITE_ID его идентификатор.
Опишем, как Django пользуется подсистемой сайтов:
•• В подсистеме переадресации (см. раздел «Переадресация» ниже) каждый объект переадресации ассоциируется с конкретным сайтом. При поиске цели переадресации Django учитывает текущее значение SITE_ID.
•• В подсистеме управления комментариями каждый комментарий ассоциируется с конкретным сайтом. При отправке комментария в поле site записывается значение SITE_ID, а в список комментариев, отображаемый соответствующим шаблонным тегом, включаются только комментарии для текущего сайта.
•• В подсистеме плоских страниц (см. раздел «Плоские страницы» ниже) каждая плоская страница ассоциируется с конкретным сайтом. При создании плоской страницы для нее задается поле site, а при отображении списка плоских страниц учитывается значение параметра SITE_ID.
•• В подсистеме синдицирования (см. главу 13) шаблоны для title и description автоматически получают доступ к переменной {{ site }}, которая ссылается на объект Site, представляющий текущий сайт. Кроме того, в URL элементов канала будет включено значение атрибута domain текущего объекта Site, если не указано полное доменное имя.
•• В подсистеме аутентификации (см. главу 14) представление django. contrib.auth.views.login передает в шаблон имя текущего сайта, в виде переменной {{ site_name }}, а сам текущий объект Site – в виде переменной {{ site }}/.
Плоские страницы
Часто бывает, что даже в динамическом приложении присутствует несколько статических страниц, например, «О программе» или «Политика конфиденциальности». Запросы к этим страницам можно было бы обслуживать с помощью стандартного веб-сервера, например Apache, но это лишь увеличивает сложность приложения, поскольку придется настраивать Apache и организовывать доступ к этим файлам для редактирования всем членам команды. К тому же при таком подходе вы
344 |
Глава 16. django.contrib |
не сможете использовать преимущества системы шаблонов Django для стилизации таких страниц.
В Django эта проблема решается за счет приложения «Плоские страницы» (flatpages), которое находится в пакете django.contrib.flatpages. Оно позволяет управлять такими нетипичными страницами с помощью административного интерфейса и определять для них стандартные шаблоны. В реализации используются модели Django, то есть сами страницы хранятся в базе данных наряду со всеми прочими данными, и для работы с ними можно применять стандартный API доступа к базе данных.
Плоские страницы индексированы по URL и по идентификатору сайта. При создании плоской страницы определяются ассоциированный с ней URL и идентификаторы одного или нескольких сайтов, которым она принадлежит. (Подробнее о сайтах см. раздел «Сайты» выше.)
Использование плоских страниц
Для установки приложения flatpages выполните следующие действия:
1.Добавьте в параметр INSTALLED_APPS строку ‘django.contrib.flatpages’. Так как это приложение зависит от django.contrib.sites, то в INSTALLED_ APPS должны присутствовать оба пакета.
2.Затем в параметр MIDDLEWARE_CLASSES добавьте строку ‘django.contrib. flatpages.middleware.FlatpageFallbackMiddleware’.
3.Выполните команду manage.py syncdb, которая создаст необходимые таблицы в базе данных.
Приложение flatpages создает две таблицы: django_flatpage и django_ flatpage_sites. Первая служит для сопоставления URL с заголовком и текстовым содержимым страницы, вторая – связующая таблица типа многие-ко-многим, которая ассоциирует плоскую страницу с одним или несколькими сайтами.
В комплект приложения входит модель FlatPage, определенная в файле django/contrib/flatpages/models.py следующим образом:
from django.db import models
from django.contrib.sites.models import Site
class FlatPage(models.Model):
url = models.CharField(max_length=100, db_index=True) title = models.CharField(max_length=200)
content = models.TextField(blank=True) enable_comments = models.BooleanField()
template_name = models.CharField(max_length=70, blank=True) registration_required = models.BooleanField()
sites = models.ManyToManyField(Site)
Рассмотрим все поля по очереди.
Плоские страницы |
345 |
•• url: URL плоской страницы, исключая доменное имя, но включая начальный символ слеша (например, /about/contact/).
•• title: заголовок страницы. Система никак не использует это поле, вы сами должны реализовать отображение содержимого этого поля в шаблоне.
•• content: содержимое страницы (ее HTML-разметка). Система никак не использует это поле, вы сами должны реализовать отображение содержимого этого поля в шаблоне.
•• enable_comments: следует ли разрешить оставлять комментарии на этой странице. Система никак не использует это поле. Вы можете проверить его значение в шаблоне и при необходимости вывести форму для ввода комментария.
•• template_name: имя шаблона для отображения страницы. Необязательное поле; если оно не задано или такого шаблона нет, то будет использоваться шаблон flatpages/default.html.
•• registration_required: требуется ли регистрация для просмотра данной страницы. Используется для интеграции с подсистемой аутентификации и управления пользователями, которая описана в главе 14.
•• sites: сайты, которым принадлежит страница. Используется для интеграции с подсистемой сайтов, которая описана в разделе «Сайты» выше.
Плоские страницы можно создавать как в административном интерфейсе Django, так и с помощью API доступа к базе данных. Дополнительные сведения см. в разделе «Добавление, изменение и удаление плоских страниц».
После того как плоские страницы созданы, всю остальную работу берет на себя дополнительный процессор FlatpageFallbackMiddleware. Всякий раз как Django пытается отправить ответ с кодом 404, этот процессор ищет в базе данных плоскую страницу с запрошенным URL. Точнее, ищется страница, для которой указан этот URL и в поле sites присутствует значение параметра SITE_ID.
Если поиск оказался успешным, то загружается шаблон плоской страницы или шаблон flatpages/default.html (если шаблон явно не задан). В шаблон передается единственная контекстная переменная flatpage – ссылка на объект FlatPage. При отображении шаблона применяется контекст RequestContext.
Если процессор FlatpageFallbackMiddleware не находит соответствия, то запрос обрабатывается как обычно.
Примечание
Этот дополнительный процессор подключается только для обработки ошибки 404 (страница не найдена) – он не используется для обработки ошибки 500 (ошибка сервера) и прочих ошибок. Отметим также, что порядок следования
346 |
Глава 16. django.contrib |
строк в списке MIDDLEWARE_CLASSES имеет значение. Вообще говоря, процессор FlatpageFallbackMiddleware лучше помещать как можно ближе к концу списка, так как это последнее средство.
Добавление, изменение и удаление плоских страниц
Добавлять, изменять и удалять плоские страницы можно двумя способами.
С помощью административного интерфейса
Если административный интерфейс Django активирован, то на главной странице появится раздел Flatpages. Редактировать плоские страницы можно точно так же, как любой другой объект.
С помощью Python API
Как уже отмечалось, плоские страницы представлены стандартной моделью Django, которая находится в файле django/contrib/flatpages/ models.py. Поэтому для работы с ними можно применять API доступа
кбазе данных, например:
>>>from django.contrib.flatpages.models import FlatPage
>>>from django.contrib.sites.models import Site
>>>fp = FlatPage.objects.create(
... |
url=’/about/’, |
... |
title=’About’, |
... |
content=’<p>About this site...</p>’, |
... |
enable_comments=False, |
... |
template_name=’’, |
... |
registration_required=False, |
... ) |
|
>>>fp.sites.add(Site.objects.get(id=1))
>>>FlatPage.objects.get(url=’/about/’) <FlatPage: /about/—About>
Шаблоны плоских страниц
По умолчанию все плоские страницы отображаются по шаблону flat pages/default.html, но с помощью поля template_name объекта FlatPage для конкретной страницы можно определить другой шаблон.
Ответственность за создание шаблона flatpages/default.html возлагается на вас. Создайте в каталоге шаблонов подкаталог flatpages, а в нем файл default.html.
В шаблон плоской страницы передается единственная контекстная переменная flatpage, являющаяся ссылкой на объект Flatpage. Ниже приводится пример файла flatpages/default.html:
Переадресация |
347 |
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN” “http://www.w3.org/TR/REC-html40/loose.dtd”>
<html>
<head>
<title>{{ flatpage.title }}</title> </head>
<body>
{{ flatpage.content|safe }} </body>
</html>
Отметим, что мы воспользовались шаблонным фильтром safe, который допускает наличие HTML-разметки в поле flatpage.content и отменяет автоматическое экранирование.
Переадресация
Подсистема переадресации в Django позволяет управлять переадресацией, сохраняя необходимую информацию в базе данных в виде обычных объектов модели. Например, можно сказать фреймворку Django: «Переадресуй любой запрос к /music/ на /sections/arts/music/». Это удобно, когда требуется изменить структуру сайта: веб-разработчик обязан принимать все меры к тому, чтобы не было «битых» ссылок.
Использование подсистемы переадресации
Для установки приложения выполните следующие действия:
1.Добавьте в параметр INSTALLED_APPS строку ‘django.contrib.redirects’.
2.Затем в параметр MIDDLEWARE_CLASSES добавьте строку ‘django.contrib. redirects.middleware.RedirectFallbackMiddleware’.
3.Выполните команду manage.py syncdb, которая создаст необходимую таблицу в базе данных.
Команда manage.py syncdb создаст в базе данных таблицу django_redirect, содержащую поля site_id, old_path и new_path.
Создавать объекты переадресации можно как в административном интерфейсе, так и с помощью API доступа к базе данных. Дополнительные сведения см. в разделе «Добавление, изменение и удаление объектов переадресации».
После создания всех объектов переадресации всю остальную работу берет на себя дополнительный процессор RedirectFallbackMiddleware. Всякий раз когда Django пытается отправить ответ с кодом 404, этот процессор отыскивает в базе данных объект переадресации с указанным URL в поле old_path со значением в поле site_id, которое указано в параметре SITE_ID. (О параметре SITE_ID и подсистеме сайтов см. раздел «Сайты» выше.) Далее выполняются следующие действия:
348 |
Глава 16. django.contrib |
1.Если найденная запись содержит непустое поле new_path, то производится переадресация на URL new_path.
2.Если найденная запись содержит пустое поле new_path, то отправляется ответ с кодом 410 («Gone») и пустым содержимым.
3.Если запись не найдена, запрос обрабатывается как обычно.
Примечание
Этот дополнительный процессор подключается только для обработки ошибки 404 (страница не найдена) – он не используется для обработки ошибки 500 (ошибка сервера) и прочих. Отметим также, что порядок следования строк в списке MIDDLEWARE_CLASSES имеет значение. Вообще говоря, процессор RedirectFallbackMiddleware лучше помещать как можно ближе к концу списка, так как это последнее средство.
Примечание
Если вы одновременно используете переадресацию и плоские страницы, подумайте, что следует проверять раньше. Мы рекомендуем сначала проверять плоские страницы, а потом переадресацию (то есть располагать процессор FlatpageFallbackMiddleware в списке раньше, чем RedirectFallback Middleware), но у вас может быть иное мнение.
Добавление, изменение и удаление объектов переадресации
Добавлять, изменять и удалять объекты переадресации можно двумя способами.
С помощью административного интерфейса
Если административный интерфейс Django активирован, то на главной странице появится раздел Redirects. Редактировать объект переадресации можно точно так же, как любой другой объект.
С помощью Python API
Объекты переадресации представлены стандартной моделью Django, которая находится в файле django/contrib/redirects/models.py. Поэтому для работы с ними можно применять API доступа к базе данных, например:
>>>from django.contrib.redirects.models import Redirect
>>>from django.contrib.sites.models import Site
>>>red = Redirect.objects.create(
... site=Site.objects.get(id=1),
... old_path=’/music/’,
... new_path=’/sections/arts/music/’,
Защита от атак CSRF |
349 |
... )
>>> Redirect.objects.get(old_path=’/music/’) <Redirect: /music/ ---> /sections/arts/music/>
Защита от атак CSRF
Пакет django.contrib.csrf защищает от подделки HTTP-запросов методом CSRF (cross-site request forgery), который иногда еще называют «угон сеанса». Это происходит, когда вредоносный сайт заставляет ничего не подозревающего пользователя загрузить URL с сайта, на котором пользователь уже аутентифицирован, и тем самым приобретает все права аутентифицированного пользователя. Чтобы понять, как это происходит, рассмотрим два примера.
Простой пример CSRF-атаки
Предположим, что вы зашли на страницу просмотра содержимого своего почтового ящика на сайте example.com. На этом сайте имеется ссылка Выйти с адресом URL example.com/logout. Это означает, что для выхода из системы вам нужно всего лишь перейти на страницу example.com/logout.
Вредоносный сайт может заставить вас посетить страницу example.com/ logout, включив этот URL в виде скрытого тега <iframe> на собственной странице. Таким образом, если вы прошли процедуру аутентификации на сайте example.com, а потом зашли на страницу вредоносного сайта, где присутствует тег <iframe> со ссылкой на example.com/logout, то сам факт посещения вредоносной страницы приведет к выходу из почты на сайте example.com.
Очевидно, что такое завершение сеанса работы с сайтом веб-почты – не такая уж страшная брешь в системе защиты, но точно такую же атаку можно организовать против любого сайта, доверяющего своим пользователям, например, предоставляющего банковские услуги или осуществляющего торговые операции. В этом случае можно было бы инициировать перевод денег или оплату заказа без ведома пользователя.
Более сложный пример CSRF-атаки
В предыдущем примере вина отчасти лежала и на сайте example.com, потому что он разрешил изменить состояние (выход из системы) в результате GET-запроса. Гораздо правильнее было бы реализовать изменение состояния на сервере исключительно с помощью POST-запросов. Но даже сайты, неукоснительно придерживающиеся такой политики, уязвимы для CSRF-атак.
Предположим, что функция выхода на сайте example.com была усовершенствована, и теперь кнопка выхода в форме <form> отправляет POSTзапрос на URL example.com/logout. Кроме того, в той же форме появилось скрытое поле: