Skip to content

Commit

Permalink
add SODARAppSettingsFormMixin, refactor set_app_setting_field() (#1545)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Jan 16, 2025
1 parent ba80c65 commit 1f17dee
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 264 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Added
- ``SiteReadOnlySettingAjaxView`` Ajax view (#24)
- ``siteappsettings`` site app plugin (#1304)
- ``SiteAppSettingsFormView`` view (#1304)
- ``SODARAppSettingFormMixin`` form helper mixin (#1545)

Changed
-------
Expand All @@ -41,6 +42,7 @@ Changed
- Deprecate ``AppSettingAPI.get_all()`` (#1534)
- Allow no role for old owner in ``RoleAssignmentOwnerTransferMixin`` (#836, #1391)
- Allow no role for old owner in ``perform_owner_transfer()`` (#836, #1391)
- Move app setting form helpers in ``SODARAppSettingFormMixin`` (#1545)
- **Tokens**
- Update UI for site read-only mode (#24)
- **Userprofile**
Expand Down
282 changes: 104 additions & 178 deletions projectroles/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
SITE_MODE_SOURCE = SODAR_CONSTANTS['SITE_MODE_SOURCE']
SITE_MODE_TARGET = SODAR_CONSTANTS['SITE_MODE_TARGET']
APP_SETTING_SCOPE_PROJECT = SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT']
APP_SETTING_SCOPE_USER = SODAR_CONSTANTS['APP_SETTING_SCOPE_USER']
APP_SETTING_SCOPE_SITE = SODAR_CONSTANTS['APP_SETTING_SCOPE_SITE']
APP_SETTING_TYPE_BOOLEAN = SODAR_CONSTANTS['APP_SETTING_TYPE_BOOLEAN']
APP_SETTING_TYPE_INTEGER = SODAR_CONSTANTS['APP_SETTING_TYPE_INTEGER']
Expand Down Expand Up @@ -100,6 +101,100 @@ def add_error(self, field, error):
self.logger.error(log_msg)
super().add_error(field, error) # Call the error method in Django forms


class SODARAppSettingsFormMixin:
"""Helpers for app settings handling in forms"""

def set_app_setting_field(self, plugin_name, s_field, s_def):
"""
Helper for setting app setting field, widget and value.
:param plugin_name: App plugin name
:param s_field: Form field name
:param s_def: PluginAppSettingDef object
"""
scope = s_def.scope
scope_kwargs = {}
if scope == APP_SETTING_SCOPE_PROJECT:
scope_kwargs = {
'project': self.instance if self.instance.pk else None
}
elif scope == APP_SETTING_SCOPE_USER:
scope_kwargs = {'user': self.user} # NOTE: Requires self.user
s_widget_attrs = s_def.widget_attrs
if scope == APP_SETTING_SCOPE_PROJECT:
s_project_types = s_def.project_types or [PROJECT_TYPE_PROJECT]
s_widget_attrs['data-project-types'] = ','.join(
s_project_types
).lower()
if s_def.placeholder is not None:
s_widget_attrs['placeholder'] = s_def.placeholder
setting_kwargs = {
'required': False,
'label': s_def.label or '{}.{}'.format(plugin_name, s_def.name),
'help_text': s_def.description,
}

# Option
if s_def.options and callable(s_def.options):
values = s_def.options(**scope_kwargs)
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(str(value[0]), str(value[1]))
if isinstance(value, tuple)
else (str(value), str(value))
)
for value in values
],
**setting_kwargs,
)
elif s_def.options:
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(int(option), int(option))
if s_def.type == APP_SETTING_TYPE_INTEGER
else (option, option)
)
for option in s_def.options
],
**setting_kwargs,
)
# Other types
elif s_def.type == APP_SETTING_TYPE_STRING:
self.fields[s_field] = forms.CharField(
widget=forms.TextInput(attrs=s_widget_attrs), **setting_kwargs
)
elif s_def.type == APP_SETTING_TYPE_INTEGER:
self.fields[s_field] = forms.IntegerField(
widget=forms.NumberInput(attrs=s_widget_attrs), **setting_kwargs
)
elif s_def.type == APP_SETTING_TYPE_BOOLEAN:
self.fields[s_field] = forms.BooleanField(**setting_kwargs)
# JSON
elif s_def.type == APP_SETTING_TYPE_JSON:
# NOTE: Attrs MUST be supplied here (#404)
if 'class' in s_widget_attrs:
s_widget_attrs['class'] += ' sodar-json-input'
else:
s_widget_attrs['class'] = 'sodar-json-input'
self.fields[s_field] = forms.CharField(
widget=forms.Textarea(attrs=s_widget_attrs), **setting_kwargs
)

# Add optional attributes from plugin (#404)
self.fields[s_field].widget.attrs.update(s_widget_attrs)
# Set initial value
value = app_settings.get(
plugin_name=plugin_name,
setting_name=s_def.name,
**scope_kwargs,
)
if s_def.type == APP_SETTING_TYPE_JSON:
value = json.dumps(value)
self.initial[s_field] = value

def get_app_setting_label(self, plugin, label):
"""Return label for app setting key"""
if plugin:
Expand Down Expand Up @@ -268,7 +363,7 @@ def __init__(
# Project form -----------------------------------------------------------------


class ProjectForm(SODARModelForm):
class ProjectForm(SODARAppSettingsFormMixin, SODARModelForm):
"""Form for Project creation/updating"""

# Set up owner field
Expand Down Expand Up @@ -389,98 +484,7 @@ def _init_remote_sites(self):
required=False,
)

def _set_app_setting_field(self, plugin_name, s_field, s_def):
"""
Internal helper for setting app setting field, widget and value.
:param plugin_name: App plugin name
:param s_field: Form field name
:param s_def: PluginAppSettingDef object
"""
s_widget_attrs = s_def.widget_attrs
s_project_types = s_def.project_types or [PROJECT_TYPE_PROJECT]
s_widget_attrs['data-project-types'] = ','.join(s_project_types).lower()
if s_def.placeholder is not None:
s_widget_attrs['placeholder'] = s_def.placeholder
setting_kwargs = {
'required': False,
'label': s_def.label or '{}.{}'.format(plugin_name, s_def.name),
'help_text': s_def.description,
}

# Option
if s_def.options and callable(s_def.options) and self.instance.pk:
values = s_def.options(project=self.instance)
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(str(value[0]), str(value[1]))
if isinstance(value, tuple)
else (str(value), str(value))
)
for value in values
],
**setting_kwargs,
)
elif s_def.options and callable(s_def.options) and not self.instance.pk:
values = s_def.options(project=None)
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(str(value[0]), str(value[1]))
if isinstance(value, tuple)
else (str(value), str(value))
)
for value in values
],
**setting_kwargs,
)
elif s_def.options:
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(int(option), int(option))
if s_def.type == APP_SETTING_TYPE_INTEGER
else (option, option)
)
for option in s_def.options
],
**setting_kwargs,
)
# Other types
elif s_def.type == APP_SETTING_TYPE_STRING:
self.fields[s_field] = forms.CharField(
widget=forms.TextInput(attrs=s_widget_attrs), **setting_kwargs
)
elif s_def.type == APP_SETTING_TYPE_INTEGER:
self.fields[s_field] = forms.IntegerField(
widget=forms.NumberInput(attrs=s_widget_attrs), **setting_kwargs
)
elif s_def.type == APP_SETTING_TYPE_BOOLEAN:
self.fields[s_field] = forms.BooleanField(**setting_kwargs)
# JSON
elif s_def.type == APP_SETTING_TYPE_JSON:
# NOTE: Attrs MUST be supplied here (#404)
if 'class' in s_widget_attrs:
s_widget_attrs['class'] += ' sodar-json-input'
else:
s_widget_attrs['class'] = 'sodar-json-input'
self.fields[s_field] = forms.CharField(
widget=forms.Textarea(attrs=s_widget_attrs), **setting_kwargs
)

# Add optional attributes from plugin (#404)
self.fields[s_field].widget.attrs.update(s_widget_attrs)
# Set initial value
value = app_settings.get(
plugin_name=plugin_name,
setting_name=s_def.name,
project=self.instance if self.instance.pk else None,
)
if s_def.type == APP_SETTING_TYPE_JSON:
value = json.dumps(value)
self.initial[s_field] = value

# TODO: Refactor into generic helper in SODARAppSettingsFormMixin
def _set_app_setting_notes(self, s_field, s_def, plugin):
"""
Internal helper for setting app setting label notes.
Expand All @@ -505,6 +509,7 @@ def _set_app_setting_notes(self, s_field, s_def, plugin):
plugin, self.fields[s_field].label
)

# TODO: Refactor into generic helper in SODARAppSettingsFormMixin
def _init_app_settings(self):
"""
Initialize app settings fields in the form.
Expand All @@ -531,10 +536,11 @@ def _init_app_settings(self):
for s_def in s_defs.values():
s_field = 'settings.{}.{}'.format(plugin_name, s_def.name)
# Set field, widget and value
self._set_app_setting_field(plugin_name, s_field, s_def)
self.set_app_setting_field(plugin_name, s_field, s_def)
# Set label notes
self._set_app_setting_notes(s_field, s_def, plugin)

# TODO: Refactor into generic helper in SODARAppSettingsFormMixin
@classmethod
def _validate_app_settings(
self,
Expand Down Expand Up @@ -1191,94 +1197,13 @@ def save(self, *args, **kwargs):
# Site app settings form -------------------------------------------------------


class SiteAppSettingsForm(SODARForm):
class SiteAppSettingsForm(SODARAppSettingsFormMixin, SODARForm):
"""Form for managing site app settings"""

# TODO: Unify repeated code with ProjectForm and UserAppSettingsForm
def _set_app_setting_field(self, plugin_name, s_field, s_def):
"""
Set site app setting field, widget and value.
:param plugin_name: App plugin name
:param s_field: Form field name
:param s_def: PluginAppSettingDef object
"""
s_widget_attrs = s_def.widget_attrs
if s_def.placeholder is not None:
s_widget_attrs['placeholder'] = s_def.placeholder
setting_kwargs = {
'required': False,
'label': s_def.label or '{}.{}'.format(plugin_name, s_def.name),
'help_text': s_def.description,
}
# Disable global settings if on target site
if (
s_def.global_edit
and settings.PROJECTROLES_SITE_MODE == SITE_MODE_TARGET
):
setting_kwargs['label'] += ' ' + SETTING_DISABLE_LABEL
setting_kwargs['help_text'] += ' ' + SETTING_SOURCE_ONLY_MSG
setting_kwargs['disabled'] = True

if s_def.options and callable(s_def.options):
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(str(value[0]), str(value[1]))
if isinstance(value, tuple)
else (str(value), str(value))
)
for value in s_def.options()
],
**setting_kwargs,
)
elif s_def.options:
self.fields[s_field] = forms.ChoiceField(
choices=[
(
(int(option), int(option))
if s_def.type == APP_SETTING_TYPE_INTEGER
else (option, option)
)
for option in s_def.options
],
**setting_kwargs,
)
elif s_def.type == APP_SETTING_TYPE_STRING:
self.fields[s_field] = forms.CharField(
widget=forms.TextInput(attrs=s_widget_attrs),
**setting_kwargs,
)
elif s_def.type == APP_SETTING_TYPE_INTEGER:
self.fields[s_field] = forms.IntegerField(
widget=forms.NumberInput(attrs=s_widget_attrs),
**setting_kwargs,
)
elif s_def.type == APP_SETTING_TYPE_BOOLEAN:
self.fields[s_field] = forms.BooleanField(**setting_kwargs)
elif s_def.type == APP_SETTING_TYPE_JSON:
# NOTE: Attrs MUST be supplied here (#404)
if 'class' in s_widget_attrs:
s_widget_attrs['class'] += ' sodar-json-input'
else:
s_widget_attrs['class'] = 'sodar-json-input'
self.fields[s_field] = forms.CharField(
widget=forms.Textarea(attrs=s_widget_attrs),
**setting_kwargs,
)

# Modify initial value and attributes
self.fields[s_field].widget.attrs.update(s_widget_attrs)
value = app_settings.get(
plugin_name=plugin_name, setting_name=s_def.name
)
if s_def.type == APP_SETTING_TYPE_JSON:
value = json.dumps(value)
self.initial[s_field] = value

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add settings fields
# TODO: Refactor into generic helper in SODARAppSettingsFormMixin
self.project_plugins = get_active_plugins(plugin_type='project_app')
self.site_plugins = get_active_plugins(plugin_type='site_app')
self.app_plugins = self.project_plugins + self.site_plugins
Expand All @@ -1298,12 +1223,13 @@ def __init__(self, *args, **kwargs):
)
for s_def in s_defs.values():
s_field = 'settings.{}.{}'.format(plugin_name, s_def.name)
self._set_app_setting_field(plugin_name, s_field, s_def)
self.set_app_setting_field(plugin_name, s_field, s_def)
self.fields[s_field].label = self.get_app_setting_label(
plugin, self.fields[s_field].label
)

def clean(self):
# TODO: Refactor into generic helper in SODARAppSettingsFormMixin
for plugin in self.app_plugins + [None]:
p_kwargs = {'user_modifiable': True}
if plugin:
Expand Down
Loading

0 comments on commit 1f17dee

Please sign in to comment.