пятница, 4 апреля 2008 г.

Собственная форма добавления

Собственная форма добавления:

У нас есть интерфейс:

1.# -*- coding: koi8-r -*-
2.from zope.interface import Interface
3.from zope.schema import TextLine
4.
5.class IMark(Interface):
6.
7. datax = TextLine(
8. title=u"Data",
9. description=u"Des",
10. required=False)

И есть его реализация:

1.# -*- coding: koi8-r -*-
2.__docformat__ = 'restructuredtext'
3.
4.from zope.interface import implements
5.from zope.app.container.contained import Contained
6.
7.from boom.interfaces import IMark
8.from persistent import Persistent
9.
10.class Mark(Persistent):
11. implements(IMark)
12.
13. datax = u''
14.
15.from zope.component.factory import Factory
16.
17.markFactory = Factory(
18. Mark,
19. title=u"Create a new mark",
20. description = u"This factory instantiates new mark."
21. )

Здесь мы добавляем в строке 17 фабрику, которая служит для создания нашего объекта.
Теперь нужно написать собственный класс добавления
:

1.from zope.component import createObject
2.
3.from zope.formlib.form import AddForm, Fields, applyChanges
4.from boom.interfaces import IMark
5.
6.from zope.formlib.namedtemplate import NamedTemplate
7.from zope.formlib.namedtemplate import NamedTemplateImplementation
8.from zope.app.pagetemplate import ViewPageTemplateFile
9.
10.class MarkAddForm(AddForm):
11.
12. form_fields = Fields(IMark)
13. label = u"Add BookMarker"
14.
15. template = NamedTemplate('mark.form')
16.
17.
18. def create(self, data):
19. mark = createObject(u'boom.mark')
20. applyChanges(mark, self.form_fields, data)
21. return mark
22.
23.form_template = NamedTemplateImplementation(
24. ViewPageTemplateFile('form.pt'))

Класс добавления наследуется от базового AddForm, в строке 12 мы указываем
что поля для формы нужно брать из нашего интерфейса.
В
15 строке мы указываем шаблон который будет использоваться для формы.
В строке
18 определяем метод который перекрывает создание объекта.
В строке
23 создаем объект который нужен для связки шаблона.

Конфигурация:

1.<configure
2. xmlns="http://namespaces.zope.org/zope"
3. xmlns:browser="http://namespaces.zope.org/browser">
4.
5. <interface
6. interface=".interfaces.IMark"
7. type="zope.app.content.interfaces.IContentType"
8. />
9.
10. <content class=".bookmarker.Mark">
11. <require
12. permission="zope.ManageContent"
13. interface=".interfaces.IMark"/>
14. <require
15. permission="zope.ManageContent"
16. set_schema=".interfaces.IMark"
17. />
18. </content>
19.
20.<browser:addMenuItem
21. factory="boom.mark"
22. title="Mark"
23. permission="zope.ManageContent"
24. view="boom.mark"
25. />
26.
27. <browser:page
28. for="zope.app.container.interfaces.IAdding"
29. name="boom.mark"
30. class=".form.MarkAddForm"
31. permission="zope.ManageContent"
32. />
33.
34. <adapter
35. factory=".form.form_template"
36. for=".form.MarkAddForm"
37. name="mark.form"
38. />
39.
40. <utility
41. component=".bookmarker.markFactory"
42. name="boom.mark"
43. permission="zope.Public"
44. />
45.</configure>

1-3: Объявление директив
5-8: Указываем что наш объект контентный
10-18: Указываем права для нашего объекта
20-25: Директива добавления
21 – указываем фабрику для создания объекта
24 – вид для добавления
27-32: Директива для класса добавления
29 — имя совпадающее с видом в директиве addMenuItem
34-37: Адаптер для класса добавления, определяет шаблон
40-43: Утилита определения фабрики

Шаблон form.pt

