Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
330
Добавлен:
01.03.2016
Размер:
4.88 Mб
Скачать

340

Глава 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. Кроме того, в той же форме появилось скрытое поле:

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]