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

Django_-_podrobnoe_rukovodstvo

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

290

Глава 13. Создание содержимого в формате, отличном от HTML

Sitemap-классы

Sitemap-класс – это обычный класс Python, который представляет отдельный раздел в карте сайта. Например, один Sitemap-класс может представлять все записи о блоге, а другой – все записи в календаре событий.

В простейшем случае все разделы объединяются в один файл sitemap. xml, но подсистема­ может создать индекс карт сайта, который ссылается на отдельные карты, по одной на каждый раздел (см. ниже).

Все Sitemap-классы должны наследовать класс django.contrib.sitemaps. Sitemap и могут находиться в любом месте дерева проекта. Предположим, к примеру, что имеется система­ блогов с моделью Entry, и ваша задача – построить карту сайта, которая включала бы ссылки на отдельные записи в блогах. Вот как может выглядеть соответствующий

Sitemap-класс:

from django.contrib.sitemaps import Sitemap from mysite.blog.models import Entry

class BlogSitemap(Sitemap): changefreq = “never” priority = 0.5

def items(self):

return Entry.objects.filter(is_draft=False)

def lastmod(self, obj): return obj.pub_date

Объявление Sitemap-класса очень напоминает объявление Feed-класса. Это не случайно. Как и в случае Feed-классов, члены Sitemap-класса могут быть как методами, так и атрибутами. О том, как работает этот механизм, см. раздел «Более сложный канал» выше.

В Sitemap-классе могут быть определены следующие методы или атрибуты:

•• items (обязательный): предоставляет список объектов. Тип объектов системе­ безразличен, важно лишь, что они передаются методам location(), lastmod(), changefreq() и priority().

•• location (необязательный): возвращает абсолютный URL данного объекта. Здесь под «абсолютным» понимается URL, не содержащий протокола и доменного имени, например:

•• Правильно: ‘/foo/bar/’

•• Неправильно: ‘example.com/foo/bar/’

•• Неправильно: ‘http://example.com/foo/bar/’

В случае отсутствия атрибута location подсистема­ будет вызывать метод get_absolute_url() для каждого объекта, возвращаемого методом items().

Карта сайта

291

•• lastmod (необязательный): дата последней модификации объекта в виде экземпляра класса Python datetime.

•• changefreq (необязательный): как часто объект изменяется. Допустимы следующие значения (описанные в спецификации Sitemaps):

•• 'always'

•• 'hourly'

•• 'daily'

•• 'weekly'

•• 'monthly'

•• 'yearly'

•• 'never'

•• priority (необязательный): рекомендуемый приоритет индексирования, значение между 0.0 и 1.0. По умолчанию принимается приоритет 0.5; дополнительные сведения о механизме работы приоритетов см. в документации на сайте http://www.sitemaps.org/.

Вспомогательные классы

Подсистема­ карты сайта предлагает два готовых класса для наиболее распространенных случаев. Они описаны в следующих разделах.

FlatPageSitemap

Класс django.contrib.sitemaps.FlatPageSitemap отыскивает все «плоские страницы» сайта и для каждой создает одну запись в карте. В этих записях присутствует только атрибут location; атрибуты lastmod, changefreq, priority отсутствуют.

Дополнительные сведения о плоских страницах см. в главе 16.

GenericSitemap

Класс GenericSitemap работает с любыми имеющимися обобщенными представлениями (см. главу 11).

Чтобы воспользоваться им, создайте экземпляр, передав ему такой же словарь info_dict, как обобщенным представлениям. Единственное требование – в словаре должен быть ключ queryset. Может присутствовать также ключ date_field, в котором задается дата для объектов, извлеченных из queryset. Она станет значением атрибута lastmod в сгенерированной карте сайта. Конструктору класса GenericSitemap можно также передать именованные аргументы priority и changefreq, определяющие значения одноименных атрибутов для всех URL.

Ниже приводится пример конфигурации URL, в которой используются оба класса FlatPageSitemap и GenericSiteMap (с тем же гипотетическим объектом Entry, что и выше).

292 Глава 13. Создание содержимого в формате, отличном от HTML

from django.conf.urls.defaults import *

from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap from mysite.blog.models import Entry

info_dict = {

‘queryset’: Entry.objects.all(), ‘date_field’: ‘pub_date’,

}

sitemaps = {

‘flatpages’: FlatPageSitemap,

‘blog’: GenericSitemap(info_dict, priority=0.6),

}

urlpatterns = patterns(‘’,

#некое обобщенное представление, в котором используется

#info_dict

#...

#карта сайта

(r’^sitemap\.xml$’, ‘django.contrib.sitemaps.views.sitemap’, {‘sitemaps’: sitemaps})

)

