def test_set_field_order(self): schema = JSONSchema() schema.add_property('foo', {'type': 'string'}) schema.add_property('bar', {'type': 'string'}) schema.set_field_order(['foo', 'bar']) expected = { '$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'properties': { 'foo': {'type': 'string'}, 'bar': {'type': 'string'}, }, 'field_order': ['foo', 'bar'], } serialized = schema.serialize() self.assertEqual(expected, serialized)
class JSONSchemaBuilder(object): """Builds a JSON Schema representation of a single GEVER type. """ def __init__(self, portal_type): self.portal_type = portal_type self.schema = None if portal_type in GEVER_TYPES: self.type_dumper = TypeDumper() elif portal_type in GEVER_SQL_TYPES: self.type_dumper = SQLTypeDumper() else: raise Exception("Unmapped type: %r" % portal_type) def build_schema(self): type_dump = self.type_dumper.dump(self.portal_type) self.schema = JSONSchema( title=type_dump['title'], additional_properties=False, ) field_order = [] # Collect field info from all schemas (base schema + behaviors) for _schema in type_dump['schemas']: # Note: This is not the final / "correct" field order as displayed # in the user interface (which should eventually honor fieldsets # and plone.autoform directives). # The intent here is rather to keep a *consistent* order for now. field_order.extend([field['name'] for field in _schema['fields']]) translated_title_fields = [] for field in _schema['fields']: prop_def = self._property_definition_from_field(field) self.schema.add_property(field['name'], prop_def) if field['name'] in TRANSLATED_TITLE_NAMES: translated_title_fields.append(field['name']) if translated_title_fields: self.schema.require_any_of(translated_title_fields) self.schema.set_field_order(field_order) return self.schema def _property_definition_from_field(self, field): """Create a JSON Schema property definition from a field info dump. """ prop_def = self._js_type_from_zope_type(field['type']) prop_def['title'] = field['title'] prop_def['description'] = field['description'] prop_def['_zope_schema_type'] = field['type'] self._process_choice(prop_def, field) self._process_max_length(prop_def, field) self._process_default(prop_def, field) self._process_vocabulary(prop_def, field) self._process_required(prop_def, field) return prop_def def _js_type_from_zope_type(self, zope_type): """Map a zope.schema type to a JSON schema (JavaScript) type. Returns a minimal property definition dict including at least a 'type', and possibly a 'format' as well. """ if zope_type not in JSON_SCHEMA_FIELD_TYPES: raise Exception( "Don't know what JS type to map %r to. Please map it in " "JSON_SCHEMA_FIELD_TYPES first." % zope_type) type_spec = JSON_SCHEMA_FIELD_TYPES[zope_type].copy() return type_spec def _process_choice(self, prop_def, field): if field['type'] == 'Choice': prop_def['type'] = field['value_type'] def _process_max_length(self, prop_def, field): if field.get('max_length') is not None: prop_def['maxLength'] = field['max_length'] def _process_default(self, prop_def, field): if 'default' in field: prop_def['default'] = field['default'] def _process_vocabulary(self, prop_def, field): if field.get('vocabulary'): vocab = field['vocabulary'] if isinstance(vocab, basestring) and vocab.startswith('<'): # Placeholder vocabulary (like <Any valid user> ) prop_def['_vocabulary'] = vocab else: prop_def['enum'] = vocab def _process_required(self, prop_def, field): if field.get('required', False): if field['name'] not in TRANSLATED_TITLE_NAMES: self.schema.set_required(field['name'])