Django_-_podrobnoe_rukovodstvo
.pdf280 |
Глава 13. Создание содержимого в формате, отличном от HTML |
•• Наконец, важно не забыть вызвать для объекта PDF методы show Page() и save(), иначе получится испорченный PDF-файл.
Создание сложных PDF-документов
При создании сложного PDF-документа (как и любого большого двоичного объекта) имеет смысл воспользоваться библиотекой cStringIO для временного хранения создаваемого файла. Она предоставляет интерфейс «файлоподобного» объекта, написанный на языке C для достижения максимальной эффективности.
Ниже приводится тот же самый пример «Hello World», переписанный с использованием cStringIO:
from cStringIO import StringIO from reportlab.pdfgen import canvas
from django.http import HttpResponse
def hello_pdf(request):
# Создать объект HttpResponse с заголовками для формата PDF. response = HttpResponse(mimetype=’application/pdf’) response[‘Content-Disposition’] = ‘attachment; filename=hello.pdf’
temp = StringIO()
#Создать объект PDF, используя объект StringIO в качестве
#“файла”.
p = canvas.Canvas(temp)
#Рисовать в PDF. Именно здесь происходит создание
#содержимого PDF-документа.
#Полное описание функциональности см. в документации ReportLab. p.drawString(100, 100, “Hello world.”)
#Закрыть объект PDF.
p.showPage()
p.save()
# Получить значение из буфера StringIO и записать его в ответ. response.write(temp.getvalue())
return response
Прочие возможности
Языком Python поддерживается возможность создания содержимого во множестве других форматов. Ниже перечислены ссылки на некоторые библиотеки, позволяющие это делать.
•• ZIP-файлы. В стандартную библиотеку Python входит модуль zipfile, который позволяет читать и записывать сжатые файлы в формате ZIP. С его помощью можно по запросу создавать архивы, включающие несколько файлов, или сжимать объемные документы. А мо-
Создание каналов синдицирования |
281 |
дуль tarfile из стандартной библиотеки позволяет создавать архивы в формате TAR.
•• Динамические изображения. Библиотека Python Imaging Library (PIL; http://www.pythonware.com/products/pil/) включает фантастический набор инструментов для создания изображений в форматах PNG, JPEG, GIF и многих других. С ее помощью можно автоматически создавать миниатюры изображений, объединять несколько изображений в одно и даже реализовывать интерактивную обработку картинок на сайте.
•• Графики и диаграммы. Существует целый ряд мощных библиотек на языке Python, предназначенных для рисования графиков и диаграмм. С их помощью можно производить визуализацию данных по запросу. Перечислить все нет никакой возможности, но две ссылки мы все же дадим:
•• matplotlib (http://matplotlib.sourceforge.net/) позволяет создавать высококачественные графики, аналогичные тем, что создаются в программах MatLab или Mathematica.
•• pygraphviz (http://networkx.lanl.gov/pygraphviz/) – интерфейс к пакету Graphviz для рисования графиков (http://graphviz.org/). Можно использовать для создания структурированных рисунков, содержащих графики и схемы.
Вообще говоря, к Django можно подключить любую библиотеку на языке Python, умеющую писать в файл. Возможности поистине безграничны.
Теперь, познакомившись с основами создания содержимого в формате, отличном от HTML, перейдем на следующий уровень абстракции. В дистрибутив Django входит ряд удобных инструментов для создания файлов в нескольких распространенных форматах.
Создание каналов синдицирования
В Django имеется высокоуровневая система для создания каналов синдицирования, упрощающая генерацию лент новостей в форматах RSS и Atom.
Что такое RSS и Atom?
RSS и Atom – основанные на XML форматы, позволяющие авто-
матически обновлять ленту новостей вашего сайта. Подробнее об RSS можно прочитать на сайте http://www.whatisrss.com/, а об Atom – на сайте http://www.atomenabled.org/.
282 |
Глава 13. Создание содержимого в формате, отличном от HTML |
Для создания синдицированного канала достаточно написать простой класс на Python. Количество каналов не ограничено.
В основе системы создания каналов лежит представление, с которым по соглашению ассоциирован образец URL /feeds/. Окончание URL (все, что находится после /feeds/) Django использует для идентификации канала.
Чтобы создать канал, напишите Feed-класс и добавьте ссылку на него в конфигурации URL.
Инициализация
Чтобы активировать систему каналов синдицирования на своем Djangoсайте, добавьте в конфигурацию URL такую строку:
(r’^feeds/(?P<url>.*)/$’, ‘django.contrib.syndication.views.feed’, {‘feed_dict’: feeds}
),
Она послужит для Django инструкцией к использованию системы RSS для обработки всех URL, начинающихся с feeds/. (При желании префикс feeds/ можно заменить другим.)
В этой строке присутствует дополнительный аргумент: {‘feed_dict’: feeds}. С его помощью можно сообщить, какие каналы следует публиковать для данного URL.
Точнее, feed_dict – это словарь, отображающий ярлык канала (короткая метка в URL) на его Feed-класс. Определить его можно прямо в конфигурации URL, например:
from django.conf.urls.defaults import *
from mysite.feeds import LatestEntries, LatestEntriesByCategory
feeds = {
‘latest’: LatestEntries,
‘categories’: LatestEntriesByCategory,
}
urlpatterns = patterns(‘’,
# ...
(r’^feeds/(?P<url>.*)/$’, ‘django.contrib.syndication.views.feed’, {‘feed_dict’: feeds}),
# ...
)
Здесь регистрируются два канала:
•• Канал по адресу feeds/latest/, представленный классом LatestEntries.
•• Канал по адресу feeds/categories/, представленный классом Latest EntriesByCategory.
Теперь необходимо реализовать сами Feed-классы.
Создание каналов синдицирования |
283 |
Класс Feed – это обычный класс на языке Python, описывающий канал синдицирования. Канал может быть совсем простым (например, лента новостей сайта, в которой присутствуют последние записи в блоге) или более сложным (например, лента, содержащая записи в блоге, относящиеся к конкретной категории, причем категория заранее неизвестна).
Всеклассыканаловдолжнынаследоватьклассdjango.contrib.syndication. feeds.Feed. Находиться они могут в любом месте проекта.
Простая лента новостей
В следующем примере описывается канал, содержащий последние пять записей из указанного блога:
from django.contrib.syndication.feeds import Feed from mysite.blog.models import Entry
class LatestEntries(Feed): title = “Мой блог” link = “/archive/”
description = “Последние новости по теме.”
def items(self):
return Entry.objects.order_by(‘-pub_date’)[:5]
Отметим следующие существенные моменты:
•• Класс является производным от django.contrib.syndication.feeds.Feed.
•• Атрибуты title, link и description соответствуют определенным в стандарте RSS элементам <title>, <link> и <description> соответственно.
•• Метод items() возвращает список объектов, который следует включить в ленту в виде элементов <item>. В этом примере возвращаются объекты Entry, полученные с помощью API доступа к данным, но в общем случае возвращать можно не только экземпляры моделей.
Остался еще один шаг. Каждый элемент <item> в RSS-канале должен содержать подэлементы <title>, <link> и <description>. Мы должны сообщить, какие данные в эти элементы помещать.
•• Чтобы определить содержимое элементов <title> и <description>, создайте шаблоны feeds/latest_title.html и feeds/latest_description.html, где latest – ярлык для данного канала в конфигурации URL. Расширение .html обязательно.
Система производит отображение этого шаблона для каждого элемента канала, передавая в контексте две переменные:
•• obj: текущий объект (один из тех, что вернул метод items()).
•• site: объект класса django.models.core.sites.Site, представляющий текущий сайт. Удобно использовать в виде таких конструкций, как {{ site.domain }} или {{ site.name }}.
284 |
Глава 13. Создание содержимого в формате, отличном от HTML |
Если для заголовка или описания нет шаблона, то по умолчанию система будет использовать шаблон “{{ obj }}”, то есть обычное представление объекта в виде строки. (Для объектов моделей значением будет результат вызова метода __unicode__().)
Имена обоих шаблонов можно изменить с помощью атрибутов title_ template и description_template вашего Feed-класса.
•• Определить элемент <link> можно двумя способами. Для каждого объекта, возвращаемого методом items(), Django сначала пытается вызвать его метод get_absolute_url(). Если такого метода нет, то Django пытается вызвать метод item_link() Feed-класса, передавая ему в качестве единственного параметра item сам объект.
Методы get_absolute_url() и item_link() должны возвращать URL объекта в виде обычной строки Python.
•• В приведенном выше классе LatestEntries можно было бы ограничиться очень простыми шаблонами канала. Файл latest_title.html содержит строку
{{obj.title }}
афайл latest_description.html – строку
{{obj.description }}
Но это слишком просто…
Более сложная лента новостей
Фреймворк поддерживает также возможность создания более сложных каналов с помощью параметров.
Допустим, что ваш блог предлагает отдельный RSS-канал для каждого тега классификации записей. Было бы глупо создавать Feed-класс для каждого тега; это явилось бы прямым нарушением принципа DRY (Don’t Repeat Yourself – не повторяйся), кроме того, это привело бы к образованию тесной связи между данными и логикой программы.
Вместо этого система синдицирования позволяет создать обобщенный канал, который возвращает различные данные в зависимости от информации, имеющейся в URL канала.
Адреса URL каналов для разных тегов могли бы выглядеть так:
•• http://example.com/feeds/tags/python/: возвращает последние записи с тегом «python».
•• http://example.com/feeds/tags/cats/: возвращает последние записи
стегом «cats».
Вданном случае ярлыком канала является tags. Система извлекает часть URL, следующую за ярлыком – python или cats – и передает ваше-
Создание каналов синдицирования |
285 |
му классу дополнительную информацию, на основе которой можно решить, какие элементы следует публиковать в канале.
Поясним на примере. Вот код формирования содержимого канала в зависимости от тега:
from django.core.exceptions import ObjectDoesNotExist from mysite.blog.models import Entry, Tag
class TagFeed(Feed):
def get_object(self, bits):
#На случай URL вида “/feeds/tags/cats/dogs/mice/”
#проверим, содержит ли bits единственный компонент. if len(bits) != 1:
raise ObjectDoesNotExist
return Tag.objects.get(tag=bits[0])
def title(self, obj):
return “Мой блог: записи с тегом %s” % obj.tag
def link(self, obj):
return obj.get_absolute_url()
def description(self, obj):
return “Записи с тегом %s” % obj.tag
def items(self, obj):
entries = Entry.objects.filter(tags__id__exact=obj.id) return entries.order_by(‘-pub_date’)[:30]
Опишем алгоритм системы генерации RSS на примере этого класса и обращения к URL /feeds/tags/python/:
1.Система получает URL /feeds/tags/python/ и видит, что за ярлыком есть остаток. Этот остаток разбивается на части по символу /, после чего вызывается метод get_object() Feed-класса, которому передается получившийся список.
Вданном случае список состоит из одного элемента [python]. При обра-
щении к URL /feeds/tags/python/django/ получился бы список [‘python’, ‘django’].
2.Метод get_object() отвечает за извлечение объекта Tag из параметра bits.
Вданном случае для этого используется API доступа к базе данных. Отметим, что при получении недопустимых параметров метод get_object() должен возбудить исключение django.core.exceptions. ObjectDoesNotExist. Вызов метода Tag.objects.get() не вложен в блок try/except, потому что в этом нет необходимости. Эта функция в случае ошибки возбуждает исключение типа Tag.DoesNotExist, а класс
Tag.DoesNotExist является подклассом ObjectDoesNotExist. Исключение ObjectDoesNotExist, возбужденное в методе get_object(), заставляет Django вернуть в ответ на этот запрос ошибку 404.
286 |
Глава 13. Создание содержимого в формате, отличном от HTML |
3.Для создания элементов <title>, <link> и <description> Django пользуется методами title(), link() и description(). В предыдущем примере это были простые строковые атрибуты класса, но теперь мы видим, что они с равным успехом могут быть и методами. Для каждого элемента применяется следующий алгоритм:
a.Пытаемся вызвать метод, передав ему аргумент obj, где obj – объект, полученный от get_object().
b.В случае ошибки пытаемся вызвать метод без аргументов.
c.В случае ошибки берем атрибут класса.
4.Наконец, отметим, что в этом примере метод items() принимает аргумент obj. Алгоритм здесь такой же, как и выше: сначала производится вызов items(obj), потом items() и в конце выполняется попытка обратиться к атрибуту класса с именем items (который должен быть списком).
Полную информацию обо всех методах и атрибутах Feed-классов можно найти в официальной документации по Django (http://docs.djangoproject. com/en/dev/ref/contrib/syndication/).
Определение типа канала
По умолчанию система синдицирования создает канал в формате RSS 2.0. Чтобы выбрать другой формат, необходимо добавить в свой Feed-класс атрибут feed_type:
from django.utils.feedgenerator import Atom1Feed
class MyFeed(Feed):
feed_type = Atom1Feed
Отметим, что атрибут feed_type определяется на уровне класса, а не экземпляра. Поддерживаемые в настоящее время типы каналов приведены в табл. 13.1.
Таблица 13.1. Типы каналов синдицирования
Feed-класс |
Формат |
|
|
|
|
django.utils.feedgenerator.Rss201rev2Feed |
RSS 2.01 |
(по умолчанию) |
django.utils.feedgenerator.RssUserland091Feed |
RSS 0.91 |
|
django.utils.feedgenerator.Atom1Feed |
Atom 1.0 |
|
|
|
|
Вложения
Для добавления вложений (то есть мультимедийных ресурсов, ассоциированных с элементом канала, например, подкаст в формате MP3) применяются точки подключения item_enclosure_url, item_enclosure_length и item_enclosure_mime_type:
Создание каналов синдицирования |
287 |
from myproject.models import Song
class MyFeedWithEnclosures(Feed):
title = “Пример канала с вложениями” link = “/feeds/example-with-enclosures/”
def items(self):
return Song.objects.all()[:30]
def item_enclosure_url(self, item): return item.song_url
def item_enclosure_length(self, item): return item.song_length item_enclosure_mime_type = “audio/mpeg”
Здесь предполагается наличие объекта класса Song с полями song_url и song_length (размер в байтах).
Язык
В каналы, созданные системой синдицирования, автоматически включается элемент <language> (RSS 2.0) или атрибут xml:lang (Atom). Его значение берется из параметра LANGUAGE_CODE.
URL-адреса
Метод (или атрибут) link может возвращать абсолютный URL (например, /blog/) или URL, содержащий полное доменное имя и протокол (например, http://www.example.com/blog/). Если домен не указан в link, то система синдицирования вставит доменное имя текущего сайта из параметра SITE_ID. (Об этом параметре и о подсистеме сайтов вообще см. главу 16.)
Для каналов в формате Atom необходимо определить элемент в виде <link rel=”self”> с описанием текущего местоположения канала. Система синдицирования подставляет значение автоматически.
Одновременная публикация новостей в форматах Atom и RSS
Некоторые разработчики предпочитают публиковать новости в обоих форматах – Atom и RSS. В Django это нетрудно: достаточно создать подкласс своего Feed-класса и записать в атрибут feed_type альтернативное значение. Затем следует включить в конфигурацию URL дополнительную запись. Например:
from django.contrib.syndication.feeds import Feed from django.utils.feedgenerator import Atom1Feed from mysite.blog.models import Entry
class RssLatestEntries(Feed): title = “Мой блог”
288 Глава 13. Создание содержимого в формате, отличном от HTML
link = “/archive/”
description = “Последние новости по теме.”
def items(self):
return Entry.objects.order_by(‘-pub_date’)[:5]
class AtomLatestEntries(RssLatestEntries): feed_type = Atom1Feed
И соответствующая конфигурация URL:
from django.conf.urls.defaults import *
from myproject.feeds import RssLatestEntries, AtomLatestEntries
feeds = {
‘rss’: RssLatestEntries, ‘atom’: AtomLatestEntries,
}
urlpatterns = patterns(‘’,
# ...
(r’^feeds/(?P<url>.*)/$’, ‘django.contrib.syndication. . views.feed’, {‘feed_dict’: feeds}),
# ...
)
Карта сайта
Картой сайта (sitemap) называется хранящийся на веб-сайте XMLфайл, который сообщает поисковым системам, как часто изменяются страницы сайта и насколько одни страницы важнее других. Это помогает поисковой системе индексировать сайт более осмысленно.
В качестве примера ниже приводится фрагмент карты сайта проекта Django (http://www.djangoproject.com/sitemap.xml):
<?xml version=”1.0” encoding=”UTF-8”?>
<urlset xmlns=”http://www.sitemaps.org/schemas/sitemap/0.9”> <url>
<loc>http://www.djangoproject.com/documentation/</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url> <loc>http://www.djangoproject.com/documentation/0_90/</loc> <changefreq>never</changefreq>
<priority>0.1</priority>
</url>
...
</urlset>
Дополнительные сведения о картах сайта см. по адресу http://www.site maps.org/.
Карта сайта |
289 |
Подсистема карты сайта в Django автоматизирует создание этого XMLфайла, позволяя выразить его содержимое в виде программного кода на языке Python. Чтобы создать карту сайта, сначала необходимо написать Sitemap-класс и сослаться в нем на конфигурацию URL.
Установка
Чтобы установить приложение sitemap, выполните следующие действия:
1.Добавьте строку ‘django.contrib.sitemaps’ в параметр INSTALLED_APPS.
2.Убедитесь, что в параметре TEMPLATE_LOADERS присутствует строка
‘django.template.loaders.app_directories.load_template_source’. По умолчанию она там есть, поэтому вносить изменения придется, только если ранее вы изменили список загрузчиков шаблонов.
3.Убедитесь, что установлена подсистема сайтов (см. главу 16).
Примечание
Приложение sitemap ничего не добавляет в базу данных. Его необходимо добавить в параметр INSTALLED_APPS только для того, чтобы загрузчик шаблонов load_template_source смог отыскать шаблоны по умолчанию.
Инициализация
Чтобы активировать создание карты сайта на своем Django-сайте, добавьте в конфигурацию URL такую строку:
(r’^sitemap\.xml$’, ‘django.contrib.sitemaps.views.sitemap’, {‘sitemaps’: sitemaps})
Эта строка предписывает Django построить карту сайта при обращении к URL /sitemap.xml. (Обратите внимание, что точка в названии файла sitemap.xml экранируется символом обратного слеша, так как точка в регулярных выражениях имеет особый смысл.)
Конкретное имя файла карты сайта не важно, а вот местоположение существенно. Поисковые системы индексируют ссылки в карте сайта только на уровне текущего URL и ниже. Например, если файл sitemap. xml находится в корневом каталоге, то он может ссылаться на любой URL сайта. Но если карта сайта хранится в файле /content/sitemap.xml, то ей разрешено ссылаться только на URL, начинающиеся с /content/.
Представление карты сайта принимает еще один обязательный параметр {‘sitemaps’: sitemaps}. Здесь предполагается, что sitemaps – словарь, отображающий короткую метку раздела (например, blog или news) на соответствующий ей Sitemap-класс (например, BlogSitemap или NewsSitemap). Метке может также соответствовать экземпляр Sitemap-
класса (например, BlogSitemap(some_var)).