Все о Linux. LinuxRSP.Ru
Альт Линукс СПТ 6.0 Сертификат ФСТЭК

Cвежие новости Linux и BSD, анонсы статей и книг прямо в почтовый ящик!
Подписаться письмом


 Сегодняшние новости:

В GIMP наконец-то появилась обработка с точностью 16 и 32 разряда на канал

В одном окне: GIMP 2.8

Релиз OpenBSD 5.1

HTML-редактор: BlueGriffon 1.5

Релиз Xfce 4.10

Команда Chromium представила кластер для автоматизации выявления уязвимостей

В Microsoft Office 15 будет обеспечена поддержка формата ODF 1.2

Анонсирован новый свободный анимационный фильм Tube

Обновление ядра: Linux 3.0.29, 3.2.16, 3.3.3

Представлен OpenSSH 6.0

Релиз графического редактора Pinta 1.2, претендующего на роль аналога Paint.NET

Вышел Firefox 12

Google прекращает поддержку Linux-версии приложения Picasa

Эмулятор приставок: Mednafen 0.9.21-wip

Линус Торвальдс получил премию Millennium Technology Prize 2012

Canonical не заинтересована в участии в разработке ядра Linux

Распределенные вычисления: BOINC 7.0

Google

 Новые статьи :

Передача снапшота ZFS по сети

Правила хорошего тона: SSH

Некоторые рассуждения о защите от ddos на примере защиты от SSL denial of service attack

Быстрый проброс портов на шлюзе во внутреннюю сеть или на другой хост. Быстро NAT'им порты. rinetd.

NAT'им отдельные порты или "а что делать, если мой веб-сервер внезапно переехал на другую машину". Проброс портов в локалке на шлюзе наружу.

Как справляться с запланированной недоступностью веб-сайта

Архитектура Google 2011

Мониторинг Nginx с помощью MRTG

Решение проблем при использовании "1c предприятие" 8.2 в Linux

25 правил .htaccess, которые должен знать каждый web-разработчик

Централизованное резервное копирование данных Windows и *nix серверов средствами Bacula

Краткое руководство по GNOME Shell в Ubuntu 11.10

Advanced Bash-Scripting Guide Искусство программирования на языке сценариев командной оболочки

   Дистрибутивы и атрибутика



DeveloperWorks Россия





Rambler's Top100


 
 

Создание динамических сайтов на платформе Zope на примере сайта zope.net.ru

Автор: Олег Бройтман

ВВЕДЕНИЕ
--------

Zope - это объектно-ориентированная платформа для создания интерактивных, динамических сайтов и web-приложений. В этой статье я расскажу и покажу, как создаются динамические сайты на примере сайта zope.net.ru; интерактива на сайте пока нет, но динамические объекты есть. Сайт недавно переехал на хороший хостинг, и его можно наконец загружать примерами и пр.

Для того, чтобы не повторять уже много раз сказанное, отсылаю заинтересованного читателя к моей предыдущей статье, в которой рассказаны общие принципы, устройство web-application server Zope, дана и пояснена необходимая терминология. Особенно прошу обратить внимание на описание механизма acquisition - на сайте zope.net.ru этот механизм используется, хотя и не во всю свою силу.

В целом сайт zope.net.ru не только community site нашей Группы Пользователей, но и реальный demo-site, на котором можно посмотреть, что и как устроено. Все "конечные" объекты (страницы, видимые пользователю) имеют ссылку "Показать DTML код объекта". Но многое остается скрытым. Этой статьей я, в частности, хочу прояснить то, что остается "за кадром" DTML кода.

Предупреждение посетителям. Включите, пожалуйста, поддержку CSS в вашем браузере. Без CSS сайт выглядит не очень красиво, а некоторые вещи не работают вовсе, например, подсветка текущего раздела в горизонтальной навигации.

УСТРОЙСТВО страниц
----------

Весь сайт совершенно динамический - генерится все, кроме картинок. Генерится даже CSS. Этого можно было бы избежать, сделав CSS-файл статическим, или настроив параметры кеширования этого объекта (main.css - это экземпляр класса ZStyleSheet), тем более что этот CSS вызывается во всех страницах... но пока я не вижу нужды это делать.

Каждая HTML-страница на сайте тоже генерится, и генерится она из множества объектов.

