[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form/complexsample - __init__.py:1.1 complexsample.py:1.1 configure.zcml:1.1 interfaces.py:1.1 vocabulary.py:1.1 widgetapi.py:1.1
Fred L. Drake, Jr.
fred@zope.com
Mon, 28 Jul 2003 14:47:38 -0400
Update of /cvs-repository/Zope3/src/zope/app/browser/form/complexsample
In directory cvs.zope.org:/tmp/cvs-serv27697
Added Files:
__init__.py complexsample.py configure.zcml interfaces.py
vocabulary.py widgetapi.py
Log Message:
Sample of a "complex" widget.
=== Added File Zope3/src/zope/app/browser/form/complexsample/__init__.py ===
# This file is necessary to make this directory a package.
=== Added File Zope3/src/zope/app/browser/form/complexsample/complexsample.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Sample complex vocabulary widget with query support."""
from xml.sax.saxutils import escape, quoteattr
from zope.app.browser.form.complexsample import widgetapi
from zope.app.browser.form.complexsample.widgetapi import _, message
from zope.app.browser.form.vocabularywidget import ActionHelper
from zope.app.interfaces.form import WidgetInputError
from zope.interface.declarations import implements
from zope.schema.interfaces import ITokenizedTerm
# values for the submit button:
MOVE_UP = "moveup"
MOVE_DOWN = "movedown"
REMOVE = "remove"
# value for the query helper submit button (other actions provided by
# base class):
ADD_DONE = "adddone"
ADD_MORE = "addmore"
MORE = "more"
CLEAR = "clear"
QUERY = "query"
SELECT = "select"
DISMISS = "dismiss"
# Message ids for actions that may make more sense to translators, or
# at least make it easier to isolate each bit of text in the widget:
_msg_moveup = message(_("sampleWidget-button-move-up"), "Move Up")
_msg_movedown = message(_("sampleWidget-button-move-down"), "Move Down")
_msg_remove = message(_("sampleWidget-button-remove"), "Remove")
_msg_add_done = message(_("sampleWidget-button-add-done"), "Add+Done")
_msg_add_more = message(_("sampleWidget-button-add-more"), "Add+More")
_msg_more = message(_("sampleWidget-button-more"), "More")
_msg_clear = message(_("sampleWidget-button-clear"), "Clear Value")
_msg_query = message(_("sampleWidget-button-query"), "Search")
_msg_select = message(_("sampleWidget-button-select"), "Select")
_msg_dismiss = message(_("sampleWidget-button-dismiss"), "Dismiss")
# Messages ids for labels in the widgets:
_msg_enter_search_text = message(
_("sampleWidget-label-enter-search-text"), "Search for:")
_msg_select_content_type = message(
_("sampleWidget-label-select-content-type"), "Select content type:")
_msg_any_content_type = message(
_("sampleWidget-label-any-content-type"), "(any)")
_msg_inaccessible_object = message(_("sampleWidget-label-inaccessable-object"),
"(inaccessible or missing object)")
# HTML fragments used more than once:
_query_row_template = " <tr><th>%s</th>\n <td>%s</td></tr>\n"
# Misc. helpers:
_nulljoin = "".join
_NLjoin = "\n".join
def getTerm(vocabulary, value):
try:
return vocabulary.getTerm(value)
except LookupError:
return BrokenTerm(value)
class BrokenTerm:
"""Term class used for terms that aren't currently available."""
implements(ITokenizedTerm)
getIcon = None
title = _msg_inaccessible_object
def __init__(self, value):
self.token = value
self.value = value
class SampleWidgetMixin(object):
"""Mix-in helper for behavior specific to sample values."""
containerCssClass = "valueList"
def convertTokensToValues(self, tokens, strict=False):
# 'strict' must default to False since it needs that when
# called from the base class
# XXX assumes tokens and values are the same
L = []
for tok in tokens:
try:
v = self.vocabulary.getTermByToken(tok).value
except LookupError:
if strict:
raise WidgetInputError(
self.context.__name__,
self.context.title,
"token %r not found in vocabulary" % tok)
v = tok
L.append(v)
return L
def loadValueFromRequest(self):
value = self.request.form.get(self.name) or None
if self.queryview is not None:
value = self.queryview.performAction(value)
return value
label_counter = 0
def renderSelectionItem(self, type, name, term, selected, disabled):
# Called from renderSelectionList().
flag = ""
if selected:
flag = " checked='checked'"
if disabled:
flag += " disabled='disabled'"
if flag:
flag = "\n " + flag
count = self.label_counter
self.label_counter += 1
return ("<tr><td><input type='%s' value='%s'\n"
" name='%s'\n"
" id='%s-t%d'%s /></td>\n"
" <td><label for='%s-t%d'>\n"
" %s\n"
" %s</label></td></tr>"
% (type, term.token, name, name, count, flag,
name, count, self.renderTermIcon(term),
self.renderTerm(term)))
def renderTerm(self, term):
# The returned value is not suitable for use as an HTML
# attribute value.
if term.getIcon is None:
t = self.translate(term.title)
else:
t = term.title
return escape(t)
def renderTermIcon(self, term):
if term.getIcon is None:
return ("<strong class='sampleWidget-old-value'"
" title='(out-of-date value)'>X</strong>")
else:
return ("<img alt=%s src='%s' />"
% (quoteattr(term.title), term.getIcon))
class SampleMultiWidgetMixin(SampleWidgetMixin):
def loadValueFromRequest(self):
value = self.request.form.get(self.name, [])
if not isinstance(value, list):
value = [value]
if self.queryview is not None:
value = self.queryview.performAction(value)
return value
# Display widgets:
class SampleDisplay(SampleWidgetMixin, widgetapi.BaseVocabularyDisplay):
"""Display widget for single-value sample value fields."""
def render(self, value):
if value in (widgetapi.NullValue, None):
# missing single value
return super(SampleDisplay, self).render(value)
else:
term = getTerm(self.vocabulary, value)
pattern = "%s\n%s\n<input type='hidden' value=%s />"
return pattern % (self.renderTermIcon(term),
self.renderTerm(term),
quoteattr(term.token))
class SampleBagDisplay(SampleMultiWidgetMixin,
widgetapi.BaseVocabularyBagDisplay):
"""Display widget for unordered multi-value sample value fields."""
def render(self, value):
return renderMultiDisplay(self, value)
class SampleListDisplay(SampleMultiWidgetMixin,
widgetapi.BaseVocabularyListDisplay):
"""Display widget for ordered multi-value sample value fields."""
def render(self, value):
return renderMultiDisplay(self, value)
def renderMultiDisplay(widget, value):
if not value:
# missing multiple value
return widget.translate(widgetapi._msg_missing_multiple_value)
else:
pattern = ("<li>%s\n"
" %s\n"
" <input type='hidden' name=%s value=%s /></li>")
vocabulary = widget.vocabulary
L = []
name = quoteattr(widget.name)
for v in value:
term = getTerm(vocabulary, v)
L.append(pattern % (widget.renderTermIcon(term),
widget.renderTerm(term),
name,
quoteattr(term.token)))
return ("<%s class=%s id=%s>\n%s\n</%s>"
% (widget.containerElementType,
quoteattr(widget.containerCssClass),
quoteattr(widget.name),
"\n".join(L),
widget.containerElementType))
# Edit widgets:
def renderEditWidget(self, value):
L = ["<div class=",
quoteattr(self.containerCssClass),
" id=",
quoteattr(self.name),
">\n",
self.renderValueArea(value),
"\n"]
if self.queryview is not None:
L.append("<div class='widget-work-area'>\n")
s = self.queryview.renderResults(value).rstrip()
if s:
s += "\n"
L.extend([
s,
self.queryview.renderInput(),
"\n</div>"
])
L.append("</div>")
return _nulljoin(L)
def renderSelectionList(widget, type, info, name):
L = [widget.renderSelectionItem(type, name, *item) for item in info]
return "<table>\n%s\n</table>" % _NLjoin(L)
class SampleEdit(ActionHelper, SampleWidgetMixin,
widgetapi.BaseVocabularyWidget):
"""Edit widget for single-value sample value fields."""
def initialize(self):
super(SampleEdit, self).initialize()
self.addAction(CLEAR, _msg_clear)
def loadValueFromRequest(self):
value = super(SampleEdit, self).loadValueFromRequest()
action = self.getAction()
if action == CLEAR and not self.context.required:
value = None
return value
def render(self, value):
return renderEditWidget(self, value)
def renderValueArea(self, value):
disabled = False
if value in (None, widgetapi.NullValue):
disabled = True
L = ["<span class='display'>",
self.translate(widgetapi._msg_missing_single_value),
"</span>"]
else:
term = getTerm(self.vocabulary, value)
L = ["<span class='display'>",
self.renderTerm(term),
"</span>\n"
"<input type='hidden' name=",
quoteattr(self.name),
" value=",
quoteattr(term.token),
" />"]
if not self.context.required:
clear_disabled = value in (None, widgetapi.NullValue)
L.extend(["\n<br />\n",
self.renderAction(CLEAR, clear_disabled)])
return _nulljoin(L)
class SampleUniqueListEdit(ActionHelper, SampleMultiWidgetMixin,
widgetapi.BaseVocabularyWidget):
"""Edit widget for unique-list-valued sample value fields."""
def initialize(self):
super(SampleUniqueListEdit, self).initialize()
self.addAction(MOVE_UP, _msg_moveup)
self.addAction(MOVE_DOWN, _msg_movedown)
self.addAction(REMOVE, _msg_remove)
self.selections_name = self.name + ".picks"
# Load supplemental data from request:
L = self.request.form.get(self.selections_name, [])
if not isinstance(L, list):
L = [L]
self.selections = self.convertTokensToValues(L)
def loadValueFromRequest(self):
value = super(SampleUniqueListEdit, self).loadValueFromRequest()
action = self.getAction()
if action == REMOVE:
value = self.removeSelections(value)
elif action == MOVE_UP:
value = self.moveUp(value)
elif action == MOVE_DOWN:
value = self.moveDown(value)
return value
def removeSelections(self, value):
for item in self.selections:
if item in value:
value.remove(item)
return value
def moveUp(self, value):
"""Move selected items toward the front of the list."""
items = self.getMoveItems(value)
# Things at the beginning of the list can't be moved up; skip
# over leading selected items.
start = 0
while items and items[0][0] == start:
del items[0]
start += 1
# Move the remaining items forward:
self.moveByOffset(value, items, -1)
return value
def moveDown(self, value):
"""Move selected items toward the end of the list."""
items = self.getMoveItems(value)
# Ignore moves already at the end:
end = len(value) - 1
while items and items[-1][0] == end:
del items[-1]
end -= 1
items.reverse()
self.moveByOffset(value, items, +1)
return value
def getMoveItems(self, value):
indexes = {}
for item in self.selections:
if item in value:
indexes[value.index(item)] = item
items = indexes.items()
items.sort()
return items
def moveByOffset(self, value, items, offset):
for i, item in items:
del value[i]
value.insert(i + offset, item)
def render(self, value):
return renderEditWidget(self, value)
def renderValueArea(self, value):
selections = [v for v in value if v in self.selections]
items = [(getTerm(self.vocabulary, v), v in selections, False)
for v in value]
if items:
L = [renderSelectionList(self, "checkbox", items,
self.selections_name),
self.renderAction(REMOVE),
self.renderAction(MOVE_UP, len(items) < 2),
self.renderAction(MOVE_DOWN, len(items) < 2),
]
current_value = _NLjoin(L)
else:
current_value = self.translate(
widgetapi._msg_missing_multiple_value)
L = ["<div class='value'>\n",
current_value,
"\n",
_nulljoin([("<input name='%s' type='hidden' value=%s/>\n"
% (self.name, quoteattr(term.token)))
for (term, selected, disabled) in items]),
"</div>"]
return _nulljoin(L)
class SampleListEdit(SampleUniqueListEdit):
"""Edit widget for list-valued sample value fields."""
def renderSelectionItem(self, type, name, term, selected, disabled):
return super(SampleListEdit, self).renderSelectionItem(
type, name, term, selected, False)
# Query views:
class BaseSampleQueryView(ActionHelper, widgetapi.BaseQueryView):
queryResultBatchSize = 8
def initialize(self):
super(BaseSampleQueryView, self).initialize()
self.addAction(QUERY, _msg_query)
self.addAction(MORE, _msg_more)
self.addAction(DISMISS, _msg_dismiss)
name = self.name
self.query_text_name = name
self.query_index_name = name + ".start"
self.selections_name = name + ".picks"
# Load data from the form:
get = self.request.form.get
s = get(self.query_text_name, '')
if isinstance(s, list):
s = s[0] # XXX ignore extra values
self.query_text = s.strip()
s = get(self.query_index_name) or None
i = None
if s:
try:
i = int(s)
except ValueError:
pass
else:
if i < 0:
i = None
self.query_index = i
selections = get(self.selections_name, [])
if not isinstance(selections, list):
selections = [selections]
self.selections = self.widget.convertTokensToValues(selections,
strict=True)
def performAction(self, value):
self.action = self.getAction()
if self.action == QUERY:
self.query_index = 0
self.selections = []
elif self.action == MORE:
self.selections = []
elif self.action == DISMISS:
self.query_index = None
self.selections = []
return value
def renderInput(self):
s = self.renderSupplementaryInput().rstrip()
if s:
s += "\n"
L = ["<div class='query'>\n"
"<table>\n",
s]
label = self.renderLabel(self.query_text_name,
_msg_enter_search_text)
widget = ("<input name='%s' id='%s' value=%s />"
% (self.query_text_name, self.query_text_name,
quoteattr(self.query_text)))
L.extend((_query_row_template % (label, widget),
"</table>\n",
self.renderAction(QUERY),
"\n</div>"))
return _nulljoin(L)
def renderLabel(self, name, msgid):
return ("<label for=%s>%s</label>"
% (quoteattr(name), self.translate(msgid)))
def renderQueryResults(self, results, value):
info, more = self.computeResultInfo(results, value)
L = ["<div class='results'>",
"<input type='hidden' name='%s' value='%d' />"
% (self.query_index_name, self.query_index),
renderSelectionList(self.widget, self.selectionsType, info,
self.selections_name),
self.renderQueryResultsActions(more),
self.renderAction(MORE, not more),
self.renderAction(DISMISS),
"</div>"]
return _NLjoin(L)
def renderSupplementaryInput(self):
return ""
def computeResultInfo(self, results, value):
it = iter(results)
if self.query_index:
try:
for i in range(self.query_index):
it.next()
except StopIteration:
return [], False
more = True
L = []
for i in range(self.queryResultBatchSize):
try:
term = it.next()
except StopIteration:
more = False
break
else:
L.append(self.resultInfoForTerm(term, value))
else:
try:
it.next()
except StopIteration:
more = False
return L, more
class SingleSelectionHelper(object):
selectionsType = "radio"
def initialize(self):
super(SingleSelectionHelper, self).initialize()
self.addAction(SELECT, _msg_select)
def performAction(self, value):
value = super(SingleSelectionHelper, self).performAction(value)
if self.action == SELECT:
if len(self.selections) == 1:
value = self.selections[0]
self.query_index = None
self.selections = []
else:
# XXX some sort of advisory error...
pass
return value
def renderQueryResultsActions(self, more):
return self.renderAction(SELECT)
def resultInfoForTerm(self, term, value):
return term, term.value == value, False
class SampleQueryView(SingleSelectionHelper, BaseSampleQueryView):
def getResults(self):
if self.query_index is None:
return None
return self.context.query(self.query_text)
class FancySampleQueryView(SingleSelectionHelper, BaseSampleQueryView):
def initialize(self):
super(FancySampleQueryView, self).initialize()
self.query_type_name = self.name + ".type"
# Load data from the form:
self.query_type = self.request.form.get(self.query_type_name) or None
self.reference_types = self.context.getReferenceTypes()
if self.query_type is not None:
for term in self.reference_types:
if term.token == self.query_type:
self.query_type = term.value
break
else:
self.query_type = None
def getResults(self):
if self.query_index is None:
return None
return self.context.query(self.query_text, type=self.query_type)
def renderSupplementaryInput(self):
content_types = self.reference_types
len_content_types = len(content_types)
if len_content_types > 1:
L = []
label = self.renderLabel(self.query_type_name,
_msg_select_content_type)
flags = ""
if not self.query_type:
flags = " selected"
W = ["<select name='%s' id='%s'>\n"
% (self.query_type_name, self.query_type_name),
" <option value=''%s>%s</option>\n"
% (flags, self.translate(_msg_any_content_type))]
for ct in content_types:
flags = ""
if ct.value == self.query_type:
flags = " selected"
W.append(" <option value='%s'%s>%s</option>\n"
% (ct.token, flags, ct.title))
W.append(" </select>")
widget = _nulljoin(W)
L.append(_query_row_template % (label, widget))
return _nulljoin(L)
elif len_content_types == 1:
# This use of iter() allows getReferenceTypes() to return
# things other than sequences, like... vocabularies!
return ("<input type='hidden' name='%s' value='%s' />"
% (self.query_type_name,
iter(content_types).next().token))
else:
return ""
class MultiSelectionHelper(object):
selectionsType = "checkbox"
def initialize(self):
super(MultiSelectionHelper, self).initialize()
self.addAction(ADD_DONE, _msg_add_done)
self.addAction(ADD_MORE, _msg_add_more)
def performAction(self, value):
value = super(MultiSelectionHelper, self).performAction(value)
if self.action == ADD_DONE:
value = self.addSelections(value)
self.query_index = None
elif self.action == ADD_MORE:
value = self.addSelections(value)
self.query_index += self.queryResultBatchSize
self.selections = []
elif self.action == MORE:
self.query_index += self.queryResultBatchSize
return value
def renderQueryResultsActions(self, more):
return _NLjoin([self.renderAction(ADD_DONE),
self.renderAction(ADD_MORE, not more)])
class UniqueSelectionHelper(MultiSelectionHelper):
def addSelections(self, value):
for v in self.selections:
if v not in value:
value.append(v)
return value
def resultInfoForTerm(self, term, value):
if value in (None, widgetapi.NullValue):
disabled = False
selected = term.value in self.selections
else:
disabled = term.value in value
selected = disabled or term.value in self.selections
return term, selected, disabled
class NonUniqueSelectionHelper(MultiSelectionHelper):
def addSelections(self, value):
value.extend(self.selections)
return value
def resultInfoForTerm(self, term, value):
selected = term.value in self.selections
if value not in (None, widgetapi.NullValue):
selected = selected or term.value in value
return term, selected, False
class SampleListQueryView(NonUniqueSelectionHelper, SampleQueryView):
pass
class SampleUniqueListQueryView(UniqueSelectionHelper, SampleQueryView):
pass
class FancySampleListQueryView(NonUniqueSelectionHelper,
FancySampleQueryView):
pass
class FancySampleUniqueListQueryView(UniqueSelectionHelper,
FancySampleQueryView):
pass
=== Added File Zope3/src/zope/app/browser/form/complexsample/configure.zcml ===
<?xml version="1.0" encoding="UTF-8"?>
<zopeConfigure xmlns='http://namespaces.zope.org/zope'>
<!-- Sample vocabulary support -->
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-display-widget"
factory=".complexsample.SampleDisplay"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-display-list-widget"
factory=".complexsample.SampleListDisplay"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-display-unique-list-widget"
factory=".complexsample.SampleListDisplay"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-edit-widget"
factory=".complexsample.SampleEdit"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-edit-list-widget"
factory=".complexsample.SampleListEdit"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabulary"
name="field-edit-unique-list-widget"
factory=".complexsample.SampleUniqueListEdit"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabularyQuery"
name="widget-query-helper"
factory=".complexsample.SampleQueryView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabularyQuery"
name="widget-query-list-helper"
factory=".complexsample.SampleListQueryView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.ISampleVocabularyQuery"
name="widget-query-unique-list-helper"
factory=".complexsample.SampleUniqueListQueryView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.IFancySampleVocabularyQuery"
name="widget-query-helper"
factory=".complexsample.FancySampleQueryView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.IFancySampleVocabularyQuery"
name="widget-query-list-helper"
factory=".complexsample.FancySampleListQueryView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserPresentation"
for=".interfaces.IFancySampleVocabularyQuery"
name="widget-query-unique-list-helper"
factory=".complexsample.FancySampleUniqueListQueryView"
/>
</zopeConfigure>
=== Added File Zope3/src/zope/app/browser/form/complexsample/interfaces.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Interfaces used with the sample widget."""
from zope.schema import BytesLine
from zope.schema.interfaces import IVocabularyQuery
from zope.schema.interfaces import IVocabularyTokenized, IBaseVocabulary
from zope.schema.interfaces import ITokenizedTerm
from zope.app.browser.form.complexsample.widgetapi import _
class ITitledTokenizedTerm(ITokenizedTerm):
"""ITokenizedTerm that also has a title attribute."""
title = BytesLine(title=_(u"Title"))
class ISampleVocabulary(IBaseVocabulary, IVocabularyTokenized):
"""Vocabulary representing sample values."""
class ISampleVocabularyQuery(IVocabularyQuery):
def query(text):
"""Returns a subset sample vocabulary based on canned data."""
class IFancySampleVocabularyQuery(IVocabularyQuery):
def query(text, type=None):
"""Returns a subset sample vocabulary based on canned data."""
def getReferenceTypes():
"""Return a sequence of type values that can be passed to query()."""
=== Added File Zope3/src/zope/app/browser/form/complexsample/vocabulary.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Sample vocabularies for use with the complexsample widgets.
$Id: vocabulary.py,v 1.1 2003/07/28 18:47:30 fdrake Exp $
"""
from __future__ import generators
from zope.interface.declarations import implements
from zope.schema.interfaces import ITokenizedTerm, IIterableVocabulary
from zope.app.browser.form.complexsample.interfaces import \
ISampleVocabularyQuery, \
IFancySampleVocabularyQuery, \
ITitledTokenizedTerm, \
ISampleVocabulary
class SampleTerm(object):
implements(ITokenizedTerm)
def __init__(self, value, title, keywords):
self.value = value
self.token = "tok%03d" % value
self.title = title
self.keywords = keywords
self.group = groupNames[value % 3]
groupNames = {0: "1. first third",
1: "2. second third",
2: "3. final third"}
# XXX We could probably use more data, but creativity ebbs.
# title keywords
TERM_DATA = '''\
Zero alpha bravo
One bravo charlie
Two charlie david
Three david bowie
Four bowie clapton
Five clapton guitar
Six guitar drums
Seven drums beat
Eight beat rhythm
Nine rhythm dance
Ten dance steps
Eleven stepping out
Twelve on the floor
Thirteen at the rave
Fourteen bravo zope
Fifteen twisty charlie
Sixteen clapton cream
Seventeen cream pudding
Eighteen dance pudding
Nineteen barry plays drums
Twenty dance on floor
'''
allTerms = []
for line in TERM_DATA.strip().split("\n"):
parts = line.split()
allTerms.append(SampleTerm(len(allTerms), parts[0], parts[1:]))
del line, parts
class SampleVocabularyQuery(object):
implements(ISampleVocabularyQuery)
def __init__(self, vocabulary):
self.vocabulary = vocabulary
def query(self, text):
terms = text.split()
if terms:
L = []
for term in self.vocabulary.terms:
for t in terms:
if t not in term.keywords:
break
else:
L.append(term)
else:
L = self.vocabulary.terms
return SubsetSampleVocabulary(self.vocabulary, L)
class FancySampleVocabularyQuery(object):
implements(IFancySampleVocabularyQuery)
def __init__(self, vocabulary):
self.vocabulary = vocabulary
def query(self, text, group=None):
L = SampleVocabularyQuery(self.vocabulary).query(text).terms
if group is not None:
L = [term for term in L if term.group == group]
return SubsetSampleVocabulary(self.vocabulary, L)
def getReferenceTypes(self):
return self.vocabulary.allGroups
class SampleVocabulary(object):
"""A simple vocabulary."""
implements(ISampleVocabulary)
queryFactory = SampleVocabularyQuery
def __init__(self, context=None):
self.context = context
self.terms = allTerms
self.allGroups = []
for term in self.terms:
if term.group not in self.allGroups:
self.allGroups.append(term.group)
self.allGroups.sort()
def __contains__(self, value):
for term in self.terms:
if value == term.value:
return True
return False
def getQuery(self):
return self.queryFactory(self)
def getTerm(self, value):
for term in self.terms:
if term.value == value:
return term
raise LookupError(value)
def getTermByToken(self, token):
for term in self.terms:
if term.token == token:
return term
raise LookupError(token)
class SubsetSampleVocabulary(SampleVocabulary):
implements(ISampleVocabulary, IIterableVocabulary)
def __init__(self, vocabulary, terms):
self.vocabulary = vocabulary
self.terms = terms
self.allGroups = []
for term in self.terms:
if term.group not in self.allGroups:
self.allGroups.append(term.group)
self.allGroups.sort()
def __iter__(self):
return iter(self.terms)
def __len__(self):
return len(self.terms)
=== Added File Zope3/src/zope/app/browser/form/complexsample/widgetapi.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Alternate base classes for IBrowserWidget implementations.
The base classes provided here implement the IBrowserWidget API and
provide a simpler API that derived classes are expected to implement.
"""
from xml.sax.saxutils import escape, quoteattr
from zope.app.interfaces.browser.form import IBrowserWidget
from zope.app.interfaces.form import WidgetInputError
from zope.i18n.messageid import MessageIDFactory
DOMAIN = "zope-widget-examples"
_ = MessageIDFactory(DOMAIN)
def message(msgid, default):
# XXX This treats MessageID objects as mutable objects, but is
# only used when the MessageID is first created. Using a separate
# function is needed since the message catalog tools require that
# _() take exactly one positional string literal argument, else we
# could just call the MessageID constructor directly.
assert msgid.default == msgid
assert msgid.domain == DOMAIN
msgid.default = default
return msgid
# Since NullValue must be a singleton, be safe in the face of
# reload():
try:
NullValue
except NameError:
NullValue = object()
_msg_missing_single_value = message(
_("widget-missing-single-value"), "(no value)")
_msg_missing_multiple_value = message(
_("widget-missing-multiple-value"), "(no values)")
class BaseWidget(object):
__implements__ = IBrowserWidget
name = property(lambda self: self.__prefix + self.context.__name__)
required = property(lambda self: self.context.required)
title = property(lambda self: self.context.title)
__initialized = False
__initial_value = NullValue
__prefix = "field."
def __init__(self, context, request):
self.context = context
self.request = request
# Form management methods.
# Subclasses should not to override these.
def getData(self):
if not self.__initialized:
self.__initialize()
return self.__computed_value
def haveData(self):
marker_name = self.name + "-marker"
return marker_name in self.request.form
def setData(self, value):
assert (self.__initial_value is NullValue
or (not self.__initialized)
or self.__initial_value == value)
self.__initial_value = value
def setPrefix(self, prefix):
assert not self.__initialized
if prefix[-1] != ".":
prefix += "."
self.__prefix = prefix
def __initialize(self):
self.__initialized = True
self.initialize()
if self.haveData():
self.__computed_value = self.loadValueFromRequest()
elif self.__initial_value is NullValue:
self.__computed_value = self.context.default
else:
self.__computed_value = self.__initial_value
def applyChanges(self, content):
field = self.context
value = self.getData()
change = field.query(content, self) != value
if change:
field.set(content, value)
return change
# Rendering methods:
# (These should not need to be overridden.)
def __call__(self):
if not self.__initialized:
self.__initialize()
marker_name = self.name + "-marker"
marker = "<input type='hidden' name='%s' value='x' />\n" % marker_name
have_data = marker_name in self.request.form
return marker + self.render(self.__computed_value)
def row(self):
return ("<div class='label'>%s</div>\n"
"<div class='field'>%s</div>"
% (self.label(), self()))
def translate(self, msgid):
return msgid.default
# API for subclasses to implement:
def initialize(self):
"""Initialize internal data structures needed by the widget.
This method should not load values from the request.
Derived classes should call the base class initialize() before
performing specialized initialization. This requirement is
waived for classes which inherit directly from, and *only*
from, BaseWidget.
"""
def label(self):
# Subclasses may want to override this.
return escape(self.title)
def loadValueFromRequest(self):
"""Load the value from data in the request."""
raise NotImplementedError(
"BaseWidget subclasses must implement loadValueFromRequest()")
def render(self, value):
raise NotImplementedError(
"BaseWidget subclasses must implement render()")
class BaseVocabularyWidget(BaseWidget):
query = None
queryview = None
def __init__(self, context, request):
super(BaseVocabularyWidget, self).__init__(context, request)
self.vocabulary = context
# Helpers used by the vocabulary widget machinery;
# these should not be overriden.
def setField(self, field):
assert self.context is self.vocabulary
# only allow this to happen for a bound field
assert field.context is not None
self.context = field
def setQuery(self, query, queryview):
assert self.query is None
assert self.queryview is None
assert query is not None
assert queryview is not None
self.query = query
self.queryview = queryview
# Use of a hyphen to form the name for the query widget
# ensures that it won't clash with anything else, since
# field names are normally Python identifiers.
queryview.setName(self.name + "-query")
# Override the methods in the subclass interface:
def initialize(self):
"""Make sure the query view has a chance to initialize itself."""
if self.queryview is not None:
self.queryview.initialize()
def loadValueFromRequest(self):
"""Load the value from data in the request.
If self.queryview is not None, this method is responsible for
calling the query view's performAction() method with the value
loaded, and returning the result::
value = ...load value from request data...
if self.queryview is not None:
value = self.queryview.performAction(value)
return value
"""
return super(BaseVocabularyWidget, self).loadValueFromRequest()
# Convenience method:
def convertTokensToValues(self, tokens):
"""Convert a list of tokens to a list of values.
If an invalid token is encountered, WidgetInputError is raised.
"""
L = []
for token in tokens:
try:
term = self.vocabulary.getTermByToken(token)
except LookupError:
raise WidgetInputError(
self.context.__name__,
self.context.title,
"token %r not found in vocabulary" % token)
else:
L.append(term.value)
return L
class BaseVocabularyDisplay(BaseVocabularyWidget):
def render(self, value):
if value in (NullValue, None):
# missing single value
return self.translate(_msg_missing_single_value)
else:
return self.renderTerm(self.vocabulary.getTerm(value))
def renderTerm(self, term):
"""Return textual presentation for term."""
raise NotImplementedError("BaseVocabularyMultiDisplay subclasses"
" must implement renderTerm()")
class BaseVocabularyMultiDisplay(BaseVocabularyDisplay):
"""Base class for display widgets of multi-valued vocabulary fields."""
def render(self, value):
if not value:
# missing multiple value
return self.translate(_msg_missing_multiple_value)
else:
pattern = ("<li>%s\n"
" <input type='hidden' name=%s value=%s /></li>")
vocabulary = self.vocabulary
L = []
name = quoteattr(self.name)
for v in value:
term = vocabulary.getTerm(v)
L.append(pattern % (self.renderTerm(term), name,
quoteattr(term.token)))
return ("<%s class=%s id=%s>\n%s\n</%s>"
% (self.containerElementType,
quoteattr(self.containerCssClass),
quoteattr(self.name),
"\n".join(L),
self.containerElementType))
containerCssClass = "values"
class BaseVocabularyBagDisplay(BaseVocabularyMultiDisplay):
"""Base class for display widgets of unordered multi-valued
vocabulary fields."""
containerElementType = "ul"
class BaseVocabularyListDisplay(BaseVocabularyMultiDisplay):
"""Base class for display widgets of ordered multi-valued
vocabulary fields."""
containerElementType = "ol"
class BaseQueryView(object):
name = None
widget = None
__initialized = False
def __init__(self, context, request):
self.context = context
self.request = request
# Methods called by the vocabulary widget construction machinery;
# subclasses should not need to override these.
def setName(self, name):
assert not self.__initialized
assert not name.endswith(".")
assert self.name is None
self.name = name
def setWidget(self, widget):
assert not self.__initialized
assert self.widget is None
assert widget is not None
self.widget = widget
# Methods which may be overriden by subclasses:
def initialize(self):
"""Initialization which does not require reading the request.
Derived classes should call the base class initialize() before
performing specialized initialization.
"""
# Should loading from the request happen here?
assert self.name is not None
assert self.widget is not None
assert not self.__initialized
self.__initialized = True
def renderResults(self, value):
"""Render query results if we have any, otherwise return an
empty string.
"""
results = self.getResults()
if results is None:
return ""
else:
return self.renderQueryResults(results, value)
# Methods which should be overriden by subclasses:
def performAction(self, value):
"""Perform any modifications to the value based on user actions.
This method should be overriden if the query view provides any
actions which can modify the value of the field.
"""
return value
# Methods which must be overriden by subclasses:
def getResults(self):
"""Perform the query, or return None.
The return value should be None if there is no query to
execute, or an object that can be rendered as a set of results
by renderQueryResults().
If the query results in an empty set of results, some value
other than None should be used to represent the results so
that renderQueryResults() can provide a helpful message.
"""
raise NotImplementedError(
"BaseQueryView subclasses must implement getResults()")
def renderInput(self):
"""Render the input area of the query view."""
raise NotImplementedError(
"BaseQueryView subclasses must implement renderInput()")
def renderQueryResults(self, results, value):
"""Render the results returned by getResults()."""
raise NotImplementedError(
"BaseQueryView subclasses must implement renderQueryResults()")