Использование модификатора "method:" в Zope3
В статье описывается как в Zope3 создать форму, имеющую более одной кнопки submit и как обслуживать ее, используя модификатор :method запроса.
Использование модификатора "method:" в Zope3:
Широко известно, что записывая запросы к Zope3 можно указывать различные модификаторы в ключах аргументов запроса. В соответствии с ними значения аргументов преобразуются к определенным типам и формируются в определенные структуры. Разбор модификаторов и соответствующие преобразования значений выполняются этим методом:
zope.publisher.browser.BrowserRequest.processInputs
Подробному рассказу об этом место в другой статье, а здесь расскажем о модификаторе :method. Модификатор method широко использовался в Zope2, но в Zope3 используется достаточно редко, возможно, из-за низкой информированности разработчиков.
Попробуем восполнить этот пробел приведя пример, удобный для копирования: ответим на три вопроса: зачем, идея решения и пример.
Зачем? или постановка задачи :
Пусть существует форма, в которой присутствует более одной кнопки submit. Нажатие на каждую кнопку должен обрабатывать независимый обработчик, желательно реализованный на языке питон.
Характерный пример такой формы - управление различными контейнерами: от папки до корзины покупок.
Идея решения :
Свяжем с каждой кнопкой submit свое собственное имя, например:
<input type="submit" name="ww0" value="..."/>
... ... ... ... ... ...
<input type="submit" name="wwn" value="..."/>
Тогда нажатие на любую из кнопок сформирует уникальный запрос, в котором существует только ключ, указанный в декларации именно той кнопки, на которую нажали. Очевидно, несложная проверка на наличие ключа, позволяет обработать нажатие на конкретную кнопку особым образом.
Так пишут все - от perl:CGI до PHP, но разработчики Zope3 (точнее говоря, Zope2), вынесли проверку этого ключа в специальный диспетчер. Как это работает: если в запросе обнаруживается ключ с модификтором method (например ww0:method), то по окончании процедуры траверса, будет вызван не последний объект, найденный в траверсе, а метод ww0 этого объекта.
Причем, безразлично о каком объекте идет речь: контент-объекте или адаптере вида. Важно, что бы аттрибут ww0 был и его можно было вызвать.
Пример 1: Простой и неправильный :
Что бы протестировать вышеописанный диспетчер, напишем два zpt-метода: qq и ww.
Метод qq:
<html>
<body>
<h1>QQ</H1>
<form action="" tal:attributes="action context/@@absolute_url">
<input type="submit" name="qq:method" value="QQ"/>
<input type="submit" name="ww:method" value="WW"/>
</form>
</body>
</html>
Метод ww:
<html>
<body>
<h1>WW</H1>
<form action="" tal:attributes="action context/@@absolute_url">
<input type="submit" name="qq:method" value="QQ"/>
<input type="submit" name="ww:method" value="WW"/>
</form>
</body>
</html>
Создав оба метода в одной папке и вызвав один из них, можно понажимать на кнопки WW & QQ и убедится, что происходит вызов метода, соответствующего нажатой кнопке.
Раз все работает, то почему пример неверный? Потому, что в программировании под Zope3 принято разделять контент-объект и адаптер вида, причем вся логика взаимодействия с пользователем выносится именно в адаптер вида, сущетственная часть которого, зачастую, пишется на питоне.
Пример 2: Сложный и из реальной жизни:
Это адаптер вида, некоторого объекта, для которого нужно просматривать кой-какую статистическую информацию, а кроме того выполнять два действия: "перегенерацию" и "очистку". В чем смысл этих действий - не суть, контент объект мы рассматривать не будем.
Начнем с темлейта statistic.pt:
<html metal:use-macro="views/standard_macros/view">
<body metal:fill-slot="body">
<h1>Cache Statistics</H1>
<dl>
<dt>Size:</dt><dd tal:content="context/__len__">0</dd>
<tal:block tal:condition="options/count | nothing">
<dt>Regenerated:</dt>
<dd tal:content="options/count | nothing">0</dd>
</tal:block>
<tal:block tal:condition="options/count | nothing">
<dt>Elapsed:</dt>
<dd tal:content="options/elapsed | nothing">0</dd>
</tal:block>
</dl>
<form action="" tal:attributes="action string: ${context/@@absolute_url}/">
<input type="submit" name="regenerate_all:method" value="Regenerate All"/>
<input type="submit" name="clean:method" value="Clean"/>
</form>
</body>
</html>
В первой части темплейта выводится вышеупомянутая статистика, в т.ч. статистика выполняемых методов (обратите внимание на использование пространства имен options, который будут упомянут ниже). Вторая часть - это уже знакомая по предыдущему примеру формочка с двумя кнопками, вызывающие два разных метода. Разумеется, эти методы должны возвращать нечто, читабельное через браузер: удобно, если нечто основано на том же самом темплейте.
Методы должны быть сконфигурены, приведем кусок из configure.zcml:
<pages
permission="zope.ManageContent"
class=".statistic.Statistic"
for="..interfaces.ICachestore"
>
<page
name="statistic_view"
attribute="statistic_view"
menu="zmi_views" title="Statistic"
/>
<page
name="clean"
attribute="clean"
/>
<page
name="regenerate_all"
attribute="regenerate_all"
/>
/>
</pages>
Эта конфигурация предполагает, что основанный на классе .statistic.Statistic адаптер вида, предоставляет три метода, ориентированных на вызов через веб: statistic_view (вид по умолчанию) и clean & regenerate_all : выполняющие действия по кнопкам в темплейте.
Последняя часть - тот самый питоновский код, в который, благодаря такому подходу, удалось без проблем вынести обработчики кнопок.
Модуль statistic.py:
from zope.app.pagetemplate.viewpagetemplatefile \
import ViewPageTemplateFile
class Statistic(object) :
# Темлейт который используется для возврата
# результата во всех трех вызовах
statistic_view = ViewPageTemplateFile("statistic.pt")
# Обработчик кнопки regenerate_all:method
def regenerate_all(self,*kv,**kw) :
count,elapsed = self.context.regenerate_all()
# Обратите внимание, результаты вычислений передаются
# в темлейт как ключевые аргументы: в темплейте они попадут
# в пространство имен options
return self.statistic_view(self,count=count,elapsed=elapsed,*kv,**kw)
def clean(self,*kv,**kw) :
self.context.clean()
return self.statistic_view(self,*kv,**kw)
Вот такой простой класс позволяет эффективно запрограммировать на питоне всю логику взаимодействия адаптера вида с контент-объектом и оставить в темплейте только отображение.
Заключение:
Разумеется, вместо такого подхода, основанного на использовании диспетчера, встроенного в Zope3, можно добавить в код темплейта собственноручно изготовленный диспетчер примерно такого вида:
<tal:block tal:condition="request/forms/clean | nothing"
tal:content="view/clean"/>
<tal:block tal:condition="request/forms/regenerator_all | nothing"
tal:content="view/regenerator_all"/>
Не длиннее, ни короче: дело вкуса, хотя на мой вкус, использование модификатора method оставляет больше свободы.