Каждая страница, очевидно, имеет стандартную обвязку (оформление) и уникальное содержание. Поэтому каждая страница сайта - это DTML Method стандартного устройства:

   <dtml-var standard_html_header>
      ...здесь содержание страницы...
   <dtml-var standard_html_footer>

Это, разумеется, не единственный способ создания страниц, но Зоп в каком-то смысле навязывает именно его. А именно в том смысле, что когда верстальщик создает новый DTML Method или Document, standard_html_header и footer вставляются в текст автоматически. Их можно убрать, конечно.

Некоторые разработчики находят это неправильным. По той причине, что если верстальщику надо изменить оформление, ему приходится редактировать header и footer отдельно, что неудобно. Для таких капризных разработчиков можно придумать следующий способ: в корне сайта ставится Метод index_html с фиксированным содержанием, у которого оформление и содержание "вывернуты наизнанку":

   <HTML>... и прочее оформление...
   <dtml-var folder_real_content>
   ...и подвал...</HTML>

и во всех папках создаются объекты с именем folder_real_content, хранящие только содержание. При обращении к такой папке будет произведено заимствование index_html (acquisition!), который заимствуется из корня, и сам заимствует folder_real_content из текущей папки.

Лично мне такой подход кажется неправильным. Во-первых, сама отсылка на верстальщика мало помогает. Все равно оформление создается разработчиком из немалого числа объектов, и уменьшение этого числа на 1 мало чему помогает. Во-вторых, я теперь не могу создать страницу phd2.html - каждая страница обязана быть папкой. Неоправданное ограничение.

Поэтому на сайте zope.net.ru все устроено именно так, как навязывает Zope.

standard_html_header - с чего начинается каждая страница сайта
--------------------

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

http://zope.net.ru/standard_html_header/view_source?pp=1

Здесь я вычисляю URL корня, отделяю его от path, и при необходимости (установлена переменная text-version) добавляю строку "/text"; это все для текстовой версии, подробности ниже. Плюс в URLn запоминаю первый объект в path после корня - это для горизонтальной навигации и тому подобного.

ГОРИЗОНТАЛЬНАЯ НАВИГАЦИЯ (О нас | Python | Zope)
------------------------

Очень простой DTML Метод global-nav, вызывается из standard_html_header: http://zope.net.ru/global-nav/view_source?pp=1

Я проверяю упомянутую URLn, если не index_html - значит рендерится не корень, и я вставляю в HTML ссылку на корень. Затем по очереди проверяю каждый из главных подразделов сайта, и подсвечиваю тот из них, в котором находимся.

Функционирование этой навигационной плашки демонстрируется на следующей последовательности адресов:
http://zope.net.ru/
http://zope.net.ru/About/
http://zope.net.ru/Python/
http://zope.net.ru/Zope/

ВЕРТИКАЛЬНАЯ НАВИГАЦИЯ (левая колонка сайта)
----------------------

В самом начале существования сайта я не стал заморачиваться со сложной левой колонкой. Для начала я хотел, чтобы там был простой список подразделов текущего раздела, плюс ссылки на другие главные разделы сайта. Поскольку я хотел их писать в угодном мне порядке, я не стал обходить дозором сайт, а просто поместил в корень и в главные разделы сайта списки с именем left-col-list, и левая колонка (left-column) их заимствовала из текущего контекста. И идею, и способ реализации я подглядел на zope.org:

http://www.zope.org/Members/phd (см. левую колонку), http://www.zope.org/Members/phd/local_nav/view_source?pp=1

Альтернативным вариантом было бы промаркировать каждую из папок, которую я хочу поместить в навигацию, каким-нибудь атрибутом (скажем, left-col-view) и показывать в навигации папки не из заранее заготовленного списка, а те, у которых этот атрибут установлен. А для сортировки сделать этот атрибут не булевским, а числовым - весом. Но тогда неудобно сортировать список папок. Если мне надо поменять местами 2 папки, приходится открывать множество экранов и редактировать этот атрибут отдельно. Неудобно, поэтому я так и остался со списком left-col-list.