1.<html xmlns="http://www.w3.org/1999/xhtml"
2. xmlns:tal="http://xml.zope.org/namespaces/tal"
3. xmlns:metal="http://xml.zope.org/namespaces/metal"
4. metal:use-macro="context/@@standard_macros/view">
5.<body>
6.<div metal:fill-slot="body">
7.<form class="edit-form" enctype="multipart/form-data" method="post"
8. action="." tal:attributes="action request/URL">
9.
10. <h1 tal:content="view/label">Edit something</h1>
11.
12. <div class="summary" tal:condition="view/status"
13. tal:content="view/status">Status</div>
14.
15. <div class="row" tal:repeat="widget view/widgets">
16. <div class="label">
17. <label for="field.name" title="The widget's hint"
18. tal:attributes="for widget/name; title widget/hint"
19. tal:content="widget/label">Label</label>
20. </div>
21.
22. <div tal:condition="widget/error"
23. tal:content="structure widget/error">Error</div>
24.
25. <div class="field">
26. <input tal:replace="structure widget" />
27. </div>
28. </div>
29.
30. <span class="actionButtons" tal:condition="view/availableActions">
31. <input tal:repeat="action view/actions"
32. tal:replace="structure action/render"
33. />
34. </span>
35.
36.</form>
37.</div>
38.</body>
39.</html>

Выводит стандартные виджеты ввода, определение виджетов происходит
по полям указанным в классе добавления формы.

Как в Zope3 генерируется нужная форма.

Допустим у нас есть интерфейс:

1.# -*- coding: utf-8 -*-
2.__docformat__ = 'restructuredtext'
3.
4.from zope.interface import Interface
5.from zope.schema import TextLine
6.
7.class IMark(Interface):
8.
9. data = TextLine(
10. title=u"Title",
11. description=u"Des",
12. required=False)

У него есть атрибут data который является экземпляром класса TextLine
импортированного из shema.


И есть реализация:

1.# -*- coding: utf-8 -*-
2.__docformat__ = 'restructuredtext'
3.
4.from zope.interface import implements
5.from zope.app.container.contained import Contained
6.
7.from boom.interfaces import IMark
8.
9.class Mark(Contained):
10. implements(IMark)

В реализации мы показываем что класс Mark представляет интерфейс IMark.

Конфигурация:

1.<configure
2. xmlns="http://namespaces.zope.org/zope"
3. xmlns:browser="http://namespaces.zope.org/browser">
4.
5. <interface
6. interface=".interfaces.IMark"
7. type="zope.app.content.interfaces.IContentType"
8. />
9.
10. <content class=".bookmarker.Mark">
11. <implements
12. interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
13. />
14. <factory
15. id="boom.bookmarker.Mark"
16. description="A book mark."
17. />
18. <require
19. permission="zope.ManageContent"
20. interface=".interfaces.IMark"/>
21. <require
22. permission="zope.ManageContent"
23. set_schema=".interfaces.IMark"
24. />
25. </content>
26.
27.<browser:addMenuItem
28. class="boom.bookmarker.Mark"
29. title="Mark"
30. description="Test"
31. permission="zope.ManageContent"
32. view="AddMark.html"
33. />
34.
35. <browser:addform
36. label="Add Mark"
37. name="AddMark.html"
38. schema="boom.interfaces.IMark"
39. content_factory="boom.bookmarker.Mark"
40. permission="zope.ManageContent"
41. />
42.</configure>

Строки 2742 как раз позволяют добавить нам наш объект и сгенерировать для него форму,
директива addform имеет имя которое совпадает с видом в директиве addMenuItem,
таким образом при добавлении объекта Mark будет на основе интерфейса boom.interfaces.IMark
прописанного в строке
38 сгенерирована нужная нам форма.
Строка 39 позволяет привязаться к нашей реализации.
Остальные параметры можно посмотреть в apidoc — Справка по ZCML.
В представлении HTML форма имеет следующий вид:

