Skip to content

Commit

Permalink
Add query criteria schema to types service
Browse files Browse the repository at this point in the history
  • Loading branch information
davisagli committed Nov 10, 2018
1 parent dd4a694 commit c1f21ea
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 36 deletions.
8 changes: 6 additions & 2 deletions CHANGES.rst
@@ -1,10 +1,14 @@
Changelog
=========

3.5.1 (unreleased)
3.6.0 (unreleased)
------------------

- Nothing changed yet.
New Features:

- Improve the types service to include schema for the criteria
that can be saved in a Collection's query field.
[davisagli]


3.5.0 (2018-11-06)
Expand Down
5 changes: 5 additions & 0 deletions src/plone/restapi/tests/test_services_types.py
Expand Up @@ -112,6 +112,11 @@ def test_file_type(self):
self.assertIn(
'file.data', response['properties']['file']['properties'])

def test_collection_type(self):
response = self.api_session.get('/@types/Collection')
response = response.json()
self.assertIn('anyOf', response['properties']['query']['items'])

def test_addable_types_for_non_manager_user(self):
user = api.user.create(
email='noam.chomsky@example.com',
Expand Down
108 changes: 74 additions & 34 deletions src/plone/restapi/types/adapters.py
Expand Up @@ -3,6 +3,8 @@
from plone.autoform.interfaces import WIDGETS_KEY

from plone.app.textfield.interfaces import IRichText
from plone.app.querystring.interfaces import IQuerystringRegistryReader
from plone.registry.interfaces import IRegistry
from plone.schema import IJSONField
from zope.component import adapter
from zope.component import getMultiAdapter
Expand All @@ -16,7 +18,6 @@
from zope.schema.interfaces import IBytes
from zope.schema.interfaces import IChoice
from zope.schema.interfaces import ICollection
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.interfaces import IDate
from zope.schema.interfaces import IDatetime
from zope.schema.interfaces import IDecimal
Expand All @@ -30,11 +31,11 @@
from zope.schema.interfaces import IText
from zope.schema.interfaces import ITextLine
from zope.schema.interfaces import ITuple
from zope.schema.interfaces import IVocabularyFactory

from plone.restapi.types.interfaces import IJsonSchemaProvider
from plone.restapi.types.utils import get_fieldsets, get_tagged_values
from plone.restapi.types.utils import get_jsonschema_properties
from plone.restapi.types.utils import get_jsonschema_for_vocab


@adapter(IField, Interface, Interface)
Expand Down Expand Up @@ -248,43 +249,20 @@ def get_type(self):
return 'string'

def additional(self):
# choices and enumNames are v5 proposals, for now we implement both
choices = []
enum = []
enum_names = []
vocabulary = None
if not self.should_render_choices:
return {}

if getattr(self.field, 'vocabularyName', None):
vocabulary = getUtility(
IVocabularyFactory,
name=self.field.vocabularyName)(self.context)
elif getattr(self.field, 'vocabulary', None):
vocabulary = None
if getattr(self.field, 'vocabulary', None):
vocabulary = self.field.vocabulary
else:
vocab_name = getattr(self.field, 'vocabularyName', None)
if not vocab_name and not vocabulary:
tagged = get_tagged_values([self.field.interface], WIDGETS_KEY)
tagged_field_values = tagged.get(self.field.getName(), {})
vocab_name = tagged_field_values.get('vocabulary', None)
if vocab_name:
vocab_fac = getUtility(IVocabularyFactory, name=vocab_name)
vocabulary = vocab_fac(self.context)

if IContextSourceBinder.providedBy(vocabulary):
vocabulary = vocabulary(self.context)

if hasattr(vocabulary, '__iter__') and self.should_render_choices:
for term in vocabulary:
title = translate(term.title, context=self.request)
choices.append((term.token, title))
enum.append(term.token)
enum_names.append(title)

return {
'enum': enum,
'enumNames': enum_names,
'choices': choices,
}
else:
return {}

return get_jsonschema_for_vocab(
self.context, self.request, vocab_name, vocabulary)


@adapter(IObject, Interface, Interface)
Expand Down Expand Up @@ -398,3 +376,65 @@ def get_type(self):

def get_widget(self):
return 'json'


QUERY_FIELD_WIDGETS = {
'DateRangeWidget': 'daterange',
'RelativeDateWidget': 'relativedate',
'ReferenceWidget': 'reference',
}


@adapter(IList, Interface, Interface)
@implementer(IJsonSchemaProvider)
class QueryFieldJsonSchemaProvider(ListJsonSchemaProvider):

def get_items(self):
result = super(QueryFieldJsonSchemaProvider, self).get_items()
criteria_schemas = []
for fname, field, opname, operation in self._iter_criteria():
value_schema = {
"type": "string",
}
vocab_name = field.get('vocabulary')
if vocab_name:
value_schema.update(
get_jsonschema_for_vocab(
self.context, self.request, vocab_name))
widget = operation.get('widget')
if widget == 'DateWidget':
value_schema['type'] = 'datetime'
widget = QUERY_FIELD_WIDGETS.get(widget)
if widget:
value_schema['widget'] = widget

criteria_schemas.append({
"properties": {
"i": {
"const": fname,
"title": field['title'],
"description": field['description'],
},
"o": {
"const": opname,
"title": operation['title'],
"description": operation['description'],
},
"v": value_schema,
}
})
result['anyOf'] = criteria_schemas
return result

def _iter_criteria(self):
registry = getUtility(IRegistry)
reader = getMultiAdapter(
(registry, self.request), IQuerystringRegistryReader)
values = reader.parseRegistry()
for fname, field in values.get(reader.prefix + '.field').items():
for opname in field['operations']:
operation = values.get(opname)
if opname.startswith('plone.app.querystring.operation.'):
opname = opname[
len('plone.app.querystring.operation') + 1:]
yield fname, field, opname, operation
1 change: 1 addition & 0 deletions src/plone/restapi/types/configure.zcml
Expand Up @@ -29,5 +29,6 @@
<adapter factory=".adapters.DateJsonSchemaProvider" />
<adapter factory=".adapters.DatetimeJsonSchemaProvider" />
<adapter factory=".adapters.JSONFieldSchemaProvider" />
<adapter factory=".adapters.QueryFieldJsonSchemaProvider" name="ICollection.query" />

</configure>
34 changes: 34 additions & 0 deletions src/plone/restapi/types/utils.py
Expand Up @@ -25,8 +25,11 @@
from z3c.form import form as z3c_form
from zope.component import getMultiAdapter
from zope.component import queryMultiAdapter
from zope.component import getUtility
from zope.globalrequest import getRequest
from zope.i18n import translate
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.interfaces import IVocabularyFactory


def create_form(context, request, base_schema, additional_schemata=None):
Expand Down Expand Up @@ -205,3 +208,34 @@ def get_jsonschema_for_portal_type(portal_type, context, request,
fti = ttool[portal_type]
return get_jsonschema_for_fti(
fti, context, request, excluded_fields=excluded_fields)


def get_jsonschema_for_vocab(
context, request, vocab_name=None, vocabulary=None):
if vocab_name:
vocabulary = getUtility(IVocabularyFactory, name=vocab_name)(context)
if vocabulary is None:
return {}

if IContextSourceBinder.providedBy(vocabulary):
vocabulary = vocabulary(context)

if hasattr(vocabulary, '__iter__'):
# choices and enumNames are v5 proposals, for now we implement both
choices = []
enum = []
enum_names = []

for term in vocabulary:
title = translate(term.title, context=request)
choices.append((term.token, title))
enum.append(term.token)
enum_names.append(title)

return {
'enum': enum,
'enumNames': enum_names,
'choices': choices,
}
else:
return {}

0 comments on commit c1f21ea

Please sign in to comment.