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

Django_-_podrobnoe_rukovodstvo

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

280

Глава 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)).

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