1.<form enctype="multipart/form-data" method="post"
action
="http://localhost:091/+/AddMark.html%3D">
2. <div>
3. <h3>Add Mark</h3>
4. <div class="row">
5.
6. <div class="label">
7. <label title="Des" for="field.data">Title</label>
8. </div>
9.
10. <div class="field"><input type="text" value="" size="20"
name
="field.data" id="field.data" class="textType"/>
11.</div>
12.
13. </div>
14. <div class="separator"/>
15. <div class="separator"/>
16.
17. </div>
18. <br/><br/>
19. <div class="row">
20. <div class="controls"><hr/>
21. <input type="submit" value="Обновить"/>
22. <input type="submit" name="UPDATE_SUBMIT" value="Добавить"/>
23.
24. <b>Название объекта</b>
25. <input type="text" value="" name="add_input_name"/>
26.
27. </div>
28. </div>
29.
30. <div class="separator"/>
31. </form>

Как было описано выше форма генерируется на основе нашего интерфейса IMark
в котором прописан TextLine, теперь посмотрим его реализацию.

TextLine импортируется из zope.shema, смотрим __init__.py:
from zope.schema._field import Text, TextLine, Bool, Int, Float
видно что импорт из _field. где он опять же импортируется:
from zope.schema._bootstrapfields import Text, TextLine, Bool, Int, Password

так же указыватся что он представляет интерфейс ITextLine:
classImplements(TextLine, ITextLine)

смотрим _bootstrapfields:

1.class TextLine(Text):
2. """A text field with no newlines."""
3.
4. def constraint(self, value):
5. return '\n' not in value and '\r' not in value

Там видно что TextLine наследуется от Text который в свою очередь наследуется от Field,
где как раз обьекту присваиваются параметры из нашей data.

...
1.self.title = title
2.self.description = description
3.self.required = required
4.self.readonly = readonly
...
Теперь собственно посмотрим реализацию форм.

Деректива addform обрабатывается в zope.app.form.browser.metaconfigure.AddFormDirective,

где как раз устанавливается шаблон по умолчанию add.pt
...
1.<html metal:use-macro="context/@@standard_macros/page"
2. i18n:domain="zope">
3. <body>
4. <div metal:fill-slot="body">
5. <div metal:define-macro="addform">
6. <form action="." tal:attributes="action request/URL"
7. method="post" enctype="multipart/form-data">
8. <div metal:define-macro="formbody">
9. <h3 tal:condition="view/label"
10. tal:content="view/label"
11. metal:define-slot="heading"
12. i18n:translate=""
13. >Edit something</h3>
14. <p tal:define="status view/update"
15. tal:condition="status"
16. tal:content="status"
17. i18n:translate=""/>
18. <p tal:condition="view/errors" i18n:translate="">
19. There are <strong tal:content="python:len(view.errors)"
20. i18n:name="num_errors">6</strong> input errors.
21. </p>
22. <div metal:define-slot="extra_info" tal:replace="nothing">
23. </div>
24. <div class="row" metal:define-slot="extra_top" tal:replace="nothing">
25. <div class="label">Extra top</div>
26. <div class="label"><input type="text"
style
="width:100%" /></div>
27. </div>
28. <div metal:use-macro="context/@@form_macros/widget_rows" />
29. <div class="separator"></div>
30. <div class="row"
31. metal:define-slot="extra_bottom" tal:replace="nothing">
32. <div class="label">Extra bottom</div>
33. <div class="field"><input type="text"
style
="width:100%" /></div>
34. </div>
35. <div class="separator"></div>
36. </div>
37. <br/><br/>
38. <div class="row">
39. <div class="controls"><hr />
40. <input type='submit' value='Refresh'
41. i18n:attributes='value refresh-button' />
42. <input type='submit' value='Add' name='UPDATE_SUBMIT'
43. i18n:attributes='value add-button' />
44. <span tal:condition="context/nameAllowed|nothing" tal:omit-tag="">
45. &nbsp;&nbsp;<b i18n:translate="">Object Name</b>&nbsp;&nbsp;
46. <input type='text' name='add_input_name'
47. tal:attributes="value context/contentName" />
48. </span>
49. </div>
50. </div>
51. <div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
52. </div>
53. <div class="separator"></div>
54. </form>
55. </div>
56. </div>
57. </body>
58.</html>
...

