From 1aa0792b88b7b38eaee97ba8027c11730fe62733 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sat, 16 Sep 2023 23:38:29 -0700
Subject: [PATCH] Support title assignment in consumption templates
---
...mption-template-edit-dialog.component.html | 1 +
...sumption-template-edit-dialog.component.ts | 9 ++--
.../data/paperless-consumption-template.ts | 2 +
src/documents/consumer.py | 46 ++++++++++++++++++-
.../migrations/1039_consumptiontemplate.py | 10 ++++
src/documents/models.py | 11 +++++
src/documents/serialisers.py | 21 ++-------
7 files changed, 78 insertions(+), 22 deletions(-)
diff --git a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html
index e75ca1a5b..3981bc8be 100644
--- a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html
+++ b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html
@@ -15,6 +15,7 @@
+
diff --git a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
index 2f79b9fbc..e6df00b23 100644
--- a/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
+++ b/src-ui/src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.ts
@@ -86,15 +86,16 @@ export class ConsumptionTemplateEditDialogComponent extends EditDialogComponent<
filter_path: new FormControl(null),
order: new FormControl(null),
sources: new FormControl([]),
+ assign_title: new FormControl(null),
assign_tags: new FormControl([]),
assign_owner: new FormControl(null),
assign_document_type: new FormControl(null),
assign_correspondent: new FormControl(null),
assign_storage_path: new FormControl(null),
- assign_view_users: new FormControl(null),
- assign_view_groups: new FormControl(null),
- assign_change_users: new FormControl(null),
- assign_change_groups: new FormControl(null),
+ assign_view_users: new FormControl([]),
+ assign_view_groups: new FormControl([]),
+ assign_change_users: new FormControl([]),
+ assign_change_groups: new FormControl([]),
})
}
diff --git a/src-ui/src/app/data/paperless-consumption-template.ts b/src-ui/src/app/data/paperless-consumption-template.ts
index f424e23f2..bc7eab734 100644
--- a/src-ui/src/app/data/paperless-consumption-template.ts
+++ b/src-ui/src/app/data/paperless-consumption-template.ts
@@ -17,6 +17,8 @@ export interface PaperlessConsumptionTemplate extends ObjectWithPermissions {
filter_path: string
+ assign_title?: string
+
assign_tags?: number[] // PaperlessTag.id
assign_document_type?: number // PaperlessDocumentType.id
diff --git a/src/documents/consumer.py b/src/documents/consumer.py
index 63251e0f9..cd505f4c2 100644
--- a/src/documents/consumer.py
+++ b/src/documents/consumer.py
@@ -602,7 +602,7 @@ class Consumer(LoggingMixin):
for template in ConsumptionTemplate.objects.all().order_by("order"):
template_overrides = DocumentMetadataOverrides()
- if int(input_doc.source) in list(template.sources) and (
+ if int(input_doc.source) in [int(x) for x in list(template.sources)] and (
(
template.filter_filename is not None
and fnmatch(
@@ -616,6 +616,8 @@ class Consumer(LoggingMixin):
)
):
self.log.info(f"Document matched consumption template {template.name}")
+ if template.assign_title is not None:
+ template_overrides.title = template.assign_title
if template.assign_tags is not None:
template_overrides.tag_ids = [
tag.pk for tag in template.assign_tags.all()
@@ -654,6 +656,38 @@ class Consumer(LoggingMixin):
)
return overrides
+ def _parse_title_placeholders(self, title: str) -> str:
+ local_added = timezone.now()
+
+ correspondent_name = (
+ Correspondent.objects.get(pk=self.override_correspondent_id).name
+ if self.override_correspondent_id is not None
+ else None
+ )
+ doc_type_name = (
+ DocumentType.objects.get(pk=self.override_document_type_id).name
+ if self.override_correspondent_id is not None
+ else None
+ )
+ owner_username = (
+ User.objects.get(pk=self.override_owner_id).username
+ if self.override_owner_id is not None
+ else None
+ )
+
+ return title.format(
+ correspondent=correspondent_name or None,
+ document_type=doc_type_name or None,
+ added=local_added.isoformat(),
+ added_year=local_added.strftime("%Y"),
+ added_year_short=local_added.strftime("%y"),
+ added_month=local_added.strftime("%m"),
+ added_month_name=local_added.strftime("%B"),
+ added_month_name_short=local_added.strftime("%b"),
+ added_day=local_added.strftime("%d"),
+ owner_username=owner_username or None,
+ ).strip()
+
def _store(
self,
text: str,
@@ -686,9 +720,15 @@ class Consumer(LoggingMixin):
storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+ print("override_title", self.override_title)
+
with open(self.path, "rb") as f:
document = Document.objects.create(
- title=(self.override_title or file_info.title)[:127],
+ title=(
+ self._parse_title_placeholders(self.override_title)
+ if self.override_title is not None
+ else file_info.title
+ )[:127],
content=text,
mime_type=mime_type,
checksum=hashlib.md5(f.read()).hexdigest(),
@@ -800,6 +840,8 @@ def merge_overrides(
overridesA: DocumentMetadataOverrides,
overridesB: DocumentMetadataOverrides,
) -> DocumentMetadataOverrides:
+ if overridesA.title is None:
+ overridesA.title = overridesB.title
if overridesA.tag_ids is None:
overridesA.tag_ids = overridesB.tag_ids
if overridesA.correspondent_id is None:
diff --git a/src/documents/migrations/1039_consumptiontemplate.py b/src/documents/migrations/1039_consumptiontemplate.py
index fb27239f0..5ca928159 100644
--- a/src/documents/migrations/1039_consumptiontemplate.py
+++ b/src/documents/migrations/1039_consumptiontemplate.py
@@ -167,6 +167,16 @@ class Migration(migrations.Migration):
verbose_name="assign this tag",
),
),
+ (
+ "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_view_groups",
models.ManyToManyField(
diff --git a/src/documents/models.py b/src/documents/models.py
index 231e5dba2..84bb7d9d1 100644
--- a/src/documents/models.py
+++ b/src/documents/models.py
@@ -786,6 +786,17 @@ class ConsumptionTemplate(ModelWithOwner):
),
)
+ assign_title = models.CharField(
+ _("assign title"),
+ max_length=256,
+ null=True,
+ blank=True,
+ help_text=_(
+ "Assign a document title, can include some placeholders,"
+ "see documentation.",
+ ),
+ )
+
assign_tags = models.ManyToManyField(
Tag,
blank=True,
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index aacd978ae..7c861c2c1 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -1066,6 +1066,7 @@ class ConsumptionTemplateSerializer(OwnedObjectSerializer):
"sources",
"filter_path",
"filter_filename",
+ "assign_title",
"assign_tags",
"assign_correspondent",
"assign_document_type",
@@ -1085,20 +1086,8 @@ class ConsumptionTemplateSerializer(OwnedObjectSerializer):
super().update(instance, validated_data)
return instance
- # def create(self, validated_data):
- # if "assign_tags" in validated_data:
- # assign_tags = validated_data.pop("assign_tags")
- # mail_rule = super().create(validated_data)
- # if assign_tags:
- # mail_rule.assign_tags.set(assign_tags)
- # return mail_rule
+ def validate(self, attrs):
+ if len(attrs["filter_filename"]) == 0 and len(attrs["filter_path"]) == 0:
+ raise serializers.ValidationError("File name or path filter are required")
- # def validate(self, attrs):
- # TODO: require path or filename filter
- # if (
- # attrs["action"] == ConsumptionTemplate.MailAction.TAG
- # or attrs["action"] == ConsumptionTemplate.MailAction.MOVE
- # ) and attrs["action_parameter"] is None:
- # raise serializers.ValidationError("An action parameter is required.")
-
- # return attrs
+ return attrs