From 3f4d2a4b5a3f794fe379a31b2f1b92c7c7a3a9e1 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 4 Sep 2026 07:14:58 -0700 Subject: [PATCH] And the backend --- src/documents/serialisers.py | 10 +++++++++- src/documents/tests/test_tag_hierarchy.py | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 4193bed72..f84ee9922 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -1220,11 +1220,19 @@ class DocumentSerializer( prev_tags = set(instance.tags.all()) requested_tags = set(validated_data["tags"]) - # Tags being removed in this update and all descendants + # Tags newly added in this update and the ancestors they require + added_tags = requested_tags - prev_tags + required_by_add_tags = set(added_tags) + for t in added_tags: + required_by_add_tags.update(t.get_ancestors()) + + # Tags being removed in this update and all descendants, except + # those required by a tag that is being added in this same update removed_tags = prev_tags - requested_tags blocked_tags = set(removed_tags) for t in removed_tags: blocked_tags.update(t.get_descendants()) + blocked_tags.difference_update(required_by_add_tags) # Add all parent tags final_tags = set(requested_tags) diff --git a/src/documents/tests/test_tag_hierarchy.py b/src/documents/tests/test_tag_hierarchy.py index c50812edc..b439d5d70 100644 --- a/src/documents/tests/test_tag_hierarchy.py +++ b/src/documents/tests/test_tag_hierarchy.py @@ -75,6 +75,19 @@ class TestTagHierarchy(DirectoriesMixin, APITestCase): tags = set(self.document.tags.values_list("pk", flat=True)) assert tags == {self.parent.pk, self.child.pk} + def test_document_api_add_child_keeps_parent_already_assigned(self) -> None: + # https://github.com/paperless-ngx/paperless-ngx/issues/13970 + inbox = Tag.objects.create(name="Inbox", is_inbox_tag=True) + self.document.add_nested_tags([inbox, self.parent]) + self.client.patch( + f"/api/documents/{self.document.pk}/", + {"tags": [self.child.pk]}, + format="json", + ) + self.document.refresh_from_db() + tags = set(self.document.tags.values_list("pk", flat=True)) + assert tags == {self.parent.pk, self.child.pk} + def test_document_api_remove_parent_removes_children(self) -> None: self.document.add_nested_tags([self.parent, self.child]) self.client.patch(