[Zope3-dev] Mail delivery failed: returning message to sender
Mail Delivery System
Mailer-Daemon at python.org
Tue Feb 17 07:54:41 EST 2004
This message was created automatically by mail delivery software.
A message that you sent could not be delivered to one or more of its
recipients. This is a permanent error. The following address(es) failed:
corey at streamreel.net
SMTP error from remote mailer after RCPT TO:<corey at streamreel.net>:
host iris1.directnic.com [204.251.10.81]: 550 5.7.1 No such recipient
------ This is a copy of the message, including all the headers. ------
Return-path: <zope3-dev at zope.org>
Received: from cache1.zope.org ([12.155.117.38])
by mail.python.org with esmtp (Exim 4.22)
id 1At4jf-0007dB-KX; Tue, 17 Feb 2004 07:53:59 -0500
From: zope3-dev at zope.org (srichter)
Reply-To: zope3-dev at zope.org
To: ;
Subject: [VocabulariesAndFields] (new) first draft (generated from LaTeX file)
Message-ID: <20040217075359EST at dev.zope.org>
X-BeenThere: zope3-dev at zope.org
X-Zwiki-Version: 0.21.1
Precedence: bulk
List-Id: <zope3-dev at zope.org>
List-Post: <mailto:zope3-dev at zope.org>
List-Subscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/VocabulariesAndFields/subscribeform>
List-Unsubscribe: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/VocabulariesAndFields/subscribeform>
List-Archive: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/VocabulariesAndFields>
List-Help: <http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture>
Date: Tue, 17 Feb 2004 07:53:59 -0500
X-Spam-Status: OK (default 0.000)
Vocabularies and their Fields
Status
IsDraft
Authors
StephanRichter
Difficulty
Sprinter Level
Skills
- Be familiar with the 'schema' package, including widgets.
Problem/Task
You will agree that schemas in combination with widgets and forms
are pretty cool. The times of writing boring HTML forms and data
verification are over. However, the standard fields make it hard (if not
impossible) to create a dynamic list of possible values to choose from.
To solve this problem, the vocabulary, vocabulary field and corresponding
widgets were developed. In this recipe we will demonstrate one of the
common usages of vocabularies in Zope 3.
Recipe
Introduction
A common user interface pattern is to provide the user with a list
of available or possible values from which one or more might be selected.
This is done to reduce the amount of errors the user could possibly make.
Often the list of choices is static, meaning they do not change over time
or are dependent on a particular situations. On the other hand, you
commonly have choices for a field that depend strongly on the situation
you are presented with.
Standard enumerated fields do not have a mechanism to allow for
dynamic choices. It would also be hard for them to create dynamic
choices, since they are not aware of the context in which they are used.
To address this problem, vocabularies were created, which are able to
provide dynamic lists. The developer can then use a 'VocabularyField'
in her/his schema that specifies a specific vocabulary that should be
used.
Vocabularies in itself are a pretty complex subject, since
implementations can range from providing the values of a simple list to
backend SQL queries. For large data sets vocabularies also have a simple
query support, so that we can build a sane user interface for the data.
Generally, there are two ways to use vocabularies in Zope 3, the ones
that do not and others that do need a place to generate the data.
Vocabularies that do not need a place can be created as singeltons and
would be useful when data is retrieved from a file, RDB or any other
Zope-external data source. In this recipe, however, we are going to
implement a vocabulary that provides a list of all the items of a
container (or any other 'IReadMapping' object), so that the location
matters.
Vocabularies that need a location, cannot exist as singletons, but
the location must be passed into the constructor. Zope 3 provides a
vocabulary registry with which one can register vocabulary factories
(which are usually just the classes) by name. The ZCML looks like this::
01 <vocabulary
02 name="VocabularyName"
03 factory=".vocab.Vocabulary" />
You can then use the vocabulary in a schema by declaring a
vocabulary field::
01 field = VocabularyField(
02 title=u"...",
03 description=u"...",
04 vocabulary="VocabularyName")
If the 'vocabulary' argument value is a string, then it is used as a
vocabulary name, and the vocabulary is created with a context whenever
needed. But the argument also accepts 'IVocabulary' instances, which
are directly used.
Okay, I wrote already too much. Let's see how we can achieve our
task using vocabularies.
The Vocabulary and its Terms
A vocabulary has a very simple interface. It is almost like a simple
mapping objects with some additional functionality. The main idea is that
a vocabulary provides 'ITerm' objects. A term has simply a 'value'
that can be any Python object. However, for Web forms (and other user
interfaces) this minimalistic interface does not suffice, since we have
now way of reliably specifiying a unique id (a string) for a term, which
we need to do to create any HTML input element with the terms. To solve
this problem, the 'ITokenizedTerm' was developed, which provides a 'token'
attribute that must be a string identifying the term.
However, since our vocabulary deals with folder item names, our 'ITerm''value'
is equal to the 'token' . Therefore, we only need a minimal
implementation of 'ITokenizedTerm' as seen below.::
01 from zope.interface import implements
02 from zope.schema.interfaces import ITokenizedTerm
03 from zope.interface.common.mapping import IEnumerableMapping
05 class ItemTerm(object):
06 """A simple term implementation for items."""
07 implements(ITokenizedTerm)
08 def __init__(self, value):
09 self.value = self.token = value
Create a new product called 'itemvocabulary' in 'ZOPE3/src/zope/products/demo'
. Place the above code in the '_' file.
Next we need to implement the vocabulary. Since the context of the
vocabulary is an 'IReadMapping' object, the implementation is
straightforward::
01 from zope.schema.interfaces import IVocabulary, IVocabularyTokenized
02 from zope.interface.common.mapping import IEnumerableMapping
04 class ItemVocabulary(object):
05 """A vocabulary that provides the keys of any IEnumerableMapping object.
07 Every dictionary will qualify for this vocabulary."""
08 implements(IVocabulary, IVocabularyTokenized)
09 __used_for__ = IEnumerableMapping
11 def __init__(self, context):
12 self.context = context
14 def __iter__(self):
15 """See zope.schema.interfaces.IIterableVocabulary"""
16 return iter([ItemTerm(key) for key in self.context.keys()])
18 def __len__(self):
19 """See zope.schema.interfaces.IIterableVocabulary"""
20 return len(self.context)
22 def __contains__(self, value):
23 """See zope.schema.interfaces.IBaseVocabulary"""
24 return value in self.context.keys()
26 def getQuery(self):
27 """See zope.schema.interfaces.IBaseVocabulary"""
28 return None
30 def getTerm(self, value):
31 """See zope.schema.interfaces.IBaseVocabulary"""
32 if value not in self.context.keys():
33 raise LookupError, value
34 return ItemTerm(value)
36 def getTermByToken(self, token):
37 """See zope.schema.interfaces.IVocabularyTokenized"""
38 return self.getTerm(token)
o Line 8: Make sure that you implement both, 'IVocabulary' and 'IVocabularyTokenized'
, so that the widget mechanism will work correctly later.
o Line 14-16: Make sure that the values of the iterator are 'ITerm'
objects and not simple strings.
o Line 26-28: We do not support queries in this implementation.
The interface specifies that vocabularies not supporting queries must
return 'None' .
o Line 30-34: We must be careful here and not just create an 'ItemToken'
from the value, since the interface specifies that if the value is not
available in the vocabulary, a 'LookupError' should be raised.
o Line 36-38: Since the 'token' and the 'value' are equal, we can
just forward the request to 'getTerm()' .
Since the vocabulary requires a context for initiation, we need to
register it with the vocabulary registry. The vocabulary is also used in
untrusted environments, so that we have to make security assertions for
it and the term. Place the ZCML directives below in the 'configure.zcml'
of the package.::
01 <configure
02 xmlns="http://namespaces.zope.org/zope"
03 i18n_domain="itemvocabulary">
05 <vocabulary
06 name="Items"
07 factory=".ItemVocabulary" />
09 <content class=".ItemVocabulary">
10 <allow interface="zope.schema.interfaces.IVocabulary"/>
11 <allow interface="zope.schema.interfaces.IVocabularyTokenized"/>
12 </content>
14 <content class=".ItemTerm">
15 <allow interface="zope.schema.interfaces.ITokenizedTerm"/>
16 <allow attributes="title"/>
17 </content>
19 </configure>
o Line 5-7: Register the vocabulary under the name "Items". The
vocabulary directive is available in the default "zope" namespace.
o Line 9-16: We simply open up all of the interfaces to the
public, since the objects that provide the data are protected
themselves.
That was easy, right? Now, let's write some quick tests for this
code.
Testing the Vocabulary
The tests are as straightforward as the code itself. We are going to
only test the vocabulary, since it uses the trivial term. In the doc
string of the 'ItemVocabulary' class add the following example and
test code::
01 """
02 Example::
04 >>> data = {'a': 'Anton', 'b': 'Berta', 'c': 'Charlie'}
05 >>> vocab = ItemVocabulary(data)
06 >>> iterator = iter(vocab)
07 >>> iterator.next().token
08 'a'
09 >>> len(vocab)
10 3
11 >>> 'c' in vocab
12 True
13 >>> vocab.getQuery() is None
14 True
15 >>> vocab.getTerm('b').value
16 'b'
17 >>> vocab.getTerm('d')
18 Traceback (most recent call last):
19 ...
20 LookupError: d
21 >>> vocab.getTermByToken('b').token
22 'b'
23 >>> vocab.getTermByToken('d')
24 Traceback (most recent call last):
25 ...
26 LookupError: d
27 """
The tests are activated via a doc test that is initialized in 'tests.py'
with the following code::
01 import unittest
02 from zope.testing.doctestunit import DocTestSuite
04 def test_suite():
05 return unittest.TestSuite((
06 DocTestSuite('zope.products.demo.itemvocabulary'),
07 ))
09 if __name__ == '__main__':
10 unittest.main(defaultTest='test_suite')
You can execute the tests as usual via the Zope 3 test runner or
call the test file directly after you have set the correct Python path.
The Default Item Folder
To see the vocabulary working, we will develop a derived Folder,
which simply keeps track of a default item (whatever "default" may mean).
Since the folder is part of a browser demonstration, we write the folder
interface and implementation in 'browser.py'::
01 from zope.interface import implements, Interface
02 from zope.schema.vocabulary import VocabularyField
03 from zope.app.content.folder import Folder
05 class IDefaultItem(Interface):
07 default = VocabularyField(
08 title=u"Default Item Key",
09 description=u"Key of the default item in the folder.",
10 vocabulary="Items")
12 class DefaultItemFolder(Folder):
13 implements(IDefaultItem)
15 default = None
o Line 7-10: The 'VocabularyField' is like any other field, except
that you can specify a vocabulary. The 'vocabulary' argument can
either be the vocabulary name or a vocabulary instance, as pointed out
earlier in this recipe.
o Line 12-15: A trivial content component implementation that
combines 'IFolder' and 'IDefaultItem' .
Now we only have we just have to register the new content component,
make some security assertions and create an edit form for the 'default'
value. All of this can be done with the following three ZCML directives::
01 <content class=".browser.DefaultItemFolder">
02 <require like_class="zope.app.content.folder.Folder"/>
04 <require
05 permission="zope.View"
06 interface=".browser.IDefaultItem" />
08 <require
09 permission="zope.ManageContent"
10 set_schema=".browser.IDefaultItem" />
11 </content>
13 <browser:addMenuItem
14 class=".browser.DefaultItemFolder"
15 title="Default Item Folder"
16 permission="zope.ManageContent" />
18 <browser:editform
19 schema=".browser.IDefaultItem"
20 for=".browser.IDefaultItem"
21 label="Change Default Item"
22 name="defaultItem.html"
23 permission="zope.ManageContent"
24 menu="zmi_views" title="Default Item" />
Don't forget to register the 'browser' namespace in the 'configure'
tag::
xmlns:browser="http://namespaces.zope.org/browser"
You are now ready to go. Restart Zope 3. Once you refresh the
ZMI, you will see that you can now add a "Default Item Folder". Create
such a folder and add a couple other components to it, like images and
files. If you now click on the "Default Item" tab, you will see a
selection box with the names of all contained objects. Select one and
submit the form. You now stored the name of the object that will be
considered the "default". As you can see, there exist widgets that know
how to display a vocabulary field. See exercise 1 for changing the used
widget.
Exercises
- Exercise 1: Change the 'defaultItem.html' of the 'DefaultItemFolder'
so that it uses radio buttons instead of a drop-down menu. (Hint: You
need to create a Python view class for the 'editform' directive
and add a custom widget.)
--
forwarded from http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/VocabulariesAndFields
More information about the Zope3-dev
mailing list