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.
Files changed (54) hide show
  1. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/PKG-INFO +1 -1
  2. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/__init__.py +3 -3
  3. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/base.py +3 -3
  4. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/errors.py +4 -6
  5. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/ext.py +15 -0
  6. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/ext_config.py +2 -0
  7. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/permission_policy.py +4 -11
  8. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/events.py +10 -1
  9. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/policy.py +11 -1
  10. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/requests.py +14 -4
  11. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/multiple_entities/__init__.py +1 -1
  12. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/components/workflow.py +9 -15
  13. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/__init__.py +2 -2
  14. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/generators.py +51 -2
  15. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/record_permission_policy.py +8 -17
  16. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/.gitignore +0 -0
  17. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/LICENSE +0 -0
  18. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/README.md +0 -0
  19. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/initial_config.py +0 -0
  20. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/__init__.py +0 -0
  21. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/__init__.py +0 -0
  22. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/__init__.py +0 -0
  23. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/draft_record.py +0 -0
  24. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/parent_record.py +0 -0
  25. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/parent_record_metadata.py +0 -0
  26. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/record.py +0 -0
  27. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/records/workflows_mapping.py +0 -0
  28. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/__init__.py +0 -0
  29. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/__init__.py +0 -0
  30. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/parent_record_schema.py +0 -0
  31. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/record_schema.py +0 -0
  32. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/model/presets/services/records/service_config.py +0 -0
  33. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/proxies.py +0 -0
  34. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/__init__.py +0 -0
  35. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/__init__.py +0 -0
  36. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/state.py +0 -0
  37. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/records/systemfields/workflow.py +0 -0
  38. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/__init__.py +0 -0
  39. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/__init__.py +0 -0
  40. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/auto.py +0 -0
  41. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/conditionals.py +0 -0
  42. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/multiple_entities.py +0 -0
  43. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/generators/recipient_generator.py +0 -0
  44. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/requests/permissions.py +0 -0
  45. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/__init__.py +0 -0
  46. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/resolvers/auto_approve/__init__.py +0 -0
  47. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/__init__.py +0 -0
  48. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/auto_approve/__init__.py +0 -0
  49. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/components/__init__.py +0 -0
  50. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/multiple_entities/__init__.py +0 -0
  51. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/permissions/workflow_permissions.py +0 -0
  52. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/results.py +0 -0
  53. {oarepo_workflows-2.0.0.dev6 → oarepo_workflows-2.0.0.dev8}/oarepo_workflows/services/uow.py +0 -0
  54. {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.dev6
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
- WorkflowRecordPermissionPolicy,
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.0dev6"
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
- "WorkflowRecordPermissionPolicy",
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 WorkflowRecordPermissionPolicy
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, WorkflowRecordPermissionPolicy):
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(Exception):
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, request_type: str, event_type: str, workflow_code: str) -> None:
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 on request type {self.request_type} in workflow {self.workflow}."
89
+ return f"Event type {self.event_type} is not set in a workflow."
92
90
 
93
91
 
94
- class RequestTypeNotInWorkflowError(Exception):
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(
@@ -20,3 +20,5 @@ WORKFLOWS: dict[str, Workflow] = {}
20
20
  """Configuration of workflows, must be provided by the user inside, for example, invenio.cfg."""
21
21
 
22
22
  WORKFLOWS_ALLOWED_REQUEST_RECEIVERS = ["multiple"]
23
+
24
+ WORKFLOWS_DEFAULT_WORKFLOW = "individual"
@@ -17,18 +17,16 @@ from __future__ import annotations
17
17
 
18
18
  from typing import TYPE_CHECKING, Any, override
19
19
 
20
- from invenio_records_permissions.policies.records import RecordPermissionPolicy
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.record_permission_policy import (
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 ReplaceBaseClass(
48
- "PermissionPolicy",
49
- RecordPermissionPolicy,
50
- WorkflowRecordPermissionPolicy,
51
- subclass=True,
52
- )
45
+ yield PrependMixin("PermissionPolicy", WorkflowRecordPermissionPolicyMixin)
@@ -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)
@@ -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
- return self.requests_by_id[request_type_id]
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
@@ -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=dict)
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
- return current_request_type_registry.lookup(self._request_type, quiet=False)
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 if self._entity else self._resolve()
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.errors import MissingWorkflowError
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 as e: # pragma: no cover
61
- # sanity check, should be handled by policy before the component is called
62
- raise MissingWorkflowError( # pragma: no cover
63
- "Workflow not defined in input. As this should be handled by a policy, "
64
- "make sure you are using workflow-enabled policy.",
65
- record=data,
66
- ) from e
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 WorkflowRecordPermissionPolicy
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
- "WorkflowRecordPermissionPolicy",
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, MissingWorkflowError
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
- raise MissingWorkflowError("Workflow not defined in input.", record=data)
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 ...proxies import current_oarepo_workflows
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 WorkflowRecordPermissionPolicy(RecordPermissionPolicy):
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 = (FromRecordWorkflow("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
- workflows = current_oarepo_workflows.record_workflows
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)