Index: main/waeup.sirp/trunk/src/waeup/sirp/schema/__init__.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/schema/__init__.py	(revision 6784)
+++ main/waeup.sirp/trunk/src/waeup/sirp/schema/__init__.py	(revision 6784)
@@ -0,0 +1,3 @@
+from waeup.sirp.schema.field import *
+
+__all__ = tuple(name for name in globals() if not name.startswith('_'))
Index: main/waeup.sirp/trunk/src/waeup/sirp/schema/field.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/schema/field.py	(revision 6784)
+++ main/waeup.sirp/trunk/src/waeup/sirp/schema/field.py	(revision 6784)
@@ -0,0 +1,141 @@
+##
+## field.py
+## Login : <uli@pu.smp.net>
+## Started on  Fri Sep 16 16:02:55 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Special fields.
+"""
+from zope.interface import implements
+from zope.schema import TextLine
+from zope.schema.interfaces import (
+    ITextLine, IBaseVocabulary, ISource, IContextSourceBinder, InvalidValue,)
+from zope.schema.vocabulary import (
+    SimpleVocabulary, getVocabularyRegistry, VocabularyRegistryError)
+
+class CustomizableErrorMsg(object):
+    # Still work in progress
+
+    def __init__(self, not_in_vocab=InvalidValue, **kw):
+        self.not_in_vocab = not_in_vocab
+        return
+
+class TextLineChoice(TextLine, CustomizableErrorMsg):
+    """A TextLine field that also accepts sources, vocabs and values.
+
+    You can use this field like a regular zope.schema.TextLine field.
+
+    You can additionally give `source`, `values` or `vocabulary`
+    parameters which will work mainly as with regular
+    zope.schema.Choice fields (hence the name). Different to
+    `Choice` field, these parameters are not mandatory. Leave them
+    out and you have a regular `TextLine` field.
+
+    If you pass a simple source parameter, only those values are
+    allowed which also appear in the source. The source should of
+    course provide string values as suitable for TextLine fields only!
+
+    If, for example you create a TextLineChoice like this:
+
+      name = TextLineChoice(
+        title = u'Some name',
+        values = [u'foo', u'bar']  # unicode allowed only!
+        )
+
+    any formlib form rendering this field will only accept input u'foo'
+    or u'bar'.
+
+    The main adavantage of this modified TextLine field is to support
+    contextual sources. That means you can define some
+    IContextSourceBinder component that looks up catalogs or something
+    external else and decide then, whether the entered value of a form
+    is allowed or not.
+
+    The code herein is mainly copied over from
+    zope.schema._field.Choice with slight modifications.
+    """
+    implements(ITextLine)
+
+    def __init__(self, values=None, vocabulary=None, source=None, **kw):
+        if vocabulary is not None:
+            assert (isinstance(vocabulary, basestring)
+                    or IBaseVocabulary.providedBy(vocabulary))
+            assert source is None, (
+                "You cannot specify both source and vocabulary.")
+        elif source is not None:
+            vocabulary = source
+
+        assert values is None or vocabulary is None, (
+               "You cannot specify both values and vocabulary.")
+
+        self.vocabulary = None
+        self.vocabularyName = None
+        if values is not None:
+            self.vocabulary = SimpleVocabulary.fromValues(values)
+        elif isinstance(vocabulary, (unicode, str)):
+            self.vocabularyName = vocabulary
+        elif (ISource.providedBy(vocabulary) or
+              IContextSourceBinder.providedBy(vocabulary)):
+            self.vocabulary = vocabulary
+        # Before a default value is checked, it is validated. However, a
+        # named vocabulary is usually not complete when these fields are
+        # initialized. Therefore signal the validation method to ignore
+        # default value checks during initialization of a Choice tied to a
+        # registered vocabulary.
+        #self._init_field = (bool(self.vocabularyName) or
+        #                    IContextSourceBinder.providedBy(self.vocabulary))
+        super(TextLineChoice, self).__init__(**kw)
+        #self._init_field = False
+        return
+
+    source = property(lambda self: self.vocabulary)
+
+    def bind(self, object):
+        """See zope.schema._bootstrapinterfaces.IField."""
+        clone = super(TextLineChoice, self).bind(object)
+        # get registered vocabulary if needed:
+        if IContextSourceBinder.providedBy(self.vocabulary):
+            clone.vocabulary = self.vocabulary(object)
+            assert ISource.providedBy(clone.vocabulary)
+        elif clone.vocabulary is None and self.vocabularyName is not None:
+            vr = getVocabularyRegistry()
+            clone.vocabulary = vr.get(object, self.vocabularyName)
+            assert ISource.providedBy(clone.vocabulary)
+        return clone
+
+    def _validate(self, value):
+        """First validate against the regular TextLine rules. Then check any
+        vocabularies/sources.
+        """
+        # Do TextLine validation
+        super(TextLineChoice, self)._validate(value)
+
+        ## Pass all validations during initialization
+        #if self._init_field == 12:
+        #    return
+        # Check allowed value range (the vocabulary part)
+        vocabulary = self.vocabulary
+        if vocabulary is None and self.vocabularyName is not None:
+            vr = getVocabularyRegistry()
+            try:
+                vocabulary = vr.get(None, self.vocabularyName)
+            except VocabularyRegistryError:
+                raise ValueError("Can't validate value without vocabulary")
+        if vocabulary and value not in vocabulary:
+            raise InvalidValue(value)
+        return
Index: main/waeup.sirp/trunk/src/waeup/sirp/schema/tests/test_textlinechoice.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/schema/tests/test_textlinechoice.py	(revision 6784)
+++ main/waeup.sirp/trunk/src/waeup/sirp/schema/tests/test_textlinechoice.py	(revision 6784)
@@ -0,0 +1,183 @@
+"""Test TextLineChoice
+"""
+import unittest
+from zope.interface import implements, directlyProvides
+from zope.schema import vocabulary
+from zope.schema.interfaces import (
+    InvalidValue, ValidationError,
+    IVocabulary, ISource, IContextSourceBinder,)
+from zope.schema.tests.test_strfield import TextLineTest
+from waeup.sirp.schema import TextLineChoice
+
+# Define some helper functions, classes, vars for tests
+class DummyRegistry(vocabulary.VocabularyRegistry):
+    def get(self, object, name):
+        v = SampleVocabulary()
+        v.object = object
+        v.name = name
+        return v
+
+class SampleTerm(object):
+    pass
+
+SAMPLE_VALUES = [u'a', u'b', u'c', u'd']
+class SampleVocabulary(object):
+    implements(IVocabulary)
+
+    def __iter__(self):
+        return iter([self.getTerm(x) for x in SAMPLE_VALUES])
+
+    def __contains__(self, value):
+        return value in SAMPLE_VALUES
+        return 0 <= value < 10
+
+    def __len__(self):
+        return len(SAMPLE_VALUES)
+
+    def getTerm(self, value):
+        if value in self:
+            t = SampleTerm()
+            t.value = value
+            t.upper = value.upper() #2 * value
+            return t
+        raise LookupError("no such value: %r" % value)
+
+class LetterASource(object):
+    # A source that contains all strings with letter 'a'
+    implements(ISource)
+    required_letter = 'a'
+    def __contains__(self, value):
+        return self.required_letter in value
+letter_a_source = LetterASource()
+
+def letter_source_binder(context):
+    source = LetterASource()
+    source.required_letter = context.req_letter
+    return source
+directlyProvides(letter_source_binder, IContextSourceBinder)
+
+class SampleContext(object):
+    req_letter = 'b'
+
+
+
+class TextLineChoiceAsTextLineTest(TextLineTest):
+    # Tests to guarantee TextLine like behaviour of TextLineChoice
+    # i.e. make sure that a TextLineChoice can everything a TextLine can
+    _Field_Factory = TextLineChoice
+
+class TextLineChoice_Values_Tests(unittest.TestCase):
+    # Tests to guarantee values support
+    def test_create_vocabulary(self):
+        choice = TextLineChoice(values=[1, 3])
+        self.assertEqual([term.value for term in choice.vocabulary], [1, 3])
+
+    def test_validate_string(self):
+        choice = TextLineChoice(values=[u'a', u'c'])
+        choice.validate(u'a')
+        #choice.validate('c')
+        choice.validate(u'c')
+        self.assertRaises(InvalidValue, choice.validate, u'd')
+
+    def test_validate_strings(self):
+        choice = TextLineChoice(values=[u'foo', u'bar'])
+        choice.validate(u'foo')
+        choice.validate(u'bar')
+        choice.validate(u'bar')
+        self.assertRaises(InvalidValue, choice.validate, u'baz')
+
+class TextLineChoice_Vocabulary_Tests(unittest.TestCase):
+    # Tests of the TextLineChoice Field using vocabularies.
+    # Most tests were copied (slightly modified) from zope.schema.tests.
+
+    def setUp(self):
+        vocabulary._clear()
+
+    def tearDown(self):
+        vocabulary._clear()
+
+    def check_preconstructed(self, cls, okval, badval):
+        v = SampleVocabulary()
+        field = cls(vocabulary=v)
+        self.assert_(field.vocabulary is v)
+        self.assert_(field.vocabularyName is None)
+        bound = field.bind(None)
+        self.assert_(bound.vocabulary is v)
+        self.assert_(bound.vocabularyName is None)
+        bound.default = okval
+        self.assertEqual(bound.default, okval)
+        self.assertRaises(ValidationError, setattr, bound, "default", badval)
+
+    def test_preconstructed_vocabulary(self):
+        self.check_preconstructed(TextLineChoice, u'a', u'z')
+
+    def check_constructed(self, cls, okval, badval):
+        vocabulary.setVocabularyRegistry(DummyRegistry())
+        field = cls(vocabulary="vocab")
+        self.assert_(field.vocabulary is None)
+        self.assertEqual(field.vocabularyName, "vocab")
+        o = object()
+        bound = field.bind(o)
+        self.assert_(isinstance(bound.vocabulary, SampleVocabulary))
+        bound.default = okval
+        self.assertEqual(bound.default, okval)
+        self.assertRaises(ValidationError, setattr, bound, "default", badval)
+
+    def test_constructed_vocabulary(self):
+        self.check_constructed(TextLineChoice, u'a', u'z')
+
+    def test_create_vocabulary(self):
+        vocabulary.setVocabularyRegistry(DummyRegistry())
+        field = TextLineChoice(vocabulary="vocab")
+        o = object()
+        bound = field.bind(o)
+        self.assertEqual([term.value for term in bound.vocabulary],
+                         SAMPLE_VALUES)
+
+    def test_undefined_vocabulary(self):
+        choice = TextLineChoice(vocabulary="unknown")
+        self.assertRaises(ValueError, choice.validate, u"value")
+
+class TextLineChoice_Source_Tests(unittest.TestCase):
+    # Tests of the TextLineChoice Field using sources.
+
+    def test_simple_source(self):
+        choice = TextLineChoice(__name__='astring', source=letter_a_source)
+        bound = choice.bind(object())
+        self.assertEqual(bound.vocabulary, letter_a_source)
+        return
+
+    def test_validate_simple_source(self):
+        choice = TextLineChoice(__name__='astring', source=letter_a_source)
+        bound = choice.bind(object())
+        bound.validate(u'string_with_letter_a')
+        self.assertRaises(
+            InvalidValue, bound.validate, u'other_string')
+        return
+
+    def test_contextual_source(self):
+        # make sure we can pass contextual sources
+        choice = TextLineChoice(__name__='astring', source=letter_source_binder)
+        bound = choice.bind(SampleContext()) # requires 'b' instead of 'a'
+        self.assertTrue(isinstance(bound.vocabulary, LetterASource))
+        self.assertTrue(bound.vocabulary is not letter_a_source)
+        return
+
+    def test_validate_contextual_source(self):
+        # make sure the values from context are considered
+        choice = TextLineChoice(__name__='astring', source=letter_source_binder)
+        bound = choice.bind(SampleContext()) # requires 'b' instead of 'a'
+        bound.validate(u'string_with_letter_b')
+        self.assertRaises(
+            InvalidValue, bound.validate, u'other_string') # no 'b'
+        return
+
+def test_suite():
+    # We must define a test suite as we do not want the imported
+    # TextLineTest to be collected by testrunner.
+    return unittest.TestSuite((
+        unittest.makeSuite(TextLineChoiceAsTextLineTest),
+        unittest.makeSuite(TextLineChoice_Values_Tests),
+        unittest.makeSuite(TextLineChoice_Vocabulary_Tests),
+        unittest.makeSuite(TextLineChoice_Source_Tests),
+        ))