Создание индекса карт сайта

Подсистема­ карты сайта умеет также создавать индекс карт сайта, который ссылается на отдельные карты, по одной для каждого раздела, определенного в словаре sitemaps. Есть только два отличия:

•• В конфигурации URL должно быть определено два представления: django.contrib.sitemaps.views.index и django.contrib.sitemaps.views. sitemap.

•• Представление django.contrib.sitemaps.views.sitemap должно принимать именованный параметр section.

Вот как выглядят соответствующие строки конфигурации URL для предыдущего примера:

(r’^sitemap.xml$’, ‘django.contrib.sitemaps.views.index’, {‘sitemaps’: sitemaps}),

(r’^sitemap-(?P<section>.+).xml$’, ‘django.contrib.sitemaps.views.sitemap’, {‘sitemaps’: sitemaps})

При этом автоматически будет создан файл sitemap.xml, ссылающийся на файлы sitemap-flatpages.xml и sitemap-blog.xml. Sitemap-классы и словарь sitemaps никак не изменятся.

Что дальше?

293

Извещение Google

При изменении карты своего сайта вы, возможно, захотите известить Google о необходимости переиндексировать сайт. Для этого имеется специальная функция django.contrib.sitemaps.ping_google().

Данная функция принимает необязательный параметр sitemap_url, который должен содержать абсолютный URL карты сайта (например, ‘/sitemap.xml’). Если этот аргумент опущен, то ping_google() попытается определить адрес карты сайта путем просмотра конфигурации URL.

Если ping_google() не удается определить URL карты сайта, она возбудит исключение django.contrib.sitemaps.SitemapNotFound.

from django.contrib.sitemaps import ping_google

class Entry(models.Model):

# ...

def save(self, *args, **kwargs): super(Entry, self).save(*args, **kwargs) try:

ping_google() except Exception:

#Тип исключения не уточняется, потому что возможны также

#различные исключения, связанные с HTTP

pass

Однако гораздо эффективнее вызывать ping_google() из cron-сценария или иной периодически выполняемой задачи. Эта функция отправляет запрос на серверы Google, поэтому не стоит вызывать ее при каждом обращении к методу save() из-за возможных сетевых задержек.

Наконец, если в параметре INSTALLED_APPS присутствует строка ‘django. contrib.sitemaps’, то сценарий manage.py будет реагировать также на команду ping_google. Это позволяет обратиться к Google с напоминанием из командной строки, например:

python manage.py ping_google /sitemap.xml

Что дальше?

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

14

Сеансы, пользователи и регистрация

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

Но на самом деле это, конечно, неверно. За каждым броузером, обращающимся к нашему сайту, стоит конкретный человек (ну, по большей части). И нельзя забывать, что Интернет по-настоящему имеет смысл, когда используется для соединения людей, а не машин. Разрабатывая неотразимый сайт, мы все же ориентируемся на тех, кто смотрит на экран броузера.

К сожалению, не все так просто. Протокол HTTP спроектирован так, что

не сохраняет информацию о состоянии соединения, то есть все запросы независимы друг от друга. Между предыдущим и следующим запросом нет никакой связи, и не существует такого свойства запроса (IP-адрес, агент пользователя и т. п.), которое позволило бы надежно идентифицировать цепочку последовательных запросов от одного и того же лица.

В этой главе мы расскажем, как можно компенсировать отсутствие информации о состоянии. Начнем с самого низкого уровня (cookie), а затем перейдем к более высокоуровневым средствам поддержки сеансов, аутентификации пользователей и регистрации.

Cookies

Разработчики броузеров уже давно поняли, что отсутствие информации о состоянии в протоколе HTTP ставит серьезную проблему перед веб-программистами. Поэтому на свет появились cookies. Cookie – это небольшой блок информации, который отправляется веб-сервером и сохраняется броузером. Запрашивая любую страницу с некоторого

Cookies

295

сервера, броузер посылает ему блок информации, который получил от него ранее.

Посмотрим, как действует этот механизм. Когда вы открываете броузер и вводите в адресной строке google.com, броузер посылает серверу Google HTTP-запрос, который начинается так:

GET / HTTP/1.1 Host: google.com

...

Полученный от Google ответ выглядит приблизительно так:

HTTP/1.1 200 OK Content-Type: text/html

Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671; expires=Sun, 17-Jan-2038 19:14:07 GMT;

path=/; domain=.google.com Server: GWS/2.1

...

Обратите внимание на заголовок Set-Cookie. Броузер сохранит значение cookie (PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671) и будет отправлять его Google при каждом обращении к этому сайту. Поэтому при следующем посещении сайта Google запрос, отправленный броузером, будет иметь такой вид:

GET / HTTP/1.1 Host: google.com

Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671

