mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-17 10:13:56 -05:00
Fix: allow removing dead document links from UI, validate via API (#8081)
This commit is contained in:
parent
d4a20c7e30
commit
35907313e8
@ -706,7 +706,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
||||||
<context context-type="linenumber">51</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/permissions-dialog/permissions-dialog.component.html</context>
|
||||||
@ -4857,18 +4857,29 @@
|
|||||||
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
||||||
<context context-type="linenumber">43</context>
|
<context context-type="linenumber">43</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
||||||
|
<context context-type="linenumber">49</context>
|
||||||
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1388712764439031120" datatype="html">
|
<trans-unit id="1388712764439031120" datatype="html">
|
||||||
<source>Open link</source>
|
<source>Open link</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">45</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/url/url.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/url/url.component.html</context>
|
||||||
<context context-type="linenumber">14</context>
|
<context context-type="linenumber">14</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="6595008830732269870" datatype="html">
|
||||||
|
<source>Not found</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/input/document-link/document-link.component.html</context>
|
||||||
|
<context context-type="linenumber">50</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="5676637575587497817" datatype="html">
|
<trans-unit id="5676637575587497817" datatype="html">
|
||||||
<source>Search for documents</source>
|
<source>Search for documents</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
|
@ -41,9 +41,15 @@
|
|||||||
<ng-template ng-label-tmp let-document="item">
|
<ng-template ng-label-tmp let-document="item">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<button class="btn p-0 lh-1" *ngIf="!disabled" (click)="unselect(document)" title="Remove link" i18n-title><i-bs name="x"></i-bs></button>
|
<button class="btn p-0 lh-1" *ngIf="!disabled" (click)="unselect(document)" title="Remove link" i18n-title><i-bs name="x"></i-bs></button>
|
||||||
|
@if (document.title) {
|
||||||
<a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();" title="Open link" i18n-title>
|
<a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();" title="Open link" i18n-title>
|
||||||
<i-bs width="0.9em" height="0.9em" name="file-text"></i-bs> <span>{{document.title}}</span>
|
<i-bs width="0.9em" height="0.9em" name="file-text"></i-bs> <span>{{document.title}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
} @else {
|
||||||
|
<span class="badge bg-light text-muted" (click)="unselect(document)" title="Remove link" i18n-title>
|
||||||
|
<i-bs width="0.9em" height="0.9em" name="exclamation-triangle-fill"></i-bs> <span i18n>Not found</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ng-loadingspinner-tmp>
|
<ng-template ng-loadingspinner-tmp>
|
||||||
|
@ -71,9 +71,9 @@ export class DocumentLinkComponent
|
|||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((documentResults) => {
|
.subscribe((documentResults) => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.selectedDocuments = documentIDs
|
this.selectedDocuments = documentIDs.map((id) =>
|
||||||
.map((id) => documentResults.results.find((d) => d.id === id))
|
documentResults.results.find((d) => d.id === id)
|
||||||
.filter((d) => d)
|
)
|
||||||
super.writeValue(documentIDs)
|
super.writeValue(documentIDs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ export class DocumentLinkComponent
|
|||||||
|
|
||||||
unselect(document: Document): void {
|
unselect(document: Document): void {
|
||||||
this.selectedDocuments = this.selectedDocuments.filter(
|
this.selectedDocuments = this.selectedDocuments.filter(
|
||||||
(d) => d.id !== document.id
|
(d) => d && d.id !== document.id
|
||||||
)
|
)
|
||||||
this.onChange(this.selectedDocuments.map((d) => d.id))
|
this.onChange(this.selectedDocuments.map((d) => d.id))
|
||||||
}
|
}
|
||||||
|
@ -651,6 +651,14 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
|
|||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
f"Value must be index of an element in {select_options}",
|
f"Value must be index of an element in {select_options}",
|
||||||
)
|
)
|
||||||
|
elif field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
|
||||||
|
doc_ids = data["value"]
|
||||||
|
if Document.objects.filter(id__in=doc_ids).count() != len(
|
||||||
|
data["value"],
|
||||||
|
):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"Some documents in value don't exist or were specified twice.",
|
||||||
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -740,6 +740,42 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(CustomFieldInstance.objects.count(), 0)
|
self.assertEqual(CustomFieldInstance.objects.count(), 0)
|
||||||
self.assertEqual(len(doc.custom_fields.all()), 0)
|
self.assertEqual(len(doc.custom_fields.all()), 0)
|
||||||
|
|
||||||
|
def test_custom_field_value_documentlink_validation(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Document & custom field exist
|
||||||
|
WHEN:
|
||||||
|
- API request to set a field value to a document that does not exist
|
||||||
|
THEN:
|
||||||
|
- HTTP 400 is returned
|
||||||
|
- No field instance is created or attached to the document
|
||||||
|
"""
|
||||||
|
|
||||||
|
doc = Document.objects.create(
|
||||||
|
title="WOW",
|
||||||
|
content="the content",
|
||||||
|
checksum="123",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
)
|
||||||
|
custom_field_documentlink = CustomField.objects.create(
|
||||||
|
name="Test Custom Field Doc Link",
|
||||||
|
data_type=CustomField.FieldDataType.DOCUMENTLINK,
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = self.client.patch(
|
||||||
|
f"/api/documents/{doc.id}/",
|
||||||
|
data={
|
||||||
|
"custom_fields": [
|
||||||
|
{"field": custom_field_documentlink.id, "value": [999]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(CustomFieldInstance.objects.count(), 0)
|
||||||
|
self.assertEqual(len(doc.custom_fields.all()), 0)
|
||||||
|
|
||||||
def test_custom_field_not_null(self):
|
def test_custom_field_not_null(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user