diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py index 6a91f4319c..79ed3e1f20 100644 --- a/src/documents/bulk_edit.py +++ b/src/documents/bulk_edit.py @@ -42,6 +42,7 @@ from documents.plugins.helpers import DocumentsStatusManager from documents.tasks import bulk_update_documents from documents.tasks import consume_file from documents.tasks import remove_document_from_index +from documents.tasks import run_workflows_for_documents from documents.tasks import update_document_content_maybe_archive_file from documents.versioning import get_latest_version_for_root from documents.versioning import get_root_document @@ -445,6 +446,25 @@ def reprocess(doc_ids: list[int], *, remote_ocr: bool = False) -> Literal["OK"]: return "OK" +def run_workflow( + doc_ids: list[int], + workflow: int, + *, + filter_documents: bool = True, +) -> Literal["OK"]: + """ + Manually run a single workflow against the given documents. + + Done asynchronously via a Celery task. + """ + run_workflows_for_documents.delay( + document_ids=doc_ids, + workflow_id=workflow, + filter_documents=filter_documents, + ) + return "OK" + + def set_permissions( doc_ids: list[int], set_permissions: PermissionsSpec, diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 3fce749904..fd65b11e0e 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1829,6 +1829,7 @@ class BulkEditSerializer( "modify_tags", "modify_custom_fields", "set_permissions", + "run_workflow", *LEGACY_DOCUMENT_ACTION_METHODS, ], label="Method", @@ -1918,6 +1919,8 @@ class BulkEditSerializer( return bulk_edit.reprocess elif method == "set_permissions": return bulk_edit.set_permissions + elif method == "run_workflow": + return bulk_edit.run_workflow elif method == "rotate": return bulk_edit.rotate elif method == "merge": @@ -2157,6 +2160,27 @@ class BulkEditSerializer( else: parameters["remote_ocr"] = False + def _validate_parameters_run_workflow(self, parameters) -> None: + if "workflow" not in parameters: + raise serializers.ValidationError("workflow not specified") + try: + workflow = Workflow.objects.get(id=parameters["workflow"]) + except (Workflow.DoesNotExist, ValueError, TypeError): + raise serializers.ValidationError("Workflow does not exist") + if not workflow.triggers.filter( + type=WorkflowTrigger.WorkflowTriggerType.MANUAL, + ).exists(): + raise serializers.ValidationError( + "Workflow does not have a manual trigger", + ) + if "filter_documents" in parameters: + if not isinstance(parameters["filter_documents"], bool): + raise serializers.ValidationError( + "filter_documents must be a boolean", + ) + else: + parameters["filter_documents"] = True + def validate_parameters_remove_password(self, parameters): if "password" not in parameters: raise serializers.ValidationError("password not specified") @@ -2223,6 +2247,8 @@ class BulkEditSerializer( self.validate_parameters_remove_password(parameters) elif method == bulk_edit.reprocess: self._validate_parameters_reprocess(parameters) + elif method == bulk_edit.run_workflow: + self._validate_parameters_run_workflow(parameters) return attrs diff --git a/src/documents/views.py b/src/documents/views.py index 0b289e8da0..5f568e0744 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -3020,6 +3020,7 @@ class DocumentOperationPermissionMixin(PassUserMixin, DocumentSelectionMixin): bulk_edit.edit_pdf, bulk_edit.merge_as_versions, bulk_edit.remove_password, + bulk_edit.run_workflow, ] ) or ( @@ -3159,6 +3160,7 @@ class BulkEditView(DocumentOperationPermissionMixin): "edit_pdf": None, "reprocess": "checksum", "remove_password": None, + "run_workflow": None, } serializer_class = BulkEditSerializer