...

Обнаружив заголовок Cookie, Google понимает, что запрос пришел от человека, уже посещавшего сайт. Значением cookie может быть, например, ключ в таблице базы данных, где хранятся сведения о пользователе. И Google мог бы (да, собственно, так и делает) отобразить на странице имя вашей учетной записи.

Получение и установка cookies

Для сохранения состояния в Django обычно предпочтительнее работать на уровне сеансов или пользователей, о чем речь пойдет ниже. Но сначала посмотрим, как можно читать и записывать cookies на нижнем уровне. Это поможет вам понять истинный механизм работы средств, рассматриваемых далее в этой главе, и пригодится, если когда-нибудь потребуется манипулировать значениями cookie напрямую.

Прочитать ранее установленный cookies совсем просто. В каждом объекте HttpRequest имеется объект COOKIES, который выглядит как словарь. Из него можно извлечь все cookie, переданные броузером в представление:

296

Глава 14. Сеансы, пользователи и регистрация

def show_color(request):

if “favorite_color” in request.COOKIES:

return HttpResponse(“Ваш любимый цвет %s” % \ request.COOKIES[“favorite_color”])

else:

return HttpResponse(“У вас нет любимого цвета.”)

Операция записи в cookie выглядит чуть сложнее. Для этого потребуется вызвать метод set_cookie() объекта HttpResponse. Вот как устанавливается cookie с идентификатором favorite_color, исходя из параметра GET запроса:

def set_color(request):

if “favorite_color” in request.GET:

# Создать объект HttpResponse ...

response = HttpResponse(“Теперь ваш любимый цвет %s” % \ request.GET[“favorite_color”])

# ... и установить в ответе cookie response.set_cookie(“favorite_color”,

request.GET[“favorite_color”]) return response

else:

return HttpResponse(“Вы не указали любимый цвет.”)

Методу response.set_cookie() можно также передать дополнительные параметры, управляющие различными аспектами формирования cookie (табл. 14.1).

Таблица 14.1. Параметры cookie

Параметр

Значение по

Описание

 

умолчанию

 

 

 

 

max_age

None

Время хранения cookie в секундах. Если параметр

 

 

равен None, то cookie будет храниться до момента

 

 

закрытия броузера.

expires

None

Точная дата и время окончания срока хранения

 

 

cookie. Задается в формате “Wdy, DD-Mth-YY HH:MM:SS

 

 

GMT”. Если этот параметр задан, то он отменяет

 

 

параметр max_age.

path

“/”

Префикс пути, для которого действует этот

 

 

cookie. Броузер будет передавать данный cookie

 

 

только при обращении к страницам, URL кото-

 

 

рых начинается с этого префикса. Следовательно,

 

 

этот параметр позволяет предотвратить отправку

 

 

cookie в другие разделы сайта.

 

 

Это особенно полезно, когда домен верхнего уров-

 

 

ня сайта вне вашего контроля.

 

 

 

Cookies

 

 

297

 

Параметр

 

Значение по

 

Описание

 

 

 

 

 

 

умолчанию

 

 

 

 

 

 

 

 

 

domain

 

None

 

Домен, для которого действует этот cookie.

 

 

 

 

 

С помощью данного параметра можно установить

 

 

 

 

 

междоменный cookie. Например, cookie со значе-

 

 

 

 

 

нием параметра domain=”.example.com”, будет до-

 

 

 

 

 

ступен в доменах www.example.com, www2.example.com

 

 

 

 

 

и an.other.sub.domain.example.com.

 

 

 

 

 

Если этот параметр равен None, то cookie будет до-

 

 

 

 

 

ступен только серверу, установившему его.

 

secure

 

False

 

Если этот параметр равен True, то броузер будет

 

 

 

 

 

посылать данный cookie только по защищенному

 

 

 

 

 

HTTPS-соединению.

 

 

 

 

 

 

Обратная сторона cookies

Возможно, вы заметили кое-какие потенциальные проблемы, присущие механизму cookie. Рассмотрим наиболее важные из них:

•• Сохранение cookies – дело добровольное; клиент не обязан принимать и сохранять cookies. На самом деле все броузеры позволяют пользователям самостоятельно определять порядок приема cookies. Чтобы ощутить, насколько важны cookies во Всемирной паутине, попробуйте включить в броузере режим подтверждения при приеме каждого cookie.

Несмотря на практически повсеместное использование, cookies попрежнему остаются весьма ненадежным средством передачи информации. Поэтому прежде чем полагаться на них, веб-приложение должно проверить, принимает ли их клиент пользователя.

