[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