From e1d8680698b91f335b1130773f10179cfda829bc Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 31 Jan 2025 07:39:22 -0800
Subject: [PATCH] Fix: also ensure symmetric doc link removal on bulk edit
 (#8963)

---
 src/documents/bulk_edit.py            | 23 +++++++++++++++++----
 src/documents/tests/test_bulk_edit.py | 29 ++++++++++++++++++---------
 2 files changed, 38 insertions(+), 14 deletions(-)

diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py
index 7e2d2088e..f0522eddc 100644
--- a/src/documents/bulk_edit.py
+++ b/src/documents/bulk_edit.py
@@ -178,12 +178,27 @@ def modify_custom_fields(
                 field_id=field_id,
                 defaults=defaults,
             )
-            if (
-                custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK
-                and value
-            ):
+            if custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK:
                 doc = Document.objects.get(id=doc_id)
                 reflect_doclinks(doc, custom_field, value)
+
+    # For doc link fields that are being removed, remove symmetrical links
+    for doclink_being_removed_instance in CustomFieldInstance.objects.filter(
+        document_id__in=affected_docs,
+        field__id__in=remove_custom_fields,
+        field__data_type=CustomField.FieldDataType.DOCUMENTLINK,
+        value_document_ids__isnull=False,
+    ):
+        for target_doc_id in doclink_being_removed_instance.value:
+            remove_doclink(
+                document=Document.objects.get(
+                    id=doclink_being_removed_instance.document.id,
+                ),
+                field=doclink_being_removed_instance.field,
+                target_doc_id=target_doc_id,
+            )
+
+    # Finally, remove the custom fields
     CustomFieldInstance.objects.filter(
         document_id__in=affected_docs,
         field_id__in=remove_custom_fields,
diff --git a/src/documents/tests/test_bulk_edit.py b/src/documents/tests/test_bulk_edit.py
index 2d8af025b..7fde5f8ee 100644
--- a/src/documents/tests/test_bulk_edit.py
+++ b/src/documents/tests/test_bulk_edit.py
@@ -282,14 +282,9 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
             document=self.doc2,
             field=cf3,
         )
-        doc3: Document = Document.objects.create(
-            title="doc3",
-            content="content",
-            checksum="D3",
-        )
         bulk_edit.modify_custom_fields(
             [self.doc1.id, self.doc2.id],
-            add_custom_fields={cf2.id: None, cf3.id: [doc3.id]},
+            add_custom_fields={cf2.id: None, cf3.id: [self.doc3.id]},
             remove_custom_fields=[cf.id],
         )
 
@@ -306,7 +301,7 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
         )
         self.assertEqual(
             self.doc1.custom_fields.get(field=cf3).value,
-            [doc3.id],
+            [self.doc3.id],
         )
         self.assertEqual(
             self.doc2.custom_fields.count(),
@@ -314,12 +309,11 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
         )
         self.assertEqual(
             self.doc2.custom_fields.get(field=cf3).value,
-            [doc3.id],
+            [self.doc3.id],
         )
         # assert reflect document link
-        doc3.refresh_from_db()
         self.assertEqual(
-            doc3.custom_fields.first().value,
+            self.doc3.custom_fields.first().value,
             [self.doc2.id, self.doc1.id],
         )
 
@@ -327,6 +321,21 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
         args, kwargs = self.async_task.call_args
         self.assertCountEqual(kwargs["document_ids"], [self.doc1.id, self.doc2.id])
 
+        # removal of document link cf, should also remove symmetric link
+        bulk_edit.modify_custom_fields(
+            [self.doc3.id],
+            add_custom_fields={},
+            remove_custom_fields=[cf3.id],
+        )
+        self.assertNotIn(
+            self.doc3.id,
+            self.doc1.custom_fields.filter(field=cf3).first().value,
+        )
+        self.assertNotIn(
+            self.doc3.id,
+            self.doc2.custom_fields.filter(field=cf3).first().value,
+        )
+
     def test_delete(self):
         self.assertEqual(Document.objects.count(), 5)
         bulk_edit.delete([self.doc1.id, self.doc2.id])