From d511cb80ad5c8d1a37f0de4bc7de670ce6d604cf Mon Sep 17 00:00:00 2001 From: Jochen Klar Date: Wed, 4 Oct 2023 08:09:18 +0200 Subject: [PATCH 1/5] Refactor Option.additional_input to be a choice field and add textarea input for options (#594) --- rdmo/core/xml.py | 28 +++++++-- .../assets/js/actions/configActions.js | 5 +- rdmo/management/assets/js/api/OptionsApi.js | 4 ++ .../assets/js/components/edit/EditOption.js | 10 ++-- .../assets/js/components/edit/common/Radio.js | 59 +++++++++++++++++++ rdmo/options/imports.py | 2 +- .../0032_alter_option_additional_input.py | 27 +++++++++ rdmo/options/models.py | 13 +++- rdmo/options/urls/v1.py | 3 +- rdmo/options/viewsets.py | 5 ++ .../projects/css/project_questions.scss | 7 +++ .../project_questions_form_group_radio.html | 9 ++- 12 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 rdmo/management/assets/js/components/edit/common/Radio.js create mode 100644 rdmo/options/migrations/0032_alter_option_additional_input.py 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') && onChange(field, event.target.value)}/> + {option.text} + + )) + } + + + {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 $}: + +