Skip to content

Commit

Permalink
Refactor Option.additional_input to be a choice field and add textarea
Browse files Browse the repository at this point in the history
input for options (#594)
  • Loading branch information
jochenklar committed Nov 24, 2023
1 parent c737247 commit d511cb8
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 18 deletions.
28 changes: 22 additions & 6 deletions rdmo/core/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re

import defusedxml.ElementTree as ET
from packaging.version import parse

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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():
Expand Down
5 changes: 3 additions & 2 deletions rdmo/management/assets/js/actions/configActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
})))
}

Expand Down
4 changes: 4 additions & 0 deletions rdmo/management/assets/js/api/OptionsApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/')
}
Expand Down
10 changes: 5 additions & 5 deletions rdmo/management/assets/js/components/edit/EditOption.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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})
Expand Down Expand Up @@ -81,10 +82,6 @@ const EditOption = ({ config, option, elements, elementActions }) => {
<Checkbox config={config} element={option} field="locked"
onChange={updateOption} />
</div>
<div className="col-sm-6">
<Checkbox config={config} element={option} field="additional_input"
onChange={updateOption} />
</div>
</div>

<Tabs id="#option-tabs" defaultActiveKey={0} animation={false}>
Expand All @@ -102,6 +99,9 @@ const EditOption = ({ config, option, elements, elementActions }) => {
}
</Tabs>

<Radio config={config} element={option} field="additional_input"
options={additionalInputs} onChange={updateOption} />

{get(config, 'settings.multisite') && <Select config={config} element={option} field="editors"
options={sites} onChange={updateOption} isMulti />}
</div>
Expand Down
59 changes: 59 additions & 0 deletions rdmo/management/assets/js/components/edit/common/Radio.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className={className}>
<label className="control-label" htmlFor={id}>{label}</label>

<div>
{
options.map((option, index) => (
<label key={index} className="radio-inline">
<input type="radio" name="inlineRadioOptions" disabled={element.read_only}
checked={value === option.id}
value={option.id} onChange={event => onChange(field, event.target.value)}/>
<span>{option.text}</span>
</label>
))
}
</div>

{help && <p className="help-block">{help}</p>}

{errors && <ul className="help-block list-unstyled">
{errors.map((error, index) => <li key={index}>{error}</li>)}
</ul>}
</div>
)
}

Radio.propTypes = {
config: PropTypes.object,
element: PropTypes.object,
field: PropTypes.string,
options: PropTypes.array,
onChange: PropTypes.func
}

export default Radio
2 changes: 1 addition & 1 deletion rdmo/options/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
27 changes: 27 additions & 0 deletions rdmo/options/migrations/0032_alter_option_additional_input.py
Original file line number Diff line number Diff line change
@@ -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 ''

This comment has been minimized.

Copy link
@MyPyDavid

MyPyDavid Feb 9, 2024

Member

Hi @jochenklar , we have one Issue with the update to 2.1.2 (from 2.0.2) in the text and options.
The previous additional_input=True fields are missing and all converted into -----.

Could this 'True' be a typo in the run_data_migration and cause of this data migration issue?

option.additional_input == 'True'
# should be 
option.additional_input == True
# or simply
if option.additional_input 

This comment has been minimized.

Copy link
@jochenklar

jochenklar Feb 9, 2024

Author Member

Crap, yes this would cause the problem. Lets make a fix asap.

This comment has been minimized.

Copy link
@jochenklar

jochenklar Feb 9, 2024

Author Member

A colleague told me about the same problem yesterday.

This comment has been minimized.

Copy link
@MyPyDavid

MyPyDavid Feb 9, 2024

Member

a fix and a test yes!
It is repairable when there is a backup of the DB but requires some admin shell commands

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),
]
13 changes: 11 additions & 2 deletions rdmo/options/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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.')
)
Expand Down
3 changes: 2 additions & 1 deletion rdmo/options/urls/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
5 changes: 5 additions & 0 deletions rdmo/options/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions rdmo/projects/static/projects/css/project_questions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@
margin: 0;
}

.radio-control,
.checkbox-control {
label {
width: 100%;
}
}

.file-control {
display: block;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@
<span>{$ option.text $}</span><span ng-show="option.additional_input">:</span>

<input class="form-control input-sm" type="text"
ng-show="option.additional_input"
ng-show="option.additional_input == 'text'"
ng-model="value.additional_input[option.id]"
ng-change="value.selected = option.id.toString(); service.changed(value, true)"
ng-focus="value.selected = option.id.toString()"
ng-disabled="service.project.read_only" />

<textarea class="form-control input-sm" type="text" rows="4"
ng-show="option.additional_input == 'textarea'"
ng-model="value.additional_input[option.id]"
ng-change="value.selected = option.id.toString(); service.changed(value, true)"
ng-focus="value.selected = option.id.toString()"
Expand Down

0 comments on commit d511cb8

Please sign in to comment.