Make content edits target a specific version

This commit is contained in:
shamoon
2026-02-12 10:27:49 -08:00
parent 6a0fae67e9
commit b7d3be6f75
6 changed files with 77 additions and 10 deletions

View File

@@ -740,6 +740,18 @@ describe('DocumentDetailComponent', () => {
) )
}) })
it('save should target currently selected version', () => {
initNormally()
component.selectedVersionId = 10
const patchSpy = jest.spyOn(documentService, 'patch')
patchSpy.mockReturnValue(of(doc))
component.save()
expect(patchSpy).toHaveBeenCalled()
expect(patchSpy.mock.calls[0][1]).toEqual(10)
})
it('should show toast error on save if error occurs', () => { it('should show toast error on save if error occurs', () => {
currentUserHasObjectPermissions = true currentUserHasObjectPermissions = true
initNormally() initNormally()

View File

@@ -1079,7 +1079,7 @@ export class DocumentDetailComponent
this.networkActive = true this.networkActive = true
;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change')) ;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change'))
this.documentsService this.documentsService
.patch(this.getChangedFields()) .patch(this.getChangedFields(), this.selectedVersionId)
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
next: (docValues) => { next: (docValues) => {
@@ -1134,7 +1134,7 @@ export class DocumentDetailComponent
this.networkActive = true this.networkActive = true
this.store.next(this.documentForm.value) this.store.next(this.documentForm.value)
this.documentsService this.documentsService
.patch(this.getChangedFields()) .patch(this.getChangedFields(), this.selectedVersionId)
.pipe( .pipe(
switchMap((updateResult) => { switchMap((updateResult) => {
this.savedViewService.maybeRefreshDocumentCounts() this.savedViewService.maybeRefreshDocumentCounts()

View File

@@ -298,6 +298,14 @@ describe(`DocumentService`, () => {
expect(req.request.body.remove_inbox_tags).toEqual(true) expect(req.request.body.remove_inbox_tags).toEqual(true)
}) })
it('should pass selected version to patch when provided', () => {
subscription = service.patch(documents[0], 123).subscribe()
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${documents[0].id}/?version=123`
)
expect(req.request.method).toEqual('PATCH')
})
it('should call appropriate api endpoint for getting audit log', () => { it('should call appropriate api endpoint for getting audit log', () => {
subscription = service.getHistory(documents[0].id).subscribe() subscription = service.getHistory(documents[0].id).subscribe()
const req = httpTestingController.expectOne( const req = httpTestingController.expectOne(

View File

@@ -227,11 +227,14 @@ export class DocumentService extends AbstractPaperlessService<Document> {
return this.http.get<number>(this.getResourceUrl(null, 'next_asn')) return this.http.get<number>(this.getResourceUrl(null, 'next_asn'))
} }
patch(o: Document): Observable<Document> { patch(o: Document, versionID: number = null): Observable<Document> {
o.remove_inbox_tags = !!this.settingsService.get( o.remove_inbox_tags = !!this.settingsService.get(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
) )
return super.patch(o) this.clearCache()
return this.http.patch<Document>(this.getResourceUrl(o.id), o, {
params: versionID ? { version: versionID.toString() } : {},
})
} }
uploadDocument(formData) { uploadDocument(formData) {

View File

@@ -499,6 +499,43 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
self.assertEqual(root.content, "root-content") self.assertEqual(root.content, "root-content")
self.assertEqual(v1.content, "v1-content") self.assertEqual(v1.content, "v1-content")
def test_patch_content_updates_selected_version_content(self) -> None:
root = Document.objects.create(
title="root",
checksum="root",
mime_type="application/pdf",
content="root-content",
)
v1 = Document.objects.create(
title="v1",
checksum="v1",
mime_type="application/pdf",
root_document=root,
content="v1-content",
)
v2 = Document.objects.create(
title="v2",
checksum="v2",
mime_type="application/pdf",
root_document=root,
content="v2-content",
)
resp = self.client.patch(
f"/api/documents/{root.id}/?version={v1.id}",
{"content": "edited-v1"},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.data["content"], "edited-v1")
root.refresh_from_db()
v1.refresh_from_db()
v2.refresh_from_db()
self.assertEqual(v1.content, "edited-v1")
self.assertEqual(v2.content, "v2-content")
self.assertEqual(root.content, "root-content")
def test_retrieve_returns_latest_version_content(self) -> None: def test_retrieve_returns_latest_version_content(self) -> None:
root = Document.objects.create( root = Document.objects.create(
title="root", title="root",

View File

@@ -859,13 +859,17 @@ class DocumentViewSet(
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False) partial = kwargs.pop("partial", False)
root_doc = self.get_object() root_doc = self.get_object()
content_doc = (
self._resolve_file_doc(root_doc, request)
if "version" in request.query_params
else self._get_latest_doc_for_root(root_doc)
)
content_updated = "content" in request.data content_updated = "content" in request.data
updated_content = request.data.get("content") if content_updated else None updated_content = request.data.get("content") if content_updated else None
latest_doc = self._get_latest_doc_for_root(root_doc)
data = request.data.copy() data = request.data.copy()
serializer_partial = partial serializer_partial = partial
if content_updated and latest_doc.id != root_doc.id: if content_updated and content_doc.id != root_doc.id:
if updated_content is None: if updated_content is None:
raise ValidationError({"content": ["This field may not be null."]}) raise ValidationError({"content": ["This field may not be null."]})
data.pop("content", None) data.pop("content", None)
@@ -879,15 +883,18 @@ class DocumentViewSet(
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
self.perform_update(serializer) self.perform_update(serializer)
if content_updated and latest_doc.id != root_doc.id: if content_updated and content_doc.id != root_doc.id:
latest_doc.content = updated_content content_doc.content = updated_content
latest_doc.save(update_fields=["content", "modified"]) content_doc.save(update_fields=["content", "modified"])
if getattr(root_doc, "_prefetched_objects_cache", None): if getattr(root_doc, "_prefetched_objects_cache", None):
root_doc._prefetched_objects_cache = {} root_doc._prefetched_objects_cache = {}
refreshed_doc = self.get_queryset().get(pk=root_doc.pk) refreshed_doc = self.get_queryset().get(pk=root_doc.pk)
response = Response(self.get_serializer(refreshed_doc).data) response_data = self.get_serializer(refreshed_doc).data
if "version" in request.query_params and "content" in response_data:
response_data["content"] = content_doc.content
response = Response(response_data)
from documents import index from documents import index