diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 86641a243..da0d14063 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -113,6 +113,12 @@ class ConsumerPluginMixin: self.filename = self.metadata.filename or self.input_doc.original_file.name + if input_doc.head_version_id: + self.log.debug(f"Document head version id: {input_doc.head_version_id}") + head_version = Document.objects.get(pk=input_doc.head_version_id) + version_index = head_version.versions.count() + self.filename += f"_v{version_index}" + def _send_progress( self, current_progress: int, @@ -470,12 +476,28 @@ class ConsumerPlugin( try: with transaction.atomic(): # store the document. - document = self._store( - text=text, - date=date, - page_count=page_count, - mime_type=mime_type, - ) + if self.input_doc.head_version_id: + # If this is a new version of an existing document, we need + # to make sure we're not creating a new document, but updating + # the existing one. + original_document = Document.objects.get( + pk=self.input_doc.head_version_id, + ) + self.log.debug("Saving record for updated version to database") + original_document.pk = None + original_document.head_version = Document.objects.get( + pk=self.input_doc.head_version_id, + ) + original_document.modified = timezone.now() + original_document.save() + document = original_document + else: + document = self._store( + text=text, + date=date, + page_count=page_count, + mime_type=mime_type, + ) # If we get here, it was successful. Proceed with post-consume # hooks. If they fail, nothing will get changed. diff --git a/src/documents/data_models.py b/src/documents/data_models.py index fbba36dcc..8ffca6010 100644 --- a/src/documents/data_models.py +++ b/src/documents/data_models.py @@ -156,6 +156,7 @@ class ConsumableDocument: source: DocumentSource original_file: Path + head_version_id: int | None = None mailrule_id: int | None = None mime_type: str = dataclasses.field(init=False, default=None) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index db58b29bb..192c4e36a 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1869,6 +1869,15 @@ class PostDocumentSerializer(serializers.Serializer): return created.date() +class DocumentVersionSerializer(serializers.Serializer): + document = serializers.FileField( + label="Document", + write_only=True, + ) + + validate_document = PostDocumentSerializer().validate_document + + class BulkDownloadSerializer(DocumentListSerializer): content = serializers.ChoiceField( choices=["archive", "originals", "both"], diff --git a/src/documents/tasks.py b/src/documents/tasks.py index 17bfce3b0..f6690687f 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -145,13 +145,17 @@ def consume_file( if overrides is None: overrides = DocumentMetadataOverrides() - plugins: list[type[ConsumeTaskPlugin]] = [ - ConsumerPreflightPlugin, - CollatePlugin, - BarcodePlugin, - WorkflowTriggerPlugin, - ConsumerPlugin, - ] + plugins: list[type[ConsumeTaskPlugin]] = ( + [ConsumerPreflightPlugin,ConsumerPlugin] + if input_doc.head_version_id is not None + else [ + ConsumerPreflightPlugin, + CollatePlugin, + BarcodePlugin, + WorkflowTriggerPlugin, + ConsumerPlugin, + ] + ) with ( ProgressManager( diff --git a/src/documents/views.py b/src/documents/views.py index bb25d608b..5b5df794e 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -147,6 +147,7 @@ from documents.serialisers import CustomFieldSerializer from documents.serialisers import DocumentListSerializer from documents.serialisers import DocumentSerializer from documents.serialisers import DocumentTypeSerializer +from documents.serialisers import DocumentVersionSerializer from documents.serialisers import NotesSerializer from documents.serialisers import PostDocumentSerializer from documents.serialisers import RunTaskViewSerializer @@ -1104,6 +1105,56 @@ class DocumentViewSet( "Error emailing document, check logs for more detail.", ) + @action(methods=["post"], detail=True) + def update_version(self, request, pk=None): + serializer = DocumentVersionSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + try: + doc = Document.objects.select_related("owner").get(pk=pk) + if request.user is not None and not has_perms_owner_aware( + request.user, + "change_document", + doc, + ): + return HttpResponseForbidden("Insufficient permissions") + except Document.DoesNotExist: + raise Http404 + + try: + doc_name, doc_data = serializer.validated_data.get("document") + + t = int(mktime(datetime.now().timetuple())) + + settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True) + + temp_file_path = Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR)) / Path( + pathvalidate.sanitize_filename(doc_name), + ) + + temp_file_path.write_bytes(doc_data) + + os.utime(temp_file_path, times=(t, t)) + + input_doc = ConsumableDocument( + source=DocumentSource.ApiUpload, + original_file=temp_file_path, + head_version_id=doc.pk, + ) + + async_task = consume_file.delay( + input_doc, + ) + logger.debug( + f"Updated document {doc.id} with new version", + ) + return Response(async_task.id) + except Exception as e: + logger.warning(f"An error occurred updating document: {e!s}") + return HttpResponseServerError( + "Error updating document, check logs for more detail.", + ) + @extend_schema_view( list=extend_schema(