oarepo-workflows 2.0.0.dev6__tar.gz → 2.0.0.dev8__tar.gz
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.
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/PKG-INFO +1 -1
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/__init__.py +3 -3
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/base.py +3 -3
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/errors.py +4 -6
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/ext.py +15 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/ext_config.py +2 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/permission_policy.py +4 -11
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/events.py +10 -1
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/policy.py +11 -1
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/requests.py +14 -4
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/multiple_entities/__init__.py +1 -1
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/components/workflow.py +9 -15
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/__init__.py +2 -2
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/generators.py +51 -2
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/record_permission_policy.py +8 -17
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/.gitignore +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/LICENSE +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/README.md +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/initial_config.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/draft_record.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/parent_record.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/parent_record_metadata.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/record.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/workflows_mapping.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/parent_record_schema.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/record_schema.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/service_config.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/proxies.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/state.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/workflow.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/auto.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/conditionals.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/multiple_entities.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/recipient_generator.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/permissions.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/auto_approve/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/auto_approve/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/components/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/multiple_entities/__init__.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/workflow_permissions.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/results.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/uow.py +0 -0
- {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oarepo-workflows
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev8
|
|
4
4
|
Summary: OARepo module allowing record workflow functionality
|
|
5
5
|
Project-URL: Homepage, https://github.com/oarepo/oarepo-workflows
|
|
6
6
|
Author-email: Ronald Krist <krist@cesnet.cz>
|
|
@@ -13,7 +13,7 @@ from oarepo_workflows.services.permissions import (
|
|
|
13
13
|
FromRecordWorkflow,
|
|
14
14
|
IfInState,
|
|
15
15
|
WorkflowPermission,
|
|
16
|
-
|
|
16
|
+
WorkflowRecordPermissionPolicyMixin,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from .base import Workflow
|
|
@@ -27,7 +27,7 @@ from .requests import (
|
|
|
27
27
|
WorkflowTransitions,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
__version__ = "2.0.
|
|
30
|
+
__version__ = "2.0.0dev8"
|
|
31
31
|
"""Version of the library."""
|
|
32
32
|
|
|
33
33
|
|
|
@@ -38,7 +38,7 @@ __all__ = (
|
|
|
38
38
|
"IfInState",
|
|
39
39
|
"Workflow",
|
|
40
40
|
"WorkflowPermission",
|
|
41
|
-
"
|
|
41
|
+
"WorkflowRecordPermissionPolicyMixin",
|
|
42
42
|
"WorkflowRequest",
|
|
43
43
|
"WorkflowRequestEscalation",
|
|
44
44
|
"WorkflowRequestPolicy",
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import dataclasses
|
|
13
13
|
from typing import TYPE_CHECKING, Any, Protocol
|
|
14
14
|
|
|
15
|
-
from . import
|
|
15
|
+
from . import WorkflowRecordPermissionPolicyMixin
|
|
16
16
|
from .requests import WorkflowRequestPolicy
|
|
17
17
|
from .services.permissions import DefaultWorkflowPermissions
|
|
18
18
|
|
|
@@ -48,7 +48,7 @@ class Workflow:
|
|
|
48
48
|
|
|
49
49
|
def requests(self) -> WorkflowRequestPolicy:
|
|
50
50
|
"""Return instance of request policy for this workflow."""
|
|
51
|
-
return self.request_policy_cls()
|
|
51
|
+
return self.request_policy_cls(self)
|
|
52
52
|
|
|
53
53
|
@property
|
|
54
54
|
def permission_policy_with_requests_cls(self) -> type[DefaultWorkflowPermissions]:
|
|
@@ -69,7 +69,7 @@ class Workflow:
|
|
|
69
69
|
|
|
70
70
|
This is just a sanity check to raise an error as soon as possible.
|
|
71
71
|
"""
|
|
72
|
-
if issubclass(self.permission_policy_cls,
|
|
72
|
+
if issubclass(self.permission_policy_cls, WorkflowRecordPermissionPolicyMixin):
|
|
73
73
|
raise TypeError(
|
|
74
74
|
f"Workflow permission policy {self.permission_policy_cls} is not a "
|
|
75
75
|
f"subclass of WorkflowRecordPermissionPolicy."
|
|
@@ -76,22 +76,20 @@ class InvalidConfigurationError(Exception):
|
|
|
76
76
|
"""Exception raised when a configuration is invalid."""
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
class EventTypeNotInWorkflowError(
|
|
79
|
+
class EventTypeNotInWorkflowError(KeyError):
|
|
80
80
|
"""Exception raised when user tries to create a request with a request type that is not defined in the workflow."""
|
|
81
81
|
|
|
82
|
-
def __init__(self,
|
|
82
|
+
def __init__(self, event_type: str) -> None:
|
|
83
83
|
"""Initialize the exception."""
|
|
84
|
-
self.request_type = request_type
|
|
85
|
-
self.workflow = workflow_code
|
|
86
84
|
self.event_type = event_type
|
|
87
85
|
|
|
88
86
|
@property
|
|
89
87
|
def description(self) -> str:
|
|
90
88
|
"""Exception's description."""
|
|
91
|
-
return f"Event type {self.event_type} is not
|
|
89
|
+
return f"Event type {self.event_type} is not set in a workflow."
|
|
92
90
|
|
|
93
91
|
|
|
94
|
-
class RequestTypeNotInWorkflowError(
|
|
92
|
+
class RequestTypeNotInWorkflowError(KeyError):
|
|
95
93
|
"""Exception raised when user tries to create a request with a request type that is not defined in the workflow."""
|
|
96
94
|
|
|
97
95
|
def __init__(self, request_type: str, workflow_code: str) -> None:
|
|
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
15
15
|
|
|
16
16
|
from invenio_records_resources.services.uow import unit_of_work
|
|
17
17
|
|
|
18
|
+
from oarepo_workflows import current_oarepo_workflows
|
|
18
19
|
from oarepo_workflows.errors import (
|
|
19
20
|
InvalidWorkflowError,
|
|
20
21
|
MissingWorkflowError,
|
|
@@ -67,6 +68,7 @@ class OARepoWorkflows:
|
|
|
67
68
|
from . import ext_config
|
|
68
69
|
|
|
69
70
|
app.config.setdefault("WORKFLOWS", ext_config.WORKFLOWS)
|
|
71
|
+
app.config.setdefault("WORKFLOWS_DEFAULT_WORKFLOW", ext_config.WORKFLOWS_DEFAULT_WORKFLOW)
|
|
70
72
|
app.config.setdefault("REQUESTS_ALLOWED_RECEIVERS", []).extend(ext_config.WORKFLOWS_ALLOWED_REQUEST_RECEIVERS)
|
|
71
73
|
|
|
72
74
|
def init_app(self, app: Flask) -> None:
|
|
@@ -150,6 +152,18 @@ class OARepoWorkflows:
|
|
|
150
152
|
self.app.config.get("DEFAULT_WORKFLOW_EVENTS", {}),
|
|
151
153
|
)
|
|
152
154
|
|
|
155
|
+
@property
|
|
156
|
+
def default_workflow(self) -> Workflow:
|
|
157
|
+
"""Return the default workflow."""
|
|
158
|
+
try:
|
|
159
|
+
return self.workflow_by_code[self.app.config["WORKFLOWS_DEFAULT_WORKFLOW"]]
|
|
160
|
+
except KeyError as exc:
|
|
161
|
+
raise MissingWorkflowError(
|
|
162
|
+
f"Default workflow is needed to run but there is no {self.app.config['WORKFLOWS_DEFAULT_WORKFLOW']} "
|
|
163
|
+
f"defined. Please define this workflow or change WORKFLOWS_DEFAULT_WORKFLOW to point "
|
|
164
|
+
f"to the default workflow."
|
|
165
|
+
) from exc
|
|
166
|
+
|
|
153
167
|
def get_workflow(self, record: Record | dict[str, Any]) -> Workflow:
|
|
154
168
|
"""Get the workflow for a record.
|
|
155
169
|
|
|
@@ -179,6 +193,7 @@ class OARepoWorkflows:
|
|
|
179
193
|
except KeyError as e:
|
|
180
194
|
raise MissingWorkflowError("Parent record does not have a workflow attribute.", record=record) from e
|
|
181
195
|
try:
|
|
196
|
+
workflow_id = workflow_id or current_oarepo_workflows.default_workflow.code
|
|
182
197
|
return self.workflow_by_code[workflow_id]
|
|
183
198
|
except KeyError as e:
|
|
184
199
|
raise InvalidWorkflowError(
|
|
@@ -17,18 +17,16 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from typing import TYPE_CHECKING, Any, override
|
|
19
19
|
|
|
20
|
-
from
|
|
21
|
-
from oarepo_model.customizations import Customization, ReplaceBaseClass
|
|
20
|
+
from oarepo_model.customizations.prepend_mixin import PrependMixin
|
|
22
21
|
from oarepo_model.presets import Preset
|
|
23
22
|
|
|
24
|
-
from oarepo_workflows.services.permissions
|
|
25
|
-
WorkflowRecordPermissionPolicy,
|
|
26
|
-
)
|
|
23
|
+
from oarepo_workflows.services.permissions import WorkflowRecordPermissionPolicyMixin
|
|
27
24
|
|
|
28
25
|
if TYPE_CHECKING:
|
|
29
26
|
from collections.abc import Generator
|
|
30
27
|
|
|
31
28
|
from oarepo_model.builder import InvenioModelBuilder
|
|
29
|
+
from oarepo_model.customizations import Customization
|
|
32
30
|
from oarepo_model.model import InvenioModel
|
|
33
31
|
|
|
34
32
|
|
|
@@ -44,9 +42,4 @@ class WorkflowsPermissionPolicyPreset(Preset):
|
|
|
44
42
|
model: InvenioModel,
|
|
45
43
|
dependencies: dict[str, Any],
|
|
46
44
|
) -> Generator[Customization]:
|
|
47
|
-
yield
|
|
48
|
-
"PermissionPolicy",
|
|
49
|
-
RecordPermissionPolicy,
|
|
50
|
-
WorkflowRecordPermissionPolicy,
|
|
51
|
-
subclass=True,
|
|
52
|
-
)
|
|
45
|
+
yield PrependMixin("PermissionPolicy", WorkflowRecordPermissionPolicyMixin)
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/events.py
RENAMED
|
@@ -11,8 +11,9 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import dataclasses
|
|
13
13
|
from functools import cached_property
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
14
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
15
15
|
|
|
16
|
+
from oarepo_workflows.errors import EventTypeNotInWorkflowError
|
|
16
17
|
from oarepo_workflows.requests.generators.multiple_entities import (
|
|
17
18
|
MultipleEntitiesGenerator,
|
|
18
19
|
)
|
|
@@ -39,3 +40,11 @@ class WorkflowEvent:
|
|
|
39
40
|
def submitter_generator(self) -> Generator:
|
|
40
41
|
"""Return the requesters as a single requester generator."""
|
|
41
42
|
return MultipleEntitiesGenerator(self.submitters)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class WorkflowEvents(dict[str, WorkflowEvent]):
|
|
46
|
+
"""Class representing a collection of workflow events."""
|
|
47
|
+
|
|
48
|
+
def __missing__(self, key: str) -> NoReturn:
|
|
49
|
+
"""Raise EventTypeNotInWorkflowError when a key is not found."""
|
|
50
|
+
raise EventTypeNotInWorkflowError(key)
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/policy.py
RENAMED
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
from functools import cached_property
|
|
13
13
|
from typing import TYPE_CHECKING, Any
|
|
14
14
|
|
|
15
|
+
from ..errors import RequestTypeNotInWorkflowError
|
|
15
16
|
from .requests import (
|
|
16
17
|
WorkflowRequest,
|
|
17
18
|
)
|
|
@@ -20,6 +21,8 @@ if TYPE_CHECKING:
|
|
|
20
21
|
from flask_principal import Identity
|
|
21
22
|
from invenio_records_resources.records.api import Record
|
|
22
23
|
|
|
24
|
+
from .. import Workflow
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
class WorkflowRequestPolicy:
|
|
25
28
|
"""Base class for workflow request policies.
|
|
@@ -48,6 +51,10 @@ class WorkflowRequestPolicy:
|
|
|
48
51
|
|
|
49
52
|
"""
|
|
50
53
|
|
|
54
|
+
def __init__(self, workflow: Workflow) -> None:
|
|
55
|
+
"""Create the policy."""
|
|
56
|
+
self.workflow = workflow
|
|
57
|
+
|
|
51
58
|
@cached_property
|
|
52
59
|
def requests(self) -> list[WorkflowRequest]:
|
|
53
60
|
"""Return the list of request types and their instances.
|
|
@@ -71,7 +78,10 @@ class WorkflowRequestPolicy:
|
|
|
71
78
|
|
|
72
79
|
def __getitem__(self, request_type_id: str) -> WorkflowRequest:
|
|
73
80
|
"""Get the workflow request type by its id."""
|
|
74
|
-
|
|
81
|
+
try:
|
|
82
|
+
return self.requests_by_id[request_type_id]
|
|
83
|
+
except KeyError as exc:
|
|
84
|
+
raise RequestTypeNotInWorkflowError(request_type_id, self.workflow.code) from exc
|
|
75
85
|
|
|
76
86
|
def applicable_workflow_requests(
|
|
77
87
|
self, identity: Identity, *, record: Record, **context: Any
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/requests.py
RENAMED
|
@@ -21,6 +21,7 @@ from invenio_requests.proxies import (
|
|
|
21
21
|
|
|
22
22
|
from oarepo_workflows.errors import InvalidConfigurationError
|
|
23
23
|
from oarepo_workflows.proxies import current_oarepo_workflows
|
|
24
|
+
from oarepo_workflows.requests.events import WorkflowEvents
|
|
24
25
|
from oarepo_workflows.requests.generators.multiple_entities import (
|
|
25
26
|
MultipleEntitiesGenerator,
|
|
26
27
|
)
|
|
@@ -37,6 +38,7 @@ if TYPE_CHECKING:
|
|
|
37
38
|
|
|
38
39
|
from oarepo_workflows.requests.events import WorkflowEvent
|
|
39
40
|
|
|
41
|
+
|
|
40
42
|
log = getLogger(__name__)
|
|
41
43
|
|
|
42
44
|
|
|
@@ -55,10 +57,10 @@ class WorkflowRequest:
|
|
|
55
57
|
recipients: Sequence[InvenioGenerator]
|
|
56
58
|
"""Generators that define who can approve the request."""
|
|
57
59
|
|
|
58
|
-
events: dict[str, WorkflowEvent] = dataclasses.field(default_factory=
|
|
60
|
+
events: dict[str, WorkflowEvent] = dataclasses.field(default_factory=WorkflowEvents)
|
|
59
61
|
"""Events that can be submitted with the request."""
|
|
60
62
|
|
|
61
|
-
transitions: WorkflowTransitions = dataclasses.field(default_factory=lambda: WorkflowTransitions())
|
|
63
|
+
transitions: WorkflowTransitions = dataclasses.field(default_factory=lambda: WorkflowTransitions()) # noqa PLW0108
|
|
62
64
|
"""Transitions applied to the state of the topic of the request."""
|
|
63
65
|
|
|
64
66
|
escalations: list[WorkflowRequestEscalation] = dataclasses.field(default_factory=list)
|
|
@@ -113,12 +115,20 @@ class WorkflowRequest:
|
|
|
113
115
|
|
|
114
116
|
def __set_name__(self, owner: type, name: str) -> None:
|
|
115
117
|
"""Set the name of the workflow request to the request type id."""
|
|
116
|
-
self._request_type = name
|
|
118
|
+
self._request_type = name.replace("_", "-")
|
|
119
|
+
|
|
120
|
+
def __post_init__(self):
|
|
121
|
+
"""Post init to convert events dictionary to WorkflowEvents."""
|
|
122
|
+
if self.events:
|
|
123
|
+
self.events = WorkflowEvents(self.events)
|
|
117
124
|
|
|
118
125
|
@property
|
|
119
126
|
def request_type(self) -> RequestType:
|
|
120
127
|
"""Return the request type."""
|
|
121
|
-
|
|
128
|
+
try:
|
|
129
|
+
return current_request_type_registry.lookup(self._request_type)
|
|
130
|
+
except KeyError: # pragma: no cover
|
|
131
|
+
return current_request_type_registry.lookup(self._request_type.replace("-", "_")) # pragma: no cover
|
|
122
132
|
|
|
123
133
|
|
|
124
134
|
@dataclasses.dataclass
|
|
@@ -70,7 +70,7 @@ class MultipleEntitiesProxy(EntityProxy):
|
|
|
70
70
|
def get_needs(self, ctx: dict | None = None) -> list[Need | ItemNeed]:
|
|
71
71
|
"""Get needs that the entity generate."""
|
|
72
72
|
ret: list[Need | ItemNeed] = []
|
|
73
|
-
entity = self._entity
|
|
73
|
+
entity = self._entity or self._resolve()
|
|
74
74
|
for subentity_proxy in entity.entities:
|
|
75
75
|
ret.extend(subentity_proxy.get_needs(ctx) or [])
|
|
76
76
|
return ret
|
|
@@ -14,7 +14,8 @@ from typing import TYPE_CHECKING, Any, override
|
|
|
14
14
|
from invenio_records_resources.services.records.components.base import ServiceComponent
|
|
15
15
|
from oarepo_runtime.typing import require_kwargs
|
|
16
16
|
|
|
17
|
-
from oarepo_workflows
|
|
17
|
+
from oarepo_workflows import current_oarepo_workflows
|
|
18
|
+
from oarepo_workflows.errors import InvalidWorkflowError
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from flask_principal import Identity
|
|
@@ -48,20 +49,13 @@ class WorkflowComponent(ServiceComponent):
|
|
|
48
49
|
**kwargs: Any,
|
|
49
50
|
) -> None:
|
|
50
51
|
"""Implement record creation checks and set the workflow on the created record."""
|
|
51
|
-
if not data:
|
|
52
|
-
# sanity check, should be handled by policy before the component is called
|
|
53
|
-
raise MissingWorkflowError(
|
|
54
|
-
"Workflow not defined in input. As this should be handled by a policy, "
|
|
55
|
-
"make sure you are using workflow-enabled policy.",
|
|
56
|
-
record=data,
|
|
57
|
-
) # pragma: no cover
|
|
58
52
|
try:
|
|
59
53
|
workflow_id = data["parent"]["workflow"]
|
|
60
|
-
except KeyError
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
record=data,
|
|
66
|
-
)
|
|
54
|
+
except KeyError:
|
|
55
|
+
return
|
|
56
|
+
if workflow_id not in current_oarepo_workflows.workflow_by_code:
|
|
57
|
+
raise InvalidWorkflowError(
|
|
58
|
+
f"Workflow {workflow_id} does not exist in the configuration.",
|
|
59
|
+
record=data or record,
|
|
60
|
+
)
|
|
67
61
|
record.parent.workflow = workflow_id # type: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from .generators import FromRecordWorkflow, IfInState, WorkflowPermission
|
|
13
|
-
from .record_permission_policy import
|
|
13
|
+
from .record_permission_policy import WorkflowRecordPermissionPolicyMixin
|
|
14
14
|
from .workflow_permissions import DefaultWorkflowPermissions
|
|
15
15
|
|
|
16
16
|
__all__ = (
|
|
@@ -18,5 +18,5 @@ __all__ = (
|
|
|
18
18
|
"FromRecordWorkflow",
|
|
19
19
|
"IfInState",
|
|
20
20
|
"WorkflowPermission",
|
|
21
|
-
"
|
|
21
|
+
"WorkflowRecordPermissionPolicyMixin",
|
|
22
22
|
)
|
|
@@ -20,7 +20,7 @@ from oarepo_runtime.services.generators import (
|
|
|
20
20
|
Generator,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
from oarepo_workflows.errors import InvalidWorkflowError
|
|
23
|
+
from oarepo_workflows.errors import InvalidWorkflowError
|
|
24
24
|
from oarepo_workflows.proxies import current_oarepo_workflows
|
|
25
25
|
from oarepo_workflows.requests import RecipientGeneratorMixin
|
|
26
26
|
|
|
@@ -37,6 +37,55 @@ if TYPE_CHECKING:
|
|
|
37
37
|
from oarepo_workflows.services.permissions import DefaultWorkflowPermissions
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def query_filters_from_all_workflows(action: str, **context: Any) -> list[dsl.query.Query]:
|
|
41
|
+
"""Get query filters to match records depending on the records' workflow."""
|
|
42
|
+
workflows = current_oarepo_workflows.record_workflows
|
|
43
|
+
queries = []
|
|
44
|
+
for workflow in workflows:
|
|
45
|
+
q_in_workflow = dsl.Q("term", **{"parent.workflow": workflow.code})
|
|
46
|
+
workflow_filters = workflow.permissions(action, **context).query_filters
|
|
47
|
+
if not workflow_filters:
|
|
48
|
+
workflow_filters = [dsl.Q("match_none")]
|
|
49
|
+
query = reduce(lambda f1, f2: f1 | f2, workflow_filters) & q_in_workflow
|
|
50
|
+
queries.append(query)
|
|
51
|
+
return [q for q in queries if q]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class InAnyWorkflow(Generator):
|
|
55
|
+
"""InAnyWorkflow generator.
|
|
56
|
+
|
|
57
|
+
Warning: if some workflow uses generators with excludes, they can clash with needs in different workflows leading
|
|
58
|
+
to the generator excluding users even though they are allowed in the workflow without the excludes generator.
|
|
59
|
+
This is due to how flask allows() is implemented.
|
|
60
|
+
|
|
61
|
+
Eg. If workflow 1 defines provides need for User 1 and Workflow 2 excludes User 1,
|
|
62
|
+
the generator will treat user 1 as excluded despite being allowed in the first workflow.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, action: str) -> None:
|
|
66
|
+
"""Construct the generator."""
|
|
67
|
+
self._action = action
|
|
68
|
+
|
|
69
|
+
@override
|
|
70
|
+
def needs(self, **context: Any) -> Sequence[Need]:
|
|
71
|
+
ret = set()
|
|
72
|
+
for workflow in current_oarepo_workflows.record_workflows:
|
|
73
|
+
ret |= set(workflow.permissions(self._action, **context).needs)
|
|
74
|
+
return list(ret)
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
def excludes(self, **context: Any) -> Sequence[Need]:
|
|
78
|
+
ret = set()
|
|
79
|
+
for workflow in current_oarepo_workflows.record_workflows:
|
|
80
|
+
ret |= set(workflow.permissions(self._action, **context).excludes)
|
|
81
|
+
return list(ret)
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def query_filter(self, **context: Any) -> dsl.query.Query:
|
|
85
|
+
queries = query_filters_from_all_workflows(self._action, **context)
|
|
86
|
+
return reduce(operator.or_, queries)
|
|
87
|
+
|
|
88
|
+
|
|
40
89
|
class FromRecordWorkflow(Generator):
|
|
41
90
|
"""Permission delegating check to workflow.
|
|
42
91
|
|
|
@@ -84,7 +133,7 @@ class FromRecordWorkflow(Generator):
|
|
|
84
133
|
data = context.get("data", {})
|
|
85
134
|
workflow_code = data.get("parent", {}).get("workflow", {})
|
|
86
135
|
if not workflow_code:
|
|
87
|
-
|
|
136
|
+
return current_oarepo_workflows.default_workflow
|
|
88
137
|
if workflow_code not in current_oarepo_workflows.workflow_by_code:
|
|
89
138
|
raise InvalidWorkflowError(
|
|
90
139
|
f"Workflow {workflow_code} does not exist in the configuration.",
|
|
@@ -9,31 +9,30 @@
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from functools import reduce
|
|
13
12
|
from typing import TYPE_CHECKING
|
|
14
13
|
|
|
15
|
-
from invenio_records_permissions import RecordPermissionPolicy
|
|
16
14
|
from invenio_records_permissions.generators import (
|
|
17
15
|
AnyUser,
|
|
18
16
|
SystemProcess,
|
|
19
17
|
)
|
|
20
|
-
from invenio_search.engine import dsl
|
|
21
18
|
|
|
22
|
-
from
|
|
23
|
-
from .generators import FromRecordWorkflow
|
|
19
|
+
from .generators import FromRecordWorkflow, InAnyWorkflow, SameAs, query_filters_from_all_workflows
|
|
24
20
|
|
|
25
21
|
if TYPE_CHECKING:
|
|
22
|
+
from invenio_records_permissions import RecordPermissionPolicy as InvenioRecordPermissionPolicy
|
|
26
23
|
from opensearch_dsl.query import Query
|
|
24
|
+
else:
|
|
25
|
+
InvenioRecordPermissionPolicy = object
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
class
|
|
28
|
+
class WorkflowRecordPermissionPolicyMixin(InvenioRecordPermissionPolicy):
|
|
30
29
|
"""Permission policy to be used in permission presets directly on RecordServiceConfig.permission_policy_cls.
|
|
31
30
|
|
|
32
31
|
Do not use this class in Workflow constructor.
|
|
33
32
|
"""
|
|
34
33
|
|
|
35
34
|
can_commit_files = (FromRecordWorkflow("commit_files"),)
|
|
36
|
-
can_create = (
|
|
35
|
+
can_create = (InAnyWorkflow("create"),)
|
|
37
36
|
can_create_files = (FromRecordWorkflow("create_files"),)
|
|
38
37
|
can_delete = (FromRecordWorkflow("delete"),)
|
|
39
38
|
can_delete_draft = (FromRecordWorkflow("delete_draft"),)
|
|
@@ -116,6 +115,7 @@ class WorkflowRecordPermissionPolicy(RecordPermissionPolicy):
|
|
|
116
115
|
can_remove_record = (FromRecordWorkflow("remove_record"),)
|
|
117
116
|
can_review = (FromRecordWorkflow("review"),)
|
|
118
117
|
can_view = (FromRecordWorkflow("view"),)
|
|
118
|
+
can_view_deposit_page = (SameAs("can_create"),)
|
|
119
119
|
|
|
120
120
|
@property
|
|
121
121
|
def query_filters(self) -> list[Query]:
|
|
@@ -127,13 +127,4 @@ class WorkflowRecordPermissionPolicy(RecordPermissionPolicy):
|
|
|
127
127
|
"read_all_records",
|
|
128
128
|
):
|
|
129
129
|
return super().query_filters # type: ignore[no-any-return]
|
|
130
|
-
|
|
131
|
-
queries = []
|
|
132
|
-
for workflow in workflows:
|
|
133
|
-
q_in_workflow = dsl.Q("term", **{"parent.workflow": workflow.code})
|
|
134
|
-
workflow_filters = workflow.permissions(self.action, **self.over).query_filters
|
|
135
|
-
if not workflow_filters:
|
|
136
|
-
workflow_filters = [dsl.Q("match_none")]
|
|
137
|
-
query = reduce(lambda f1, f2: f1 | f2, workflow_filters) & q_in_workflow
|
|
138
|
-
queries.append(query)
|
|
139
|
-
return [q for q in queries if q]
|
|
130
|
+
return query_filters_from_all_workflows(self.action, **self.over)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/initial_config.py
RENAMED
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/permissions.py
RENAMED
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/results.py
RENAMED
|
File without changes
|
{oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/uow.py
RENAMED
|
File without changes
|
|
File without changes
|