mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Feature: workflow removal action (#5928)
--------- Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from enum import Enum
|
||||
from pathlib import Path
|
||||
from subprocess import CompletedProcess
|
||||
from subprocess import run
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Optional
|
||||
|
||||
import magic
|
||||
@@ -35,6 +36,7 @@ from documents.models import FileInfo
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import Workflow
|
||||
from documents.models import WorkflowAction
|
||||
from documents.models import WorkflowTrigger
|
||||
from documents.parsers import DocumentParser
|
||||
from documents.parsers import ParseError
|
||||
@@ -63,9 +65,26 @@ class WorkflowTriggerPlugin(
|
||||
"""
|
||||
Get overrides from matching workflows
|
||||
"""
|
||||
msg = ""
|
||||
overrides = DocumentMetadataOverrides()
|
||||
for workflow in Workflow.objects.filter(enabled=True).order_by("order"):
|
||||
template_overrides = DocumentMetadataOverrides()
|
||||
for workflow in (
|
||||
Workflow.objects.filter(enabled=True)
|
||||
.prefetch_related("actions")
|
||||
.prefetch_related("actions__assign_view_users")
|
||||
.prefetch_related("actions__assign_view_groups")
|
||||
.prefetch_related("actions__assign_change_users")
|
||||
.prefetch_related("actions__assign_change_groups")
|
||||
.prefetch_related("actions__assign_custom_fields")
|
||||
.prefetch_related("actions__remove_tags")
|
||||
.prefetch_related("actions__remove_correspondents")
|
||||
.prefetch_related("actions__remove_document_types")
|
||||
.prefetch_related("actions__remove_storage_paths")
|
||||
.prefetch_related("actions__remove_custom_fields")
|
||||
.prefetch_related("actions__remove_owners")
|
||||
.prefetch_related("triggers")
|
||||
.order_by("order")
|
||||
):
|
||||
action_overrides = DocumentMetadataOverrides()
|
||||
|
||||
if document_matches_workflow(
|
||||
self.input_doc,
|
||||
@@ -73,49 +92,137 @@ class WorkflowTriggerPlugin(
|
||||
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||
):
|
||||
for action in workflow.actions.all():
|
||||
if action.assign_title is not None:
|
||||
template_overrides.title = action.assign_title
|
||||
if action.assign_tags is not None:
|
||||
template_overrides.tag_ids = [
|
||||
tag.pk for tag in action.assign_tags.all()
|
||||
]
|
||||
if action.assign_correspondent is not None:
|
||||
template_overrides.correspondent_id = (
|
||||
action.assign_correspondent.pk
|
||||
)
|
||||
if action.assign_document_type is not None:
|
||||
template_overrides.document_type_id = (
|
||||
action.assign_document_type.pk
|
||||
)
|
||||
if action.assign_storage_path is not None:
|
||||
template_overrides.storage_path_id = (
|
||||
action.assign_storage_path.pk
|
||||
)
|
||||
if action.assign_owner is not None:
|
||||
template_overrides.owner_id = action.assign_owner.pk
|
||||
if action.assign_view_users is not None:
|
||||
template_overrides.view_users = [
|
||||
user.pk for user in action.assign_view_users.all()
|
||||
]
|
||||
if action.assign_view_groups is not None:
|
||||
template_overrides.view_groups = [
|
||||
group.pk for group in action.assign_view_groups.all()
|
||||
]
|
||||
if action.assign_change_users is not None:
|
||||
template_overrides.change_users = [
|
||||
user.pk for user in action.assign_change_users.all()
|
||||
]
|
||||
if action.assign_change_groups is not None:
|
||||
template_overrides.change_groups = [
|
||||
group.pk for group in action.assign_change_groups.all()
|
||||
]
|
||||
if action.assign_custom_fields is not None:
|
||||
template_overrides.custom_field_ids = [
|
||||
field.pk for field in action.assign_custom_fields.all()
|
||||
]
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(action, WorkflowAction)
|
||||
msg += f"Applying {action} from {workflow}\n"
|
||||
if action.type == WorkflowAction.WorkflowActionType.ASSIGNMENT:
|
||||
if action.assign_title is not None:
|
||||
action_overrides.title = action.assign_title
|
||||
if action.assign_tags is not None:
|
||||
action_overrides.tag_ids = list(
|
||||
action.assign_tags.values_list("pk", flat=True),
|
||||
)
|
||||
|
||||
if action.assign_correspondent is not None:
|
||||
action_overrides.correspondent_id = (
|
||||
action.assign_correspondent.pk
|
||||
)
|
||||
if action.assign_document_type is not None:
|
||||
action_overrides.document_type_id = (
|
||||
action.assign_document_type.pk
|
||||
)
|
||||
if action.assign_storage_path is not None:
|
||||
action_overrides.storage_path_id = (
|
||||
action.assign_storage_path.pk
|
||||
)
|
||||
if action.assign_owner is not None:
|
||||
action_overrides.owner_id = action.assign_owner.pk
|
||||
if action.assign_view_users is not None:
|
||||
action_overrides.view_users = list(
|
||||
action.assign_view_users.values_list("pk", flat=True),
|
||||
)
|
||||
if action.assign_view_groups is not None:
|
||||
action_overrides.view_groups = list(
|
||||
action.assign_view_groups.values_list("pk", flat=True),
|
||||
)
|
||||
if action.assign_change_users is not None:
|
||||
action_overrides.change_users = list(
|
||||
action.assign_change_users.values_list("pk", flat=True),
|
||||
)
|
||||
if action.assign_change_groups is not None:
|
||||
action_overrides.change_groups = list(
|
||||
action.assign_change_groups.values_list(
|
||||
"pk",
|
||||
flat=True,
|
||||
),
|
||||
)
|
||||
if action.assign_custom_fields is not None:
|
||||
action_overrides.custom_field_ids = list(
|
||||
action.assign_custom_fields.values_list(
|
||||
"pk",
|
||||
flat=True,
|
||||
),
|
||||
)
|
||||
overrides.update(action_overrides)
|
||||
elif action.type == WorkflowAction.WorkflowActionType.REMOVAL:
|
||||
# Removal actions overwrite the current overrides
|
||||
if action.remove_all_tags:
|
||||
overrides.tag_ids = []
|
||||
elif overrides.tag_ids:
|
||||
for tag in action.remove_custom_fields.filter(
|
||||
pk__in=overrides.tag_ids,
|
||||
):
|
||||
overrides.tag_ids.remove(tag.pk)
|
||||
|
||||
if action.remove_all_correspondents or (
|
||||
overrides.correspondent_id is not None
|
||||
and action.remove_correspondents.filter(
|
||||
pk=overrides.correspondent_id,
|
||||
).exists()
|
||||
):
|
||||
overrides.correspondent_id = None
|
||||
|
||||
if action.remove_all_document_types or (
|
||||
overrides.document_type_id is not None
|
||||
and action.remove_document_types.filter(
|
||||
pk=overrides.document_type_id,
|
||||
).exists()
|
||||
):
|
||||
overrides.document_type_id = None
|
||||
|
||||
if action.remove_all_storage_paths or (
|
||||
overrides.storage_path_id is not None
|
||||
and action.remove_storage_paths.filter(
|
||||
pk=overrides.storage_path_id,
|
||||
).exists()
|
||||
):
|
||||
overrides.storage_path_id = None
|
||||
|
||||
if action.remove_all_custom_fields:
|
||||
overrides.custom_field_ids = []
|
||||
elif overrides.custom_field_ids:
|
||||
for field in action.remove_custom_fields.filter(
|
||||
pk__in=overrides.custom_field_ids,
|
||||
):
|
||||
overrides.custom_field_ids.remove(field.pk)
|
||||
|
||||
if action.remove_all_owners or (
|
||||
overrides.owner_id is not None
|
||||
and action.remove_owners.filter(
|
||||
pk=overrides.owner_id,
|
||||
).exists()
|
||||
):
|
||||
overrides.owner_id = None
|
||||
|
||||
if action.remove_all_permissions:
|
||||
overrides.view_users = []
|
||||
overrides.view_groups = []
|
||||
overrides.change_users = []
|
||||
overrides.change_groups = []
|
||||
else:
|
||||
if overrides.view_users:
|
||||
for user in action.remove_view_users.filter(
|
||||
pk__in=overrides.view_users,
|
||||
):
|
||||
overrides.view_users.remove(user.pk)
|
||||
if overrides.change_users:
|
||||
for user in action.remove_change_users.filter(
|
||||
pk__in=overrides.change_users,
|
||||
):
|
||||
overrides.change_users.remove(user.pk)
|
||||
if overrides.view_groups:
|
||||
for user in action.remove_view_groups.filter(
|
||||
pk__in=overrides.view_groups,
|
||||
):
|
||||
overrides.view_groups.remove(user.pk)
|
||||
if overrides.change_groups:
|
||||
for user in action.remove_change_groups.filter(
|
||||
pk__in=overrides.change_groups,
|
||||
):
|
||||
overrides.change_groups.remove(user.pk)
|
||||
|
||||
overrides.update(template_overrides)
|
||||
self.metadata.update(overrides)
|
||||
return msg
|
||||
|
||||
|
||||
class ConsumerError(Exception):
|
||||
|
@@ -0,0 +1,223 @@
|
||||
# Generated by Django 4.2.10 on 2024-02-21 21:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("documents", "1045_alter_customfieldinstance_value_monetary"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_correspondents",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="remove all correspondents",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_custom_fields",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="remove all custom fields",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_document_types",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="remove all document types",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_owners",
|
||||
field=models.BooleanField(default=False, verbose_name="remove all owners"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_permissions",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="remove all permissions",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_storage_paths",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="remove all storage paths",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_all_tags",
|
||||
field=models.BooleanField(default=False, verbose_name="remove all tags"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_change_groups",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="auth.group",
|
||||
verbose_name="remove change permissions for these groups",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_change_users",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="remove change permissions for these users",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_correspondents",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.correspondent",
|
||||
verbose_name="remove these correspondent(s)",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_custom_fields",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.customfield",
|
||||
verbose_name="remove these custom fields",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_document_types",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.documenttype",
|
||||
verbose_name="remove these document type(s)",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_owners",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="remove these owner(s)",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_storage_paths",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.storagepath",
|
||||
verbose_name="remove these storage path(s)",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_tags",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.tag",
|
||||
verbose_name="remove these tag(s)",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_view_groups",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="auth.group",
|
||||
verbose_name="remove view permissions for these groups",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="remove_view_users",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="remove view permissions for these users",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="assign_correspondent",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to="documents.correspondent",
|
||||
verbose_name="assign this correspondent",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="assign_document_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to="documents.documenttype",
|
||||
verbose_name="assign this document type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="assign_storage_path",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to="documents.storagepath",
|
||||
verbose_name="assign this storage path",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="assign_tags",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.tag",
|
||||
verbose_name="assign this tag",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="type",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[(1, "Assignment"), (2, "Removal")],
|
||||
default=1,
|
||||
verbose_name="Workflow Action Type",
|
||||
),
|
||||
),
|
||||
]
|
@@ -997,7 +997,14 @@ class WorkflowTrigger(models.Model):
|
||||
|
||||
class WorkflowAction(models.Model):
|
||||
class WorkflowActionType(models.IntegerChoices):
|
||||
ASSIGNMENT = 1, _("Assignment")
|
||||
ASSIGNMENT = (
|
||||
1,
|
||||
_("Assignment"),
|
||||
)
|
||||
REMOVAL = (
|
||||
2,
|
||||
_("Removal"),
|
||||
)
|
||||
|
||||
type = models.PositiveIntegerField(
|
||||
_("Workflow Action Type"),
|
||||
@@ -1019,6 +1026,7 @@ class WorkflowAction(models.Model):
|
||||
assign_tags = models.ManyToManyField(
|
||||
Tag,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("assign this tag"),
|
||||
)
|
||||
|
||||
@@ -1027,6 +1035,7 @@ class WorkflowAction(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
verbose_name=_("assign this document type"),
|
||||
)
|
||||
|
||||
@@ -1035,6 +1044,7 @@ class WorkflowAction(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
verbose_name=_("assign this correspondent"),
|
||||
)
|
||||
|
||||
@@ -1043,6 +1053,7 @@ class WorkflowAction(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
verbose_name=_("assign this storage path"),
|
||||
)
|
||||
|
||||
@@ -1090,6 +1101,111 @@ class WorkflowAction(models.Model):
|
||||
verbose_name=_("assign these custom fields"),
|
||||
)
|
||||
|
||||
remove_tags = models.ManyToManyField(
|
||||
Tag,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these tag(s)"),
|
||||
)
|
||||
|
||||
remove_all_tags = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all tags"),
|
||||
)
|
||||
|
||||
remove_document_types = models.ManyToManyField(
|
||||
DocumentType,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these document type(s)"),
|
||||
)
|
||||
|
||||
remove_all_document_types = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all document types"),
|
||||
)
|
||||
|
||||
remove_correspondents = models.ManyToManyField(
|
||||
Correspondent,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these correspondent(s)"),
|
||||
)
|
||||
|
||||
remove_all_correspondents = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all correspondents"),
|
||||
)
|
||||
|
||||
remove_storage_paths = models.ManyToManyField(
|
||||
StoragePath,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these storage path(s)"),
|
||||
)
|
||||
|
||||
remove_all_storage_paths = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all storage paths"),
|
||||
)
|
||||
|
||||
remove_owners = models.ManyToManyField(
|
||||
User,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these owner(s)"),
|
||||
)
|
||||
|
||||
remove_all_owners = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all owners"),
|
||||
)
|
||||
|
||||
remove_view_users = models.ManyToManyField(
|
||||
User,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove view permissions for these users"),
|
||||
)
|
||||
|
||||
remove_view_groups = models.ManyToManyField(
|
||||
Group,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove view permissions for these groups"),
|
||||
)
|
||||
|
||||
remove_change_users = models.ManyToManyField(
|
||||
User,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove change permissions for these users"),
|
||||
)
|
||||
|
||||
remove_change_groups = models.ManyToManyField(
|
||||
Group,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove change permissions for these groups"),
|
||||
)
|
||||
|
||||
remove_all_permissions = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all permissions"),
|
||||
)
|
||||
|
||||
remove_custom_fields = models.ManyToManyField(
|
||||
CustomField,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
verbose_name=_("remove these custom fields"),
|
||||
)
|
||||
|
||||
remove_all_custom_fields = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("remove all custom fields"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("workflow action")
|
||||
verbose_name_plural = _("workflow actions")
|
||||
|
@@ -1471,6 +1471,23 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
|
||||
"assign_change_users",
|
||||
"assign_change_groups",
|
||||
"assign_custom_fields",
|
||||
"remove_all_tags",
|
||||
"remove_tags",
|
||||
"remove_all_correspondents",
|
||||
"remove_correspondents",
|
||||
"remove_all_document_types",
|
||||
"remove_document_types",
|
||||
"remove_all_storage_paths",
|
||||
"remove_storage_paths",
|
||||
"remove_custom_fields",
|
||||
"remove_all_custom_fields",
|
||||
"remove_all_owners",
|
||||
"remove_owners",
|
||||
"remove_all_permissions",
|
||||
"remove_view_users",
|
||||
"remove_view_groups",
|
||||
"remove_change_users",
|
||||
"remove_change_groups",
|
||||
]
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -1551,10 +1568,22 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
||||
assign_change_users = action.pop("assign_change_users", None)
|
||||
assign_change_groups = action.pop("assign_change_groups", None)
|
||||
assign_custom_fields = action.pop("assign_custom_fields", None)
|
||||
remove_tags = action.pop("remove_tags", None)
|
||||
remove_correspondents = action.pop("remove_correspondents", None)
|
||||
remove_document_types = action.pop("remove_document_types", None)
|
||||
remove_storage_paths = action.pop("remove_storage_paths", None)
|
||||
remove_custom_fields = action.pop("remove_custom_fields", None)
|
||||
remove_owners = action.pop("remove_owners", None)
|
||||
remove_view_users = action.pop("remove_view_users", None)
|
||||
remove_view_groups = action.pop("remove_view_groups", None)
|
||||
remove_change_users = action.pop("remove_change_users", None)
|
||||
remove_change_groups = action.pop("remove_change_groups", None)
|
||||
|
||||
action_instance, _ = WorkflowAction.objects.update_or_create(
|
||||
id=action.get("id"),
|
||||
defaults=action,
|
||||
)
|
||||
|
||||
if assign_tags is not None:
|
||||
action_instance.assign_tags.set(assign_tags)
|
||||
if assign_view_users is not None:
|
||||
@@ -1567,6 +1596,27 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
||||
action_instance.assign_change_groups.set(assign_change_groups)
|
||||
if assign_custom_fields is not None:
|
||||
action_instance.assign_custom_fields.set(assign_custom_fields)
|
||||
if remove_tags is not None:
|
||||
action_instance.remove_tags.set(remove_tags)
|
||||
if remove_correspondents is not None:
|
||||
action_instance.remove_correspondents.set(remove_correspondents)
|
||||
if remove_document_types is not None:
|
||||
action_instance.remove_document_types.set(remove_document_types)
|
||||
if remove_storage_paths is not None:
|
||||
action_instance.remove_storage_paths.set(remove_storage_paths)
|
||||
if remove_custom_fields is not None:
|
||||
action_instance.remove_custom_fields.set(remove_custom_fields)
|
||||
if remove_owners is not None:
|
||||
action_instance.remove_owners.set(remove_owners)
|
||||
if remove_view_users is not None:
|
||||
action_instance.remove_view_users.set(remove_view_users)
|
||||
if remove_view_groups is not None:
|
||||
action_instance.remove_view_groups.set(remove_view_groups)
|
||||
if remove_change_users is not None:
|
||||
action_instance.remove_change_users.set(remove_change_users)
|
||||
if remove_change_groups is not None:
|
||||
action_instance.remove_change_groups.set(remove_change_groups)
|
||||
|
||||
set_actions.append(action_instance)
|
||||
|
||||
instance.triggers.set(set_triggers)
|
||||
|
@@ -20,6 +20,7 @@ from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from filelock import FileLock
|
||||
from guardian.shortcuts import remove_perm
|
||||
|
||||
from documents import matching
|
||||
from documents.caching import clear_metadata_cache
|
||||
@@ -34,6 +35,7 @@ from documents.models import MatchingModel
|
||||
from documents.models import PaperlessTask
|
||||
from documents.models import Tag
|
||||
from documents.models import Workflow
|
||||
from documents.models import WorkflowAction
|
||||
from documents.models import WorkflowTrigger
|
||||
from documents.permissions import get_objects_for_user_owner_aware
|
||||
from documents.permissions import set_permissions_for_object
|
||||
@@ -529,122 +531,231 @@ def run_workflow(
|
||||
document: Document,
|
||||
logging_group=None,
|
||||
):
|
||||
for workflow in Workflow.objects.filter(
|
||||
enabled=True,
|
||||
triggers__type=trigger_type,
|
||||
).order_by("order"):
|
||||
for workflow in (
|
||||
Workflow.objects.filter(
|
||||
enabled=True,
|
||||
triggers__type=trigger_type,
|
||||
)
|
||||
.prefetch_related("actions")
|
||||
.prefetch_related("actions__assign_view_users")
|
||||
.prefetch_related("actions__assign_view_groups")
|
||||
.prefetch_related("actions__assign_change_users")
|
||||
.prefetch_related("actions__assign_change_groups")
|
||||
.prefetch_related("actions__assign_custom_fields")
|
||||
.prefetch_related("actions__remove_tags")
|
||||
.prefetch_related("actions__remove_correspondents")
|
||||
.prefetch_related("actions__remove_document_types")
|
||||
.prefetch_related("actions__remove_storage_paths")
|
||||
.prefetch_related("actions__remove_custom_fields")
|
||||
.prefetch_related("actions__remove_owners")
|
||||
.prefetch_related("triggers")
|
||||
.order_by("order")
|
||||
):
|
||||
if matching.document_matches_workflow(
|
||||
document,
|
||||
workflow,
|
||||
trigger_type,
|
||||
):
|
||||
action: WorkflowAction
|
||||
for action in workflow.actions.all():
|
||||
logger.info(
|
||||
f"Applying {action} from {workflow}",
|
||||
extra={"group": logging_group},
|
||||
)
|
||||
if action.assign_tags.all().count() > 0:
|
||||
document.tags.add(*action.assign_tags.all())
|
||||
|
||||
if action.assign_correspondent is not None:
|
||||
document.correspondent = action.assign_correspondent
|
||||
if action.type == WorkflowAction.WorkflowActionType.ASSIGNMENT:
|
||||
if action.assign_tags.all().count() > 0:
|
||||
document.tags.add(*action.assign_tags.all())
|
||||
|
||||
if action.assign_document_type is not None:
|
||||
document.document_type = action.assign_document_type
|
||||
if action.assign_correspondent is not None:
|
||||
document.correspondent = action.assign_correspondent
|
||||
|
||||
if action.assign_storage_path is not None:
|
||||
document.storage_path = action.assign_storage_path
|
||||
if action.assign_document_type is not None:
|
||||
document.document_type = action.assign_document_type
|
||||
|
||||
if action.assign_owner is not None:
|
||||
document.owner = action.assign_owner
|
||||
if action.assign_storage_path is not None:
|
||||
document.storage_path = action.assign_storage_path
|
||||
|
||||
if action.assign_title is not None:
|
||||
try:
|
||||
document.title = parse_doc_title_w_placeholders(
|
||||
action.assign_title,
|
||||
(
|
||||
document.correspondent.name
|
||||
if document.correspondent is not None
|
||||
else ""
|
||||
),
|
||||
(
|
||||
document.document_type.name
|
||||
if document.document_type is not None
|
||||
else ""
|
||||
),
|
||||
(
|
||||
document.owner.username
|
||||
if document.owner is not None
|
||||
else ""
|
||||
),
|
||||
timezone.localtime(document.added),
|
||||
(
|
||||
document.original_filename
|
||||
if document.original_filename is not None
|
||||
else ""
|
||||
),
|
||||
timezone.localtime(document.created),
|
||||
if action.assign_owner is not None:
|
||||
document.owner = action.assign_owner
|
||||
|
||||
if action.assign_title is not None:
|
||||
try:
|
||||
document.title = parse_doc_title_w_placeholders(
|
||||
action.assign_title,
|
||||
(
|
||||
document.correspondent.name
|
||||
if document.correspondent is not None
|
||||
else ""
|
||||
),
|
||||
(
|
||||
document.document_type.name
|
||||
if document.document_type is not None
|
||||
else ""
|
||||
),
|
||||
(
|
||||
document.owner.username
|
||||
if document.owner is not None
|
||||
else ""
|
||||
),
|
||||
timezone.localtime(document.added),
|
||||
(
|
||||
document.original_filename
|
||||
if document.original_filename is not None
|
||||
else ""
|
||||
),
|
||||
timezone.localtime(document.created),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
f"Error occurred parsing title assignment '{action.assign_title}', falling back to original",
|
||||
extra={"group": logging_group},
|
||||
)
|
||||
|
||||
if (
|
||||
(
|
||||
action.assign_view_users is not None
|
||||
and action.assign_view_users.count() > 0
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
f"Error occurred parsing title assignment '{action.assign_title}', falling back to original",
|
||||
extra={"group": logging_group},
|
||||
or (
|
||||
action.assign_view_groups is not None
|
||||
and action.assign_view_groups.count() > 0
|
||||
)
|
||||
or (
|
||||
action.assign_change_users is not None
|
||||
and action.assign_change_users.count() > 0
|
||||
)
|
||||
or (
|
||||
action.assign_change_groups is not None
|
||||
and action.assign_change_groups.count() > 0
|
||||
)
|
||||
):
|
||||
permissions = {
|
||||
"view": {
|
||||
"users": action.assign_view_users.all().values_list(
|
||||
"id",
|
||||
)
|
||||
or [],
|
||||
"groups": action.assign_view_groups.all().values_list(
|
||||
"id",
|
||||
)
|
||||
or [],
|
||||
},
|
||||
"change": {
|
||||
"users": action.assign_change_users.all().values_list(
|
||||
"id",
|
||||
)
|
||||
or [],
|
||||
"groups": action.assign_change_groups.all().values_list(
|
||||
"id",
|
||||
)
|
||||
or [],
|
||||
},
|
||||
}
|
||||
set_permissions_for_object(
|
||||
permissions=permissions,
|
||||
object=document,
|
||||
merge=True,
|
||||
)
|
||||
|
||||
if (
|
||||
(
|
||||
action.assign_view_users is not None
|
||||
and action.assign_view_users.count() > 0
|
||||
)
|
||||
or (
|
||||
action.assign_view_groups is not None
|
||||
and action.assign_view_groups.count() > 0
|
||||
)
|
||||
or (
|
||||
action.assign_change_users is not None
|
||||
and action.assign_change_users.count() > 0
|
||||
)
|
||||
or (
|
||||
action.assign_change_groups is not None
|
||||
and action.assign_change_groups.count() > 0
|
||||
)
|
||||
):
|
||||
permissions = {
|
||||
"view": {
|
||||
"users": action.assign_view_users.all().values_list("id")
|
||||
or [],
|
||||
"groups": action.assign_view_groups.all().values_list("id")
|
||||
or [],
|
||||
},
|
||||
"change": {
|
||||
"users": action.assign_change_users.all().values_list("id")
|
||||
or [],
|
||||
"groups": action.assign_change_groups.all().values_list(
|
||||
"id",
|
||||
)
|
||||
or [],
|
||||
},
|
||||
}
|
||||
set_permissions_for_object(
|
||||
permissions=permissions,
|
||||
object=document,
|
||||
merge=True,
|
||||
)
|
||||
if action.assign_custom_fields is not None:
|
||||
for field in action.assign_custom_fields.all():
|
||||
if (
|
||||
CustomFieldInstance.objects.filter(
|
||||
field=field,
|
||||
document=document,
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
# can be triggered on existing docs, so only add the field if it doesn't already exist
|
||||
CustomFieldInstance.objects.create(
|
||||
field=field,
|
||||
document=document,
|
||||
)
|
||||
|
||||
if action.assign_custom_fields is not None:
|
||||
for field in action.assign_custom_fields.all():
|
||||
if (
|
||||
CustomFieldInstance.objects.filter(
|
||||
field=field,
|
||||
document=document,
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
# can be triggered on existing docs, so only add the field if it doesn't already exist
|
||||
CustomFieldInstance.objects.create(
|
||||
field=field,
|
||||
document=document,
|
||||
)
|
||||
elif action.type == WorkflowAction.WorkflowActionType.REMOVAL:
|
||||
if action.remove_all_tags:
|
||||
document.tags.clear()
|
||||
else:
|
||||
for tag in action.remove_tags.filter(
|
||||
pk__in=list(document.tags.values_list("pk", flat=True)),
|
||||
).all():
|
||||
document.tags.remove(tag.pk)
|
||||
|
||||
if action.remove_all_correspondents or (
|
||||
document.correspondent
|
||||
and (
|
||||
action.remove_correspondents.filter(
|
||||
pk=document.correspondent.pk,
|
||||
).exists()
|
||||
)
|
||||
):
|
||||
document.correspondent = None
|
||||
|
||||
if action.remove_all_document_types or (
|
||||
document.document_type
|
||||
and (
|
||||
action.remove_document_types.filter(
|
||||
pk=document.document_type.pk,
|
||||
).exists()
|
||||
)
|
||||
):
|
||||
document.document_type = None
|
||||
|
||||
if action.remove_all_storage_paths or (
|
||||
document.storage_path
|
||||
and (
|
||||
action.remove_storage_paths.filter(
|
||||
pk=document.storage_path.pk,
|
||||
).exists()
|
||||
)
|
||||
):
|
||||
document.storage_path = None
|
||||
|
||||
if action.remove_all_owners or (
|
||||
document.owner
|
||||
and (action.remove_owners.filter(pk=document.owner.pk).exists())
|
||||
):
|
||||
document.owner = None
|
||||
|
||||
if action.remove_all_permissions:
|
||||
permissions = {
|
||||
"view": {
|
||||
"users": [],
|
||||
"groups": [],
|
||||
},
|
||||
"change": {
|
||||
"users": [],
|
||||
"groups": [],
|
||||
},
|
||||
}
|
||||
set_permissions_for_object(
|
||||
permissions=permissions,
|
||||
object=document,
|
||||
merge=False,
|
||||
)
|
||||
elif (
|
||||
(action.remove_view_users.all().count() > 0)
|
||||
or (action.remove_view_groups.all().count() > 0)
|
||||
or (action.remove_change_users.all().count() > 0)
|
||||
or (action.remove_change_groups.all().count() > 0)
|
||||
):
|
||||
for user in action.remove_view_users.all():
|
||||
remove_perm("view_document", user, document)
|
||||
for user in action.remove_change_users.all():
|
||||
remove_perm("change_document", user, document)
|
||||
for group in action.remove_view_groups.all():
|
||||
remove_perm("view_document", group, document)
|
||||
for group in action.remove_change_groups.all():
|
||||
remove_perm("change_document", group, document)
|
||||
|
||||
if action.remove_all_custom_fields:
|
||||
CustomFieldInstance.objects.filter(document=document).delete()
|
||||
elif action.remove_custom_fields.all().count() > 0:
|
||||
CustomFieldInstance.objects.filter(
|
||||
field__in=action.remove_custom_fields.all(),
|
||||
document=document,
|
||||
).delete()
|
||||
|
||||
document.save()
|
||||
|
||||
|
@@ -202,6 +202,19 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
||||
"assign_change_groups": [self.group1.id],
|
||||
"assign_custom_fields": [self.cf2.id],
|
||||
},
|
||||
{
|
||||
"type": WorkflowAction.WorkflowActionType.REMOVAL,
|
||||
"remove_tags": [self.t3.id],
|
||||
"remove_document_types": [self.dt.id],
|
||||
"remove_correspondents": [self.c.id],
|
||||
"remove_storage_paths": [self.sp.id],
|
||||
"remove_custom_fields": [self.cf1.id],
|
||||
"remove_owners": [self.user2.id],
|
||||
"remove_view_users": [self.user3.id],
|
||||
"remove_change_users": [self.user3.id],
|
||||
"remove_view_groups": [self.group1.id],
|
||||
"remove_change_groups": [self.group1.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
|
@@ -1223,3 +1223,332 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
|
||||
title="test",
|
||||
)
|
||||
self.assertRaises(Exception, document_matches_workflow, doc, w, 4)
|
||||
|
||||
def test_removal_action_document_updated_workflow(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Workflow with removal action
|
||||
WHEN:
|
||||
- File that matches is updated
|
||||
THEN:
|
||||
- Action removals are applied
|
||||
"""
|
||||
trigger = WorkflowTrigger.objects.create(
|
||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
||||
filter_path="*",
|
||||
)
|
||||
action = WorkflowAction.objects.create(
|
||||
type=WorkflowAction.WorkflowActionType.REMOVAL,
|
||||
)
|
||||
action.remove_correspondents.add(self.c)
|
||||
action.remove_tags.add(self.t1)
|
||||
action.remove_document_types.add(self.dt)
|
||||
action.remove_storage_paths.add(self.sp)
|
||||
action.remove_owners.add(self.user2)
|
||||
action.remove_custom_fields.add(self.cf1)
|
||||
action.remove_view_users.add(self.user3)
|
||||
action.remove_view_groups.add(self.group1)
|
||||
action.remove_change_users.add(self.user3)
|
||||
action.remove_change_groups.add(self.group1)
|
||||
action.save()
|
||||
|
||||
w = Workflow.objects.create(
|
||||
name="Workflow 1",
|
||||
order=0,
|
||||
)
|
||||
w.triggers.add(trigger)
|
||||
w.actions.add(action)
|
||||
w.save()
|
||||
|
||||
doc = Document.objects.create(
|
||||
title="sample test",
|
||||
correspondent=self.c,
|
||||
document_type=self.dt,
|
||||
storage_path=self.sp,
|
||||
owner=self.user2,
|
||||
original_filename="sample.pdf",
|
||||
)
|
||||
doc.tags.set([self.t1, self.t2])
|
||||
CustomFieldInstance.objects.create(document=doc, field=self.cf1)
|
||||
doc.save()
|
||||
assign_perm("documents.view_document", self.user3, doc)
|
||||
assign_perm("documents.change_document", self.user3, doc)
|
||||
assign_perm("documents.view_document", self.group1, doc)
|
||||
assign_perm("documents.change_document", self.group1, doc)
|
||||
|
||||
superuser = User.objects.create_superuser("superuser")
|
||||
self.client.force_authenticate(user=superuser)
|
||||
|
||||
self.client.patch(
|
||||
f"/api/documents/{doc.id}/",
|
||||
{"title": "new title"},
|
||||
format="json",
|
||||
)
|
||||
doc.refresh_from_db()
|
||||
|
||||
self.assertIsNone(doc.document_type)
|
||||
self.assertIsNone(doc.correspondent)
|
||||
self.assertIsNone(doc.storage_path)
|
||||
self.assertEqual(doc.tags.all().count(), 1)
|
||||
self.assertIn(self.t2, doc.tags.all())
|
||||
self.assertIsNone(doc.owner)
|
||||
self.assertEqual(doc.custom_fields.all().count(), 0)
|
||||
self.assertFalse(self.user3.has_perm("documents.view_document", doc))
|
||||
self.assertFalse(self.user3.has_perm("documents.change_document", doc))
|
||||
group_perms: QuerySet = get_groups_with_perms(doc)
|
||||
self.assertNotIn(self.group1, group_perms)
|
||||
|
||||
def test_removal_action_document_updated_removeall(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Workflow with removal action with remove all fields set
|
||||
WHEN:
|
||||
- File that matches is updated
|
||||
THEN:
|
||||
- Action removals are applied
|
||||
"""
|
||||
trigger = WorkflowTrigger.objects.create(
|
||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
||||
filter_path="*",
|
||||
)
|
||||
action = WorkflowAction.objects.create(
|
||||
type=WorkflowAction.WorkflowActionType.REMOVAL,
|
||||
remove_all_correspondents=True,
|
||||
remove_all_tags=True,
|
||||
remove_all_document_types=True,
|
||||
remove_all_storage_paths=True,
|
||||
remove_all_custom_fields=True,
|
||||
remove_all_owners=True,
|
||||
remove_all_permissions=True,
|
||||
)
|
||||
action.save()
|
||||
|
||||
w = Workflow.objects.create(
|
||||
name="Workflow 1",
|
||||
order=0,
|
||||
)
|
||||
w.triggers.add(trigger)
|
||||
w.actions.add(action)
|
||||
w.save()
|
||||
|
||||
doc = Document.objects.create(
|
||||
title="sample test",
|
||||
correspondent=self.c,
|
||||
document_type=self.dt,
|
||||
storage_path=self.sp,
|
||||
owner=self.user2,
|
||||
original_filename="sample.pdf",
|
||||
)
|
||||
doc.tags.set([self.t1, self.t2])
|
||||
CustomFieldInstance.objects.create(document=doc, field=self.cf1)
|
||||
doc.save()
|
||||
assign_perm("documents.view_document", self.user3, doc)
|
||||
assign_perm("documents.change_document", self.user3, doc)
|
||||
assign_perm("documents.view_document", self.group1, doc)
|
||||
assign_perm("documents.change_document", self.group1, doc)
|
||||
|
||||
superuser = User.objects.create_superuser("superuser")
|
||||
self.client.force_authenticate(user=superuser)
|
||||
|
||||
self.client.patch(
|
||||
f"/api/documents/{doc.id}/",
|
||||
{"title": "new title"},
|
||||
format="json",
|
||||
)
|
||||
doc.refresh_from_db()
|
||||
|
||||
self.assertIsNone(doc.document_type)
|
||||
self.assertIsNone(doc.correspondent)
|
||||
self.assertIsNone(doc.storage_path)
|
||||
self.assertEqual(doc.tags.all().count(), 0)
|
||||
self.assertEqual(doc.tags.all().count(), 0)
|
||||
self.assertIsNone(doc.owner)
|
||||
self.assertEqual(doc.custom_fields.all().count(), 0)
|
||||
self.assertFalse(self.user3.has_perm("documents.view_document", doc))
|
||||
self.assertFalse(self.user3.has_perm("documents.change_document", doc))
|
||||
group_perms: QuerySet = get_groups_with_perms(doc)
|
||||
self.assertNotIn(self.group1, group_perms)
|
||||
|
||||
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
||||
def test_removal_action_document_consumed(self, m):
|
||||
"""
|
||||
GIVEN:
|
||||
- Workflow with assignment and removal actions
|
||||
WHEN:
|
||||
- File that matches is consumed
|
||||
THEN:
|
||||
- Action removals are applied
|
||||
"""
|
||||
trigger = WorkflowTrigger.objects.create(
|
||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||
filter_filename="*simple*",
|
||||
)
|
||||
action = WorkflowAction.objects.create(
|
||||
assign_title="Doc from {correspondent}",
|
||||
assign_correspondent=self.c,
|
||||
assign_document_type=self.dt,
|
||||
assign_storage_path=self.sp,
|
||||
assign_owner=self.user2,
|
||||
)
|
||||
action.assign_tags.add(self.t1)
|
||||
action.assign_tags.add(self.t2)
|
||||
action.assign_tags.add(self.t3)
|
||||
action.assign_view_users.add(self.user2)
|
||||
action.assign_view_users.add(self.user3)
|
||||
action.assign_view_groups.add(self.group1)
|
||||
action.assign_view_groups.add(self.group2)
|
||||
action.assign_change_users.add(self.user2)
|
||||
action.assign_change_users.add(self.user3)
|
||||
action.assign_change_groups.add(self.group1)
|
||||
action.assign_change_groups.add(self.group2)
|
||||
action.assign_custom_fields.add(self.cf1)
|
||||
action.assign_custom_fields.add(self.cf2)
|
||||
action.save()
|
||||
|
||||
action2 = WorkflowAction.objects.create(
|
||||
type=WorkflowAction.WorkflowActionType.REMOVAL,
|
||||
)
|
||||
action2.remove_correspondents.add(self.c)
|
||||
action2.remove_tags.add(self.t1)
|
||||
action2.remove_document_types.add(self.dt)
|
||||
action2.remove_storage_paths.add(self.sp)
|
||||
action2.remove_owners.add(self.user2)
|
||||
action2.remove_custom_fields.add(self.cf1)
|
||||
action2.remove_view_users.add(self.user3)
|
||||
action2.remove_change_users.add(self.user3)
|
||||
action2.remove_view_groups.add(self.group1)
|
||||
action2.remove_change_groups.add(self.group1)
|
||||
action2.save()
|
||||
|
||||
w = Workflow.objects.create(
|
||||
name="Workflow 1",
|
||||
order=0,
|
||||
)
|
||||
w.triggers.add(trigger)
|
||||
w.actions.add(action)
|
||||
w.actions.add(action2)
|
||||
w.save()
|
||||
|
||||
test_file = self.SAMPLE_DIR / "simple.pdf"
|
||||
|
||||
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
|
||||
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=test_file,
|
||||
),
|
||||
None,
|
||||
)
|
||||
m.assert_called_once()
|
||||
_, overrides = m.call_args
|
||||
self.assertIsNone(overrides["override_correspondent_id"])
|
||||
self.assertIsNone(overrides["override_document_type_id"])
|
||||
self.assertEqual(
|
||||
overrides["override_tag_ids"],
|
||||
[self.t2.pk, self.t3.pk],
|
||||
)
|
||||
self.assertIsNone(overrides["override_storage_path_id"])
|
||||
self.assertIsNone(overrides["override_owner_id"])
|
||||
self.assertEqual(overrides["override_view_users"], [self.user2.pk])
|
||||
self.assertEqual(overrides["override_view_groups"], [self.group2.pk])
|
||||
self.assertEqual(overrides["override_change_users"], [self.user2.pk])
|
||||
self.assertEqual(overrides["override_change_groups"], [self.group2.pk])
|
||||
self.assertEqual(
|
||||
overrides["override_title"],
|
||||
"Doc from {correspondent}",
|
||||
)
|
||||
self.assertEqual(
|
||||
overrides["override_custom_field_ids"],
|
||||
[self.cf2.pk],
|
||||
)
|
||||
|
||||
info = cm.output[0]
|
||||
expected_str = f"Document matched {trigger} from {w}"
|
||||
self.assertIn(expected_str, info)
|
||||
|
||||
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
||||
def test_removal_action_document_consumed_removeall(self, m):
|
||||
"""
|
||||
GIVEN:
|
||||
- Workflow with assignment and removal actions with remove all fields set
|
||||
WHEN:
|
||||
- File that matches is consumed
|
||||
THEN:
|
||||
- Action removals are applied
|
||||
"""
|
||||
trigger = WorkflowTrigger.objects.create(
|
||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||
filter_filename="*simple*",
|
||||
)
|
||||
action = WorkflowAction.objects.create(
|
||||
assign_title="Doc from {correspondent}",
|
||||
assign_correspondent=self.c,
|
||||
assign_document_type=self.dt,
|
||||
assign_storage_path=self.sp,
|
||||
assign_owner=self.user2,
|
||||
)
|
||||
action.assign_tags.add(self.t1)
|
||||
action.assign_tags.add(self.t2)
|
||||
action.assign_tags.add(self.t3)
|
||||
action.assign_view_users.add(self.user3.pk)
|
||||
action.assign_view_groups.add(self.group1.pk)
|
||||
action.assign_change_users.add(self.user3.pk)
|
||||
action.assign_change_groups.add(self.group1.pk)
|
||||
action.assign_custom_fields.add(self.cf1.pk)
|
||||
action.assign_custom_fields.add(self.cf2.pk)
|
||||
action.save()
|
||||
|
||||
action2 = WorkflowAction.objects.create(
|
||||
type=WorkflowAction.WorkflowActionType.REMOVAL,
|
||||
remove_all_correspondents=True,
|
||||
remove_all_tags=True,
|
||||
remove_all_document_types=True,
|
||||
remove_all_storage_paths=True,
|
||||
remove_all_custom_fields=True,
|
||||
remove_all_owners=True,
|
||||
remove_all_permissions=True,
|
||||
)
|
||||
|
||||
w = Workflow.objects.create(
|
||||
name="Workflow 1",
|
||||
order=0,
|
||||
)
|
||||
w.triggers.add(trigger)
|
||||
w.actions.add(action)
|
||||
w.actions.add(action2)
|
||||
w.save()
|
||||
|
||||
test_file = self.SAMPLE_DIR / "simple.pdf"
|
||||
|
||||
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
|
||||
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=test_file,
|
||||
),
|
||||
None,
|
||||
)
|
||||
m.assert_called_once()
|
||||
_, overrides = m.call_args
|
||||
self.assertIsNone(overrides["override_correspondent_id"])
|
||||
self.assertIsNone(overrides["override_document_type_id"])
|
||||
self.assertEqual(
|
||||
overrides["override_tag_ids"],
|
||||
[],
|
||||
)
|
||||
self.assertIsNone(overrides["override_storage_path_id"])
|
||||
self.assertIsNone(overrides["override_owner_id"])
|
||||
self.assertEqual(overrides["override_view_users"], [])
|
||||
self.assertEqual(overrides["override_view_groups"], [])
|
||||
self.assertEqual(overrides["override_change_users"], [])
|
||||
self.assertEqual(overrides["override_change_groups"], [])
|
||||
self.assertEqual(
|
||||
overrides["override_custom_field_ids"],
|
||||
[],
|
||||
)
|
||||
|
||||
info = cm.output[0]
|
||||
expected_str = f"Document matched {trigger} from {w}"
|
||||
self.assertIn(expected_str, info)
|
||||
|
@@ -2,7 +2,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-26 13:34-0800\n"
|
||||
"POT-Creation-Date: 2024-02-27 10:51-0800\n"
|
||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
@@ -53,7 +53,7 @@ msgstr ""
|
||||
msgid "Automatic"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:62 documents/models.py:397 documents/models.py:1102
|
||||
#: documents/models.py:62 documents/models.py:397 documents/models.py:1218
|
||||
#: paperless_mail/models.py:18 paperless_mail/models.py:93
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
@@ -687,102 +687,174 @@ msgstr ""
|
||||
msgid "workflow triggers"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1000
|
||||
#: documents/models.py:1002
|
||||
msgid "Assignment"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1003
|
||||
#: documents/models.py:1006
|
||||
msgid "Removal"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1010
|
||||
msgid "Workflow Action Type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1009
|
||||
#: documents/models.py:1016
|
||||
msgid "assign title"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1014
|
||||
#: documents/models.py:1021
|
||||
msgid ""
|
||||
"Assign a document title, can include some placeholders, see documentation."
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1022 paperless_mail/models.py:216
|
||||
#: documents/models.py:1030 paperless_mail/models.py:216
|
||||
msgid "assign this tag"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1030 paperless_mail/models.py:224
|
||||
#: documents/models.py:1039 paperless_mail/models.py:224
|
||||
msgid "assign this document type"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1038 paperless_mail/models.py:238
|
||||
#: documents/models.py:1048 paperless_mail/models.py:238
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1046
|
||||
#: documents/models.py:1057
|
||||
msgid "assign this storage path"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1055
|
||||
#: documents/models.py:1066
|
||||
msgid "assign this owner"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1062
|
||||
#: documents/models.py:1073
|
||||
msgid "grant view permissions to these users"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1069
|
||||
#: documents/models.py:1080
|
||||
msgid "grant view permissions to these groups"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1076
|
||||
#: documents/models.py:1087
|
||||
msgid "grant change permissions to these users"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1083
|
||||
#: documents/models.py:1094
|
||||
msgid "grant change permissions to these groups"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1090
|
||||
#: documents/models.py:1101
|
||||
msgid "assign these custom fields"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1094
|
||||
msgid "workflow action"
|
||||
#: documents/models.py:1108
|
||||
msgid "remove these tag(s)"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1095
|
||||
msgid "workflow actions"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1104 paperless_mail/models.py:95
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1110
|
||||
msgid "triggers"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1117
|
||||
msgid "actions"
|
||||
#: documents/models.py:1113
|
||||
msgid "remove all tags"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1120
|
||||
msgid "remove these document type(s)"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1125
|
||||
msgid "remove all document types"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1132
|
||||
msgid "remove these correspondent(s)"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1137
|
||||
msgid "remove all correspondents"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1144
|
||||
msgid "remove these storage path(s)"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1149
|
||||
msgid "remove all storage paths"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1156
|
||||
msgid "remove these owner(s)"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1161
|
||||
msgid "remove all owners"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1168
|
||||
msgid "remove view permissions for these users"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1175
|
||||
msgid "remove view permissions for these groups"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1182
|
||||
msgid "remove change permissions for these users"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1189
|
||||
msgid "remove change permissions for these groups"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1194
|
||||
msgid "remove all permissions"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1201
|
||||
msgid "remove these custom fields"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1206
|
||||
msgid "remove all custom fields"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1210
|
||||
msgid "workflow action"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1211
|
||||
msgid "workflow actions"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1220 paperless_mail/models.py:95
|
||||
msgid "order"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1226
|
||||
msgid "triggers"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1233
|
||||
msgid "actions"
|
||||
msgstr ""
|
||||
|
||||
#: documents/models.py:1236
|
||||
msgid "enabled"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:113
|
||||
#: documents/serialisers.py:114
|
||||
#, python-format
|
||||
msgid "Invalid regular expression: %(error)s"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:407
|
||||
#: documents/serialisers.py:408
|
||||
msgid "Invalid color."
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:1073
|
||||
#: documents/serialisers.py:1070
|
||||
#, python-format
|
||||
msgid "File type %(type)s not supported"
|
||||
msgstr ""
|
||||
|
||||
#: documents/serialisers.py:1176
|
||||
#: documents/serialisers.py:1173
|
||||
msgid "Invalid variable detected."
|
||||
msgstr ""
|
||||
|
||||
|
Reference in New Issue
Block a user