mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-26 01:16:16 +00:00
Feature: Workflows (#5121)
This commit is contained in:
@@ -0,0 +1,513 @@
|
||||
# Generated by Django 4.2.7 on 2023-12-23 22:51
|
||||
|
||||
import django.db.models.deletion
|
||||
import multiselectfield.db.fields
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
||||
from documents.models import Correspondent
|
||||
from documents.models import CustomField
|
||||
from documents.models import DocumentType
|
||||
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 paperless_mail.models import MailRule
|
||||
|
||||
|
||||
def add_workflow_permissions(apps, schema_editor):
|
||||
# create permissions without waiting for post_migrate signal
|
||||
for app_config in apps.get_app_configs():
|
||||
app_config.models_module = True
|
||||
create_permissions(app_config, apps=apps, verbosity=0)
|
||||
app_config.models_module = None
|
||||
|
||||
add_permission = Permission.objects.get(codename="add_document")
|
||||
workflow_permissions = Permission.objects.filter(
|
||||
codename__contains="workflow",
|
||||
)
|
||||
|
||||
for user in User.objects.filter(Q(user_permissions=add_permission)).distinct():
|
||||
user.user_permissions.add(*workflow_permissions)
|
||||
|
||||
for group in Group.objects.filter(Q(permissions=add_permission)).distinct():
|
||||
group.permissions.add(*workflow_permissions)
|
||||
|
||||
|
||||
def remove_workflow_permissions(apps, schema_editor):
|
||||
workflow_permissions = Permission.objects.filter(
|
||||
codename__contains="workflow",
|
||||
)
|
||||
|
||||
for user in User.objects.all():
|
||||
user.user_permissions.remove(*workflow_permissions)
|
||||
|
||||
for group in Group.objects.all():
|
||||
group.permissions.remove(*workflow_permissions)
|
||||
|
||||
|
||||
def migrate_consumption_templates(apps, schema_editor):
|
||||
"""
|
||||
Migrate consumption templates to workflows. At this point ConsumptionTemplate still exists
|
||||
but objects are not returned as their true model so we have to manually do that
|
||||
"""
|
||||
model_name = "ConsumptionTemplate"
|
||||
app_name = "documents"
|
||||
|
||||
ConsumptionTemplate = apps.get_model(app_label=app_name, model_name=model_name)
|
||||
|
||||
with transaction.atomic():
|
||||
for template in ConsumptionTemplate.objects.all():
|
||||
trigger = WorkflowTrigger(
|
||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||
sources=template.sources,
|
||||
filter_path=template.filter_path,
|
||||
filter_filename=template.filter_filename,
|
||||
)
|
||||
if template.filter_mailrule is not None:
|
||||
trigger.filter_mailrule = MailRule.objects.get(
|
||||
id=template.filter_mailrule.id,
|
||||
)
|
||||
trigger.save()
|
||||
|
||||
action = WorkflowAction.objects.create(
|
||||
assign_title=template.assign_title,
|
||||
)
|
||||
if template.assign_document_type is not None:
|
||||
action.assign_document_type = DocumentType.objects.get(
|
||||
id=template.assign_document_type.id,
|
||||
)
|
||||
if template.assign_correspondent is not None:
|
||||
action.assign_correspondent = Correspondent.objects.get(
|
||||
id=template.assign_correspondent.id,
|
||||
)
|
||||
if template.assign_storage_path is not None:
|
||||
action.assign_storage_path = StoragePath.objects.get(
|
||||
id=template.assign_storage_path.id,
|
||||
)
|
||||
if template.assign_owner is not None:
|
||||
action.assign_owner = User.objects.get(id=template.assign_owner.id)
|
||||
if template.assign_tags is not None:
|
||||
action.assign_tags.set(
|
||||
Tag.objects.filter(
|
||||
id__in=[t.id for t in template.assign_tags.all()],
|
||||
).all(),
|
||||
)
|
||||
if template.assign_view_users is not None:
|
||||
action.assign_view_users.set(
|
||||
User.objects.filter(
|
||||
id__in=[u.id for u in template.assign_view_users.all()],
|
||||
).all(),
|
||||
)
|
||||
if template.assign_view_groups is not None:
|
||||
action.assign_view_groups.set(
|
||||
Group.objects.filter(
|
||||
id__in=[g.id for g in template.assign_view_groups.all()],
|
||||
).all(),
|
||||
)
|
||||
if template.assign_change_users is not None:
|
||||
action.assign_change_users.set(
|
||||
User.objects.filter(
|
||||
id__in=[u.id for u in template.assign_change_users.all()],
|
||||
).all(),
|
||||
)
|
||||
if template.assign_change_groups is not None:
|
||||
action.assign_change_groups.set(
|
||||
Group.objects.filter(
|
||||
id__in=[g.id for g in template.assign_change_groups.all()],
|
||||
).all(),
|
||||
)
|
||||
if template.assign_custom_fields is not None:
|
||||
action.assign_custom_fields.set(
|
||||
CustomField.objects.filter(
|
||||
id__in=[cf.id for cf in template.assign_custom_fields.all()],
|
||||
).all(),
|
||||
)
|
||||
action.save()
|
||||
|
||||
workflow = Workflow.objects.create(
|
||||
name=template.name,
|
||||
order=template.order,
|
||||
)
|
||||
workflow.triggers.set([trigger])
|
||||
workflow.actions.set([action])
|
||||
workflow.save()
|
||||
|
||||
|
||||
def unmigrate_consumption_templates(apps, schema_editor):
|
||||
model_name = "ConsumptionTemplate"
|
||||
app_name = "documents"
|
||||
|
||||
ConsumptionTemplate = apps.get_model(app_label=app_name, model_name=model_name)
|
||||
|
||||
for workflow in Workflow.objects.all():
|
||||
template = ConsumptionTemplate.objects.create(
|
||||
name=workflow.name,
|
||||
order=workflow.order,
|
||||
sources=workflow.triggers.first().sources,
|
||||
filter_path=workflow.triggers.first().filter_path,
|
||||
filter_filename=workflow.triggers.first().filter_filename,
|
||||
filter_mailrule=workflow.triggers.first().filter_mailrule,
|
||||
assign_title=workflow.actions.first().assign_title,
|
||||
assign_document_type=workflow.actions.first().assign_document_type,
|
||||
assign_correspondent=workflow.actions.first().assign_correspondent,
|
||||
assign_storage_path=workflow.actions.first().assign_storage_path,
|
||||
assign_owner=workflow.actions.first().assign_owner,
|
||||
)
|
||||
template.assign_tags.set(workflow.actions.first().assign_tags.all())
|
||||
template.assign_view_users.set(workflow.actions.first().assign_view_users.all())
|
||||
template.assign_view_groups.set(
|
||||
workflow.actions.first().assign_view_groups.all(),
|
||||
)
|
||||
template.assign_change_users.set(
|
||||
workflow.actions.first().assign_change_users.all(),
|
||||
)
|
||||
template.assign_change_groups.set(
|
||||
workflow.actions.first().assign_change_groups.all(),
|
||||
)
|
||||
template.assign_custom_fields.set(
|
||||
workflow.actions.first().assign_custom_fields.all(),
|
||||
)
|
||||
template.save()
|
||||
|
||||
|
||||
def delete_consumption_template_content_type(apps, schema_editor):
|
||||
with transaction.atomic():
|
||||
apps.get_model("contenttypes", "ContentType").objects.filter(
|
||||
app_label="documents",
|
||||
model="consumptiontemplate",
|
||||
).delete()
|
||||
|
||||
|
||||
def undelete_consumption_template_content_type(apps, schema_editor):
|
||||
apps.get_model("contenttypes", "ContentType").objects.create(
|
||||
app_label="documents",
|
||||
model="consumptiontemplate",
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("auth", "0012_alter_user_first_name_max_length"),
|
||||
("documents", "1043_alter_savedviewfilterrule_rule_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Workflow",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(max_length=256, unique=True, verbose_name="name"),
|
||||
),
|
||||
("order", models.IntegerField(default=0, verbose_name="order")),
|
||||
(
|
||||
"enabled",
|
||||
models.BooleanField(default=True, verbose_name="enabled"),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="WorkflowAction",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.PositiveIntegerField(
|
||||
choices=[(1, "Assignment")],
|
||||
default=1,
|
||||
verbose_name="Workflow Action Type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_title",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Assign a document title, can include some placeholders, see documentation.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="assign title",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_change_groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="auth.group",
|
||||
verbose_name="grant change permissions to these groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_change_users",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="grant change permissions to these users",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_correspondent",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.correspondent",
|
||||
verbose_name="assign this correspondent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_custom_fields",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="documents.customfield",
|
||||
verbose_name="assign these custom fields",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_document_type",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.documenttype",
|
||||
verbose_name="assign this document type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_owner",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="assign this owner",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_storage_path",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.storagepath",
|
||||
verbose_name="assign this storage path",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_tags",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
to="documents.tag",
|
||||
verbose_name="assign this tag",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_view_groups",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to="auth.group",
|
||||
verbose_name="grant view permissions to these groups",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_view_users",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="grant view permissions to these users",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "workflow action",
|
||||
"verbose_name_plural": "workflow actions",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="WorkflowTrigger",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Consumption Started"),
|
||||
(2, "Document Added"),
|
||||
(3, "Document Updated"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="Workflow Trigger Type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"sources",
|
||||
multiselectfield.db.fields.MultiSelectField(
|
||||
choices=[
|
||||
(1, "Consume Folder"),
|
||||
(2, "Api Upload"),
|
||||
(3, "Mail Fetch"),
|
||||
],
|
||||
default="1,2,3",
|
||||
max_length=5,
|
||||
),
|
||||
),
|
||||
(
|
||||
"filter_path",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Only consume documents with a path that matches this if specified. Wildcards specified as * are allowed. Case insensitive.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="filter path",
|
||||
),
|
||||
),
|
||||
(
|
||||
"filter_filename",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="filter filename",
|
||||
),
|
||||
),
|
||||
(
|
||||
"filter_mailrule",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="paperless_mail.mailrule",
|
||||
verbose_name="filter documents from this mail rule",
|
||||
),
|
||||
),
|
||||
(
|
||||
"matching_algorithm",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
(
|
||||
"match",
|
||||
models.CharField(blank=True, max_length=256, verbose_name="match"),
|
||||
),
|
||||
(
|
||||
"is_insensitive",
|
||||
models.BooleanField(default=True, verbose_name="is insensitive"),
|
||||
),
|
||||
(
|
||||
"filter_has_tags",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
to="documents.tag",
|
||||
verbose_name="has these tag(s)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"filter_has_document_type",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.documenttype",
|
||||
verbose_name="has this document type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"filter_has_correspondent",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.correspondent",
|
||||
verbose_name="has this correspondent",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "workflow trigger",
|
||||
"verbose_name_plural": "workflow triggers",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
add_workflow_permissions,
|
||||
remove_workflow_permissions,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflow",
|
||||
name="actions",
|
||||
field=models.ManyToManyField(
|
||||
related_name="workflows",
|
||||
to="documents.workflowaction",
|
||||
verbose_name="actions",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflow",
|
||||
name="triggers",
|
||||
field=models.ManyToManyField(
|
||||
related_name="workflows",
|
||||
to="documents.workflowtrigger",
|
||||
verbose_name="triggers",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
migrate_consumption_templates,
|
||||
unmigrate_consumption_templates,
|
||||
),
|
||||
migrations.DeleteModel("ConsumptionTemplate"),
|
||||
migrations.RunPython(
|
||||
delete_consumption_template_content_type,
|
||||
undelete_consumption_template_content_type,
|
||||
),
|
||||
]
|
Reference in New Issue
Block a user