invenio-vocabularies 3.4.2__py2.py3-none-any.whl → 4.1.1__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.

Files changed (69) hide show
  1. invenio_vocabularies/__init__.py +1 -1
  2. invenio_vocabularies/administration/__init__.py +10 -0
  3. invenio_vocabularies/administration/views/__init__.py +10 -0
  4. invenio_vocabularies/administration/views/vocabularies.py +44 -0
  5. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/CustomAwardForm.js +8 -20
  6. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingField.js +2 -2
  7. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/FundingModal.js +5 -7
  8. invenio_vocabularies/assets/semantic-ui/js/invenio_vocabularies/src/contrib/forms/Funding/NoAwardResults.js +3 -3
  9. invenio_vocabularies/cli.py +10 -39
  10. invenio_vocabularies/config.py +33 -3
  11. invenio_vocabularies/contrib/affiliations/config.py +2 -2
  12. invenio_vocabularies/contrib/affiliations/datastreams.py +67 -0
  13. invenio_vocabularies/contrib/affiliations/jsonschemas/affiliations/affiliation-v1.0.0.json +38 -1
  14. invenio_vocabularies/contrib/affiliations/mappings/os-v1/affiliations/affiliation-v1.0.0.json +21 -0
  15. invenio_vocabularies/contrib/affiliations/mappings/os-v2/affiliations/affiliation-v1.0.0.json +21 -0
  16. invenio_vocabularies/contrib/affiliations/mappings/v7/affiliations/affiliation-v1.0.0.json +21 -0
  17. invenio_vocabularies/contrib/affiliations/schema.py +17 -3
  18. invenio_vocabularies/contrib/awards/datastreams.py +90 -3
  19. invenio_vocabularies/contrib/awards/jsonschemas/awards/award-v1.0.0.json +3 -0
  20. invenio_vocabularies/contrib/awards/mappings/os-v1/awards/award-v1.0.0.json +3 -0
  21. invenio_vocabularies/contrib/awards/mappings/os-v2/awards/award-v1.0.0.json +3 -0
  22. invenio_vocabularies/contrib/awards/mappings/v7/awards/award-v1.0.0.json +3 -0
  23. invenio_vocabularies/contrib/common/__init__.py +9 -0
  24. invenio_vocabularies/contrib/common/ror/__init__.py +9 -0
  25. invenio_vocabularies/contrib/common/ror/datastreams.py +166 -0
  26. invenio_vocabularies/contrib/funders/config.py +2 -0
  27. invenio_vocabularies/contrib/funders/datastreams.py +10 -59
  28. invenio_vocabularies/contrib/funders/jsonschemas/funders/funder-v1.0.0.json +36 -1
  29. invenio_vocabularies/contrib/funders/mappings/os-v1/funders/funder-v1.0.0.json +21 -0
  30. invenio_vocabularies/contrib/funders/mappings/os-v2/funders/funder-v1.0.0.json +21 -0
  31. invenio_vocabularies/contrib/funders/mappings/v7/funders/funder-v1.0.0.json +21 -0
  32. invenio_vocabularies/contrib/funders/schema.py +8 -0
  33. invenio_vocabularies/contrib/funders/serializer.py +2 -1
  34. invenio_vocabularies/contrib/names/jsonschemas/names/name-v1.0.0.json +3 -0
  35. invenio_vocabularies/contrib/names/mappings/os-v1/names/name-v1.0.0.json +3 -0
  36. invenio_vocabularies/contrib/names/mappings/os-v2/names/name-v1.0.0.json +3 -0
  37. invenio_vocabularies/contrib/names/mappings/v7/names/name-v1.0.0.json +3 -0
  38. invenio_vocabularies/contrib/subjects/jsonschemas/subjects/subject-v1.0.0.json +3 -0
  39. invenio_vocabularies/contrib/subjects/mappings/os-v1/subjects/subject-v1.0.0.json +3 -0
  40. invenio_vocabularies/contrib/subjects/mappings/os-v2/subjects/subject-v1.0.0.json +3 -0
  41. invenio_vocabularies/contrib/subjects/mappings/v7/subjects/subject-v1.0.0.json +3 -0
  42. invenio_vocabularies/datastreams/factories.py +1 -2
  43. invenio_vocabularies/datastreams/readers.py +96 -3
  44. invenio_vocabularies/datastreams/writers.py +2 -2
  45. invenio_vocabularies/ext.py +22 -7
  46. invenio_vocabularies/factories.py +93 -0
  47. invenio_vocabularies/proxies.py +2 -2
  48. invenio_vocabularies/records/jsonschemas/vocabularies/definitions-v1.0.0.json +7 -0
  49. invenio_vocabularies/records/jsonschemas/vocabularies/vocabulary-v1.0.0.json +1 -4
  50. invenio_vocabularies/resources/__init__.py +8 -1
  51. invenio_vocabularies/resources/config.py +105 -0
  52. invenio_vocabularies/resources/resource.py +31 -41
  53. invenio_vocabularies/services/__init__.py +5 -2
  54. invenio_vocabularies/services/config.py +179 -0
  55. invenio_vocabularies/services/permissions.py +3 -1
  56. invenio_vocabularies/services/results.py +110 -0
  57. invenio_vocabularies/services/schema.py +1 -1
  58. invenio_vocabularies/services/service.py +41 -86
  59. invenio_vocabularies/services/tasks.py +31 -1
  60. invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabularies-list.html +12 -0
  61. invenio_vocabularies/templates/semantic-ui/invenio_vocabularies/vocabulary-details.html +71 -0
  62. invenio_vocabularies/views.py +7 -0
  63. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/METADATA +32 -7
  64. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/RECORD +69 -56
  65. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/entry_points.txt +7 -0
  66. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/AUTHORS.rst +0 -0
  67. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/LICENSE +0 -0
  68. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/WHEEL +0 -0
  69. {invenio_vocabularies-3.4.2.dist-info → invenio_vocabularies-4.1.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020-2021 CERN.
3
+ # Copyright (C) 2020-2024 CERN.
4
+ # Copyright (C) 2024 Uni Münster.
4
5
  #
5
6
  # Invenio-Vocabularies is free software; you can redistribute it and/or
6
7
  # modify it under the terms of the MIT License; see LICENSE file for more
@@ -8,6 +9,8 @@
8
9
 
9
10
  """Vocabulary resource."""
10
11
 
12
+ import json
13
+
11
14
  import marshmallow as ma
12
15
  from flask import g
13
16
  from flask_resources import (
@@ -18,6 +21,7 @@ from flask_resources import (
18
21
  resource_requestctx,
19
22
  response_handler,
20
23
  )
24
+ from invenio_access.permissions import system_identity
21
25
  from invenio_records_resources.resources import (
22
26
  RecordResource,
23
27
  RecordResourceConfig,
@@ -37,55 +41,20 @@ from marshmallow import fields
37
41
  from .serializer import VocabularyL10NItemSchema
38
42
 
39
43
 
40
- #
41
- # Request args
42
- #
43
- class VocabularySearchRequestArgsSchema(SearchRequestArgsSchema):
44
- """Add parameter to parse tags."""
45
-
46
- tags = fields.Str()
47
-
48
-
49
- #
50
- # Resource config
51
- #
52
- class VocabulariesResourceConfig(RecordResourceConfig):
53
- """Vocabulary resource configuration."""
54
-
55
- blueprint_name = "vocabularies"
56
- url_prefix = "/vocabularies"
57
- routes = {"list": "/<type>", "item": "/<type>/<pid_value>", "tasks": "/tasks"}
58
-
59
- request_view_args = {
60
- "pid_value": ma.fields.Str(),
61
- "type": ma.fields.Str(required=True),
62
- }
63
-
64
- request_search_args = VocabularySearchRequestArgsSchema
65
-
66
- response_handlers = {
67
- "application/json": ResponseHandler(JSONSerializer(), headers=etag_headers),
68
- "application/vnd.inveniordm.v1+json": ResponseHandler(
69
- MarshmallowSerializer(
70
- format_serializer_cls=JSONSerializer,
71
- object_schema_cls=VocabularyL10NItemSchema,
72
- list_schema_cls=BaseListSchema,
73
- ),
74
- headers=etag_headers,
75
- ),
76
- }
77
-
78
-
79
44
  #
80
45
  # Resource
81
46
  #
82
47
  class VocabulariesResource(RecordResource):
83
- """Resource for generic vocabularies."""
48
+ """Resource for generic vocabularies.
49
+
50
+ Provide the API /api/vocabularies/
51
+ """
84
52
 
85
53
  def create_url_rules(self):
86
54
  """Create the URL rules for the record resource."""
87
55
  routes = self.config.routes
88
56
  rules = super().create_url_rules()
57
+
89
58
  rules.append(
90
59
  route("POST", routes["tasks"], self.launch),
91
60
  )
@@ -164,3 +133,24 @@ class VocabulariesResource(RecordResource):
164
133
  """Create a task."""
165
134
  self.service.launch(g.identity, resource_requestctx.data or {})
166
135
  return "", 202
136
+
137
+
138
+ class VocabulariesAdminResource(RecordResource):
139
+ """Resource for vocabularies admin interface."""
140
+
141
+ def create_url_rules(self):
142
+ """Create the URL rules for the record resource."""
143
+ routes = self.config.routes
144
+
145
+ rules = [route("GET", routes["list"], self.search)]
146
+
147
+ return rules
148
+
149
+ @request_search_args
150
+ @response_handler(many=True)
151
+ def search(self):
152
+ """Return information about _all_ vocabularies."""
153
+ identity = g.identity
154
+ hits = self.service.search(identity, params=resource_requestctx.args)
155
+
156
+ return hits.to_dict(), 200
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020-2021 CERN.
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
@@ -8,9 +8,12 @@
8
8
 
9
9
  """Services module."""
10
10
 
11
- from .service import VocabulariesService, VocabulariesServiceConfig
11
+ from .config import VocabulariesServiceConfig, VocabularyTypesServiceConfig
12
+ from .service import VocabulariesService, VocabularyTypeService
12
13
 
13
14
  __all__ = (
14
15
  "VocabulariesService",
16
+ "VocabularyTypeService",
15
17
  "VocabulariesServiceConfig",
18
+ "VocabularyTypesServiceConfig",
16
19
  )
@@ -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*}")
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020-2021 CERN.
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}
@@ -96,6 +96,7 @@ class BaseVocabularySchema(BaseRecordSchema):
96
96
  title = i18n_strings
97
97
  description = i18n_strings
98
98
  icon = fields.Str(allow_none=False)
99
+ tags = fields.List(SanitizedUnicode())
99
100
 
100
101
  # Nested field type for administration UI form generation
101
102
  administration_schema_type = "vocabulary"
@@ -105,7 +106,6 @@ class VocabularySchema(BaseVocabularySchema):
105
106
  """Service schema for vocabulary records."""
106
107
 
107
108
  props = fields.Dict(allow_none=False, keys=fields.Str(), values=fields.Str())
108
- tags = fields.List(SanitizedUnicode())
109
109
  type = fields.Str(attribute="type.id", required=True)
110
110
 
111
111
 
@@ -1,7 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2020-2021 CERN.
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.records.components import DataComponent
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 VocabularySearchOptions(SearchOptions):
41
- """Search options."""
42
-
43
- params_interpreters_cls = [
44
- FilterParam.factory(param="tags", field="tags"),
45
- ] + SearchOptions.params_interpreters_cls
46
-
47
- suggest_parser_cls = SuggestQueryParser.factory(
48
- fields=[
49
- "id.text^100",
50
- "id.text._2gram",
51
- "id.text._3gram",
52
- "title.en^5",
53
- "title.en._2gram",
54
- "title.en._3gram",
55
- ],
56
- )
57
-
58
- sort_default = "bestmatch"
59
-
60
- sort_default_no_query = "title"
61
-
62
- sort_options = {
63
- "bestmatch": dict(
64
- title=_("Best match"),
65
- fields=["_score"], # ES defaults to desc on `_score` field
66
- ),
67
- "title": dict(
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
- links_search = pagination_links("{+api}/vocabularies/{type}{?args*}")
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,6 +11,7 @@ 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
14
15
 
15
16
 
16
17
  @shared_task(ignore_result=True)
@@ -26,3 +27,32 @@ def process_datastream(config):
26
27
  if result.errors:
27
28
  for err in result.errors:
28
29
  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.exception(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 %}