Через некоторое время существования сайта я решил, что хорошо бы левую колонку усложнить и сделать покрасивее. Пусть, скажем, корневые (главные) разделы сайта будут отдельно, а подразделы текущего раздела пусть вставляются в середину списка, да еще с отступом. Очень не хотелось дублировать информацию (то есть чтобы каждый left-col-list содержал в себе еще и пункты предыдущего уровня) - слишком сложно было бы для редактирования. Устройство данных и алгоритм вполне очевидны - надо просканировать все поддерево сайта от корня до текущей папки, найти все left-col-list и объединить их в иерархическую структуру - каждый left-col-list ищет себе место в предыдущем уровне. Написать такую конструкцию на DTML... наверно, можно было бы, но сложно. Тут в первый раз за все время существования сайта я обратился к Python и написал External Method. Вот его код:

http://zope.net.ru/Zope/navigation_left_column

Там простая рекурсивная функция default_render, которая обегает полученную структуру и рендерит ее в HTML, и собственно метод navigation_leftColumn обхода сайта от корня. В процессе его создания я столкнулся с необходимостью выключить acquisition - в данном случае он оказался излишним, ведь я хочу получать реальные left-col-list в их соответствующих папках, а никак не заимствованные! Очень хорошо, никаких проблем, Zope позволяет сделать и это. Я проверяю наличие объекта не в parent, а в parent.aq_explicit - подобъекте, в котором заимствование в точности выключено. После чего ренедерю DTML-объект left-col-list в питоновский список - для этого DTML-объект надо вызывать, передав параметрами текущий контекст: leftcol_list(self, _), и простым циклом ищу, куда бы этот список залинковать на предыдущем уровне.

Кончается все вызовом функции render. Сначала это был default_render, а потом я ее переписал на DTML, чтобы легче было редактировать:

http://zope.net.ru/navigation_lcRender/view_source?pp=1

В результате левая колонка свелась к простому коду

http://zope.net.ru/left-column/view_source?pp=1

представляющему собой HTML-обрамление вызова navigation_leftColumn.

Функционирование левой колонки демонстрируется на следующей последовательности адресов:
http://zope.net.ru/
http://zope.net.ru/Python/
http://zope.net.ru/Zope/
http://zope.net.ru/Zope/HOWTO/

На любом уровне можно посмотреть содержимое left-col-list, например: http://zope.net.ru/Zope/left-col-list/view_source?pp=1

ВЕРСИЯ ДЛЯ ПЕЧАТИ и текстовая версия
-----------------

На сайте, в объекте standard_html_footer есть ссылки на текстовую версию сайта и версию страницы для распечатки:

http://zope.net.ru/standard_html_footer/view_source?pp=1

Изначально существовала только версия для распечатки. Реализована она крайне просто - в URL передается параметр pp (printable page), затем ZPublisher вводит эту переменную в пространство имен (в Zope это делается автоматом), а в standard_html_header/footer ее значение (на самом деле просто присутствие и отличие от нуля) проверяется. В случае отсутствия pp (или нуля) генерится полная версия страницы, со всем оформлением, а в случае присутствия - генерится страница только с содержанием, без оформления:
http://zope.net.ru/standard_html_header/view_source?pp=1
http://zope.net.ru/standard_html_footer/view_source?pp=1

Затем один из членов нашей Группы, Денис Откидач, предложил добавить еще специальную текстовую версию. Отличие от версии для печати - в ссылках. В версии для печати все ссылки ведут на страницы с оформлением. А в текстовой версии все ссылки должны вести опять-таки на текстовые версии страниц.

Реализация текстовой версии прошла несколько этапов. Самым первым был вариант, когда средствами Апача все адреса http://zope.net.ru/text/(.*) переписывались в http://zope.net.ru/$1 с добавлением упомянутой переменной pp :) Это не вполне работало, потому что ссылки все еще были "не туда".

Нынешняя реализация проста до неприличия за счет использования acquisition. В корне сайта создана папка /text. Она совершенно пуста. Это ничему не мешает. Если рендерится http://zope.net.ru/ - то вызовется корневой index_html, а если рендерится http://zope.net.ru/text/ - то этот index_html позаимствуется из корня.

В чем тогда суть? А суть в том, что папке /text приписаны 2 атрибута - pp и text-version. Благодаря переменной pp Метод index_html, заимствованный из http://zope.net.ru/text/ будет рендерится без оформления (переменная pp в данном случае заимствуется из /text, а не передается через URL), в отличии от непосредственного вызова http://zope.net.ru/. А переменная text-version является флагом, благодаря которому standard_html_header добавит строку "/text" к переменной VirtualRoot. Ну и остается пройтись по сайту и заставить все ссылки на корень рендерится через VirtualRoot - тогда все ссылки в текстовой версии будут опять-таки вести на URL с префиксом "/text": http://zope.net.ru/text/

