]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: title assignment placeholder error handling, fallback (#5282)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Wed, 10 Jan 2024 18:18:55 +0000 (10:18 -0800)
committerGitHub <noreply@github.com>
Wed, 10 Jan 2024 18:18:55 +0000 (10:18 -0800)
src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html
src/documents/consumer.py
src/documents/serialisers.py
src/documents/signals/handlers.py
src/documents/tests/test_api_workflows.py
src/documents/tests/test_consumer.py
src/documents/tests/test_workflows.py

index b4d4b02ca033aa942f0c819b2b5302fd65ffd95b..76d788e5955e0d4a4d696eae5b15c0303797cb47 100644 (file)
@@ -99,7 +99,7 @@
                         <input type="hidden" formControlName="id" />
                         <div class="row">
                           <div class="col">
-                            <pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>." [error]="error?.assign_title"></pngx-input-text>
+                            <pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>." [error]="error?.actions?.[i]?.assign_title"></pngx-input-text>
                             <pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
                             <pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
                             <pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
index b7a559575948d5d7b971088163ab4238227f92b8..06e9f68fc4ab0adaddaf69715e12865d2ccf21e7 100644 (file)
@@ -726,12 +726,17 @@ class Consumer(LoggingMixin):
 
         storage_type = Document.STORAGE_TYPE_UNENCRYPTED
 
+        title = file_info.title[:127]
+        if self.override_title is not None:
+            try:
+                title = self._parse_title_placeholders(self.override_title)
+            except Exception as e:
+                self.log.error(
+                    f"Error occurred parsing title override '{self.override_title}', falling back to original. Exception: {e}",
+                )
+
         document = Document.objects.create(
-            title=(
-                self._parse_title_placeholders(self.override_title)
-                if self.override_title is not None
-                else file_info.title
-            )[:127],
+            title=title,
             content=text,
             mime_type=mime_type,
             checksum=hashlib.md5(self.working_copy.read_bytes()).hexdigest(),
index 233c56367d3ed0271649851fd0dcda597866f4ca..eb0280fbd24045e431c387f800868edb2a8f1499 100644 (file)
@@ -1386,12 +1386,38 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
 
     def validate(self, attrs):
         # Empty strings treated as None to avoid unexpected behavior
-        if (
-            "assign_title" in attrs
-            and attrs["assign_title"] is not None
-            and len(attrs["assign_title"]) == 0
-        ):
-            attrs["assign_title"] = None
+        if "assign_title" in attrs:
+            if attrs["assign_title"] is not None and len(attrs["assign_title"]) == 0:
+                attrs["assign_title"] = None
+            else:
+                try:
+                    # test against all placeholders, see consumer.py `parse_doc_title_w_placeholders`
+                    attrs["assign_title"].format(
+                        correspondent="",
+                        document_type="",
+                        added="",
+                        added_year="",
+                        added_year_short="",
+                        added_month="",
+                        added_month_name="",
+                        added_month_name_short="",
+                        added_day="",
+                        added_time="",
+                        owner_username="",
+                        original_filename="",
+                        created="",
+                        created_year="",
+                        created_year_short="",
+                        created_month="",
+                        created_month_name="",
+                        created_month_name_short="",
+                        created_day="",
+                        created_time="",
+                    )
+                except (ValueError, KeyError) as e:
+                    raise serializers.ValidationError(
+                        {"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
+                    )
 
         return attrs
 
index 01c62f079b4d30658f2b0c3940eed2a87eae5378..eee06bb6eac88ca22cec951ba40df92b50097366 100644 (file)
@@ -570,19 +570,27 @@ def run_workflow(
                     document.owner = action.assign_owner
 
                 if action.assign_title is not None:
-                    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,
-                        timezone.localtime(document.created),
-                    )
+                    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 "",
+                            document.added,
+                            document.original_filename,
+                            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
index d7a7ad6ff409133dcf4249b21db3b034e4d73faf..21e887c24fd9749ec8f1439b6bd37fc7f54832ba 100644 (file)
@@ -248,6 +248,45 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
 
         self.assertEqual(WorkflowTrigger.objects.count(), 1)
 
+    def test_api_create_invalid_assign_title(self):
+        """
+        GIVEN:
+            - API request to create a workflow
+            - Invalid f-string for assign_title
+        WHEN:
+            - API is called
+        THEN:
+            - Correct HTTP 400 response
+            - No objects are created
+        """
+        response = self.client.post(
+            self.ENDPOINT,
+            json.dumps(
+                {
+                    "name": "Workflow 1",
+                    "order": 1,
+                    "triggers": [
+                        {
+                            "type": WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
+                        },
+                    ],
+                    "actions": [
+                        {
+                            "assign_title": "{created_year]",
+                        },
+                    ],
+                },
+            ),
+            content_type="application/json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertIn(
+            "Invalid f-string detected",
+            response.data["actions"][0]["assign_title"][0],
+        )
+
+        self.assertEqual(Workflow.objects.count(), 1)
+
     def test_api_create_workflow_trigger_action_empty_fields(self):
         """
         GIVEN:
index 748e49e10145e31bad413ebe6248622ddf349522..f7726597075869116d036f4047086b78669736bf 100644 (file)
@@ -423,6 +423,16 @@ class TestConsumer(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         self.assertEqual(document.title, "Override Title")
         self._assert_first_last_send_progress()
 
+    def testOverrideTitleInvalidPlaceholders(self):
+        with self.assertLogs("paperless.consumer", level="ERROR") as cm:
+            document = self.consumer.try_consume_file(
+                self.get_test_file(),
+                override_title="Override {correspondent]",
+            )
+            self.assertEqual(document.title, "sample")
+            expected_str = "Error occurred parsing title override 'Override {correspondent]', falling back to original"
+            self.assertIn(expected_str, cm.output[0])
+
     def testOverrideCorrespondent(self):
         c = Correspondent.objects.create(name="test")
 
index b4ad4aa57fb1351acf54f56ddb1e7a9711d0bad9..b688eecc9af8556320ca5fdec626c7708025d0b0 100644 (file)
@@ -966,6 +966,50 @@ class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
             expected_str = f"Document correspondent {doc.correspondent} does not match {trigger.filter_has_correspondent}"
             self.assertIn(expected_str, cm.output[1])
 
+    def test_document_added_invalid_title_placeholders(self):
+        """
+        GIVEN:
+            - Existing workflow with added trigger type
+            - Assign title field has an error
+        WHEN:
+            - File that matches is added
+        THEN:
+            - Title is not updated, error is output
+        """
+        trigger = WorkflowTrigger.objects.create(
+            type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
+            filter_filename="*sample*",
+        )
+        action = WorkflowAction.objects.create(
+            assign_title="Doc {created_year]",
+        )
+        w = Workflow.objects.create(
+            name="Workflow 1",
+            order=0,
+        )
+        w.triggers.add(trigger)
+        w.actions.add(action)
+        w.save()
+
+        now = timezone.localtime(timezone.now())
+        created = now - timedelta(weeks=520)
+        doc = Document.objects.create(
+            original_filename="sample.pdf",
+            title="sample test",
+            content="Hello world bar",
+            created=created,
+        )
+
+        with self.assertLogs("paperless.handlers", level="ERROR") as cm:
+            document_consumption_finished.send(
+                sender=self.__class__,
+                document=doc,
+            )
+            expected_str = f"Error occurred parsing title assignment '{action.assign_title}', falling back to original"
+            self.assertIn(expected_str, cm.output[0])
+
+        self.assertEqual(doc.title, "sample test")
+
     def test_document_updated_workflow(self):
         trigger = WorkflowTrigger.objects.create(
             type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,