diff --git a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts index 0908b69c0..5facc5cce 100644 --- a/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts @@ -71,6 +71,10 @@ export const DOCUMENT_SOURCE_OPTIONS = [ id: DocumentSource.MailFetch, name: $localize`Mail Fetch`, }, + { + id: DocumentSource.WebUI, + name: $localize`Web UI`, + }, ] export const SCHEDULE_DATE_FIELD_OPTIONS = [ diff --git a/src-ui/src/app/data/workflow-trigger.ts b/src-ui/src/app/data/workflow-trigger.ts index 12f76b7a3..4299356b0 100644 --- a/src-ui/src/app/data/workflow-trigger.ts +++ b/src-ui/src/app/data/workflow-trigger.ts @@ -4,6 +4,7 @@ export enum DocumentSource { ConsumeFolder = 1, ApiUpload = 2, MailFetch = 3, + WebUI = 4, } export enum WorkflowTriggerType { diff --git a/src-ui/src/app/services/upload-documents.service.ts b/src-ui/src/app/services/upload-documents.service.ts index 602e6d8ae..e2d1b52f4 100644 --- a/src-ui/src/app/services/upload-documents.service.ts +++ b/src-ui/src/app/services/upload-documents.service.ts @@ -37,6 +37,7 @@ export class UploadDocumentsService { private uploadFile(file: File) { let formData = new FormData() formData.append('document', file, file.name) + formData.append('from_webui', 'true') let status = this.websocketStatusService.newFileUpload(file.name) status.message = $localize`Connecting...` diff --git a/src/documents/data_models.py b/src/documents/data_models.py index 231e59005..406fe6b5a 100644 --- a/src/documents/data_models.py +++ b/src/documents/data_models.py @@ -144,6 +144,7 @@ class DocumentSource(IntEnum): ConsumeFolder = 1 ApiUpload = 2 MailFetch = 3 + WebUI = 4 @dataclasses.dataclass diff --git a/src/documents/migrations/1063_alter_workflowactionwebhook_url.py b/src/documents/migrations/1063_alter_workflowactionwebhook_url.py deleted file mode 100644 index e24928717..000000000 --- a/src/documents/migrations/1063_alter_workflowactionwebhook_url.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.6 on 2025-02-16 16:31 - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - dependencies = [ - ("documents", "1062_alter_savedviewfilterrule_rule_type"), - ] - - operations = [ - migrations.AlterField( - model_name="workflowactionwebhook", - name="url", - field=models.CharField( - help_text="The destination URL for the notification.", - max_length=256, - verbose_name="webhook url", - ), - ), - ] diff --git a/src/documents/migrations/1063_alter_workflowactionwebhook_url_and_more.py b/src/documents/migrations/1063_alter_workflowactionwebhook_url_and_more.py new file mode 100644 index 000000000..16c1eeb63 --- /dev/null +++ b/src/documents/migrations/1063_alter_workflowactionwebhook_url_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 5.1.6 on 2025-02-20 04:55 + +import multiselectfield.db.fields +from django.db import migrations +from django.db import models + + +# WebUI source was added, so all existing APIUpload sources should be updated to include WebUI +def update_workflow_sources(apps, schema_editor): + WorkflowTrigger = apps.get_model("documents", "WorkflowTrigger") + for trigger in WorkflowTrigger.objects.all(): + sources = list(trigger.sources) + if 2 in sources: + sources.append(4) + trigger.sources = sources + trigger.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("documents", "1062_alter_savedviewfilterrule_rule_type"), + ] + + operations = [ + migrations.AlterField( + model_name="workflowactionwebhook", + name="url", + field=models.CharField( + help_text="The destination URL for the notification.", + max_length=256, + verbose_name="webhook url", + ), + ), + migrations.AlterField( + model_name="workflowtrigger", + name="sources", + field=multiselectfield.db.fields.MultiSelectField( + choices=[ + (1, "Consume Folder"), + (2, "Api Upload"), + (3, "Mail Fetch"), + (4, "Web UI"), + ], + default="1,2,3,4", + max_length=7, + ), + ), + migrations.RunPython( + code=update_workflow_sources, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 4f9d3cb0e..b23bd8045 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -1031,6 +1031,7 @@ class WorkflowTrigger(models.Model): CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder") API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload") MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch") + WEB_UI = DocumentSource.WebUI.value, _("Web UI") class ScheduleDateField(models.TextChoices): ADDED = "added", _("Added") @@ -1045,9 +1046,9 @@ class WorkflowTrigger(models.Model): ) sources = MultiSelectField( - max_length=5, + max_length=7, choices=DocumentSourceChoices.choices, - default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch}", + default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch},{DocumentSource.WebUI}", ) filter_path = models.CharField( diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 75896ecdd..aeba5a721 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1546,6 +1546,12 @@ class PostDocumentSerializer(serializers.Serializer): required=False, ) + from_webui = serializers.BooleanField( + label="Documents are from Paperless-ngx WebUI", + write_only=True, + required=False, + ) + def validate_document(self, document): document_data = document.file.read() mime_type = magic.from_buffer(document_data, mime=True) diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py index a1bea944a..6247b0a6e 100644 --- a/src/documents/tests/test_api_documents.py +++ b/src/documents/tests/test_api_documents.py @@ -38,6 +38,7 @@ from documents.models import SavedView from documents.models import ShareLink from documents.models import StoragePath from documents.models import Tag +from documents.models import WorkflowTrigger from documents.tests.utils import DirectoriesMixin from documents.tests.utils import DocumentConsumeDelayMixin @@ -1362,6 +1363,30 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): self.assertEqual(overrides.filename, "simple.pdf") self.assertEqual(overrides.custom_field_ids, [custom_field.id]) + def test_upload_with_webui_source(self): + """ + GIVEN: A document with a source file + WHEN: Upload the document with 'from_webui' flag + THEN: Consume is called with the source set as WebUI + """ + self.consume_file_mock.return_value = celery.result.AsyncResult( + id=str(uuid.uuid4()), + ) + + with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f: + response = self.client.post( + "/api/documents/post_document/", + {"document": f, "from_webui": True}, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.consume_file_mock.assert_called_once() + + input_doc, overrides = self.get_last_consume_delay_call_args() + + self.assertEqual(input_doc.source, WorkflowTrigger.DocumentSourceChoices.WEB_UI) + def test_upload_invalid_pdf(self): """ GIVEN: Invalid PDF named "*.pdf" that mime_type is in settings.CONSUMER_PDF_RECOVERABLE_MIME_TYPES diff --git a/src/documents/views.py b/src/documents/views.py index a856883f3..aceea6699 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -1385,6 +1385,7 @@ class PostDocumentView(GenericAPIView): created = serializer.validated_data.get("created") archive_serial_number = serializer.validated_data.get("archive_serial_number") custom_field_ids = serializer.validated_data.get("custom_fields") + from_webui = serializer.validated_data.get("from_webui") t = int(mktime(datetime.now().timetuple())) @@ -1399,7 +1400,7 @@ class PostDocumentView(GenericAPIView): os.utime(temp_file_path, times=(t, t)) input_doc = ConsumableDocument( - source=DocumentSource.ApiUpload, + source=DocumentSource.WebUI if from_webui else DocumentSource.ApiUpload, original_file=temp_file_path, ) input_doc_overrides = DocumentMetadataOverrides(