В шаблоне генерируются кнопки для нашего объекта а в строке 28 вызывается макрос
widget_rows который объявлен в widget_macros.pt


1.<html i18n:domain="want an empty string, but zpt would ignore it :(">
2. <body>
3. <metal:block define-macro="widget_rows">
4. <div class="row" tal:repeat="widget view/widgets">
5. <metal:block define-macro="widget_row">
6. <div class="label">
7. <label for="field.name" title="The widget's hint"
8. tal:attributes="for widget/name; title widget/hint"
9. tal:content="widget/label" i18n:attributes="title"
10. i18n:translate=""
11. >The Label</label>
12. </div>
13. <tal:block define="error widget/error"
14. condition="error" content="structure error"
15. i18n:translate=""
16. >
17. The Error
18. </tal:block>
19. <div class="field" tal:content="structure widget">
20. <input type="text" style="width:100%"/>
21. </div>
22. </metal:block>
23. </div>
24. </metal:block>
25. </body>
26.</html>

смотрим что у них в configure.zcml
...
1.<browser:page
2. for="*"
3. name="form_macros"
4. permission="zope.Public"
5. class=".macros.FormMacros"
6. allowed_interface="zope.interface.common.mapping.IItemMapping"
7. />
8.
9. <browser:page
10. for="*"
11. name="widget_macros"
12. permission="zope.Public"
13. template="widget_macros.pt"
14. />
15.
16. <browser:page
17. for="*"
18. name="addform_macros"
19. permission="zope.Public"
20. template="add.pt"
21. />
...
Регистрируются имена для вызова макросов.
...
1.<view
2. type="zope.publisher.interfaces.browser.IBrowserRequest"
3. for="zope.schema.interfaces.ITextLine"
4. provides="zope.app.form.interfaces.IInputWidget"
5. factory=".TextWidget"
6. permission="zope.Public"
7. />
...

Видно что для интерфейса ITextLine зарегистрирован вид,посмотрим фабрику TextWidget:

1.class TextWidget(SimpleInputWidget):
2.
3. implements(ITextBrowserWidget)
4.
5. default = ''
6. displayWidth = 20
7. displayMaxWidth = ""
8. extra = ''
9. style = ''
10. convert_missing_value = True
11.
12. def __init__(self, *args):
13. super(TextWidget, self).__init__(*args)
14.
15. def __call__(self):
16. value = self._getFormValue()
17. if value is None or value == self.context.missing_value:
18. value = ''
19.
20. kwargs = {'type': self.type,
21. 'name': self.name,
22. 'id': self.name,
23. 'value': value,
24. 'cssClass': self.cssClass,
25. 'style': self.style,
26. 'size': self.displayWidth,
27. 'extra': self.extra}
28. if self.displayMaxWidth:
29. kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.
30.
31. return renderElement(self.tag, **kwargs)
32.
33. def _toFieldValue(self, input):
34. if self.convert_missing_value and input == self._missing:
35. value = self.context.missing_value
36. else:
37. # We convert everything to unicode. This might seem a bit crude,
38. # but anything contained in a TextWidget should be representable
39. # as a string. Note that you always have the choice of overriding
40. # the method.
41. try:
42. value = unicode(input)
43. except ValueError, v:
44. raise ConversionError(_("Invalid text data"), v)
45. return value

Здесь как раз возвращается при вызове отрендеренный self.tag
Небольшой тест:
>>> from zope.publisher.browser import TestRequest
>>> from zope.schema import TextLine
>>> field = TextLine(__name__='foo', title=u'on')
>>> request = TestRequest(form={'field.foo': u'Bob'})
>>> from zope.app.form.browser.textwidgets import TextWidget
>>> widget = TextWidget(field, request)
>>> widget()

<input
class="textType"
id="field.foo"
name="field.foo"
size="20"
type="text"
value="Bob"
/>
Кстати type устанавливается в SimpleInputWidget от которого наследуется TextWidget
Таким образом становится ясно что сама форма ввода генерируется виджетом.