]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: allow removing dead document links from UI, validate via API (#8081)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Mon, 28 Oct 2024 17:39:17 +0000 (10:39 -0700)
committerGitHub <noreply@github.com>
Mon, 28 Oct 2024 17:39:17 +0000 (10:39 -0700)
src-ui/messages.xlf
src-ui/src/app/components/common/input/document-link/document-link.component.html
src-ui/src/app/components/common/input/document-link/document-link.component.ts
src/documents/serialisers.py
src/documents/tests/test_api_custom_fields.py

index dfea48ad7a8e4faa4be57d1c2c047a86a9219df4..fe002792b245959782955528a16d7b2883092fa6 100644 (file)
         </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">51</context>
+          <context context-type="linenumber">57</context>
         </context-group>
         <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/input/document-link/document-link.component.html</context>
           <context context-type="linenumber">43</context>
         </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 id="1388712764439031120" datatype="html">
         <source>Open link</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">44</context>
+          <context context-type="linenumber">45</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/common/input/url/url.component.html</context>
           <context context-type="linenumber">14</context>
         </context-group>
       </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">
         <source>Search for documents</source>
         <context-group purpose="location">
index 80feb8bd94dd1f0b405d84fc5c0a4d6864aa6f3f..f494a2446a0fda729cd0fe97b05e900b348b30d3 100644 (file)
     <ng-template ng-label-tmp let-document="item">
       <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>
-        <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>&nbsp;<span>{{document.title}}</span>
-        </a>
+        @if (document.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>&nbsp;<span>{{document.title}}</span>
+          </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>&nbsp;<span i18n>Not found</span>
+          </span>
+        }
       </div>
     </ng-template>
     <ng-template ng-loadingspinner-tmp>
index 54784b83de6697f24e7f91114e604a8e94fbb4fb..a759682038058a7751fbde32d1ca171fc5a4880f 100644 (file)
@@ -71,9 +71,9 @@ export class DocumentLinkComponent
         .pipe(takeUntil(this.unsubscribeNotifier))
         .subscribe((documentResults) => {
           this.loading = false
-          this.selectedDocuments = documentIDs
-            .map((id) => documentResults.results.find((d) => d.id === id))
-            .filter((d) => d)
+          this.selectedDocuments = documentIDs.map((id) =>
+            documentResults.results.find((d) => d.id === id)
+          )
           super.writeValue(documentIDs)
         })
     }
@@ -114,7 +114,7 @@ export class DocumentLinkComponent
 
   unselect(document: Document): void {
     this.selectedDocuments = this.selectedDocuments.filter(
-      (d) => d.id !== document.id
+      (d) => d && d.id !== document.id
     )
     this.onChange(this.selectedDocuments.map((d) => d.id))
   }
index e08daff09db4810b4c8e99f74f9c6dad2ff5adf8..aeb901f81fae673449f20cffffd43ba8256793ca 100644 (file)
@@ -651,6 +651,14 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
                     raise serializers.ValidationError(
                         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
 
index bfe352d56de819bb887f243810028cdebe0bf7bf..02e856c2754ee1eff1246773adb58bfc232a5e2f 100644 (file)
@@ -740,6 +740,42 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
         self.assertEqual(CustomFieldInstance.objects.count(), 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):
         """
         GIVEN: