From 7d6da061542b5780b0a4052a19073b5b8cc757bf Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Mon, 21 Oct 2024 13:38:08 +0200 Subject: [PATCH] Refactored custom fields handling to a component --- oarepo_ui/resources/__init__.py | 2 + oarepo_ui/resources/components/__init__.py | 8 +-- oarepo_ui/resources/components/base.py | 2 +- oarepo_ui/resources/components/bleach.py | 2 +- oarepo_ui/resources/components/communities.py | 7 +-- .../resources/components/custom_fields.py | 35 +++++++++++++ oarepo_ui/resources/components/permissions.py | 2 +- oarepo_ui/resources/resource.py | 50 ++++++++++++------- oarepo_ui/resources/signposting.py | 2 +- setup.cfg | 2 +- tests/conftest.py | 12 ++--- tests/model.py | 3 +- tests/test_create.py | 3 +- tests/test_edit.py | 3 +- tests/test_ui_resource_config.py | 11 +++- 15 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 oarepo_ui/resources/components/custom_fields.py diff --git a/oarepo_ui/resources/__init__.py b/oarepo_ui/resources/__init__.py index 051cc1f3..614d49c7 100644 --- a/oarepo_ui/resources/__init__.py +++ b/oarepo_ui/resources/__init__.py @@ -8,4 +8,6 @@ "RecordsUIResource", "UIResourceConfig", "RecordsUIResourceConfig", + "PermissionsComponent", + "BabelComponent" ) diff --git a/oarepo_ui/resources/components/__init__.py b/oarepo_ui/resources/components/__init__.py index eadeb875..d0f80dce 100644 --- a/oarepo_ui/resources/components/__init__.py +++ b/oarepo_ui/resources/components/__init__.py @@ -1,9 +1,9 @@ -from .base import UIResourceComponent from .babel import BabelComponent +from .base import UIResourceComponent from .bleach import AllowedHtmlTagsComponent +from .communities import AllowedCommunitiesComponent from .files import FilesComponent from .permissions import PermissionsComponent -from .communities import AllowedCommunitiesComponent __all__ = ( "UIResourceComponent", @@ -11,5 +11,5 @@ "AllowedHtmlTagsComponent", "BabelComponent", "FilesComponent", - "AllowedCommunitiesComponent" -) \ No newline at end of file + "AllowedCommunitiesComponent", +) diff --git a/oarepo_ui/resources/components/base.py b/oarepo_ui/resources/components/base.py index eff984b7..c10af2a3 100644 --- a/oarepo_ui/resources/components/base.py +++ b/oarepo_ui/resources/components/base.py @@ -1,4 +1,4 @@ -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING, Dict from flask_principal import Identity from invenio_records_resources.services.records.results import RecordItem diff --git a/oarepo_ui/resources/components/bleach.py b/oarepo_ui/resources/components/bleach.py index cf3ba8f4..16267e5f 100644 --- a/oarepo_ui/resources/components/bleach.py +++ b/oarepo_ui/resources/components/bleach.py @@ -1,5 +1,5 @@ from flask import current_app -from invenio_config.default import ALLOWED_HTML_TAGS, ALLOWED_HTML_ATTRS +from invenio_config.default import ALLOWED_HTML_ATTRS, ALLOWED_HTML_TAGS from .base import UIResourceComponent diff --git a/oarepo_ui/resources/components/communities.py b/oarepo_ui/resources/components/communities.py index 1fe43ca1..3eb29589 100644 --- a/oarepo_ui/resources/components/communities.py +++ b/oarepo_ui/resources/components/communities.py @@ -2,13 +2,10 @@ from flask_principal import Identity from invenio_communities.communities.records.api import Community +from invenio_records_resources.services.errors import PermissionDeniedError from oarepo_runtime.i18n import lazy_gettext as _ - from .base import UIResourceComponent -from invenio_records_resources.services.errors import ( - PermissionDeniedError, -) class AllowedCommunitiesComponent(UIResourceComponent): @@ -105,8 +102,8 @@ def user_has_permission(self, identity, community, action): ) def check_user_permissions(self, community_id, workflow, identity, action): - from oarepo_workflows.proxies import current_oarepo_workflows from oarepo_workflows.errors import InvalidWorkflowError + from oarepo_workflows.proxies import current_oarepo_workflows if workflow not in current_oarepo_workflows.record_workflows: raise InvalidWorkflowError( diff --git a/oarepo_ui/resources/components/custom_fields.py b/oarepo_ui/resources/components/custom_fields.py new file mode 100644 index 00000000..20326700 --- /dev/null +++ b/oarepo_ui/resources/components/custom_fields.py @@ -0,0 +1,35 @@ +from typing import Dict + +from flask_principal import Identity +from invenio_records_resources.services.records.results import RecordItem + +from oarepo_ui.resources.components import UIResourceComponent + + +class CustomFieldsComponent(UIResourceComponent): + def form_config( + self, + *, + api_record: RecordItem = None, + record: Dict = None, + data: Dict = None, + identity: Identity, + form_config: Dict, + args: Dict, + view_args: Dict, + ui_links: Dict = None, + extra_context: Dict = None, + **kwargs, + ): + if hasattr(self.resource.config, "custom_fields"): + form_config["custom_fields"] = self.resource.config.custom_fields( + identity=identity, + api_record=api_record, + record=record, + data=data, + form_config=form_config, + args=args, + view_args=view_args, + ui_links=ui_links, + extra_context=extra_context, + ) diff --git a/oarepo_ui/resources/components/permissions.py b/oarepo_ui/resources/components/permissions.py index a97726f6..fabd024b 100644 --- a/oarepo_ui/resources/components/permissions.py +++ b/oarepo_ui/resources/components/permissions.py @@ -1,5 +1,5 @@ -from .base import UIResourceComponent from ...proxies import current_oarepo_ui +from .base import UIResourceComponent class PermissionsComponent(UIResourceComponent): diff --git a/oarepo_ui/resources/resource.py b/oarepo_ui/resources/resource.py index 3bc6d0f1..7d066b41 100644 --- a/oarepo_ui/resources/resource.py +++ b/oarepo_ui/resources/resource.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Iterator import deepmerge -from flask import abort, g, redirect, request, Response +from flask import Response, abort, g, redirect, request from flask_principal import PermissionDenied from flask_resources import ( Resource, @@ -30,14 +30,17 @@ from werkzeug.exceptions import Forbidden from oarepo_ui.utils import dump_empty -from .signposting import response_header_signposting +from .components.custom_fields import CustomFieldsComponent +from .signposting import response_header_signposting from .templating.data import FieldData if TYPE_CHECKING: from .components import UIResourceComponent # +import logging + # Resource # from ..proxies import current_oarepo_ui @@ -48,6 +51,8 @@ UIResourceConfig, ) +log = logging.getLogger(__name__) + request_export_args = request_parser( from_conf("request_export_args"), location="view_args" ) @@ -64,10 +69,26 @@ class UIComponentsMixin: # # Pluggable components # + config: UIResourceConfig @property def components(self) -> Iterator["UIResourceComponent"]: """Return initialized service components.""" + + # TODO: just for deprecation, will be removed later + for c in self.config.components or []: + if isinstance(c, CustomFieldsComponent): + break + else: + log.warning( + "CustomFieldsComponent not found in components, adding it to be backwards compatible." + ) + if not self.config.components: + self.config.components = [] + if isinstance(self.config.components, tuple): + self.config.components = list(self.config.components) + self.config.components.append(CustomFieldsComponent) + return (c(self) for c in self.config.components or []) def run_components(self, action, *args, **kwargs): @@ -240,9 +261,6 @@ def _detail(self, *, is_preview=False): args=resource_requestctx.args, view_args=resource_requestctx.view_args, ui_links=ui_links, - custom_fields=self._get_custom_fields( - api_record=api_record, resource_requestctx=resource_requestctx - ), is_preview=is_preview, ) @@ -260,10 +278,14 @@ def _detail(self, *, is_preview=False): "is_preview": is_preview, } - response = Response(current_oarepo_ui.catalog.render( - render_method, - **render_kwargs, - ), mimetype="text/html", status=200) + response = Response( + current_oarepo_ui.catalog.render( + render_method, + **render_kwargs, + ), + mimetype="text/html", + status=200, + ) response._api_record = api_record return response @@ -464,10 +486,6 @@ def edit(self): g.identity, updateUrl=api_record.links.get("self", None) ) - form_config["custom_fields"] = self._get_custom_fields( - api_record=api_record, resource_requestctx=resource_requestctx - ) - form_config["ui_model"] = self.ui_model ui_links = self.expand_detail_links(identity=g.identity, record=api_record) @@ -521,9 +539,6 @@ def edit(self): d=FieldData(record, self.ui_model), ) - def _get_custom_fields(self, **kwargs): - return self.config.custom_fields(identity=g.identity, **kwargs) - def _get_form_config(self, identity, **kwargs): return self.config.form_config(identity=identity, **kwargs) @@ -541,9 +556,6 @@ def create(self): form_config = self._get_form_config( g.identity, createUrl=f"/api{self.api_service.config.url_prefix}" ) - form_config["custom_fields"] = self._get_custom_fields( - resource_requestctx=resource_requestctx - ) form_config["ui_model"] = self.ui_model diff --git a/oarepo_ui/resources/signposting.py b/oarepo_ui/resources/signposting.py index 31daa322..a7a65454 100644 --- a/oarepo_ui/resources/signposting.py +++ b/oarepo_ui/resources/signposting.py @@ -18,7 +18,7 @@ def inner(*args, **kwargs): if not hasattr(response, "_api_record"): return response - signposting_link = response._api_record.links['self'] + signposting_link = response._api_record.links["self"] response.headers.update( { "Link": f'<{signposting_link}> ; rel="linkset" ; type="application/linkset+json"', # noqa diff --git a/setup.cfg b/setup.cfg index 7cf70da6..a14a51bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-ui -version = 5.2.14 +version = 5.2.15 description = UI module for invenio 3.5+ long_description = file: README.md long_description_content_type = text/markdown diff --git a/tests/conftest.py b/tests/conftest.py index fe1387fa..87675d51 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,12 +34,12 @@ def extra_entry_points(): def app_config(app_config): app_config["I18N_LANGUAGES"] = [("cs", "Czech")] app_config["BABEL_DEFAULT_LOCALE"] = "en" - app_config[ - "RECORDS_REFRESOLVER_CLS" - ] = "invenio_records.resolver.InvenioRefResolver" - app_config[ - "RECORDS_REFRESOLVER_STORE" - ] = "invenio_jsonschemas.proxies.current_refresolver_store" + app_config["RECORDS_REFRESOLVER_CLS"] = ( + "invenio_records.resolver.InvenioRefResolver" + ) + app_config["RECORDS_REFRESOLVER_STORE"] = ( + "invenio_jsonschemas.proxies.current_refresolver_store" + ) # for ui tests app_config["APP_THEME"] = ["semantic-ui"] diff --git a/tests/model.py b/tests/model.py index 4253ff18..c82fd0c0 100644 --- a/tests/model.py +++ b/tests/model.py @@ -21,8 +21,9 @@ from oarepo_ui.resources import ( BabelComponent, + PermissionsComponent, RecordsUIResource, - RecordsUIResourceConfig, PermissionsComponent, + RecordsUIResourceConfig, ) from oarepo_ui.resources.components.bleach import AllowedHtmlTagsComponent from oarepo_ui.resources.config import TemplatePageUIResourceConfig diff --git a/tests/test_create.py b/tests/test_create.py index c7a458ac..b5a53202 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,5 +1,6 @@ import json -from invenio_config.default import ALLOWED_HTML_TAGS, ALLOWED_HTML_ATTRS + +from invenio_config.default import ALLOWED_HTML_ATTRS, ALLOWED_HTML_TAGS def test_create( diff --git a/tests/test_edit.py b/tests/test_edit.py index 82e64cfa..455e7ce2 100644 --- a/tests/test_edit.py +++ b/tests/test_edit.py @@ -1,5 +1,6 @@ import json -from invenio_config.default import ALLOWED_HTML_TAGS, ALLOWED_HTML_ATTRS + +from invenio_config.default import ALLOWED_HTML_ATTRS, ALLOWED_HTML_TAGS def test_edit( diff --git a/tests/test_ui_resource_config.py b/tests/test_ui_resource_config.py index fa8c817f..5bf07b7b 100644 --- a/tests/test_ui_resource_config.py +++ b/tests/test_ui_resource_config.py @@ -1,5 +1,5 @@ from invenio_access.permissions import system_identity -from invenio_config.default import ALLOWED_HTML_TAGS, ALLOWED_HTML_ATTRS +from invenio_config.default import ALLOWED_HTML_ATTRS, ALLOWED_HTML_TAGS def test_ui_resource_form_config(app, test_record_ui_resource): @@ -52,4 +52,13 @@ def test_ui_resource_form_config(app, test_record_ui_resource): "can_update_files": False, "can_view": False, }, + custom_fields={ + "ui": [ + {"fields": [{"field": "bbb", "ui_widget": "Input"}], "section": "B"}, + { + "fields": [{"field": "nested_cf.aaa", "ui_widget": "Input"}], + "section": "A", + }, + ] + }, )