Backend update only version label

This commit is contained in:
shamoon
2026-02-25 19:19:19 -08:00
parent d68df8170d
commit c598275d4e
3 changed files with 217 additions and 17 deletions

View File

@@ -2080,6 +2080,22 @@ class DocumentVersionSerializer(serializers.Serializer):
validate_document = PostDocumentSerializer().validate_document
class DocumentVersionLabelSerializer(serializers.Serializer):
version_label = serializers.CharField(
label="Version label",
required=True,
allow_blank=True,
allow_null=True,
max_length=64,
)
def validate_version_label(self, value):
if value is None:
return None
normalized = value.strip()
return normalized or None
class BulkDownloadSerializer(DocumentListSerializer):
content = serializers.ChoiceField(
choices=["archive", "originals", "both"],

View File

@@ -325,6 +325,107 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_update_version_label_updates_and_trims(self) -> None:
root = Document.objects.create(
title="root",
checksum="root",
mime_type="application/pdf",
)
version = Document.objects.create(
title="v1",
checksum="v1",
mime_type="application/pdf",
root_document=root,
version_label="old",
)
resp = self.client.patch(
f"/api/documents/{root.id}/versions/{version.id}/",
{"version_label": " Label 1 "},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
version.refresh_from_db()
self.assertEqual(version.version_label, "Label 1")
self.assertEqual(resp.data["version_label"], "Label 1")
self.assertEqual(resp.data["id"], version.id)
self.assertFalse(resp.data["is_root"])
def test_update_version_label_clears_on_blank(self) -> None:
root = Document.objects.create(
title="root",
checksum="root",
mime_type="application/pdf",
version_label="Root Label",
)
resp = self.client.patch(
f"/api/documents/{root.id}/versions/{root.id}/",
{"version_label": " "},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
root.refresh_from_db()
self.assertIsNone(root.version_label)
self.assertIsNone(resp.data["version_label"])
self.assertTrue(resp.data["is_root"])
def test_update_version_label_returns_403_without_permission(self) -> None:
owner = User.objects.create_user(username="owner")
other = User.objects.create_user(username="other")
other.user_permissions.add(
Permission.objects.get(codename="change_document"),
)
root = Document.objects.create(
title="root",
checksum="root",
mime_type="application/pdf",
owner=owner,
)
version = Document.objects.create(
title="v1",
checksum="v1",
mime_type="application/pdf",
root_document=root,
)
self.client.force_authenticate(user=other)
resp = self.client.patch(
f"/api/documents/{root.id}/versions/{version.id}/",
{"version_label": "Blocked"},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
def test_update_version_label_returns_404_for_unrelated_version(self) -> None:
root = Document.objects.create(
title="root",
checksum="root",
mime_type="application/pdf",
)
other_root = Document.objects.create(
title="other",
checksum="other",
mime_type="application/pdf",
)
other_version = Document.objects.create(
title="other-v1",
checksum="other-v1",
mime_type="application/pdf",
root_document=other_root,
)
resp = self.client.patch(
f"/api/documents/{root.id}/versions/{other_version.id}/",
{"version_label": "Nope"},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_download_version_param_errors(self) -> None:
root = self._create_pdf(title="root", checksum="root")

View File

@@ -178,6 +178,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 DocumentVersionLabelSerializer
from documents.serialisers import DocumentVersionSerializer
from documents.serialisers import EmailSerializer
from documents.serialisers import NotesSerializer
@@ -1663,6 +1664,31 @@ class DocumentViewSet(
"Error updating document, check logs for more detail.",
)
def _get_root_doc_for_version_action(self, pk) -> Document:
try:
root_doc = Document.objects.select_related(
"owner",
"root_document",
).get(pk=pk)
except Document.DoesNotExist:
raise Http404
return get_root_document(root_doc)
def _get_version_doc_for_root(self, root_doc: Document, version_id) -> Document:
try:
version_doc = Document.objects.select_related("owner").get(
pk=version_id,
)
except Document.DoesNotExist:
raise Http404
if (
version_doc.id != root_doc.id
and version_doc.root_document_id != root_doc.id
):
raise Http404
return version_doc
@extend_schema(
operation_id="documents_delete_version",
parameters=[
@@ -1686,14 +1712,7 @@ class DocumentViewSet(
url_path=r"versions/(?P<version_id>\d+)",
)
def delete_version(self, request, pk=None, version_id=None):
try:
root_doc = Document.objects.select_related(
"owner",
"root_document",
).get(pk=pk)
root_doc = get_root_document(root_doc)
except Document.DoesNotExist:
raise Http404
root_doc = self._get_root_doc_for_version_action(pk)
if request.user is not None and not has_perms_owner_aware(
request.user,
@@ -1702,21 +1721,13 @@ class DocumentViewSet(
):
return HttpResponseForbidden("Insufficient permissions")
try:
version_doc = Document.objects.select_related("owner").get(
pk=version_id,
)
except Document.DoesNotExist:
raise Http404
version_doc = self._get_version_doc_for_root(root_doc, version_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
index.remove_document_from_index(version_doc)
@@ -1752,6 +1763,78 @@ class DocumentViewSet(
},
)
@extend_schema(
operation_id="documents_update_version_label",
request=DocumentVersionLabelSerializer,
parameters=[
OpenApiParameter(
name="version_id",
type=OpenApiTypes.INT,
location=OpenApiParameter.PATH,
),
],
responses=inline_serializer(
name="UpdateDocumentVersionLabelResult",
fields={
"id": serializers.IntegerField(),
"added": serializers.DateTimeField(),
"version_label": serializers.CharField(
required=False,
allow_null=True,
),
"checksum": serializers.CharField(
required=False,
allow_null=True,
),
"is_root": serializers.BooleanField(),
},
),
)
@delete_version.mapping.patch
def update_version_label(self, request, pk=None, version_id=None):
serializer = DocumentVersionLabelSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
root_doc = self._get_root_doc_for_version_action(pk)
if request.user is not None and not has_perms_owner_aware(
request.user,
"change_document",
root_doc,
):
return HttpResponseForbidden("Insufficient permissions")
version_doc = self._get_version_doc_for_root(root_doc, version_id)
old_label = version_doc.version_label
version_doc.version_label = serializer.validated_data["version_label"]
version_doc.save(update_fields=["version_label"])
if settings.AUDIT_LOG_ENABLED and old_label != version_doc.version_label:
actor = (
request.user if request.user and request.user.is_authenticated else None
)
LogEntry.objects.log_create(
instance=root_doc,
changes={
"Version Label": [old_label, version_doc.version_label],
},
action=LogEntry.Action.UPDATE,
actor=actor,
additional_data={
"reason": "Version label updated",
"version_id": version_doc.id,
},
)
return Response(
{
"id": version_doc.id,
"added": version_doc.added,
"version_label": version_doc.version_label,
"checksum": version_doc.checksum,
"is_root": version_doc.id == root_doc.id,
},
)
class ChatStreamingSerializer(serializers.Serializer):
q = serializers.CharField(required=True)