[Zope3-checkins] CVS: Zope3/src/zope/schema - vocabulary.py:1.1.2.1
Fred L. Drake, Jr.
fred@zope.com
Fri, 2 May 2003 10:11:17 -0400
Update of /cvs-repository/Zope3/src/zope/schema
In directory cvs.zope.org:/tmp/cvs-serv21481
Added Files:
Tag: schema-vocabulary-branch
vocabulary.py
Log Message:
Preliminary implementation of Vocabulary Fields, as described in:
http://dev.zope.org/Zope3/VocabularyFields
=== Added File Zope3/src/zope/schema/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.
#
##############################################################################
"""Vocabulary support for schema."""
from zope.interface import Interface, Attribute
from zope.schema import Field, TextLine
from zope.schema import errornames
from zope.schema.interfaces import IField, ValidationError
try:
basestring # new in Python 2.3
except NameError:
from types import StringTypes as basestring
class IAbstractVocabulary(Interface):
"""Representation of a vocabulary.
At this most basic level, a vocabulary only need to support a test
for containment. This can be implemented either by __contains__()
or by sequence __getitem__() (the later only being useful for
vocabularies which are intrinsically ordered).
"""
def getTerm(value):
"""Return the ITerm object for the term 'value'.
If 'value' is not a valid term, this method raises LookupError.
"""
class ITerm(Interface):
"""Object representing a single value in a vocabulary."""
value = Attribute(
"value", "The value used to represent vocabulary term in a field.")
class IIterableVocabulary(Interface):
"""Vocabulary which supports iteration over allowed values.
The objects iteration provides must conform to the ITerm
interface.
"""
def __iter__():
"""Return an iterator which provides the terms from the vocabulary."""
def __len__():
"""Return the number of valid terms, or sys.maxint."""
class ISubsetVocabulary(Interface):
"""Vocabulary which represents a subset of another vocabulary."""
def getMasterVocabulary():
"""Returns the vocabulary that this is a subset of."""
class IVocabulary(IIterableVocabulary, IAbstractVocabulary):
"""Vocabulary which is iterable."""
class IExampleQueryableVocabulary(IAbstractVocabulary):
# XXX Example only
def query(pattern):
"""Return ISubsetVocabulary with values that match pattern."""
class IVocabularyFieldMixin(Interface):
# Mix-in interface that defines interesting things common to all
# vocabulary fields.
vocabularyName = TextLine(
title=u"Vocabulary Name",
description=(u"The name of the vocabulary to be used. This name\n"
u"is intended to be used by the IVocabularyRegistry's\n"
u"get() method."),
required=False,
default=None)
vocabulary = Attribute(
"vocabulary",
("IAbstractVocabulary to be used, or None.\n"
"\n"
"If None, the vocabularyName should be used by an\n"
"IVocabularyRegistry should be used to locate an appropriate\n"
"IAbstractVocabulary object."))
class IVocabularyField(IVocabularyFieldMixin, IField):
"""Field with a vocabulary-supported value.
The value for fields of this type is a single value from the
vocabulary.
"""
class IVocabularyMultiField(IVocabularyFieldMixin, IField):
"""Field with a value containing selections from a vocabulary..
The value for fields of this type need to support at least
containment checks using 'in' and iteration.
"""
class IVocabularyRegistry(Interface):
"""Registry that provides IAbstractVocabulary objects for specific fields.
"""
def get(object, name):
"""Return the vocabulary named 'name' for the content object
'object'.
"""
class VocabularyField(Field):
"""Field that adds support for use of an external vocabulary.
The value is a single value from the vocabulary.
"""
__implements__ = IVocabularyField
def __init__(self, vocabulary=None, **kw):
# set up the vocabulary:
if isinstance(vocabulary, basestring):
self.vocabulary = None
self.vocabularyName = vocabulary
else:
self.vocabulary = vocabulary
self.vocabularyName = None
# call the base initializer
super(VocabularyField, self).__init__(**kw)
def _validate(self, value):
if self.vocabulary is None:
if self.context is not None:
raise ValueError("can't validate value without vocabulary")
# XXX can't validate without vocabulary, and can't get
# vocabulary without context
return
if value not in self.vocabulary:
raise ValidationError(errornames.ConstraintNotSatisfied,
value)
def bind(self, object):
clone = super(VocabularyField, self).bind(object)
# get registered vocabulary/presentation if needed:
if clone.vocabulary is None:
vr = getVocabularyRegistry()
clone.vocabulary = vr.get(object, self.vocabularyName)
return clone
def VocabularyMultiField(VocabularyField):
"""Field that adds support for use of an external vocabulary.
The value is a collection of values from the vocabulary.
"""
__implements__ = IVocabularyMultiField
def _validate(self, value):
vocab = self.vocabulary
if vocab is None:
raise ValueError("can't validate value without vocabulary")
for v in self.value:
if v not in vocab:
raise ValidationError(errornames.ConstraintNotSatisfied, v)
class VocabularyRegistryError(LookupError):
def __init__(self, name):
self.name = name
Exception.__init__(self, str(self))
def __str__(self):
return "unknown vocabulary: %r" % self.name
class VocabularyRegistry(object):
__slots__ = '_map',
__implements__ = IVocabularyRegistry
def __init__(self):
self._map = {}
def get(self, object, name):
try:
vtype = self._map[name]
except KeyError:
raise VocabularyRegistryError(name)
return vtype(object)
def register(self, name, factory):
self._map[name] = factory
_vocabularies = None
def getVocabularyRegistry():
"""Return the vocabulary registry.
If the registry has not been created yet, an instance of
VocabularyRegistry will be installed and used.
"""
if _vocabularies is None:
setVocabularyRegistry(VocabularyRegistry())
return _vocabularies
def setVocabularyRegistry(registry):
"""Set the vocabulary registry."""
global _vocabularies
if _vocabularies is not None:
raise ValueError("vocabulary registry has already been set")
_vocabularies = registry
def _clear():
"""Remove the registries (for use by tests)."""
global _vocabularies
_vocabularies = None
try:
from zope.testing.cleanup import addCleanUp
except ImportError:
# don't have that part of Zope
pass
else:
addCleanUp(_clear)
del addCleanUp