Russian Apache Switch to English
Switch to Russian koi8-r
windows=1251
cp-866
iso8859-5
Russian Apache Как это работает Рекоммендации Где взять Как установить Как настроить Статус и поддержка
Краткий обзор FAQ Список рассылки Благодарности Поиск по серверу Powered by Russian Apache
Russian Apache mailing list archive (apache-rus@lists.lexa.ru)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[apache-talk] zope.net.ru as an example of Zope dynamic site



Здравствуйте. Лекция.

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


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

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

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

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

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


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

   Весь сайт совершенно динамический - генерится все, кроме картинок.
Генерится даже CSS: http://zope.net.ru/main.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).

Oleg.
---- 
     Oleg Broytmann            http://phd.pp.ru/            phd@xxxxxxxxx
           Programmers don't die, they just GOSUB without RETURN.
=============================================================================
=               Apache-Talk@xxxxxxxxxxxxx mailing list                      =
Mail "unsubscribe apache-talk" to majordomo@xxxxxxxxxxxxx if you want to quit.
=       Archive avaliable at http://www.lexa.ru/apache-talk                 =






Спонсоры сайта:

[ Russian Apache ] [ Как это работает ] [ Рекомендации ] [ Где взять ] [ Как установить ] [ Как настроить ] [ Статус и поддержка ] [ Краткий обзор ] [ FAQ ] [ Список рассылки ] [ Благодарности ] [ Поиск по серверу ] [ Powered by Russian Apache ] [ Apache-talk archive ]

"Russian Apache" includes software developed by the Apache Group for use in the Apache HTTP server project (http://www.apache.org/) See Apache LICENSE.
Copyright (C) 1995-2001 The Apache Group. All rights reserved.
Copyright (C) 1996 Dm. Kryukov; Copyright (C) 1997-2009 Alex Tutubalin. Design (C) 1998 Max Smolev.