•• Cookies (особенно посылаемые не по протоколу HTTPS) никак не защищены. Поскольку по протоколу HTTP данные передаются в открытом виде, то cookies особенно уязвимы для прослушивания. То есть злоумышленник, подключившийся к кабелю, сможет перехватить cookie и прочитать его. Поэтому секретную информацию никогда не следует хранить в cookie.

Существует еще более опасный вид атак: «человек посередине», когда злоумышленник перехватывает cookie и с его помощью выдает себя за другого пользователя. Такого рода атаки и способы их предотвращения обсуждаются в главе 20.

•• Cookies не защищены даже от законных получателей. Большинство броузеров позволяют спокойно изменять содержимое cookies, а изобретательный пользователь может воспользоваться инструментальными средствами (http://wwwsearch.sourceforge.net/mechanize/) для конструирования HTTP-запросов вручную.

298

Глава 14. Сеансы, пользователи и регистрация

Поэтому не следует хранить в cookies данные, чувствительные к манипулированию. Типичная ошибка – сохранение в cookies чего-то вроде IsLoggedIn=1 после успешного входа в систему­. Вы не поверите, сколько сайтов допускают эту ошибку; чтобы обойти их систему­ защиты, достаточно нескольких секунд.

Подсистема­ сеансов в Django

С учетом всех ограничений и потенциальных уязвимостей становится понятно, что cookies и сохраняемые сеансы являются примерами болевых точек веб-разработки. Но так как фреймворк Django стремится быть эффективным целителем, в него входит подсистема­ сеансов, предназначенная для преодоления этих трудностей.

Эта подсистема­ позволяет сохранять произвольные данные о каждом посетителе сайта. Данные хранятся на сервере, а сам механизм абстрагирует отправку и получение cookies. Внутри cookies передается только свертка идентификатора сеанса, а не сами данные, что позволяет защититься от большинства проблем, связанных с cookies.

Рассмотрим, как включить поддержку сеансов и использовать их в представлениях.

Включение поддержки сеансов

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

1.Убедитесь, что параметр MIDDLEWARE_CLASSES содержит строку ‘django. contrib.sessions.middleware.SessionMiddleware’.

2.Убедитесь, что в параметре INSTALLED_APPS присутствует приложение ‘django.contrib.sessions’ (если вам пришлось его добавить, не забудьте выполнить команду manage.py syncdb).

В заготовке файла с параметрами, созданной командой startproject, обе строки уже присутствуют, поэтому, если вы их не удаляли, для включения сеансов делать ничего не придется.

Если вы не хотите использовать сеансы, можете удалить строку

SessionMiddleware из параметра MIDDLEWARE_CLASSES и строку ‘django. contrib.sessions’ из параметра INSTALLED_APPS. Накладные расходы при этом уменьшатся лишь чуть-чуть, но курочка по зернышку клюет.

Использование сеансов в представлениях

Если процессор SessionMiddleware активирован, то каждый объект HttpRequest (первый аргумент любой функции представления в Django) будет иметь атрибут session, аналогичный словарю. К нему можно обращаться как к обычному словарю. Например:

Подсистема­ сеансов в Django

299

#Установить значение переменной сеанса: request.session[“fav_color”] = “blue”

#Получить значение переменной сеанса — эта операция может выполняться

#и в другом представлении, и в текущем, и даже много запросов спустя. fav_color = request.session[“fav_color”]

#Удалить переменную сеанса:

del request.session[“fav_color”]

# Проверить наличие переменной сеанса: if “fav_color” in request.session:

...

Объект request.session поддерживает и другие методы словаря, в частности keys() и items(). Для эффективной работы с сеансами в Django существует ряд простых правил.

•• Используйте в качестве ключей словаря request.session обычные строки Python (а не целые числа, объекты и т. д.);

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

Например, не следует использовать в сеансе ключ _fav_color:

request.session[‘_fav_color’] = ‘blue’ # Не делайте так!

•• Не подменяйте request.session новым объектом, не изменяйте значения его атрибутов и не добавляйте новые атрибуты. Используйте его исключительно как словарь Python. Например:

request.session = some_other_object #

Не

делайте

так!

request.session.foo = ‘bar’

#

Не

делайте

так!

Рассмотрим несколько примеров. В следующем простеньком представлении переменной has_commented присваивается значение True после того, как пользователь отправит свой комментарий. Это простой (но не очень надежный) способ предотвратить отправку пользователем более одного комментария:

def post_comment(request):

if request.method != ‘POST’:

raise Http404(‘Разрешены только POST-запросы’)

if ‘comment’ not in request.POST:

raise Http404(‘Отсутствует комментарий’)

if request.session.get(‘has_commented’, False):

return HttpResponse(“Вы уже отправляли комментарий.”)

c = comments.Comment(comment=request.POST[‘comment’])

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