From 80149324195576d239ed0fe5a5f6e5332faec334 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:26:13 -0800 Subject: [PATCH] head --> root to avoid confusion, prevent root deletion [ci skip] --- .../document-detail.component.html | 22 ++-- .../document-detail.component.ts | 16 ++- src-ui/src/app/data/document.ts | 3 +- .../src/app/services/rest/document.service.ts | 10 +- src/documents/bulk_edit.py | 12 +- src/documents/conditionals.py | 25 ++-- src/documents/consumer.py | 18 +-- src/documents/data_models.py | 2 +- ...sion.py => 0012_document_root_document.py} | 4 +- .../migrations/0013_document_version_label.py | 2 +- src/documents/models.py | 10 +- src/documents/serialisers.py | 14 ++- src/documents/tasks.py | 2 +- src/documents/tests/test_bulk_edit.py | 8 +- src/documents/tests/test_document_model.py | 10 +- src/documents/views.py | 116 ++++++++++-------- 16 files changed, 150 insertions(+), 124 deletions(-) rename src/documents/migrations/{0012_document_head_version.py => 0012_document_root_document.py} (86%) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 8e07f2f30..65c8c6283 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -87,16 +87,18 @@ @if (selectedVersionId === version.id) { } - - Delete version - + @if (!version.is_root) { + + Delete version + + } } diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 79d576d66..b7366162e 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -451,7 +451,7 @@ export class DocumentDetailComponent } private loadDocument(documentId: number): void { - let redirectedToHead = false + let redirectedToRoot = false this.selectedVersionId = documentId this.previewUrl = this.documentsService.getPreviewUrl( this.selectedVersionId @@ -477,14 +477,14 @@ export class DocumentDetailComponent .pipe( catchError((error) => { if (error?.status === 404) { - return this.documentsService.getHeadId(documentId).pipe( + return this.documentsService.getRootId(documentId).pipe( map((result) => { - const headId = result?.head_id - if (headId && headId !== documentId) { + const rootId = result?.root_id + if (rootId && rootId !== documentId) { const section = this.route.snapshot.paramMap.get('section') || 'details' - redirectedToHead = true - this.router.navigate(['documents', headId, section], { + redirectedToRoot = true + this.router.navigate(['documents', rootId, section], { replaceUrl: true, }) } @@ -503,7 +503,7 @@ export class DocumentDetailComponent .subscribe({ next: (doc) => { if (!doc) { - if (redirectedToHead) { + if (redirectedToRoot) { return } this.router.navigate(['404'], { replaceUrl: true }) @@ -786,8 +786,6 @@ export class DocumentDetailComponent } getVersionBadge(version: DocumentVersionInfo): string { - console.log(version) - const checksum = version?.checksum ?? '' if (!checksum) return '----' return checksum.slice(0, 4).toUpperCase() diff --git a/src-ui/src/app/data/document.ts b/src-ui/src/app/data/document.ts index a936576a5..93a6cbe84 100644 --- a/src-ui/src/app/data/document.ts +++ b/src-ui/src/app/data/document.ts @@ -162,7 +162,7 @@ export interface Document extends ObjectWithPermissions { duplicate_documents?: Document[] // Versioning - head_version?: number + root_document?: number versions?: DocumentVersionInfo[] // Frontend only @@ -174,4 +174,5 @@ export interface DocumentVersionInfo { added?: Date label?: string checksum?: string + is_root: boolean } diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts index b3e897e0a..de4e535aa 100644 --- a/src-ui/src/app/services/rest/document.service.ts +++ b/src-ui/src/app/services/rest/document.service.ts @@ -211,15 +211,15 @@ export class DocumentService extends AbstractPaperlessService { }) } - getHeadId(documentId: number) { - return this.http.get<{ head_id: number }>( - this.getResourceUrl(documentId, 'head') + getRootId(documentId: number) { + return this.http.get<{ root_id: number }>( + this.getResourceUrl(documentId, 'root') ) } - deleteVersion(headDocumentId: number, versionId: number) { + deleteVersion(rootDocumentId: number, versionId: number) { return this.http.delete<{ result: string; current_version_id: number }>( - this.getResourceUrl(headDocumentId, `versions/${versionId}`) + this.getResourceUrl(rootDocumentId, `versions/${versionId}`) ) } diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py index 00570dc94..c5b921a38 100644 --- a/src/documents/bulk_edit.py +++ b/src/documents/bulk_edit.py @@ -309,13 +309,13 @@ def modify_custom_fields( @shared_task def delete(doc_ids: list[int]) -> Literal["OK"]: try: - head_ids = ( - Document.objects.filter(id__in=doc_ids, head_version__isnull=True) + root_ids = ( + Document.objects.filter(id__in=doc_ids, root_document__isnull=True) .values_list("id", flat=True) .distinct() ) version_ids = ( - Document.objects.filter(head_version_id__in=head_ids) + Document.objects.filter(root_document_id__in=root_ids) .values_list("id", flat=True) .distinct() ) @@ -407,7 +407,7 @@ def rotate(doc_ids: list[int], degrees: int) -> Literal["OK"]: ConsumableDocument( source=DocumentSource.ConsumeFolder, original_file=filepath, - head_version_id=doc.id, + root_document_id=doc.id, ), overrides, ) @@ -627,7 +627,7 @@ def delete_pages(doc_ids: list[int], pages: list[int]) -> Literal["OK"]: ConsumableDocument( source=DocumentSource.ConsumeFolder, original_file=filepath, - head_version_id=doc.id, + root_document_id=doc.id, ), overrides, ) @@ -704,7 +704,7 @@ def edit_pdf( ConsumableDocument( source=DocumentSource.ConsumeFolder, original_file=filepath, - head_version_id=doc.id, + root_document_id=doc.id, ), overrides, ) diff --git a/src/documents/conditionals.py b/src/documents/conditionals.py index 837ec5046..b809d828c 100644 --- a/src/documents/conditionals.py +++ b/src/documents/conditionals.py @@ -18,19 +18,19 @@ def _resolve_effective_doc(pk: int, request) -> Document | None: """ Resolve which Document row should be considered for caching keys: - If a version is requested, use that version - - If pk is a head doc, use its newest child version if present, else the head. + - If pk is a root doc, use its newest child version if present, else the root. - Else, pk is a version, use that version. Returns None if resolution fails (treat as no-cache). """ try: - request_doc = Document.objects.only("id", "head_version_id").get(pk=pk) + request_doc = Document.objects.only("id", "root_document_id").get(pk=pk) except Document.DoesNotExist: return None - head_doc = ( + root_doc = ( request_doc - if request_doc.head_version_id is None - else Document.objects.only("id").get(id=request_doc.head_version_id) + if request_doc.root_document_id is None + else Document.objects.only("id").get(id=request_doc.root_document_id) ) version_param = ( @@ -41,19 +41,22 @@ def _resolve_effective_doc(pk: int, request) -> Document | None: if version_param: try: version_id = int(version_param) - candidate = Document.objects.only("id", "head_version_id").get( + candidate = Document.objects.only("id", "root_document_id").get( id=version_id, ) - if candidate.id != head_doc.id and candidate.head_version_id != head_doc.id: + if ( + candidate.id != root_doc.id + and candidate.root_document_id != root_doc.id + ): return None return candidate except Exception: return None - # Default behavior: if pk is a head doc, prefer its newest child version - if request_doc.head_version_id is None: - latest = head_doc.versions.only("id").order_by("id").last() - return latest or head_doc + # Default behavior: if pk is a root doc, prefer its newest child version + if request_doc.root_document_id is None: + latest = root_doc.versions.only("id").order_by("id").last() + return latest or root_doc # pk is already a version return request_doc diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 3e9552b0c..e689fe0d8 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -116,10 +116,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() + if input_doc.root_document_id: + self.log.debug( + f"Document root document id: {input_doc.root_document_id}", + ) + root_document = Document.objects.get(pk=input_doc.root_document_id) + version_index = root_document.versions.count() self.filename += f"_v{version_index}" def _send_progress( @@ -483,17 +485,17 @@ class ConsumerPlugin( try: with transaction.atomic(): # store the document. - if self.input_doc.head_version_id: + if self.input_doc.root_document_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, + pk=self.input_doc.root_document_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.root_document = Document.objects.get( + pk=self.input_doc.root_document_id, ) file_for_checksum = ( self.unmodified_original diff --git a/src/documents/data_models.py b/src/documents/data_models.py index 89bb9cfa8..db50be171 100644 --- a/src/documents/data_models.py +++ b/src/documents/data_models.py @@ -163,7 +163,7 @@ class ConsumableDocument: source: DocumentSource original_file: Path - head_version_id: int | None = None + root_document_id: int | None = None original_path: Path | None = None mailrule_id: int | None = None mime_type: str = dataclasses.field(init=False, default=None) diff --git a/src/documents/migrations/0012_document_head_version.py b/src/documents/migrations/0012_document_root_document.py similarity index 86% rename from src/documents/migrations/0012_document_head_version.py rename to src/documents/migrations/0012_document_root_document.py index b0455539f..992636261 100644 --- a/src/documents/migrations/0012_document_head_version.py +++ b/src/documents/migrations/0012_document_root_document.py @@ -13,14 +13,14 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name="document", - name="head_version", + name="root_document", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="versions", to="documents.document", - verbose_name="head version of document", + verbose_name="root document for this version", ), ), ] diff --git a/src/documents/migrations/0013_document_version_label.py b/src/documents/migrations/0013_document_version_label.py index 058615266..72562af23 100644 --- a/src/documents/migrations/0013_document_version_label.py +++ b/src/documents/migrations/0013_document_version_label.py @@ -6,7 +6,7 @@ from django.db import models class Migration(migrations.Migration): dependencies = [ - ("documents", "0012_document_head_version"), + ("documents", "0012_document_root_document"), ] operations = [ diff --git a/src/documents/models.py b/src/documents/models.py index 4888d1439..804e1f155 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -308,13 +308,13 @@ class Document(SoftDeleteModel, ModelWithOwner): ), ) - head_version = models.ForeignKey( + root_document = models.ForeignKey( "self", blank=True, null=True, related_name="versions", on_delete=models.CASCADE, - verbose_name=_("head version of document"), + verbose_name=_("root document for this version"), ) version_label = models.CharField( @@ -441,9 +441,9 @@ class Document(SoftDeleteModel, ModelWithOwner): *args, **kwargs, ): - # If deleting a head document, move all versions to trash as well. - if self.head_version_id is None: - Document.objects.filter(head_version=self).delete() + # If deleting a root document, move all its versions to trash as well. + if self.root_document_id is None: + Document.objects.filter(root_document=self).delete() return super().delete( *args, **kwargs, diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 2b3649475..6cd392822 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1046,7 +1046,7 @@ def _get_viewable_duplicates( duplicates = Document.global_objects.filter( Q(checksum__in=checksums) | Q(archive_checksum__in=checksums), ).exclude(pk=document.pk) - duplicates = duplicates.filter(head_version__isnull=True) + duplicates = duplicates.filter(root_document__isnull=True) duplicates = duplicates.order_by("-created") allowed = get_objects_for_user_owner_aware( user, @@ -1068,6 +1068,7 @@ class DocumentVersionInfoSerializer(serializers.Serializer): added = serializers.DateTimeField() label = serializers.CharField(required=False, allow_null=True) checksum = serializers.CharField(required=False, allow_null=True) + is_root = serializers.BooleanField() @extend_schema_serializer( @@ -1090,7 +1091,7 @@ class DocumentSerializer( duplicate_documents = SerializerMethodField() notes = NotesSerializer(many=True, required=False, read_only=True) - head_version = serializers.PrimaryKeyRelatedField(read_only=True) + root_document = serializers.PrimaryKeyRelatedField(read_only=True) versions = SerializerMethodField() custom_fields = CustomFieldInstanceSerializer( @@ -1127,14 +1128,14 @@ class DocumentSerializer( @extend_schema_field(DocumentVersionInfoSerializer(many=True)) def get_versions(self, obj): - head_doc = obj if obj.head_version_id is None else obj.head_version - versions_qs = Document.objects.filter(head_version=head_doc).only( + root_doc = obj if obj.root_document_id is None else obj.root_document + versions_qs = Document.objects.filter(root_document=root_doc).only( "id", "added", "checksum", "version_label", ) - versions = [*versions_qs, head_doc] + versions = [*versions_qs, root_doc] def build_info(doc: Document) -> dict[str, object]: return { @@ -1142,6 +1143,7 @@ class DocumentSerializer( "added": doc.added, "label": doc.version_label, "checksum": doc.checksum, + "is_root": doc.id == root_doc.id, } info = [build_info(doc) for doc in versions] @@ -1336,7 +1338,7 @@ class DocumentSerializer( "remove_inbox_tags", "page_count", "mime_type", - "head_version", + "root_document", "versions", ) list_serializer_class = OwnedObjectListSerializer diff --git a/src/documents/tasks.py b/src/documents/tasks.py index f884a99ee..019042c05 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -158,7 +158,7 @@ def consume_file( plugins: list[type[ConsumeTaskPlugin]] = ( [ConsumerPreflightPlugin, ConsumerPlugin] - if input_doc.head_version_id is not None + if input_doc.root_document_id is not None else [ ConsumerPreflightPlugin, AsnCheckPlugin, diff --git a/src/documents/tests/test_bulk_edit.py b/src/documents/tests/test_bulk_edit.py index 86135d030..c212df5e8 100644 --- a/src/documents/tests/test_bulk_edit.py +++ b/src/documents/tests/test_bulk_edit.py @@ -940,7 +940,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): doc_ids, ): consumable, overrides = call.args - self.assertEqual(consumable.head_version_id, expected_id) + self.assertEqual(consumable.root_document_id, expected_id) self.assertIsNotNone(overrides) self.assertEqual(result, "OK") @@ -990,7 +990,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): self.assertIn(expected_str, output_str) self.assertEqual(mock_consume_delay.call_count, 1) consumable, overrides = mock_consume_delay.call_args[0] - self.assertEqual(consumable.head_version_id, self.doc2.id) + self.assertEqual(consumable.root_document_id, self.doc2.id) self.assertIsNotNone(overrides) self.assertEqual(result, "OK") @@ -1013,7 +1013,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): mock_pdf_save.assert_called_once() mock_consume_delay.assert_called_once() consumable, overrides = mock_consume_delay.call_args[0] - self.assertEqual(consumable.head_version_id, self.doc2.id) + self.assertEqual(consumable.root_document_id, self.doc2.id) self.assertTrue(str(consumable.original_file).endswith("_pages_deleted.pdf")) self.assertIsNotNone(overrides) self.assertEqual(result, "OK") @@ -1162,7 +1162,7 @@ class TestPDFActions(DirectoriesMixin, TestCase): self.assertEqual(result, "OK") mock_consume_delay.assert_called_once() consumable, overrides = mock_consume_delay.call_args[0] - self.assertEqual(consumable.head_version_id, self.doc2.id) + self.assertEqual(consumable.root_document_id, self.doc2.id) self.assertTrue(str(consumable.original_file).endswith("_edited.pdf")) self.assertIsNotNone(overrides) diff --git a/src/documents/tests/test_document_model.py b/src/documents/tests/test_document_model.py index 2a57ecf19..d03541d55 100644 --- a/src/documents/tests/test_document_model.py +++ b/src/documents/tests/test_document_model.py @@ -78,8 +78,8 @@ class TestDocument(TestCase): empty_trash([document.pk]) self.assertEqual(mock_unlink.call_count, 2) - def test_delete_head_deletes_versions(self) -> None: - head = Document.objects.create( + def test_delete_root_deletes_versions(self) -> None: + root = Document.objects.create( correspondent=Correspondent.objects.create(name="Test0"), title="Head", content="content", @@ -87,15 +87,15 @@ class TestDocument(TestCase): mime_type="application/pdf", ) Document.objects.create( - head_version=head, - correspondent=head.correspondent, + root_document=root, + correspondent=root.correspondent, title="Version", content="content", checksum="checksum2", mime_type="application/pdf", ) - head.delete() + root.delete() self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.deleted_objects.count(), 2) diff --git a/src/documents/views.py b/src/documents/views.py index 46c396fac..34a9dc637 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -778,7 +778,7 @@ class DocumentViewSet( def get_queryset(self): return ( - Document.objects.filter(head_version__isnull=True) + Document.objects.filter(root_document__isnull=True) .distinct() .order_by("-created") .annotate(num_notes=Count("notes")) @@ -805,25 +805,35 @@ class DocumentViewSet( ) return super().get_serializer(*args, **kwargs) - @action(methods=["get"], detail=True, url_path="head") - def head(self, request, pk=None): + @action(methods=["get"], detail=True, url_path="root") + def root(self, request, pk=None): try: doc = Document.global_objects.select_related( "owner", - "head_version", + "root_document", ).get(pk=pk) except Document.DoesNotExist: raise Http404 - head_doc = doc if doc.head_version_id is None else doc.head_version + root_doc = doc if doc.root_document_id is None else doc.root_document if request.user is not None and not has_perms_owner_aware( request.user, "view_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") - return Response({"head_id": head_doc.id}) + return Response({"root_id": root_doc.id}) + + @action(methods=["get"], detail=True, url_path="head") + def head(self, request, pk=None): + """ + Backwards-compatible alias for the `root` endpoint. + """ + response = self.root(request, pk=pk) + if isinstance(response, Response): + return Response({"head_id": response.data.get("root_id")}) + return response def update(self, request, *args, **kwargs): response = super().update(request, *args, **kwargs) @@ -861,7 +871,7 @@ class DocumentViewSet( and request.query_params["original"] == "true" ) - def _resolve_file_doc(self, head_doc: Document, request): + def _resolve_file_doc(self, root_doc: Document, request): version_param = request.query_params.get("version") if version_param: try: @@ -874,36 +884,39 @@ class DocumentViewSet( ) except Document.DoesNotExist: raise Http404 - if candidate.id != head_doc.id and candidate.head_version_id != head_doc.id: + if ( + candidate.id != root_doc.id + and candidate.root_document_id != root_doc.id + ): raise Http404 return candidate - latest = head_doc.versions.order_by("id").last() - return latest or head_doc + latest = root_doc.versions.order_by("id").last() + return latest or root_doc def file_response(self, pk, request, disposition): request_doc = Document.global_objects.select_related("owner").get(id=pk) - head_doc = ( + root_doc = ( request_doc - if request_doc.head_version_id is None + if request_doc.root_document_id is None else Document.global_objects.select_related("owner").get( - id=request_doc.head_version_id, + id=request_doc.root_document_id, ) ) if request.user is not None and not has_perms_owner_aware( request.user, "view_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") # If a version is explicitly requested, use it. Otherwise: - # - if pk is a head document: serve newest version + # - if pk is a root document: serve newest version # - if pk is a version: serve that version if "version" in request.query_params: - file_doc = self._resolve_file_doc(head_doc, request) + file_doc = self._resolve_file_doc(root_doc, request) else: file_doc = ( - self._resolve_file_doc(head_doc, request) - if request_doc.head_version_id is None + self._resolve_file_doc(root_doc, request) + if request_doc.root_document_id is None else request_doc ) return serve_file( @@ -945,17 +958,17 @@ class DocumentViewSet( def metadata(self, request, pk=None): try: request_doc = Document.objects.select_related("owner").get(pk=pk) - head_doc = ( + root_doc = ( request_doc - if request_doc.head_version_id is None + if request_doc.root_document_id is None else Document.objects.select_related("owner").get( - id=request_doc.head_version_id, + id=request_doc.root_document_id, ) ) if request.user is not None and not has_perms_owner_aware( request.user, "view_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") except Document.DoesNotExist: @@ -963,11 +976,11 @@ class DocumentViewSet( # Choose the effective document (newest version by default, or explicit via ?version=) if "version" in request.query_params: - doc = self._resolve_file_doc(head_doc, request) + doc = self._resolve_file_doc(root_doc, request) else: doc = ( - self._resolve_file_doc(head_doc, request) - if request_doc.head_version_id is None + self._resolve_file_doc(root_doc, request) + if request_doc.root_document_id is None else request_doc ) @@ -1140,26 +1153,26 @@ class DocumentViewSet( def preview(self, request, pk=None): try: request_doc = Document.objects.select_related("owner").get(id=pk) - head_doc = ( + root_doc = ( request_doc - if request_doc.head_version_id is None + if request_doc.root_document_id is None else Document.objects.select_related("owner").get( - id=request_doc.head_version_id, + id=request_doc.root_document_id, ) ) if request.user is not None and not has_perms_owner_aware( request.user, "view_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") if "version" in request.query_params: - file_doc = self._resolve_file_doc(head_doc, request) + file_doc = self._resolve_file_doc(root_doc, request) else: file_doc = ( - self._resolve_file_doc(head_doc, request) - if request_doc.head_version_id is None + self._resolve_file_doc(root_doc, request) + if request_doc.root_document_id is None else request_doc ) @@ -1178,25 +1191,25 @@ class DocumentViewSet( def thumb(self, request, pk=None): try: request_doc = Document.objects.select_related("owner").get(id=pk) - head_doc = ( + root_doc = ( request_doc - if request_doc.head_version_id is None + if request_doc.root_document_id is None else Document.objects.select_related("owner").get( - id=request_doc.head_version_id, + id=request_doc.root_document_id, ) ) if request.user is not None and not has_perms_owner_aware( request.user, "view_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") if "version" in request.query_params: - file_doc = self._resolve_file_doc(head_doc, request) + file_doc = self._resolve_file_doc(root_doc, request) else: file_doc = ( - self._resolve_file_doc(head_doc, request) - if request_doc.head_version_id is None + self._resolve_file_doc(root_doc, request) + if request_doc.root_document_id is None else request_doc ) handle = file_doc.thumbnail_file @@ -1526,7 +1539,7 @@ class DocumentViewSet( input_doc = ConsumableDocument( source=DocumentSource.ApiUpload, original_file=temp_file_path, - head_version_id=doc.pk, + root_document_id=doc.pk, ) overrides = DocumentMetadataOverrides() @@ -1554,10 +1567,10 @@ class DocumentViewSet( ) def delete_version(self, request, pk=None, version_id=None): try: - head_doc = Document.objects.select_related("owner").get(pk=pk) - if head_doc.head_version_id is not None: - head_doc = Document.objects.select_related("owner").get( - pk=head_doc.head_version_id, + root_doc = Document.objects.select_related("owner").get(pk=pk) + if root_doc.root_document_id is not None: + root_doc = Document.objects.select_related("owner").get( + pk=root_doc.root_document_id, ) except Document.DoesNotExist: raise Http404 @@ -1565,7 +1578,7 @@ class DocumentViewSet( if request.user is not None and not has_perms_owner_aware( request.user, "delete_document", - head_doc, + root_doc, ): return HttpResponseForbidden("Insufficient permissions") @@ -1576,7 +1589,12 @@ class DocumentViewSet( except Document.DoesNotExist: raise Http404 - if version_doc.head_version_id != head_doc.id: + if version_doc.id == root_doc.id: + return HttpResponseBadRequest( + "Cannot delete the root/original version. Delete the document instead.", + ) + + if version_doc.root_document_id != root_doc.id: raise Http404 from documents import index @@ -1585,14 +1603,14 @@ class DocumentViewSet( version_doc.delete() current = ( - Document.objects.filter(Q(id=head_doc.id) | Q(head_version=head_doc)) + Document.objects.filter(Q(id=root_doc.id) | Q(root_document=root_doc)) .order_by("-id") .first() ) return Response( { "result": "OK", - "current_version_id": current.id if current else head_doc.id, + "current_version_id": current.id if current else root_doc.id, }, )