diff --git a/ckanext/scheming/helpers.py b/ckanext/scheming/helpers.py
index f118c747..3bd842c5 100644
--- a/ckanext/scheming/helpers.py
+++ b/ckanext/scheming/helpers.py
@@ -27,6 +27,9 @@ def lang():
from ckantoolkit import h
return h.lang()
+@helper
+def scheming_composite_separator():
+ return config.get('scheming.composite.separator','-')
@helper
def scheming_language_text(text, prefer_lang=None):
@@ -420,16 +423,48 @@ def scheming_flatten_subfield(subfield, data):
If data already contains flattened subfields (e.g. rendering values
after a validation error) then they are returned as-is.
"""
+ from ckantoolkit import h
flat = dict(data)
+ sep = h.scheming_composite_separator()
if subfield['field_name'] not in data:
return flat
for i, record in enumerate(data[subfield['field_name']]):
- prefix = '{field_name}-{index}-'.format(
+ prefix = '{field_name}{sep}{index}{sep}'.format(
field_name=subfield['field_name'],
index=i,
+ sep=sep,
)
for k in record:
flat[prefix + k] = record[k]
return flat
+
+@helper
+def scheming_flatten_simple_subfield(subfield, data):
+ """
+ Return flattened_data that converts all nested data for this subfield
+ into {field_name}-{subfield_name} values at the top level,
+ so that it matches the names of form fields submitted.
+
+ If data already contains flattened subfields (e.g. rendering values
+ after a validation error) then they are returned as-is.
+ """
+ from ckantoolkit import h
+ flat = dict(data)
+ sep = h.scheming_composite_separator()
+
+ if subfield['field_name'] not in data:
+ return flat
+
+ subdata = data[subfield['field_name']]
+ if(isinstance(subdata, list) and len(subdata) == 1):
+ subdata = subdata[0]
+
+ for field, value in subdata.items():
+ prefix = '{field_name}{sep}'.format(
+ field_name=subfield['field_name'],
+ sep=sep,
+ )
+ flat[prefix + field] = value
+ return flat
diff --git a/ckanext/scheming/plugins.py b/ckanext/scheming/plugins.py
index 1484780d..1daaf055 100644
--- a/ckanext/scheming/plugins.py
+++ b/ckanext/scheming/plugins.py
@@ -244,7 +244,9 @@ def validate(self, context, data_dict, schema, action):
if before:
schema['__before'] = validation.validators_from_string(
- before, None, scheming_schema)
+ before, None, scheming_schema) + validation.validators_from_string('scheming_simple_subfields', None, scheming_schema)
+ else:
+ schema['__before'] = validation.validators_from_string('scheming_simple_subfields', None, scheming_schema)
if after:
schema['__after'] = validation.validators_from_string(
after, None, scheming_schema)
@@ -262,7 +264,7 @@ def validate(self, context, data_dict, schema, action):
scheming_schema,
convert_this
)
- if convert_this and 'repeating_subfields' in f:
+ if convert_this and ('repeating_subfields' in f or 'simple_subfields' in f):
composite_convert_fields.append(f['field_name'])
def composite_convert_to(key, data, errors, context):
@@ -284,6 +286,14 @@ def composite_convert_to(key, data, errors, context):
if ex['key'] not in composite_convert_fields
]
else:
+ dataset_simple_composite = {
+ f['field_name']
+ for f in scheming_schema['dataset_fields']
+ if 'simple_subfields' in f
+ }
+ if dataset_simple_composite:
+ expand_form_simple_composite(data_dict, dataset_simple_composite)
+
dataset_composite = {
f['field_name']
for f in scheming_schema['dataset_fields']
@@ -331,15 +341,16 @@ def expand_form_composite(data, fieldnames):
when submitting dataset/resource form composite fields look like
"field-0-subfield..." convert these to lists of dicts
"""
+ sep = p.toolkit.h.scheming_composite_separator()
# if "field" exists, don't look for "field-0-subfield"
fieldnames -= set(data)
if not fieldnames:
return
indexes = {}
for key in sorted(data):
- if '-' not in key:
+ if sep not in key:
continue
- parts = key.split('-')
+ parts = key.split(sep)
if parts[0] not in fieldnames:
continue
if parts[1] not in indexes:
@@ -348,16 +359,43 @@ def expand_form_composite(data, fieldnames):
parts[1] = indexes[parts[1]]
try:
try:
- comp[int(parts[1])]['-'.join(parts[2:])] = data[key]
+ comp[int(parts[1])][sep.join(parts[2:])] = data[key]
del data[key]
except IndexError:
comp.append({})
- comp[int(parts[1])]['-'.join(parts[2:])] = data[key]
+ comp[int(parts[1])][sep.join(parts[2:])] = data[key]
del data[key]
except (IndexError, ValueError):
pass # best-effort only
+def expand_form_simple_composite(data, fieldnames):
+ """
+ when submitting dataset/resource form composite fields look like
+ "field-subfield..." convert these to lists of dicts
+ """
+ sep = p.toolkit.h.scheming_composite_separator()
+ # if "field" exists, don't look for "field-subfield"
+ fieldnames -= set(data)
+ if not fieldnames:
+ return
+ for key in sorted(data):
+ if sep not in key:
+ continue
+ parts = key.split(sep)
+ if parts[0] not in fieldnames:
+ continue
+ comp = data.setdefault(parts[0], [])
+ try:
+ try:
+ comp[0][sep.join(parts[1:])] = data[key]
+ del data[key]
+ except IndexError:
+ comp.append({})
+ comp[0][sep.join(parts[1:])] = data[key]
+ del data[key]
+ except (IndexError, ValueError):
+ pass # best-effort only
class SchemingGroupsPlugin(p.SingletonPlugin, _GroupOrganizationMixin,
DefaultGroupForm, _SchemingMixin):
@@ -445,12 +483,13 @@ def before_dataset_index(self, data_dict):
for d in schemas[data_dict['type']]['dataset_fields']:
if d['field_name'] not in data_dict:
continue
- if 'repeating_subfields' in d:
+ if 'simple_subfields' in d and isinstance(data_dict[d['field_name']], list):
+ data_dict[d['field_name']] = data_dict[d['field_name']][0]
+ if 'repeating_subfields' in d or 'simple_subfields' in d:
data_dict[d['field_name']] = json.dumps(data_dict[d['field_name']])
return data_dict
-
def _load_schemas(schemas, type_field):
out = {}
for n in schemas:
@@ -516,7 +555,12 @@ def _field_output_validators(f, schema, convert_extras,
"""
Return the output validators for a scheming field f
"""
- if 'repeating_subfields' in f:
+ if 'simple_subfields' in f:
+ validators = {
+ sf['field_name']: _field_output_validators(sf, schema, False)
+ for sf in f['simple_subfields']
+ }
+ elif 'repeating_subfields' in f:
validators = {
sf['field_name']: _field_output_validators(sf, schema, False)
for sf in f['repeating_subfields']
@@ -551,7 +595,12 @@ def _field_validators(f, schema, convert_extras):
# If this field contains children, we need a special validator to handle
# them.
- if 'repeating_subfields' in f:
+ if 'simple_subfields' in f:
+ validators = {
+ sf['field_name']: _field_validators(sf, schema, False)
+ for sf in f['simple_subfields']
+ }
+ elif 'repeating_subfields' in f:
validators = {
sf['field_name']: _field_validators(sf, schema, False)
for sf in f['repeating_subfields']
@@ -579,7 +628,12 @@ def _field_create_validators(f, schema, convert_extras):
# If this field contains children, we need a special validator to handle
# them.
- if 'repeating_subfields' in f:
+ if 'simple_subfields' in f:
+ validators = {
+ sf['field_name']: _field_create_validators(sf, schema, False)
+ for sf in f['simple_subfields']
+ }
+ elif 'repeating_subfields' in f:
validators = {
sf['field_name']: _field_create_validators(sf, schema, False)
for sf in f['repeating_subfields']
diff --git a/ckanext/scheming/templates/scheming/display_snippets/repeating_subfields.html b/ckanext/scheming/templates/scheming/display_snippets/repeating_subfields.html
index f4948aa9..99756df9 100644
--- a/ckanext/scheming/templates/scheming/display_snippets/repeating_subfields.html
+++ b/ckanext/scheming/templates/scheming/display_snippets/repeating_subfields.html
@@ -9,6 +9,7 @@
{% for subfield in field.repeating_subfields %}
+ {% if subfield.field_name in field_data and field_data[subfield.field_name]|length and subfield.display_snippet is not none %}
diff --git a/ckanext/scheming/templates/scheming/display_snippets/simple_subfields.html b/ckanext/scheming/templates/scheming/display_snippets/simple_subfields.html
new file mode 100644
index 00000000..ff76f86b
--- /dev/null
+++ b/ckanext/scheming/templates/scheming/display_snippets/simple_subfields.html
@@ -0,0 +1,26 @@
+{% set fields = data[field.field_name] %}
+{% block subfield_display %}
+
+
+ {% for field_data in fields %}
+
+ {% for subfield in field.simple_subfields %}
+ {% if subfield.field_name in field_data and field_data[subfield.field_name]|length and subfield.display_snippet is not none %}
+
+ {% block removal_text %}
+ {% if 'id' in data %}
+ {{ _('These fields have been removed, click update below to save your changes.') }}
+ {% else %}
+ {{ _('These fields have been removed.') }}
+ {% endif %}
+ {% endblock %}
+
+
+
+{% endmacro %}
+
+{% set flat = h.scheming_flatten_simple_subfield(field, data) %}
+{% set flaterr = h.scheming_flatten_simple_subfield(field, errors) %}
+
+{% call form.input_block(
+ 'field-' + field.field_name,
+ h.scheming_language_text(field.label) or field.field_name,
+ [],
+ field.classes if 'classes' in field else ['control-medium'],
+ dict({"class": "form-control"}, **(field.get('form_attrs', {}))),
+ is_required=h.scheming_field_required(field)) %}
+
+{% endcall %}
diff --git a/ckanext/scheming/templates/scheming/snippets/display_field.html b/ckanext/scheming/templates/scheming/snippets/display_field.html
index 1ef4d0d9..189f99c7 100644
--- a/ckanext/scheming/templates/scheming/snippets/display_field.html
+++ b/ckanext/scheming/templates/scheming/snippets/display_field.html
@@ -5,6 +5,8 @@
{%- if not display_snippet -%}
{%- if field.repeating_subfields -%}
{%- set display_snippet = 'repeating_subfields.html' -%}
+ {%- elif field.simple_subfields -%}
+ {%- set display_snippet = 'simple_subfields.html' -%}
{%- elif h.scheming_field_choices(field) -%}
{%- set display_snippet = 'select.html' -%}
{%- else -%}
diff --git a/ckanext/scheming/templates/scheming/snippets/errors.html b/ckanext/scheming/templates/scheming/snippets/errors.html
index a8298b27..23753bfe 100644
--- a/ckanext/scheming/templates/scheming/snippets/errors.html
+++ b/ckanext/scheming/templates/scheming/snippets/errors.html
@@ -54,6 +54,38 @@
{%- endif -%}
{%- endfor -%}
+ {%- elif 'simple_subfields' in field %}
+ {%- for se in errors -%}
+ {%- if se -%}
+
{{
+ h.scheming_language_text(field.label) }}:
+
+ {%- for sf in field.simple_subfields -%}
+ {%- set se_unprocessed = se.copy() -%}
+
+ {%- if 'error_snippet' in sf -%}
+ {%- set sfe_snippet = sf.error_snippet -%}
+
+ {%- if '/' not in sfe_snippet -%}
+ {%- set sfe_snippet = 'scheming/error_snippets/' +
+ sfe_snippet -%}
+ {%- endif -%}
+
+ {%- snippet sfe_snippet, unprocessed=se_unprocessed,
+ field=sf, fields=field.simple_subfileds,
+ entity_type=entity_type, object_type=object_type -%}
+ {%- endif -%}
+
+ {%- if sf.field_name in se_unprocessed -%}
+