invenio-vocabularies 4.0.0__py2.py3-none-any.whl → 4.2.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of invenio-vocabularies might be problematic. Click here for more details.
- invenio_vocabularies/__init__.py +1 -1
- invenio_vocabularies/administration/__init__.py +10 -0
- invenio_vocabularies/administration/views/__init__.py +10 -0
- invenio_vocabularies/administration/views/vocabularies.py +45 -0
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js +8 -20
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js +2 -2
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js +5 -7
- invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/NoAwardResults.js +3 -3
- invenio_vocabularies/cli.py +5 -3
- invenio_vocabularies/config.py +35 -4
- invenio_vocabularies/contrib/affiliations/api.py +1 -2
- invenio_vocabularies/contrib/affiliations/config.py +2 -2
- invenio_vocabularies/contrib/affiliations/datastreams.py +92 -0
- invenio_vocabularies/contrib/affiliations/jsonschemas/affiliations/affiliation-v1.0.0.json +38 -1
- invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json +21 -0
- invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json +21 -0
- invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json +21 -0
- invenio_vocabularies/contrib/affiliations/schema.py +17 -3
- invenio_vocabularies/contrib/affiliations/services.py +1 -2
- invenio_vocabularies/contrib/awards/awards.py +2 -1
- invenio_vocabularies/contrib/awards/datastreams.py +1 -0
- invenio_vocabularies/contrib/awards/jsonschemas/awards/award-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/awards/services.py +1 -2
- invenio_vocabularies/contrib/common/ror/datastreams.py +140 -6
- invenio_vocabularies/contrib/funders/datastreams.py +36 -93
- invenio_vocabularies/contrib/funders/funders.py +2 -1
- invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/funders/serializer.py +2 -1
- invenio_vocabularies/contrib/names/jsonschemas/names/name-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/names/mappings/v7/names/name-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/subjects/jsonschemas/subjects/subject-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v1/subjects/subject-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/subjects/mappings/os-v2/subjects/subject-v1.0.0.json +3 -0
- invenio_vocabularies/contrib/subjects/mappings/v7/subjects/subject-v1.0.0.json +3 -0
- invenio_vocabularies/datastreams/factories.py +1 -2
- invenio_vocabularies/datastreams/readers.py +103 -0
- invenio_vocabularies/datastreams/tasks.py +25 -0
- invenio_vocabularies/datastreams/writers.py +21 -2
- invenio_vocabularies/ext.py +22 -7
- invenio_vocabularies/factories.py +16 -0
- invenio_vocabularies/proxies.py +2 -2
- invenio_vocabularies/records/jsonschemas/vocabularies/definitions-v1.0.0.json +7 -0
- invenio_vocabularies/records/jsonschemas/vocabularies/vocabulary-v1.0.0.json +1 -4
- invenio_vocabularies/records/models.py +2 -4
- invenio_vocabularies/records/pidprovider.py +1 -2
- invenio_vocabularies/resources/__init__.py +9 -1
- invenio_vocabularies/resources/config.py +105 -0
- invenio_vocabularies/resources/resource.py +31 -41
- invenio_vocabularies/resources/schema.py +2 -1
- invenio_vocabularies/services/__init__.py +5 -2
- invenio_vocabularies/services/config.py +179 -0
- invenio_vocabularies/services/custom_fields/subject.py +2 -1
- invenio_vocabularies/services/custom_fields/vocabulary.py +1 -1
- invenio_vocabularies/services/permissions.py +3 -1
- invenio_vocabularies/services/results.py +110 -0
- invenio_vocabularies/services/schema.py +11 -2
- invenio_vocabularies/services/service.py +41 -86
- invenio_vocabularies/services/tasks.py +1 -31
- invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabularies-list.html +12 -0
- invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabulary-details.html +71 -0
- invenio_vocabularies/views.py +7 -0
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/METADATA +33 -1
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/RECORD +76 -66
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/entry_points.txt +4 -0
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/AUTHORS.rst +0 -0
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/LICENSE +0 -0
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/WHEEL +0 -0
- {invenio_vocabularies-4.0.0.dist-info → invenio_vocabularies-4.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2024 CERN.
|
|
4
|
+
# Copyright (C) 2024 Uni Münster.
|
|
5
|
+
#
|
|
6
|
+
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
7
|
+
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
8
|
+
# details.
|
|
9
|
+
|
|
10
|
+
"""Vocabulary services configs."""
|
|
11
|
+
|
|
12
|
+
import sqlalchemy as sa
|
|
13
|
+
from flask import current_app
|
|
14
|
+
from invenio_i18n import lazy_gettext as _
|
|
15
|
+
from invenio_records_resources.services import (
|
|
16
|
+
Link,
|
|
17
|
+
LinksTemplate,
|
|
18
|
+
RecordService,
|
|
19
|
+
RecordServiceConfig,
|
|
20
|
+
SearchOptions,
|
|
21
|
+
pagination_links,
|
|
22
|
+
)
|
|
23
|
+
from invenio_records_resources.services.base import (
|
|
24
|
+
ConditionalLink,
|
|
25
|
+
Service,
|
|
26
|
+
ServiceListResult,
|
|
27
|
+
)
|
|
28
|
+
from invenio_records_resources.services.records.components import DataComponent
|
|
29
|
+
from invenio_records_resources.services.records.params import (
|
|
30
|
+
FilterParam,
|
|
31
|
+
SuggestQueryParser,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from ..records.api import Vocabulary
|
|
35
|
+
from ..records.models import VocabularyType
|
|
36
|
+
from . import results
|
|
37
|
+
from .components import PIDComponent, VocabularyTypeComponent
|
|
38
|
+
from .permissions import PermissionPolicy
|
|
39
|
+
from .schema import TaskSchema, VocabularySchema
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_custom_vocabulary_type(vocabulary_type, context):
|
|
43
|
+
"""Check if the vocabulary type is a custom vocabulary type."""
|
|
44
|
+
return vocabulary_type["id"] in current_app.config.get(
|
|
45
|
+
"VOCABULARIES_CUSTOM_VOCABULARY_TYPES", []
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VocabularySearchOptions(SearchOptions):
|
|
50
|
+
"""Search options for vocabularies."""
|
|
51
|
+
|
|
52
|
+
params_interpreters_cls = [
|
|
53
|
+
FilterParam.factory(param="tags", field="tags"),
|
|
54
|
+
] + SearchOptions.params_interpreters_cls
|
|
55
|
+
|
|
56
|
+
suggest_parser_cls = SuggestQueryParser.factory(
|
|
57
|
+
fields=[
|
|
58
|
+
"id.text^100",
|
|
59
|
+
"id.text._2gram",
|
|
60
|
+
"id.text._3gram",
|
|
61
|
+
"title.en^5",
|
|
62
|
+
"title.en._2gram",
|
|
63
|
+
"title.en._3gram",
|
|
64
|
+
],
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
sort_default = "bestmatch"
|
|
68
|
+
|
|
69
|
+
sort_default_no_query = "title"
|
|
70
|
+
|
|
71
|
+
sort_options = {
|
|
72
|
+
"bestmatch": dict(
|
|
73
|
+
title=_("Best match"),
|
|
74
|
+
fields=["_score"], # ES defaults to desc on `_score` field
|
|
75
|
+
),
|
|
76
|
+
"title": dict(
|
|
77
|
+
title=_("Title"),
|
|
78
|
+
fields=["title_sort"],
|
|
79
|
+
),
|
|
80
|
+
"newest": dict(
|
|
81
|
+
title=_("Newest"),
|
|
82
|
+
fields=["-created"],
|
|
83
|
+
),
|
|
84
|
+
"oldest": dict(
|
|
85
|
+
title=_("Oldest"),
|
|
86
|
+
fields=["created"],
|
|
87
|
+
),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class VocabularyTypeSearchOptions(SearchOptions):
|
|
92
|
+
"""Search options for vocabulary types."""
|
|
93
|
+
|
|
94
|
+
sort_options = {
|
|
95
|
+
"id": dict(
|
|
96
|
+
title=_("ID"),
|
|
97
|
+
fields=["id"],
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
sort_default = "id"
|
|
102
|
+
|
|
103
|
+
sort_default_no_query = "id"
|
|
104
|
+
|
|
105
|
+
sort_direction_options = {
|
|
106
|
+
"asc": dict(title=_("Ascending"), fn=sa.asc),
|
|
107
|
+
"desc": dict(title=_("Descending"), fn=sa.desc),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
sort_direction_default = "asc"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class VocabulariesServiceConfig(RecordServiceConfig):
|
|
114
|
+
"""Vocabulary service configuration."""
|
|
115
|
+
|
|
116
|
+
service_id = "vocabularies"
|
|
117
|
+
indexer_queue_name = "vocabularies"
|
|
118
|
+
permission_policy_cls = PermissionPolicy
|
|
119
|
+
record_cls = Vocabulary
|
|
120
|
+
schema = VocabularySchema
|
|
121
|
+
task_schema = TaskSchema
|
|
122
|
+
|
|
123
|
+
search = VocabularySearchOptions
|
|
124
|
+
|
|
125
|
+
components = [
|
|
126
|
+
# Order of components are important!
|
|
127
|
+
VocabularyTypeComponent,
|
|
128
|
+
DataComponent,
|
|
129
|
+
PIDComponent,
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
links_item = {
|
|
133
|
+
"self": Link(
|
|
134
|
+
"{+api}/vocabularies/{type}/{id}",
|
|
135
|
+
vars=lambda record, vars: vars.update(
|
|
136
|
+
{
|
|
137
|
+
"id": record.pid.pid_value,
|
|
138
|
+
"type": record.type.id,
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
links_search = pagination_links("{+api}/vocabularies/{type}{?args*}")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class VocabularyTypesServiceConfig(RecordServiceConfig):
|
|
148
|
+
"""Vocabulary types service configuration."""
|
|
149
|
+
|
|
150
|
+
service_id = "vocabulary_types"
|
|
151
|
+
permission_policy_cls = PermissionPolicy
|
|
152
|
+
record_cls = VocabularyType
|
|
153
|
+
schema = VocabularySchema # Works but should be VocabularyTypeSchema if this is defined at some point
|
|
154
|
+
result_list_cls = results.VocabularyTypeList
|
|
155
|
+
|
|
156
|
+
links_item = {
|
|
157
|
+
"self": ConditionalLink(
|
|
158
|
+
cond=is_custom_vocabulary_type,
|
|
159
|
+
if_=Link(
|
|
160
|
+
"{+api}/{id}",
|
|
161
|
+
vars=lambda vocab_type, vars: vars.update({"id": vocab_type["id"]}),
|
|
162
|
+
),
|
|
163
|
+
else_=Link(
|
|
164
|
+
"{+api}/vocabularies/{id}",
|
|
165
|
+
vars=lambda vocab_type, vars: vars.update({"id": vocab_type["id"]}),
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
search = VocabularyTypeSearchOptions
|
|
171
|
+
|
|
172
|
+
components = [
|
|
173
|
+
# Order of components are important!
|
|
174
|
+
VocabularyTypeComponent,
|
|
175
|
+
DataComponent,
|
|
176
|
+
PIDComponent,
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
links_search = pagination_links("{+api}/vocabularies/{type}{?args*}")
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
"""Custom fields."""
|
|
10
|
+
|
|
10
11
|
from invenio_i18n import lazy_gettext as _
|
|
11
12
|
|
|
12
13
|
from ...contrib.subjects.api import Subject
|
|
@@ -25,7 +26,7 @@ class SubjectCF(VocabularyCF):
|
|
|
25
26
|
vocabulary_id="subjects",
|
|
26
27
|
schema=SubjectRelationSchema,
|
|
27
28
|
ui_schema=SubjectRelationSchema,
|
|
28
|
-
**kwargs
|
|
29
|
+
**kwargs,
|
|
29
30
|
)
|
|
30
31
|
self.pid_field = Subject.pid
|
|
31
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2020-
|
|
3
|
+
# Copyright (C) 2020-2024 CERN.
|
|
4
4
|
#
|
|
5
5
|
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
6
6
|
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
@@ -21,3 +21,5 @@ class PermissionPolicy(RecordPermissionPolicy):
|
|
|
21
21
|
can_update = [SystemProcess()]
|
|
22
22
|
can_delete = [SystemProcess()]
|
|
23
23
|
can_manage = [SystemProcess()]
|
|
24
|
+
# this permission is needed for the /api/vocabularies/ endpoint
|
|
25
|
+
can_list_vocabularies = [SystemProcess(), AnyUser()]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2024 CERN.
|
|
4
|
+
# Copyright (C) 2024 Uni Münster.
|
|
5
|
+
#
|
|
6
|
+
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
7
|
+
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
8
|
+
# details.
|
|
9
|
+
|
|
10
|
+
"""Vocabulary results."""
|
|
11
|
+
|
|
12
|
+
from flask import current_app
|
|
13
|
+
from invenio_records_resources.proxies import current_service_registry
|
|
14
|
+
from invenio_records_resources.services import RecordServiceConfig
|
|
15
|
+
from invenio_records_resources.services.records.results import RecordList
|
|
16
|
+
from invenio_search import current_search_client
|
|
17
|
+
|
|
18
|
+
from ..proxies import current_service
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VocabularyTypeList(RecordList):
|
|
22
|
+
"""Ensures that vocabulary type metadata is returned in the proper format."""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def total(self):
|
|
26
|
+
"""Get total number of hits."""
|
|
27
|
+
return self._results.total
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def custom_vocabulary_names(self):
|
|
31
|
+
"""Checks whether vocabulary is a custom vocabulary."""
|
|
32
|
+
return current_app.config.get("VOCABULARIES_CUSTOM_VOCABULARY_TYPES", [])
|
|
33
|
+
|
|
34
|
+
def to_dict(self):
|
|
35
|
+
"""Formats result to a dict of hits."""
|
|
36
|
+
config_vocab_types = current_app.config.get(
|
|
37
|
+
"INVENIO_VOCABULARY_TYPE_METADATA", {}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
count_terms_agg = {}
|
|
41
|
+
generic_stats = self._generic_vocabulary_statistics()
|
|
42
|
+
custom_stats = self._custom_vocabulary_statistics()
|
|
43
|
+
|
|
44
|
+
for k in generic_stats.keys() | custom_stats.keys():
|
|
45
|
+
count_terms_agg[k] = generic_stats.get(k, 0) + custom_stats.get(k, 0)
|
|
46
|
+
|
|
47
|
+
hits = self._results.items
|
|
48
|
+
|
|
49
|
+
# Extend database data with configuration & aggregation data.
|
|
50
|
+
results = []
|
|
51
|
+
for db_vocab_type in hits:
|
|
52
|
+
result = {
|
|
53
|
+
"id": db_vocab_type.id,
|
|
54
|
+
"pid_type": db_vocab_type.pid_type,
|
|
55
|
+
"count": count_terms_agg.get(db_vocab_type.id, 0),
|
|
56
|
+
"is_custom_vocabulary": db_vocab_type.id
|
|
57
|
+
in self.custom_vocabulary_names,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if db_vocab_type.id in config_vocab_types:
|
|
61
|
+
for k, v in config_vocab_types[db_vocab_type.id].items():
|
|
62
|
+
result[k] = v
|
|
63
|
+
|
|
64
|
+
results.append(result)
|
|
65
|
+
|
|
66
|
+
for hit in results:
|
|
67
|
+
if self._links_item_tpl:
|
|
68
|
+
hit["links"] = self._links_item_tpl.expand(self._identity, hit)
|
|
69
|
+
|
|
70
|
+
res = {
|
|
71
|
+
"hits": {
|
|
72
|
+
"hits": results,
|
|
73
|
+
"total": self.total,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if self._params:
|
|
78
|
+
if self._links_tpl:
|
|
79
|
+
res["links"] = self._links_tpl.expand(self._identity, self.pagination)
|
|
80
|
+
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
def _custom_vocabulary_statistics(self):
|
|
84
|
+
# query database for count of terms in custom vocabularies
|
|
85
|
+
returndict = {}
|
|
86
|
+
for vocab_type in self.custom_vocabulary_names:
|
|
87
|
+
custom_service = current_service_registry.get(vocab_type)
|
|
88
|
+
record_cls = custom_service.config.record_cls
|
|
89
|
+
returndict[vocab_type] = record_cls.model_cls.query.count()
|
|
90
|
+
|
|
91
|
+
return returndict
|
|
92
|
+
|
|
93
|
+
def _generic_vocabulary_statistics(self):
|
|
94
|
+
# Opensearch query for generic vocabularies
|
|
95
|
+
config: RecordServiceConfig = (
|
|
96
|
+
current_service.config
|
|
97
|
+
) # TODO: Where to get the config from here? current_service is None
|
|
98
|
+
search_opts = config.search
|
|
99
|
+
|
|
100
|
+
search = search_opts.search_cls(
|
|
101
|
+
using=current_search_client,
|
|
102
|
+
index=config.record_cls.index.search_alias,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
search.aggs.bucket("vocabularies", {"terms": {"field": "type.id", "size": 100}})
|
|
106
|
+
|
|
107
|
+
search_result = search.execute()
|
|
108
|
+
buckets = search_result.aggs.to_dict()["vocabularies"]["buckets"]
|
|
109
|
+
|
|
110
|
+
return {bucket["key"]: bucket["doc_count"] for bucket in buckets}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2020-
|
|
3
|
+
# Copyright (C) 2020-2024 CERN.
|
|
4
4
|
#
|
|
5
5
|
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
6
6
|
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
@@ -35,6 +35,9 @@ class BaseVocabularyRelationSchema(Schema):
|
|
|
35
35
|
|
|
36
36
|
id = SanitizedUnicode(required=True)
|
|
37
37
|
|
|
38
|
+
# Nested field type for administration UI form generation
|
|
39
|
+
administration_schema_type = "vocabulary"
|
|
40
|
+
|
|
38
41
|
|
|
39
42
|
class VocabularyRelationSchema(BaseVocabularyRelationSchema):
|
|
40
43
|
"""Vocabulary relation schema."""
|
|
@@ -63,6 +66,9 @@ class ContribVocabularyRelationSchema(Schema):
|
|
|
63
66
|
ftf_name = None # free text field name
|
|
64
67
|
parent_field_name = None
|
|
65
68
|
|
|
69
|
+
# Nested field type for administration UI form generation
|
|
70
|
+
administration_schema_type = "vocabulary"
|
|
71
|
+
|
|
66
72
|
@validates_schema
|
|
67
73
|
def validate_relation_schema(self, data, **kwargs):
|
|
68
74
|
"""Validates that either id either the free text field are present."""
|
|
@@ -90,13 +96,16 @@ class BaseVocabularySchema(BaseRecordSchema):
|
|
|
90
96
|
title = i18n_strings
|
|
91
97
|
description = i18n_strings
|
|
92
98
|
icon = fields.Str(allow_none=False)
|
|
99
|
+
tags = fields.List(SanitizedUnicode())
|
|
100
|
+
|
|
101
|
+
# Nested field type for administration UI form generation
|
|
102
|
+
administration_schema_type = "vocabulary"
|
|
93
103
|
|
|
94
104
|
|
|
95
105
|
class VocabularySchema(BaseVocabularySchema):
|
|
96
106
|
"""Service schema for vocabulary records."""
|
|
97
107
|
|
|
98
108
|
props = fields.Dict(allow_none=False, keys=fields.Str(), values=fields.Str())
|
|
99
|
-
tags = fields.List(SanitizedUnicode())
|
|
100
109
|
type = fields.Str(attribute="type.id", required=True)
|
|
101
110
|
|
|
102
111
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C)
|
|
3
|
+
# Copyright (C) 2024 CERN.
|
|
4
4
|
# Copyright (C) 2021 Northwestern University.
|
|
5
|
+
# Copyright (C) 2024 Uni Münster.
|
|
5
6
|
#
|
|
6
7
|
# Invenio-Vocabularies is free software; you can redistribute it and/or
|
|
7
8
|
# modify it under the terms of the MIT License; see LICENSE file for more
|
|
@@ -9,9 +10,8 @@
|
|
|
9
10
|
|
|
10
11
|
"""Vocabulary service."""
|
|
11
12
|
|
|
13
|
+
import sqlalchemy as sa
|
|
12
14
|
from invenio_cache import current_cache
|
|
13
|
-
from invenio_db import db
|
|
14
|
-
from invenio_i18n import lazy_gettext as _
|
|
15
15
|
from invenio_records_resources.services import (
|
|
16
16
|
Link,
|
|
17
17
|
LinksTemplate,
|
|
@@ -20,97 +20,52 @@ from invenio_records_resources.services import (
|
|
|
20
20
|
SearchOptions,
|
|
21
21
|
pagination_links,
|
|
22
22
|
)
|
|
23
|
-
from invenio_records_resources.services.
|
|
24
|
-
from invenio_records_resources.services.records.params import (
|
|
25
|
-
FilterParam,
|
|
26
|
-
SuggestQueryParser,
|
|
27
|
-
)
|
|
23
|
+
from invenio_records_resources.services.base.utils import map_search_params
|
|
28
24
|
from invenio_records_resources.services.records.schema import ServiceSchemaWrapper
|
|
29
25
|
from invenio_records_resources.services.uow import unit_of_work
|
|
30
26
|
from invenio_search.engine import dsl
|
|
31
27
|
|
|
32
|
-
from ..records.api import Vocabulary
|
|
33
28
|
from ..records.models import VocabularyType
|
|
34
|
-
from .components import PIDComponent, VocabularyTypeComponent
|
|
35
|
-
from .permissions import PermissionPolicy
|
|
36
|
-
from .schema import TaskSchema, VocabularySchema
|
|
37
29
|
from .tasks import process_datastream
|
|
38
30
|
|
|
39
31
|
|
|
40
|
-
class
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
title=_("Title"),
|
|
69
|
-
fields=["title_sort"],
|
|
70
|
-
),
|
|
71
|
-
"newest": dict(
|
|
72
|
-
title=_("Newest"),
|
|
73
|
-
fields=["-created"],
|
|
74
|
-
),
|
|
75
|
-
"oldest": dict(
|
|
76
|
-
title=_("Oldest"),
|
|
77
|
-
fields=["created"],
|
|
78
|
-
),
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class VocabulariesServiceConfig(RecordServiceConfig):
|
|
83
|
-
"""Vocabulary service configuration."""
|
|
84
|
-
|
|
85
|
-
service_id = "vocabularies"
|
|
86
|
-
indexer_queue_name = "vocabularies"
|
|
87
|
-
permission_policy_cls = PermissionPolicy
|
|
88
|
-
record_cls = Vocabulary
|
|
89
|
-
schema = VocabularySchema
|
|
90
|
-
task_schema = TaskSchema
|
|
91
|
-
|
|
92
|
-
search = VocabularySearchOptions
|
|
93
|
-
|
|
94
|
-
components = [
|
|
95
|
-
# Order of components are important!
|
|
96
|
-
VocabularyTypeComponent,
|
|
97
|
-
DataComponent,
|
|
98
|
-
PIDComponent,
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
links_item = {
|
|
102
|
-
"self": Link(
|
|
103
|
-
"{+api}/vocabularies/{type}/{id}",
|
|
104
|
-
vars=lambda record, vars: vars.update(
|
|
105
|
-
{
|
|
106
|
-
"id": record.pid.pid_value,
|
|
107
|
-
"type": record.type.id,
|
|
108
|
-
}
|
|
109
|
-
),
|
|
110
|
-
),
|
|
111
|
-
}
|
|
32
|
+
class VocabularyTypeService(RecordService):
|
|
33
|
+
"""Vocabulary type service."""
|
|
34
|
+
|
|
35
|
+
def search(self, identity, params=None):
|
|
36
|
+
"""Search for vocabulary types entries."""
|
|
37
|
+
self.require_permission(identity, "list_vocabularies")
|
|
38
|
+
|
|
39
|
+
search_params = map_search_params(self.config.search, params)
|
|
40
|
+
|
|
41
|
+
query_param = search_params["q"]
|
|
42
|
+
|
|
43
|
+
filters = []
|
|
44
|
+
if query_param:
|
|
45
|
+
filters.extend([VocabularyType.id.ilike(f"%{query_param}%")])
|
|
46
|
+
|
|
47
|
+
vocabulary_types = (
|
|
48
|
+
VocabularyType.query.filter(sa.or_(*filters))
|
|
49
|
+
.order_by(
|
|
50
|
+
search_params["sort_direction"](
|
|
51
|
+
sa.text(",".join(search_params["sort"]))
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
.paginate(
|
|
55
|
+
page=search_params["page"],
|
|
56
|
+
per_page=search_params["size"],
|
|
57
|
+
error_out=False,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
112
60
|
|
|
113
|
-
|
|
61
|
+
return self.config.result_list_cls(
|
|
62
|
+
self,
|
|
63
|
+
identity,
|
|
64
|
+
vocabulary_types,
|
|
65
|
+
search_params,
|
|
66
|
+
links_tpl=LinksTemplate(self.config.links_search, context={"args": params}),
|
|
67
|
+
links_item_tpl=self.links_item_tpl,
|
|
68
|
+
)
|
|
114
69
|
|
|
115
70
|
|
|
116
71
|
class VocabulariesService(RecordService):
|
|
@@ -145,7 +100,7 @@ class VocabulariesService(RecordService):
|
|
|
145
100
|
params,
|
|
146
101
|
search_preference,
|
|
147
102
|
extra_filter=dsl.Q("term", type__id=vocabulary_type.id),
|
|
148
|
-
**kwargs
|
|
103
|
+
**kwargs,
|
|
149
104
|
).execute()
|
|
150
105
|
|
|
151
106
|
return self.result_list(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2022 CERN.
|
|
3
|
+
# Copyright (C) 2022-2024 CERN.
|
|
4
4
|
#
|
|
5
5
|
# Invenio-Vocabularies is free software; you can redistribute it and/or modify
|
|
6
6
|
# it under the terms of the MIT License; see LICENSE file for more details.
|
|
@@ -11,7 +11,6 @@ from celery import shared_task
|
|
|
11
11
|
from flask import current_app
|
|
12
12
|
|
|
13
13
|
from ..datastreams.factories import DataStreamFactory
|
|
14
|
-
from ..factories import get_vocabulary_config
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
@shared_task(ignore_result=True)
|
|
@@ -27,32 +26,3 @@ def process_datastream(config):
|
|
|
27
26
|
if result.errors:
|
|
28
27
|
for err in result.errors:
|
|
29
28
|
current_app.logger.error(err)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@shared_task()
|
|
33
|
-
def import_funders():
|
|
34
|
-
"""Import the funders vocabulary.
|
|
35
|
-
|
|
36
|
-
Only new records are imported.
|
|
37
|
-
Existing records are not updated.
|
|
38
|
-
"""
|
|
39
|
-
vc = get_vocabulary_config("funders")
|
|
40
|
-
config = vc.get_config()
|
|
41
|
-
|
|
42
|
-
# When importing funders via a Celery task, make sure that we are automatically downloading the ROR file,
|
|
43
|
-
# instead of relying on a local file on the file system.
|
|
44
|
-
if config["readers"][0]["type"] == "ror-http":
|
|
45
|
-
readers_config_with_ror_http = config["readers"]
|
|
46
|
-
else:
|
|
47
|
-
readers_config_with_ror_http = [{"type": "ror-http"}] + config["readers"]
|
|
48
|
-
|
|
49
|
-
ds = DataStreamFactory.create(
|
|
50
|
-
readers_config=readers_config_with_ror_http,
|
|
51
|
-
transformers_config=config.get("transformers"),
|
|
52
|
-
writers_config=config["writers"],
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
for result in ds.process():
|
|
56
|
-
if result.errors:
|
|
57
|
-
for err in result.errors:
|
|
58
|
-
current_app.logger.error(err)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (C) 2024 Uni Münster.
|
|
3
|
+
|
|
4
|
+
Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
5
|
+
under the terms of the MIT License; see LICENSE file for more details.
|
|
6
|
+
#}
|
|
7
|
+
{% extends "invenio_administration/search.html" %}
|
|
8
|
+
|
|
9
|
+
{% block javascript %}
|
|
10
|
+
{{ super() }}
|
|
11
|
+
{{ webpack['invenio-vocabularies-search.js'] }}
|
|
12
|
+
{% endblock %}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (C) 2024 CERN.
|
|
3
|
+
|
|
4
|
+
Invenio App RDM is free software; you can redistribute it and/or modify it
|
|
5
|
+
under the terms of the MIT License; see LICENSE file for more details.
|
|
6
|
+
#}
|
|
7
|
+
|
|
8
|
+
{%- from "invenio_administration/macros.html" import go_back %}
|
|
9
|
+
|
|
10
|
+
{% extends "invenio_administration/search.html" %}
|
|
11
|
+
|
|
12
|
+
{% block admin_main_column %}
|
|
13
|
+
<main id="admin-main-content"
|
|
14
|
+
class="ten wide mobile twelve wide tablet thirteen wide computer fourteen wide widescreen column">
|
|
15
|
+
|
|
16
|
+
<div class="ui container fluid pl-10 pr-10 pt-10 pb-10">
|
|
17
|
+
{{ go_back() }}
|
|
18
|
+
<div class="ui grid">
|
|
19
|
+
<div class="column six wide">
|
|
20
|
+
<h1 class="ui header">{{ title or name }}</h1>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="column ten wide right aligned">
|
|
23
|
+
<a class="ui icon labeled button"
|
|
24
|
+
href="{{ edit_endpoint }}">
|
|
25
|
+
<i aria-hidden="true"
|
|
26
|
+
class="cog icon"></i>{{ _("Settings") }}
|
|
27
|
+
</a>
|
|
28
|
+
<a class="ui icon labeled button"
|
|
29
|
+
href="{{ create_ui_endpoint }}">
|
|
30
|
+
<i aria-hidden="true"
|
|
31
|
+
class="calendar icon"></i>{{ _("Schedule") }}
|
|
32
|
+
</a>
|
|
33
|
+
<a class="ui icon labeled button"
|
|
34
|
+
href="{{ run_endpoint }}">
|
|
35
|
+
<i aria-hidden="true"
|
|
36
|
+
class="play icon"></i>{{ _("Run now") }}
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{% block admin_page_content %}
|
|
41
|
+
<div class="ui divider" aria-hidden="true"></div>
|
|
42
|
+
|
|
43
|
+
{%- block search_app %}
|
|
44
|
+
<div
|
|
45
|
+
id="invenio-search-config"
|
|
46
|
+
data-title='{{ title | tojson }}'
|
|
47
|
+
data-resource-name='{{ resource_name | tojson }}'
|
|
48
|
+
data-invenio-search-config='{{ search_config() | tojson }}'
|
|
49
|
+
data-fields='{{ fields | tojson }}'
|
|
50
|
+
data-display-search='{{ display_search | tojson }}'
|
|
51
|
+
data-display-read='{{ display_read | tojson }}'
|
|
52
|
+
data-display-edit='{{ display_edit | tojson }}'
|
|
53
|
+
data-display-delete='{{ display_delete | tojson }}'
|
|
54
|
+
data-resource-schema='{{ resource_schema | tojson }}'
|
|
55
|
+
data-actions='{{ actions | tojson }}'
|
|
56
|
+
data-api-endpoint='{{ api_endpoint }}'
|
|
57
|
+
data-pid-path='{{ pid_path | tojson }}'
|
|
58
|
+
data-create-endpoint='{{ create_ui_endpoint }}'
|
|
59
|
+
data-list-endpoint='{{ list_ui_endpoint }}'
|
|
60
|
+
>
|
|
61
|
+
</div>
|
|
62
|
+
{%- endblock search_app %}
|
|
63
|
+
{% endblock admin_page_content %}
|
|
64
|
+
</div>
|
|
65
|
+
</main>
|
|
66
|
+
{% endblock %}
|
|
67
|
+
|
|
68
|
+
{% block javascript %}
|
|
69
|
+
{{ super() }}
|
|
70
|
+
{{ webpack['invenio-jobs-details.js'] }}
|
|
71
|
+
{% endblock %}
|
invenio_vocabularies/views.py
CHANGED
|
@@ -44,3 +44,10 @@ def create_names_blueprint_from_app(app):
|
|
|
44
44
|
def create_subjects_blueprint_from_app(app):
|
|
45
45
|
"""Create app blueprint."""
|
|
46
46
|
return app.extensions["invenio-vocabularies"].subjects_resource.as_blueprint()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_list_blueprint_from_app(app):
|
|
50
|
+
"""Create app blueprint."""
|
|
51
|
+
return app.extensions[
|
|
52
|
+
"invenio-vocabularies"
|
|
53
|
+
].vocabulary_admin_resource.as_blueprint()
|