diff --git a/rdmo/core/xml.py b/rdmo/core/xml.py
index 6e2c53b82d..5327969433 100644
--- a/rdmo/core/xml.py
+++ b/rdmo/core/xml.py
@@ -2,6 +2,7 @@
import re
import defusedxml.ElementTree as ET
+from packaging.version import parse
log = logging.getLogger(__name__)
@@ -117,12 +118,15 @@ def strip_ns(tag, ns_map):
def convert_elements(elements, version):
- # in future versions, this method can be extended
- # using packaging.version.parse
- if version is None:
- return convert_legacy_elements(elements)
- else:
- return elements
+ parsed_version = parse('1.11.0') if version is None else parse(version)
+
+ if parsed_version < parse('2.0.0'):
+ elements = convert_legacy_elements(elements)
+
+ if parsed_version < parse('2.1.0'):
+ elements = convert_additional_input(elements)
+
+ return elements
def convert_legacy_elements(elements):
@@ -211,6 +215,18 @@ def convert_legacy_elements(elements):
return elements
+def convert_additional_input(elements):
+ for uri, element in elements.items():
+ if element['model'] == 'options.option':
+ additional_input = element.get('additional_input')
+ if additional_input == 'True':
+ element['additional_input'] = 'text'
+ else:
+ element['additional_input'] = ''
+
+ return elements
+
+
def order_elements(elements):
ordered_elements = {}
for uri, element in elements.items():
diff --git a/rdmo/management/assets/js/actions/configActions.js b/rdmo/management/assets/js/actions/configActions.js
index e3c57f2452..baf9203e5f 100644
--- a/rdmo/management/assets/js/actions/configActions.js
+++ b/rdmo/management/assets/js/actions/configActions.js
@@ -17,12 +17,13 @@ export function fetchConfig() {
CoreApi.fetchSettings(),
CoreApi.fetchSites(),
ManagementApi.fetchMeta(),
+ OptionsApi.fetchAdditionalInputs(),
OptionsApi.fetchProviders(),
QuestionsApi.fetchValueTypes(),
QuestionsApi.fetchWidgetTypes()
- ]).then(([relations, groups, settings, sites, meta, providers,
+ ]).then(([relations, groups, settings, sites, meta, additionalInputs, providers,
valueTypes, widgetTypes]) => dispatch(fetchConfigSuccess({
- relations, groups, settings, sites, meta, providers, valueTypes, widgetTypes
+ relations, groups, settings, sites, meta, additionalInputs, providers, valueTypes, widgetTypes
})))
}
diff --git a/rdmo/management/assets/js/api/OptionsApi.js b/rdmo/management/assets/js/api/OptionsApi.js
index 550e050bf3..30e5545a84 100644
--- a/rdmo/management/assets/js/api/OptionsApi.js
+++ b/rdmo/management/assets/js/api/OptionsApi.js
@@ -51,6 +51,10 @@ class OptionsApi extends BaseApi {
return this.delete(`/api/v1/options/options/${option.id}/`)
}
+ static fetchAdditionalInputs() {
+ return this.get('/api/v1/options/additionalinputs/')
+ }
+
static fetchProviders() {
return this.get('/api/v1/options/providers/')
}
diff --git a/rdmo/management/assets/js/components/edit/EditOption.js b/rdmo/management/assets/js/components/edit/EditOption.js
index 9f011d6fbc..fcb1cea748 100644
--- a/rdmo/management/assets/js/components/edit/EditOption.js
+++ b/rdmo/management/assets/js/components/edit/EditOption.js
@@ -4,6 +4,7 @@ import { Tabs, Tab } from 'react-bootstrap'
import get from 'lodash/get'
import Checkbox from './common/Checkbox'
+import Radio from './common/Radio'
import Select from './common/Select'
import Text from './common/Text'
import Textarea from './common/Textarea'
@@ -19,7 +20,7 @@ import useDeleteModal from '../../hooks/useDeleteModal'
const EditOption = ({ config, option, elements, elementActions }) => {
- const { sites } = config
+ const { additionalInputs, sites } = config
const { elementAction, parent } = elements
const updateOption = (key, value) => elementActions.updateElement(option, {[key]: value})
@@ -81,10 +82,6 @@ const EditOption = ({ config, option, elements, elementActions }) => {
-
-
-
@@ -102,6 +99,9 @@ const EditOption = ({ config, option, elements, elementActions }) => {
}
+
+
{get(config, 'settings.multisite') && }
diff --git a/rdmo/management/assets/js/components/edit/common/Radio.js b/rdmo/management/assets/js/components/edit/common/Radio.js
new file mode 100644
index 0000000000..6e2c530e97
--- /dev/null
+++ b/rdmo/management/assets/js/components/edit/common/Radio.js
@@ -0,0 +1,59 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classNames from 'classnames'
+import isEmpty from 'lodash/isEmpty'
+import isNil from 'lodash/isNil'
+import get from 'lodash/get'
+
+import { getId, getLabel, getHelp } from 'rdmo/management/assets/js/utils/forms'
+
+const Radio = ({ config, element, field, options, onChange }) => {
+ const id = getId(element, field),
+ label = getLabel(config, element, field),
+ help = getHelp(config, element, field),
+ warnings = get(element, ['warnings', field]),
+ errors = get(element, ['errors', field])
+
+ const className = classNames({
+ 'form-group': true,
+ 'has-warning': !isEmpty(warnings),
+ 'has-error': !isEmpty(errors)
+ })
+
+ const value = isNil(element[field]) ? '' : element[field]
+
+ return (
+
+
+
+
+ {
+ options.map((option, index) => (
+
+ ))
+ }
+
+
+ {help &&
{help}
}
+
+ {errors &&
+ {errors.map((error, index) => - {error}
)}
+
}
+
+ )
+}
+
+Radio.propTypes = {
+ config: PropTypes.object,
+ element: PropTypes.object,
+ field: PropTypes.string,
+ options: PropTypes.array,
+ onChange: PropTypes.func
+}
+
+export default Radio
diff --git a/rdmo/options/imports.py b/rdmo/options/imports.py
index 757fc34778..47ee721529 100644
--- a/rdmo/options/imports.py
+++ b/rdmo/options/imports.py
@@ -62,7 +62,7 @@ def import_option(element, save=False, user=None):
set_common_fields(option, element)
- option.additional_input = element.get('additional_input') or False
+ option.additional_input = element.get('additional_input')
set_lang_field(option, 'text', element)
diff --git a/rdmo/options/migrations/0032_alter_option_additional_input.py b/rdmo/options/migrations/0032_alter_option_additional_input.py
new file mode 100644
index 0000000000..5bebaf9327
--- /dev/null
+++ b/rdmo/options/migrations/0032_alter_option_additional_input.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.5 on 2023-10-01 12:55
+
+from django.db import migrations, models
+
+
+def run_data_migration(apps, schema_editor):
+ Option = apps.get_model('options', 'Option')
+
+ for option in Option.objects.all():
+ option.additional_input = 'text' if option.additional_input == 'True' else ''
+ option.save()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('options', '0031_add_editors'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='option',
+ name='additional_input',
+ field=models.CharField(blank=True, choices=[('', 'None'), ('text', 'Text'), ('textarea', 'Textarea')], default=False, help_text='Designates whether an additional input is possible for this option.', max_length=256, verbose_name='Additional input'),
+ ),
+ migrations.RunPython(run_data_migration),
+ ]
diff --git a/rdmo/options/models.py b/rdmo/options/models.py
index a450cf64ad..9cfb34c1c6 100644
--- a/rdmo/options/models.py
+++ b/rdmo/options/models.py
@@ -147,6 +147,15 @@ def __str__(self):
class Option(models.Model, TranslationMixin):
+ ADDITIONAL_INPUT_NONE = ''
+ ADDITIONAL_INPUT_TEXT = 'text'
+ ADDITIONAL_INPUT_TEXTAREA = 'textarea'
+ ADDITIONAL_INPUT_CHOICES = (
+ (ADDITIONAL_INPUT_NONE, _('None')),
+ (ADDITIONAL_INPUT_TEXT, _('Text')),
+ (ADDITIONAL_INPUT_TEXTAREA, _('Textarea'))
+ )
+
uri = models.URLField(
max_length=800, blank=True,
verbose_name=_('URI'),
@@ -202,8 +211,8 @@ class Option(models.Model, TranslationMixin):
verbose_name=_('Text (quinary)'),
help_text=_('The text for this option in the quinary language.')
)
- additional_input = models.BooleanField(
- default=False,
+ additional_input = models.CharField(
+ max_length=256, blank=True, default=False, choices=ADDITIONAL_INPUT_CHOICES,
verbose_name=_('Additional input'),
help_text=_('Designates whether an additional input is possible for this option.')
)
diff --git a/rdmo/options/urls/v1.py b/rdmo/options/urls/v1.py
index 88e879bb23..1c7980aefe 100644
--- a/rdmo/options/urls/v1.py
+++ b/rdmo/options/urls/v1.py
@@ -2,13 +2,14 @@
from rest_framework import routers
-from ..viewsets import OptionSetViewSet, OptionViewSet, ProviderViewSet
+from ..viewsets import AdditionalInputsViewSet, OptionSetViewSet, OptionViewSet, ProviderViewSet
app_name = 'v1-options'
router = routers.DefaultRouter()
router.register(r'optionsets', OptionSetViewSet, basename='optionset')
router.register(r'options', OptionViewSet, basename='option')
+router.register(r'additionalinputs', AdditionalInputsViewSet, basename='additionalinput')
router.register(r'providers', ProviderViewSet, basename='provider')
urlpatterns = [
diff --git a/rdmo/options/viewsets.py b/rdmo/options/viewsets.py
index 804e003fcc..1922d7f223 100644
--- a/rdmo/options/viewsets.py
+++ b/rdmo/options/viewsets.py
@@ -141,6 +141,11 @@ def detail_export(self, request, pk=None, export_format='xml'):
)
+class AdditionalInputsViewSet(ChoicesViewSet):
+ permission_classes = (IsAuthenticated, )
+ queryset = Option.ADDITIONAL_INPUT_CHOICES
+
+
class ProviderViewSet(ChoicesViewSet):
permission_classes = (IsAuthenticated, )
queryset = settings.OPTIONSET_PROVIDERS
diff --git a/rdmo/projects/static/projects/css/project_questions.scss b/rdmo/projects/static/projects/css/project_questions.scss
index f4141d99b9..3728c891cd 100644
--- a/rdmo/projects/static/projects/css/project_questions.scss
+++ b/rdmo/projects/static/projects/css/project_questions.scss
@@ -195,6 +195,13 @@
margin: 0;
}
+ .radio-control,
+ .checkbox-control {
+ label {
+ width: 100%;
+ }
+ }
+
.file-control {
display: block;
width: 100%;
diff --git a/rdmo/projects/templates/projects/project_questions_form_group_radio.html b/rdmo/projects/templates/projects/project_questions_form_group_radio.html
index 29e498e8c7..d521ca91b9 100644
--- a/rdmo/projects/templates/projects/project_questions_form_group_radio.html
+++ b/rdmo/projects/templates/projects/project_questions_form_group_radio.html
@@ -30,7 +30,14 @@
{$ option.text $}:
+
+