diff --git a/opengever/api/schema/adapters.py b/opengever/api/schema/adapters.py
index 2f8e231c024..d94ef863555 100644
--- a/opengever/api/schema/adapters.py
+++ b/opengever/api/schema/adapters.py
@@ -1,13 +1,24 @@
from opengever.api.schema.schema import TYPE_TO_BE_ADDED_KEY
from opengever.base.interfaces import IOpengeverBaseLayer
from plone.restapi.types.adapters import ChoiceJsonSchemaProvider
+from plone.restapi.types.adapters import CollectionJsonSchemaProvider
+from plone.restapi.types.adapters import ListJsonSchemaProvider
+from plone.restapi.types.adapters import SetJsonSchemaProvider
+from plone.restapi.types.adapters import TupleJsonSchemaProvider
from plone.restapi.types.interfaces import IJsonSchemaProvider
+from plone.restapi.types.z3crelationadapter import ChoiceslessRelationListSchemaProvider
+from z3c.relationfield.interfaces import IRelationList
from zope.annotation import IAnnotations
from zope.component import adapter
+from zope.component import getMultiAdapter
from zope.component.hooks import getSite
from zope.interface import implementer
from zope.interface import Interface
from zope.schema.interfaces import IChoice
+from zope.schema.interfaces import ICollection
+from zope.schema.interfaces import IList
+from zope.schema.interfaces import ISet
+from zope.schema.interfaces import ITuple
@adapter(IChoice, Interface, IOpengeverBaseLayer)
@@ -20,14 +31,20 @@ class GEVERChoiceJsonSchemaProvider(ChoiceJsonSchemaProvider):
def additional(self):
result = super(GEVERChoiceJsonSchemaProvider, self).additional()
+ # Get information about parent field so that we can use its name to
+ # render URLs to sources on anonymous inner value_type Choice fields
+ parent_field = getattr(self, 'parent_field', None)
+
# Postprocess the ChoiceJsonSchemaProvider to re-build the vocabulary
# like URLs with (possibly) schema-intent aware ones.
if 'source' in result:
- result['source']['@id'] = get_source_url(self.field, self.context, self.request)
+ result['source']['@id'] = get_source_url(self.field, self.context, self.request,
+ parent_field=parent_field)
if 'querysource' in result:
- result['querysource']['@id'] = get_querysource_url(self.field, self.context, self.request)
+ result['querysource']['@id'] = get_querysource_url(self.field, self.context, self.request,
+ parent_field=parent_field)
if 'vocabulary' in result:
# Extract vocab_name from URL
@@ -38,6 +55,88 @@ def additional(self):
return result
+# These IJsonSchemaProviders below are customized so that we can retain
+# a link to the parent field. We do this so that we can use the parent field's
+# name to render URLs to sources on anonymous inner value_type Choice fields.
+
+
+@adapter(ICollection, Interface, IOpengeverBaseLayer)
+@implementer(IJsonSchemaProvider)
+class GEVERCollectionJsonSchemaProvider(CollectionJsonSchemaProvider):
+
+ def get_items(self):
+ """Get items properties."""
+ value_type_adapter = getMultiAdapter(
+ (self.field.value_type, self.context, self.request), IJsonSchemaProvider
+ )
+
+ # Retain information about parent field
+ value_type_adapter.parent_field = self.field
+ return value_type_adapter.get_schema()
+
+
+@adapter(ITuple, Interface, IOpengeverBaseLayer)
+@implementer(IJsonSchemaProvider)
+class GEVERTupleJsonSchemaProvider(TupleJsonSchemaProvider):
+
+ def get_items(self):
+ """Get items properties."""
+ value_type_adapter = getMultiAdapter(
+ (self.field.value_type, self.context, self.request), IJsonSchemaProvider
+ )
+
+ # Retain information about parent field
+ value_type_adapter.parent_field = self.field
+ return value_type_adapter.get_schema()
+
+
+@adapter(ISet, Interface, IOpengeverBaseLayer)
+@implementer(IJsonSchemaProvider)
+class GEVERSetJsonSchemaProvider(SetJsonSchemaProvider):
+
+ def get_items(self):
+ """Get items properties."""
+ value_type_adapter = getMultiAdapter(
+ (self.field.value_type, self.context, self.request), IJsonSchemaProvider
+ )
+
+ # Retain information about parent field
+ value_type_adapter.parent_field = self.field
+ return value_type_adapter.get_schema()
+
+
+@adapter(IList, Interface, IOpengeverBaseLayer)
+@implementer(IJsonSchemaProvider)
+class GEVERListJsonSchemaProvider(ListJsonSchemaProvider):
+
+ def get_items(self):
+ """Get items properties."""
+ value_type_adapter = getMultiAdapter(
+ (self.field.value_type, self.context, self.request), IJsonSchemaProvider
+ )
+
+ # Retain information about parent field
+ value_type_adapter.parent_field = self.field
+ return value_type_adapter.get_schema()
+
+
+@adapter(IRelationList, Interface, IOpengeverBaseLayer)
+@implementer(IJsonSchemaProvider)
+class GEVERChoiceslessRelationListSchemaProvider(ChoiceslessRelationListSchemaProvider):
+ def get_items(self):
+ """Get items properties."""
+ value_type_adapter = getMultiAdapter(
+ (self.field.value_type, self.context, self.request), IJsonSchemaProvider
+ )
+
+ # Prevent rendering all choices.
+ value_type_adapter.should_render_choices = False
+
+ # Retain information about parent field
+ value_type_adapter.parent_field = self.field
+
+ return value_type_adapter.get_schema()
+
def get_vocab_like_url(endpoint, locator, context, request):
"""Construct a schema-intent aware URL to a vocabulary-like endpoint.
@@ -76,9 +175,27 @@ def get_vocabulary_url(vocab_name, context, request, portal_type=None):
return get_vocab_like_url('@vocabularies', vocab_name, context, request)
-def get_querysource_url(field, context, request, portal_type=None):
- return get_vocab_like_url('@querysources', field.getName(), context, request)
+def get_querysource_url(field, context, request, portal_type=None, parent_field=None):
+ field_name = field.getName()
+ if parent_field:
+ # If we're getting passed a parent_field, we assume that our actual
+ # field is an anonymous inner Choice field that's being used as the
+ # value_type for the multivalued parent_field. In that case, we omit
+ # the inner field's empty string name from the URL, and instead
+ # construct an URL that points to the parent field.
+ field_name = parent_field.getName()
+
+ return get_vocab_like_url('@querysources', field_name, context, request)
+
+def get_source_url(field, context, request, portal_type=None, parent_field=None):
+ field_name = field.getName()
+ if parent_field:
+ # If we're getting passed a parent_field, we assume that our actual
+ # field is an anonymous inner Choice field that's being used as the
+ # value_type for the multivalued parent_field. In that case, we omit
+ # the inner field's empty string name from the URL, and instead
+ # construct an URL that points to the parent field.
+ field_name = parent_field.getName()
-def get_source_url(field, context, request, portal_type=None):
- return get_vocab_like_url('@sources', field.getName(), context, request)
+ return get_vocab_like_url('@sources', field_name, context, request)
diff --git a/opengever/api/schema/configure.zcml b/opengever/api/schema/configure.zcml
index ed463ac9bbc..cfeb1b4c30d 100644
--- a/opengever/api/schema/configure.zcml
+++ b/opengever/api/schema/configure.zcml
@@ -83,5 +83,10 @@
/>
+
+
+
+
+
diff --git a/opengever/api/schema/querysources.py b/opengever/api/schema/querysources.py
index 65296f0b413..8b6136f78d6 100644
--- a/opengever/api/schema/querysources.py
+++ b/opengever/api/schema/querysources.py
@@ -8,6 +8,7 @@
from z3c.formwidget.query.interfaces import IQuerySource
from zope.component import getMultiAdapter
from zope.interface import alsoProvides
+from zope.schema.interfaces import ICollection
class GEVERQuerySourcesGet(GEVERSourcesGet):
@@ -52,10 +53,20 @@ def reply(self):
)
bound_field = field.bind(self.context)
- if hasattr(bound_field, "value_type"):
- source = bound_field.value_type.source
- else:
- source = bound_field.source
+ # Look for a source directly on the field first
+ source = getattr(bound_field, 'source', None)
+
+ # Handle ICollections (like Tuples, Lists and Sets). These don't have
+ # sources themselves, but instead are multivalued, and their
+ # items are backed by a value_type of Choice with a source
+ if ICollection.providedBy(bound_field):
+ source = self._get_value_type_source(bound_field)
+ if not source:
+ ftype = bound_field.__class__.__name__
+ return self._error(
+ 404, "Not Found",
+ "%r Field %r does not have a value_type of Choice with "
+ "an IQuerySource" % (ftype, fieldname))
if not IQuerySource.providedBy(source):
return self._error(
diff --git a/opengever/api/schema/sources.py b/opengever/api/schema/sources.py
index 98dae455d58..76638ee403b 100644
--- a/opengever/api/schema/sources.py
+++ b/opengever/api/schema/sources.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from opengever.base.interfaces import IDuringContentCreation
from plone.dexterity.utils import iterSchemata
from plone.dexterity.utils import iterSchemataForType
@@ -7,6 +6,8 @@
from zope.component import getMultiAdapter
from zope.interface import alsoProvides
from zope.schema import getFieldsInOrder
+from zope.schema.interfaces import IChoice
+from zope.schema.interfaces import ICollection
from zope.schema.interfaces import IIterableSource
from zope.schema.interfaces import ISource
@@ -18,10 +19,15 @@ def __init__(self, context, request):
self.request = request
super(GEVERSourcesGet, self).__init__(context, request)
- def publishTraverse(self, request, name):
- # Treat any path segments after /@sources as parameters
- self.params.append(name)
- return self
+ def _get_value_type_source(self, field):
+ """Get the source of a Choice field that is used as the `value_type`
+ for a multi-valued ICollection field, like ITuple.
+ """
+ value_type = getattr(field, 'value_type', None)
+ value_type_source = getattr(value_type, 'source', None)
+ if not value_type or not IChoice.providedBy(value_type) or not value_type_source:
+ return None
+ return value_type_source
def reply(self):
if len(self.params) not in (1, 2):
@@ -59,10 +65,20 @@ def reply(self):
bound_field = field.bind(self.context)
- if hasattr(bound_field, "value_type"):
- source = bound_field.value_type.source
- else:
- source = bound_field.source
+ # Look for a source directly on the field first
+ source = getattr(bound_field, 'source', None)
+
+ # Handle ICollections (like Tuples, Lists and Sets). These don't have
+ # sources themselves, but instead are multivalued, and their
+ # items are backed by a value_type of Choice with a source
+ if ICollection.providedBy(bound_field):
+ source = self._get_value_type_source(bound_field)
+ if not source:
+ ftype = bound_field.__class__.__name__
+ return self._error(
+ 404, "Not Found",
+ "%r Field %r does not have a value_type of Choice with "
+ "an ISource" % (ftype, fieldname))
if not ISource.providedBy(source):
return self._error(
diff --git a/opengever/base/configure.zcml b/opengever/base/configure.zcml
index 4583fe4c8bc..cf76edc8d62 100644
--- a/opengever/base/configure.zcml
+++ b/opengever/base/configure.zcml
@@ -28,6 +28,15 @@
+
+