ПОИСК
-----

В Zope есть встроенный механизм поиска - ZCatalog. Он не работает с морфологией, не ищет по регулярным выражениям. Что-то вроде htDig, к которому не прикрутили морфологию. Но! Есть у Z-Каталога одно большое достоинство - тесная интеграция с Zope. Я могу индексировать только определенные объекты, по дате, могу ограничиться только объектами, для которых у роли X есть право доступа Y и т.п. Кроме того, после индексации объекты сами говорят своим каталогам "я изменился - переиндексируй меня", о чем в htDig приходится только мечтать. Аналогично и при добавлении новых объектов и удалении старых - они посылают сообщение каталогу. Точнее, могут посылать - для этого их классы надо наследовать от CatalogAware.

Для начала работы надо добавить на сайт экземпляр или несколько экземпляров класса ZCatalog. Я добавил 1 в корень, и назвал его search-catalog. Затем сайт первый раз индексируется. Я проиндексировал полностью все объекты, у которых Anonimous имеет право View - хочу сделать публичный поиск. В процессе индексации Z-Каталог создает несколько индексов. Какие именно - дело менеджера. Я не стал менять умолчания, и поэтому у меня создались:

  • текстовый индекс для полнотекстового поиска по содержанию
  • текстовый индекс для поиска по атрибуту title каждого объекта

и еще несколько, которые здесь неинтересны.

Форму для поиска я загнал в отдельный мелкий Метод http://zope.net.ru/search-form/view_source?pp=1, для того, чтобы иметь одну копию формы (с параметрами - показывать ли кнопку "Искать", и размер поля ввода), а саму форму вставлять в разные места.

Первое место, где эта форма используется - отдельная страница поиска http://zope.net.ru/search/. Устроена она просто:

http://zope.net.ru/search/view_source?pp=1. Стандартное оформление плюс вызов упомянутого Метода с параметром "показать кнопку".

Сам поиск реализован на DTML же... ну то есть на DTML написан вызов Z-Каталога и оформление результатов:

http://zope.net.ru/search-results/view_source?pp=1.

Сначала я получаю ссылку на сам объект каталог: catalog=_.getitem('search-catalog', 0), затем проверяю, был ли передан в форме параметр text_search. Если да - делаю 2 поиска по каталогу - по содержимому текстов (индекс PrincipiaSearchSource) и по заголовкам (индекс title). Результаты двух поисков склеиваю - это такой способ выполнить операцию OR. Операция AND поддерживается в таком виде: catalog(id="index_html", title="Python"). О памяти/скорости не беспокоюсь - ZCatalog полностью поддерживает lazy evaluation, и даже суммирование результатов не заставляет его грузить в память все объекты.

Если text_search не было - просто делаю пустой запрос к каталогу; при этом найдутся все объекты.

Ну и выдача результирующего HTML - простой цикл по списку результатов с разбивкой на страницы.

Текстовая версия тоже работает. Работает как переход их полной версии в текстовую, так и версия для распечатки, причем ссылки из текстовой версии результатов поиска честно ведут на текстовые версии документов. Я почему это подчеркиваю? Да потому что я потратил на текстовую версию не больше полчаса, и с тех пор пользуюсь результатами. Плюс еще минут 10 я потратил, чтобы передать запрос на странице результатов поиска в ссылки на текстовые и печатные версии.

НОВОСТИ и импорт новостей
-------

Самой активной, часто меняющейся частью сайта являются разделы импортируемых новостей. Новости импортируются из источников по Питону и Зоп (плюс несколько других, менее интересных). Поток новостей идет в формате RSS 0.91. Разбором приходящего XML занимается компонент RSS Channel, он же и хранит список элементов потока, плюс простые DTML Методы для оформления результатов.

Импорт осуществляется по команде программы, запускающейся из cron несколько раз в сутки. Сейчас сайт хостится в Питере, у провайдера http://square.spb.ru/, программы запускаются в Москве и обращаются к сайту по HTTP. Это один из двух главных протоколов RPC, по которому можно обратиться к Zope (второй - это, конечно, XML-RPC).

