diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index d0c3f4b9..beaff20e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
-# Copyright (C) 2020 CERN.
+# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2022 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
@@ -30,17 +30,18 @@ jobs:
Tests:
runs-on: ubuntu-20.04
strategy:
+ fail-fast: false
matrix:
- python-version: [3.8, 3.9]
- requirements-level: [pypi]
+ python-version: ['3.9', '3.10', '3.11', '3.12']
db-service: [postgresql14]
search-service: [opensearch2, elasticsearch7]
- node-version: [16.x]
+ node-version: [18.x, 20.x]
include:
- search-service: opensearch2
SEARCH_EXTRAS: "opensearch2"
- search-service: elasticsearch7
SEARCH_EXTRAS: "elasticsearch7"
+
env:
DB: ${{ matrix.db-service }}
SEARCH: ${{ matrix.search-service }}
@@ -48,42 +49,32 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
+
- name: Run eslint test
run: ./run-js-linter.sh -i
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
-
- - name: Generate dependencies
- run: |
- pip install wheel requirements-builder
- requirements-builder -e "$EXTRAS" --level=${{ matrix.requirements-level }} setup.py > .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt
-
- - name: Cache pip
- uses: actions/cache@v2
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('.${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt') }}
+ cache: pip
+ cache-dependency-path: setup.cfg
- name: Install dependencies
run: |
- pip install -r .${{ matrix.requirements-level }}-${{ matrix.python-version }}-requirements.txt
pip install ".[$EXTRAS]"
pip freeze
docker --version
docker-compose --version
- name: Run tests
- run: |
- ./run-tests.sh
+ run: ./run-tests.sh
- name: Install deps for frontend tests
working-directory: ./invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies
diff --git a/CHANGES.rst b/CHANGES.rst
index 41e1d08c..6f375d31 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,5 +1,5 @@
..
- Copyright (C) 2020-2023 CERN.
+ Copyright (C) 2020-2024 CERN.
Invenio-Vocabularies is free software; you can redistribute it and/or
modify it under the terms of the MIT License; see LICENSE file for more
@@ -8,6 +8,29 @@
Changes
=======
+Version 3.3.0 (released 2024-04-16)
+
+- assets: add overridable awards and funding
+
+Version 3.2.0 (released 2024-03-22)
+
+- funding: add country and ror to funder search results
+- init: move record_once to finalize_app (removes deprecation on `before_first_request`)
+- installation: upgrade invenio-app
+
+
+Version 3.1.0 (released 2024-03-05)
+
+- custom_fields: added subject field
+- custom_fields: add pid_field to custom fields
+- mappings: change "dynamic" values to string
+- ci: upgrade tests matrix
+- bumps react-invenio-forms
+
+Version 3.0.0 (released 2024-01-30)
+
+- installation: bump invenio-records-resources
+
Version 2.4.0 (2023-12-07)
- schema: add validation for affiliations
diff --git a/invenio_vocabularies/__init__.py b/invenio_vocabularies/__init__.py
index 1a2cc653..51337af8 100644
--- a/invenio_vocabularies/__init__.py
+++ b/invenio_vocabularies/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020-2023 CERN.
+# Copyright (C) 2020-2024 CERN.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -10,6 +10,6 @@
from .ext import InvenioVocabularies
-__version__ = "2.4.0"
+__version__ = "3.3.0"
__all__ = ("__version__", "InvenioVocabularies")
diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js
index 764e2c1d..0161811b 100644
--- a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js
+++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js
@@ -12,17 +12,24 @@ import { TextField, RemoteSelectField } from "react-invenio-forms";
import { i18next } from "@translations/invenio_rdm_records/i18next";
import _isEmpty from "lodash/isEmpty";
+import Overridable from "react-overridable";
+
function CustomAwardForm({ deserializeFunder, selectedFunding }) {
function deserializeFunderToDropdown(funderItem) {
let funderName = null;
let funderPID = null;
+ let funderCountry = null;
if (funderItem.name) {
funderName = funderItem.name;
}
- if (funderItem.pid) {
- funderPID = funderItem.pid;
+ if (funderItem.id) {
+ funderPID = funderItem.id;
+ }
+
+ if (funderItem.country) {
+ funderCountry = funderItem.country;
}
if (!funderName && !funderPID) {
@@ -30,7 +37,7 @@ function CustomAwardForm({ deserializeFunder, selectedFunding }) {
}
return {
- text: funderName || funderPID,
+ text: [funderName, funderCountry, funderPID].filter((val) => val).join(", "),
value: funderItem.id,
key: funderItem.id,
...(funderName && { name: funderName }),
@@ -48,64 +55,85 @@ function CustomAwardForm({ deserializeFunder, selectedFunding }) {
return (
);
diff --git a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js
index 819c4dd6..62d424f1 100644
--- a/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js
+++ b/invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js
@@ -18,6 +18,8 @@ import FundingModal from "./FundingModal";
import { i18next } from "@translations/invenio_rdm_records/i18next";
+import Overridable from "react-overridable";
+
function FundingFieldForm(props) {
const {
label,
@@ -108,46 +110,52 @@ function FundingFieldForm(props) {
);
})}
-
-
- {i18next.t("Add award")}
-
- }
- onAwardChange={(selectedFunding) => {
- formikArrayPush(selectedFunding);
- }}
- mode="standard"
- action="add"
- deserializeAward={deserializeAward}
- deserializeFunder={deserializeFunder}
- computeFundingContents={computeFundingContents}
- />
-
-
- {i18next.t("Add custom")}
-
- }
- onAwardChange={(selectedFunding) => {
- formikArrayPush(selectedFunding);
- }}
- mode="custom"
- action="add"
- deserializeAward={deserializeAward}
- deserializeFunder={deserializeFunder}
- computeFundingContents={computeFundingContents}
- />
+
+
+
+
+ {i18next.t("Add award")}
+
+ }
+ onAwardChange={(selectedFunding) => {
+ formikArrayPush(selectedFunding);
+ }}
+ mode="standard"
+ action="add"
+ deserializeAward={deserializeAward}
+ deserializeFunder={deserializeFunder}
+ computeFundingContents={computeFundingContents}
+ />
+
+
+
+
+
+ {i18next.t("Add custom")}
+
+ }
+ onAwardChange={(selectedFunding) => {
+ formikArrayPush(selectedFunding);
+ }}
+ mode="custom"
+ action="add"
+ deserializeAward={deserializeAward}
+ deserializeFunder={deserializeFunder}
+ computeFundingContents={computeFundingContents}
+ />
+
);
diff --git a/invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json b/invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json
index 891033c0..07429f3b 100644
--- a/invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json
+++ b/invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json
@@ -84,7 +84,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json b/invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json
index 891033c0..07429f3b 100644
--- a/invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json
+++ b/invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json
@@ -84,7 +84,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json b/invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json
index 891033c0..07429f3b 100644
--- a/invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json
+++ b/invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json
@@ -84,7 +84,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json b/invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json
index 22cd91fb..8fcdc343 100644
--- a/invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json
+++ b/invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json
@@ -47,7 +47,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"number": {
"type": "keyword"
diff --git a/invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json b/invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json
index 22cd91fb..8fcdc343 100644
--- a/invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json
+++ b/invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json
@@ -47,7 +47,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"number": {
"type": "keyword"
diff --git a/invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json b/invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json
index 22cd91fb..8fcdc343 100644
--- a/invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json
+++ b/invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json
@@ -47,7 +47,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"number": {
"type": "keyword"
diff --git a/invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json b/invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json
index a52fc80d..2c635b57 100644
--- a/invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json
+++ b/invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json
@@ -62,7 +62,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json b/invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json
index a52fc80d..2c635b57 100644
--- a/invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json
+++ b/invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json
@@ -62,7 +62,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json b/invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json
index a52fc80d..2c635b57 100644
--- a/invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json
+++ b/invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json
@@ -62,7 +62,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/datastreams/readers.py b/invenio_vocabularies/datastreams/readers.py
index 0b02128b..af7a6122 100644
--- a/invenio_vocabularies/datastreams/readers.py
+++ b/invenio_vocabularies/datastreams/readers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2021-2022 CERN.
+# Copyright (C) 2021-2024 CERN.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -23,6 +23,7 @@
from lxml.html import parse as html_parse
from .errors import ReaderError
+from .xml import etree_to_dict
class BaseReader(ABC):
@@ -206,32 +207,11 @@ def _iter(self, fp, *args, **kwargs):
class XMLReader(BaseReader):
"""XML reader."""
- @classmethod
- def _etree_to_dict(cls, tree):
- d = {tree.tag: {} if tree.attrib else None}
- children = list(tree)
- if children:
- dd = defaultdict(list)
- for dc in map(cls._etree_to_dict, children):
- for k, v in dc.items():
- dd[k].append(v)
- d = {tree.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
- if tree.attrib:
- d[tree.tag].update(("@" + k, v) for k, v in tree.attrib.items())
- if tree.text:
- text = tree.text.strip()
- if children or tree.attrib:
- if text:
- d[tree.tag]["#text"] = text
- else:
- d[tree.tag] = text
- return d
-
def _iter(self, fp, *args, **kwargs):
"""Read and parse an XML file to dict."""
# NOTE: We parse HTML, to skip XML validation and strip XML namespaces
xml_tree = html_parse(fp).getroot()
- record = self._etree_to_dict(xml_tree)["html"]["body"].get("record")
+ record = etree_to_dict(xml_tree)["html"]["body"].get("record")
if not record:
raise ReaderError(f"Record not found in XML entry.")
diff --git a/invenio_vocabularies/datastreams/transformers.py b/invenio_vocabularies/datastreams/transformers.py
index 09e74663..d4274a68 100644
--- a/invenio_vocabularies/datastreams/transformers.py
+++ b/invenio_vocabularies/datastreams/transformers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2021-2022 CERN.
+# Copyright (C) 2021-2024 CERN.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -9,11 +9,11 @@
"""Transformers module."""
from abc import ABC, abstractmethod
-from collections import defaultdict
from lxml import etree
from .errors import TransformerError
+from .xml import etree_to_dict
class BaseTransformer(ABC):
@@ -37,34 +37,13 @@ def _xml_to_etree(cls, xml):
"""Converts XML to a lxml etree."""
return etree.HTML(xml)
- @classmethod
- def _etree_to_dict(cls, tree):
- d = {tree.tag: {} if tree.attrib else None}
- children = list(tree)
- if children:
- dd = defaultdict(list)
- for dc in map(cls._etree_to_dict, children):
- for k, v in dc.items():
- dd[k].append(v)
- d = {tree.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
- if tree.attrib:
- d[tree.tag].update(("@" + k, v) for k, v in tree.attrib.items())
- if tree.text:
- text = tree.text.strip()
- if children or tree.attrib:
- if text:
- d[tree.tag]["#text"] = text
- else:
- d[tree.tag] = text
- return d
-
def apply(self, stream_entry, **kwargs):
"""Applies the transformation to the stream entry.
Requires the root element to be named "record".
"""
xml_tree = self._xml_to_etree(stream_entry.entry)
- record = self._etree_to_dict(xml_tree)["html"]["body"].get("record")
+ record = etree_to_dict(xml_tree)["html"]["body"].get("record")
if not record:
raise TransformerError(f"Record not found in XML entry.")
diff --git a/invenio_vocabularies/datastreams/xml.py b/invenio_vocabularies/datastreams/xml.py
new file mode 100644
index 00000000..d26e2e22
--- /dev/null
+++ b/invenio_vocabularies/datastreams/xml.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2021-2024 CERN.
+#
+# Invenio-Vocabularies is free software; you can redistribute it and/or
+# modify it under the terms of the MIT License; see LICENSE file for more
+# details.
+
+"""XML utils."""
+
+from collections import defaultdict
+
+
+def etree_to_dict(tree):
+ """Convert an ElementTree to a dictionary."""
+ tag = tree.tag.split(":")[-1] # strip namespace
+ d = {tag: {} if tree.attrib else None}
+ children = list(tree)
+ if children:
+ dd = defaultdict(list)
+ for dc in map(etree_to_dict, children):
+ for k, v in dc.items():
+ dd[k].append(v)
+ d = {tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
+ if tree.attrib:
+ d[tag].update(("@" + k, v) for k, v in tree.attrib.items())
+ if tree.text:
+ text = tree.text.strip()
+ if children or tree.attrib:
+ if text:
+ d[tag]["#text"] = text
+ else:
+ d[tag] = text
+ return d
diff --git a/invenio_vocabularies/ext.py b/invenio_vocabularies/ext.py
index 4c34513c..4e7ab481 100644
--- a/invenio_vocabularies/ext.py
+++ b/invenio_vocabularies/ext.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2022 CERN.
+# Copyright (C) 2023 Graz University of Technology.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -123,3 +124,41 @@ def init_resource(self, app):
service=self.service,
config=app.config["VOCABULARIES_RESOURCE_CONFIG"],
)
+
+
+def finalize_app(app):
+ """Finalize app.
+
+ NOTE: replace former @record_once decorator
+ """
+ init(app)
+
+
+def api_finalize_app(app):
+ """Api Finalize app.
+
+ NOTE: replace former @record_once decorator
+ """
+ init(app)
+
+
+def init(app):
+ """Init app."""
+ # Register services - cannot be done in extension because
+ # Invenio-Records-Resources might not have been initialized.
+ sregistry = app.extensions["invenio-records-resources"].registry
+ ext = app.extensions["invenio-vocabularies"]
+ sregistry.register(ext.affiliations_service, service_id="affiliations")
+ sregistry.register(ext.awards_service, service_id="awards")
+ sregistry.register(ext.funders_service, service_id="funders")
+ sregistry.register(ext.names_service, service_id="names")
+ sregistry.register(ext.subjects_service, service_id="subjects")
+ sregistry.register(ext.service, service_id="vocabularies")
+ # Register indexers
+ iregistry = app.extensions["invenio-indexer"].registry
+ iregistry.register(ext.affiliations_service.indexer, indexer_id="affiliations")
+ iregistry.register(ext.awards_service.indexer, indexer_id="awards")
+ iregistry.register(ext.funders_service.indexer, indexer_id="funders")
+ iregistry.register(ext.names_service.indexer, indexer_id="names")
+ iregistry.register(ext.subjects_service.indexer, indexer_id="subjects")
+ iregistry.register(ext.service.indexer, indexer_id="vocabularies")
diff --git a/invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json b/invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json
index dc81057b..fdd6525b 100644
--- a/invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json
+++ b/invenio_vocabularies/records/mappings/os-v1/vocabularies/vocabulary-v1.0.0.json
@@ -81,7 +81,7 @@
},
"title": {
"type": "object",
- "dynamic": true,
+ "dynamic": "true",
"properties": {
"en": {
"type": "search_as_you_type",
@@ -91,7 +91,7 @@
},
"description": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"icon": {
"type": "keyword",
@@ -102,7 +102,7 @@
},
"props": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json b/invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json
index dc81057b..fdd6525b 100644
--- a/invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json
+++ b/invenio_vocabularies/records/mappings/os-v2/vocabularies/vocabulary-v1.0.0.json
@@ -81,7 +81,7 @@
},
"title": {
"type": "object",
- "dynamic": true,
+ "dynamic": "true",
"properties": {
"en": {
"type": "search_as_you_type",
@@ -91,7 +91,7 @@
},
"description": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"icon": {
"type": "keyword",
@@ -102,7 +102,7 @@
},
"props": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json b/invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json
index dc81057b..fdd6525b 100644
--- a/invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json
+++ b/invenio_vocabularies/records/mappings/v7/vocabularies/vocabulary-v1.0.0.json
@@ -81,7 +81,7 @@
},
"title": {
"type": "object",
- "dynamic": true,
+ "dynamic": "true",
"properties": {
"en": {
"type": "search_as_you_type",
@@ -91,7 +91,7 @@
},
"description": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
},
"icon": {
"type": "keyword",
@@ -102,7 +102,7 @@
},
"props": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/invenio_vocabularies/records/systemfields/relations.py b/invenio_vocabularies/records/systemfields/relations.py
index c78543ee..0a39788a 100644
--- a/invenio_vocabularies/records/systemfields/relations.py
+++ b/invenio_vocabularies/records/systemfields/relations.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2022 CERN.
+# Copyright (C) 2022-2024 CERN.
#
# Invenio-Records-Resources is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -38,7 +38,7 @@ def _fields(self):
relations[cf.name] = cf.relation_cls(
f"custom_fields.{cf.name}",
keys=cf.field_keys,
- pid_field=Vocabulary.pid.with_type_ctx(cf.vocabulary_id),
+ pid_field=cf.pid_field,
cache_key=cf.vocabulary_id,
)
diff --git a/invenio_vocabularies/services/custom_fields/__init__.py b/invenio_vocabularies/services/custom_fields/__init__.py
index 1ce96f3c..69e8e08f 100644
--- a/invenio_vocabularies/services/custom_fields/__init__.py
+++ b/invenio_vocabularies/services/custom_fields/__init__.py
@@ -1,12 +1,16 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2022 CERN.
+# Copyright (C) 2022-2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
"""Custom Fields for InvenioRDM."""
+from .subject import SUBJECT_FIELDS, SUBJECT_FIELDS_UI
from .vocabulary import VocabularyCF
-__all__ = "VocabularyCF"
+__all__ = [
+ "VocabularyCF",
+ "SUBJECT_FIELDS_UI" "SUBJECT_FIELDS",
+]
diff --git a/invenio_vocabularies/services/custom_fields/subject.py b/invenio_vocabularies/services/custom_fields/subject.py
new file mode 100644
index 00000000..6ee44834
--- /dev/null
+++ b/invenio_vocabularies/services/custom_fields/subject.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2024-2024 CERN.
+#
+# Invenio-RDM-Records is free software; you can redistribute it and/or modify
+# it under the terms of the MIT License; see LICENSE file for more details.
+
+
+"""Custom fields."""
+from invenio_i18n import lazy_gettext as _
+
+from ...contrib.subjects.api import Subject
+from ...contrib.subjects.schema import SubjectRelationSchema
+from .vocabulary import VocabularyCF
+
+
+class SubjectCF(VocabularyCF):
+ """Custom field for subjects."""
+
+ field_keys = ["id", "subject"]
+
+ def __init__(self, **kwargs):
+ """Constructor."""
+ super().__init__(
+ vocabulary_id="subjects",
+ schema=SubjectRelationSchema,
+ ui_schema=SubjectRelationSchema,
+ **kwargs
+ )
+ self.pid_field = Subject.pid
+
+ @property
+ def mapping(self):
+ """Return the mapping."""
+ _mapping = {
+ "type": "object",
+ "properties": {
+ "@v": {"type": "keyword"},
+ "id": {"type": "keyword"},
+ "subject": {"type": "keyword"},
+ },
+ }
+
+ return _mapping
+
+
+SUBJECT_FIELDS_UI = [
+ {
+ "section": _("Subjects"),
+ "fields": [
+ dict(
+ field="subjects",
+ ui_widget="SubjectAutocompleteDropdown",
+ isGenericVocabulary=False,
+ props=dict(
+ label="Keywords and subjects",
+ icon="tag",
+ description="The subjects related to the community",
+ placeholder="Search for a subject by name e.g. Psychology ...",
+ autocompleteFrom="api/subjects",
+ noQueryMessage="Search for subjects...",
+ autocompleteFromAcceptHeader="application/vnd.inveniordm.v1+json",
+ required=False,
+ multiple=True,
+ clearable=True,
+ allowAdditions=False,
+ ),
+ )
+ ],
+ }
+]
+
+
+SUBJECT_FIELDS = {
+ SubjectCF(
+ name="subjects",
+ multiple=True,
+ dump_options=False,
+ )
+}
diff --git a/invenio_vocabularies/services/custom_fields/vocabulary.py b/invenio_vocabularies/services/custom_fields/vocabulary.py
index f5a5e7ac..0452d1e5 100644
--- a/invenio_vocabularies/services/custom_fields/vocabulary.py
+++ b/invenio_vocabularies/services/custom_fields/vocabulary.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2022 CERN.
+# Copyright (C) 2022-2024 CERN.
#
# Invenio-RDM-Records is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.
@@ -12,6 +12,7 @@
from marshmallow import fields
from ...proxies import current_service
+from ...records.api import Vocabulary
from ...resources.serializer import VocabularyL10NItemSchema
from ...services.schema import VocabularyRelationSchema
@@ -49,6 +50,7 @@ def __init__(
self.sort_by = sort_by
self.schema = schema
self.ui_schema = ui_schema
+ self.pid_field = Vocabulary.pid.with_type_ctx(self.vocabulary_id)
@property
def mapping(self):
@@ -58,7 +60,7 @@ def mapping(self):
"properties": {
"@v": {"type": "keyword"},
"id": {"type": "keyword"},
- "title": {"type": "object", "dynamic": True},
+ "title": {"type": "object", "dynamic": "true"},
},
}
diff --git a/invenio_vocabularies/views.py b/invenio_vocabularies/views.py
index be150406..fcc20b78 100644
--- a/invenio_vocabularies/views.py
+++ b/invenio_vocabularies/views.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2022 CERN.
+# Copyright (C) 2023 Graz University of Technology.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -13,30 +14,6 @@
blueprint = Blueprint("invenio_vocabularies_ext", __name__)
-@blueprint.record_once
-def init(state):
- """Init app."""
- app = state.app
- # Register services - cannot be done in extension because
- # Invenio-Records-Resources might not have been initialized.
- sregistry = app.extensions["invenio-records-resources"].registry
- ext = app.extensions["invenio-vocabularies"]
- sregistry.register(ext.affiliations_service, service_id="affiliations")
- sregistry.register(ext.awards_service, service_id="awards")
- sregistry.register(ext.funders_service, service_id="funders")
- sregistry.register(ext.names_service, service_id="names")
- sregistry.register(ext.subjects_service, service_id="subjects")
- sregistry.register(ext.service, service_id="vocabularies")
- # Register indexers
- iregistry = app.extensions["invenio-indexer"].registry
- iregistry.register(ext.affiliations_service.indexer, indexer_id="affiliations")
- iregistry.register(ext.awards_service.indexer, indexer_id="awards")
- iregistry.register(ext.funders_service.indexer, indexer_id="funders")
- iregistry.register(ext.names_service.indexer, indexer_id="names")
- iregistry.register(ext.subjects_service.indexer, indexer_id="subjects")
- iregistry.register(ext.service.indexer, indexer_id="vocabularies")
-
-
def create_blueprint_from_app(app):
"""Create app blueprint."""
return app.extensions["invenio-vocabularies"].resource.as_blueprint()
diff --git a/invenio_vocabularies/webpack.py b/invenio_vocabularies/webpack.py
index f0281ae0..3e1cae32 100644
--- a/invenio_vocabularies/webpack.py
+++ b/invenio_vocabularies/webpack.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2019-2022 CERN.
+# Copyright (C) 2019-2024 CERN.
# Copyright (C) 2019-2022 Northwestern University.
# Copyright (C) 2022 TU Wien.
# Copyright (C) 2022 Graz University of Technology.
@@ -34,7 +34,7 @@
"react-dnd-html5-backend": "^11.1.0",
"react-dropzone": "^11.0.0",
"react-i18next": "^11.11.0",
- "react-invenio-forms": "^2.0.0",
+ "react-invenio-forms": "^3.0.0",
"react-searchkit": "^2.0.0",
"yup": "^0.32.0",
},
diff --git a/setup.cfg b/setup.cfg
index ce6dcd84..d9196202 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020-2022 CERN.
-# Copyright (C) 2022 Graz University of Technology.
+# Copyright (C) 2020-2024 CERN.
+# Copyright (C) 2022-2024 Graz University of Technology.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
@@ -28,14 +28,14 @@ python_requires = >=3.7
zip_safe = False
install_requires =
invenio-i18n>=2.0.0,<3.0.0
- invenio-records-resources>=4.0.0,<5.0.0
+ invenio-records-resources>=5.0.0,<6.0.0
lxml>=4.5.0
PyYAML>=5.4.1
[options.extras_require]
tests =
- pytest-black>=0.3.0
- invenio-app>=1.3.3,<2.0.0
+ pytest-black-ng>=0.4.0
+ invenio-app>=1.4.0,<2.0.0
invenio-db[postgresql,mysql]>=1.0.14,<2.0.0
pytest-invenio>=2.1.0,<3.0.0
Sphinx>=4.5
@@ -67,6 +67,10 @@ invenio_base.api_blueprints =
invenio_vocabularies_names = invenio_vocabularies.views:create_names_blueprint_from_app
invenio_vocabularies_subjects = invenio_vocabularies.views:create_subjects_blueprint_from_app
invenio_vocabularies_ext = invenio_vocabularies.views:blueprint
+invenio_base.api_finalize_app =
+ invenio_vocabularies = invenio_vocabularies.ext:api_finalize_app
+invenio_base.finalize_app =
+ invenio_vocabularies = invenio_vocabularies.ext:finalize_app
invenio_db.alembic =
invenio_vocabularies = invenio_vocabularies:alembic
invenio_db.models =
@@ -95,6 +99,7 @@ invenio_assets.webpack =
invenio_i18n.translations =
invenio_vocabularies = invenio_vocabularies
+
[build_sphinx]
source-dir = docs/
build-dir = docs/_build
diff --git a/tests/conftest.py b/tests/conftest.py
index 617f2c60..7ad2e3ab 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -77,12 +77,12 @@ def app_config(app_config):
app_config["JSONSCHEMAS_HOST"] = "localhost"
app_config["BABEL_DEFAULT_LOCALE"] = "en"
app_config["I18N_LANGUAGES"] = [("da", "Danish")]
- 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"
+ )
return app_config
diff --git a/tests/custom_fields/test_custom_fields.py b/tests/custom_fields/test_custom_fields.py
index 2d2b06b0..36168b67 100644
--- a/tests/custom_fields/test_custom_fields.py
+++ b/tests/custom_fields/test_custom_fields.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
-# Copyright (C) 2022 CERN.
+# Copyright (C) 2022-2024 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
@@ -44,7 +44,7 @@ def test_cf_mapping(vocabulary_cf):
"properties": {
"@v": {"type": "keyword"},
"id": {"type": "keyword"},
- "title": {"type": "object", "dynamic": True},
+ "title": {"type": "object", "dynamic": "true"},
},
}
diff --git a/tests/mock_module/mappings/v6/records/record-v1.0.0.json b/tests/mock_module/mappings/v6/records/record-v1.0.0.json
index e4111774..939689d8 100644
--- a/tests/mock_module/mappings/v6/records/record-v1.0.0.json
+++ b/tests/mock_module/mappings/v6/records/record-v1.0.0.json
@@ -38,7 +38,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/tests/mock_module/mappings/v7/records/record-v1.0.0.json b/tests/mock_module/mappings/v7/records/record-v1.0.0.json
index 4e5203ae..5e1e8396 100644
--- a/tests/mock_module/mappings/v7/records/record-v1.0.0.json
+++ b/tests/mock_module/mappings/v7/records/record-v1.0.0.json
@@ -37,7 +37,7 @@
},
"title": {
"type": "object",
- "dynamic": true
+ "dynamic": "true"
}
}
}
diff --git a/tests/resources/test_resources_l10n.py b/tests/resources/test_resources_l10n.py
index 50f2ac09..b55d8bde 100644
--- a/tests/resources/test_resources_l10n.py
+++ b/tests/resources/test_resources_l10n.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright (C) 2020-2021 CERN.
+# Copyright (C) 2020-2024 CERN.
# Copyright (C) 2023 Graz University of Technology.
#
# Invenio-Vocabularies is free software; you can redistribute it and/or
@@ -122,8 +122,20 @@ def test_get(client, example_record, h, prefix, expected_da, expected_en):
def test_search(client, example_record, h, prefix, expected_da, expected_en):
"""Test search result serialization."""
- expected_en = {"hits": {"hits": [expected_en], "total": 1}}
- expected_da = {"hits": {"hits": [expected_da], "total": 1}}
+ expected_en = {
+ "hits": {"hits": [expected_en], "total": 1},
+ "links": {
+ "self": "https://127.0.0.1:5000/api/vocabularies/resourcetypes2?page=1&size=25&sort=title"
+ },
+ "sortBy": "title",
+ }
+ expected_da = {
+ "hits": {"hits": [expected_da], "total": 1},
+ "links": {
+ "self": "https://127.0.0.1:5000/api/vocabularies/resourcetypes2?page=1&size=25&sort=title"
+ },
+ "sortBy": "title",
+ }
# Default locale
res = client.get(f"{prefix}", headers=h)