Новости показываются в правой колонке сайта, кроме корня. В корне новости показывает корневой index_html: http://zope.net.ru/index_html/view_source?pp=1 В правой колонке новости показывает сам объект right-column: http://zope.net.ru/right-column/view_source?pp=1 Он создает HTML-оформление для right-col-news и показывает на каждой страницу стандартную картинку Zope. Сам он вызывается из standard_html_footer.

Если объект right-column на сайте один - в корне, то объектов с именем right-col-news несколько - в корне и в каждом из главных разделов сайта. Когда right-column рендерится, он заимствует нужный right-col-news из текущего контекста. Так что при желании можно переопределить содержание этой колонки в любом разделе:
http://zope.net.ru/Python/right-col-news/view_source?pp=1
http://zope.net.ru/Zope/right-col-news/view_source?pp=1

MAINTAINANCE (backup, pack Data.fs)
------------

Каждый сайт требует какого-то обслуживания, регулярной чистки, резервного копирования и т.п. Наиболее просто в Zope делается backup. Зоп позволяет проэкспортировать любой объект (вплоть до корня ZODB). Экспорт может сделать в файл ZEXP (внутренний формат ZODB) или XML. Любой из экспортных файлов потом импортируется назад, при необходимости. Более того, формат ZODB и ZEXP полностью переносим между всеми платформами и ОС. Можно проэкспортировать сайт с NT на AMD и проимпортировать на спарковый Солярис! Экспортный файл можно получить по сети (по HTTP) или сохранить в файловой системе сервера. Я запускаю backup из cron раз в неделю, экспортирую весь сайт в ZEXP (до создания поиска файл занимал 300K, вместе с каталогом он теперь чуть больше мегабайта), получаю его по HTTP и складываю на своей машине. Время от времени я запускаю backup руками - для того чтобы получить самую свежую версию и положить ее на локальный сервер для отладки.

Второй процесс, уже не относящийся непосредственно к сайту - упаковка файла Data.fs. Файл этот - физическое представление ZODB с хранилищем FileStorage. Достоинство этого хранилища - простота. Zope, поставленный из дистрибутива, работает именно с этим хранилищем. Есть и другие хранилища - BerkeleyStorage и пр. Их недостаток - отсутствие Undo и Версий. Есть хранилища типа InformixStorage и OracleStorage, поддерживающие Undo и Версии, но они требуют соответствующих SQL-серверов. Зато они не растут, как Data.fs, и не требуют упаковки. Хранилища без Undo тоже не растут.

Я пользуюсь FileStorage. Это хранилище держит всю ZODB в одном файле Data.fs (плюс индекс Data.fs.index, что здесь совершенно неважно). Файл этот растет - все транзакции FileStorage дописывает только в конец файла. Поэтому время от времени следует избавляться от старых транзакций - упаковать файл. Команда Pack в Зоп требует вещественного параметра - число дней, за которые оставить транзакции в базе. Я делаю упаковку раз в неделю, в понедельник ночью, оставляя транзакций за последние 3 дня. Это позволяет мне в понедельник утром сделать Undo операции, которую я совершил в пятницу. Команду Pack я также, конечно, вызываю из cron, по HTTP.

РЕЗЮМЕ и планы на будущее
------

В своем классе продуктов - сервера web приложений (web application servers) - Zope не уникальный продукт, но обладающий массой достоинств, которыми он меня привлек, и я использую Zope со все большим удовольствием. Тем более что разработчики Zope весьма открыты, и немало моих собственных патчей, и патчей, сделанных по моей просьбе, вошло в код.

Чего не хватает именно на нашем сайте - внятной content-модели, устройства документов. План, соответственно, таков - создать, или взять готовые, или довести до ума полуготовые Z-Классы, описывающие устройство документа (заголовок - содержание - автор - дата публикации - и т.п.), и перевести все нынешние простые документы в эту структуру. Проиндексировать Z-Каталогом по отдельным полям. Это позволит, например, запросить каталог "дай список всех авторов" (то есть уникальных входов в индекс author) и создать страничку "Все авторы", со ссылками на публикации каждого автора. В будущем, если количество авторов, пишущих для сайта, станет велико, можно будет создать полноценную CMS (content management system).



      

Связь | О проекте LinuxRSP | Реклама | О Linux
© 1999-2012 LinuxRSP


Реклама: