From f2b5bac1d0a4e5c93e0afcf4f8bcc0e529b1103f Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Mon, 10 Aug 2026 14:25:37 -0700 Subject: [PATCH] The object getters in matching need to accept none for workflows --- src/paperless_ai/matching.py | 24 +++++++++++++++++------- src/paperless_ai/tests/test_matching.py | 25 ++++++------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/paperless_ai/matching.py b/src/paperless_ai/matching.py index 0cadaf36c..b2e0ec955 100644 --- a/src/paperless_ai/matching.py +++ b/src/paperless_ai/matching.py @@ -11,7 +11,7 @@ from documents.models import Correspondent from documents.models import DocumentType from documents.models import StoragePath from documents.models import Tag -from documents.permissions import get_objects_for_user_owner_aware +from documents.permissions import permitted_object_ids from documents.permissions import restrict_queryset_to_visible MATCH_THRESHOLD = 0.8 @@ -63,30 +63,40 @@ def resolve_storage_path_ids(ids: list[int], user: User | None) -> list[StorageP def _match_by_name( names: list[str], - user: User, + user: User | None, model: type[ModelT], perm: str, ) -> list[ModelT]: - queryset = get_objects_for_user_owner_aware(user, [perm], model) + # A workflow may have no user. In that case permitted_object_ids limits + # matching to unowned objects, avoiding another user's private taxonomy. + queryset = model.objects.filter( + pk__in=permitted_object_ids(user, model, perm), + ) return _match_names_to_queryset(names, queryset) -def match_tags_by_name(names: list[str], user: User) -> list[Tag]: +def match_tags_by_name(names: list[str], user: User | None) -> list[Tag]: return _match_by_name(names, user, Tag, "view_tag") def match_correspondents_by_name( names: list[str], - user: User, + user: User | None, ) -> list[Correspondent]: return _match_by_name(names, user, Correspondent, "view_correspondent") -def match_document_types_by_name(names: list[str], user: User) -> list[DocumentType]: +def match_document_types_by_name( + names: list[str], + user: User | None, +) -> list[DocumentType]: return _match_by_name(names, user, DocumentType, "view_documenttype") -def match_storage_paths_by_name(names: list[str], user: User) -> list[StoragePath]: +def match_storage_paths_by_name( + names: list[str], + user: User | None, +) -> list[StoragePath]: return _match_by_name(names, user, StoragePath, "view_storagepath") diff --git a/src/paperless_ai/tests/test_matching.py b/src/paperless_ai/tests/test_matching.py index 4dd974fe6..bbf972ad9 100644 --- a/src/paperless_ai/tests/test_matching.py +++ b/src/paperless_ai/tests/test_matching.py @@ -1,5 +1,4 @@ from collections.abc import Callable -from unittest.mock import patch import pytest import pytest_mock @@ -45,33 +44,25 @@ class TestAIMatching(TestCase): self.storage_path1 = StoragePath.objects.create(name="Test Storage Path 1") self.storage_path2 = StoragePath.objects.create(name="Test Storage Path 2") - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_tags_by_name(self, mock_get_objects) -> None: - mock_get_objects.return_value = Tag.objects.all() + def test_match_tags_by_name(self) -> None: names = ["Test Tag 1", "Nonexistent Tag"] result = match_tags_by_name(names, user=None) self.assertEqual(len(result), 1) self.assertEqual(result[0].name, "Test Tag 1") - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_correspondents_by_name(self, mock_get_objects) -> None: - mock_get_objects.return_value = Correspondent.objects.all() + def test_match_correspondents_by_name(self) -> None: names = ["Test Correspondent 1", "Nonexistent Correspondent"] result = match_correspondents_by_name(names, user=None) self.assertEqual(len(result), 1) self.assertEqual(result[0].name, "Test Correspondent 1") - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_document_types_by_name(self, mock_get_objects) -> None: - mock_get_objects.return_value = DocumentType.objects.all() + def test_match_document_types_by_name(self) -> None: names = ["Test Document Type 1", "Nonexistent Document Type"] result = match_document_types_by_name(names, user=None) self.assertEqual(len(result), 1) self.assertEqual(result[0].name, "Test Document Type 1") - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_storage_paths_by_name(self, mock_get_objects) -> None: - mock_get_objects.return_value = StoragePath.objects.all() + def test_match_storage_paths_by_name(self) -> None: names = ["Test Storage Path 1", "Nonexistent Storage Path"] result = match_storage_paths_by_name(names, user=None) self.assertEqual(len(result), 1) @@ -83,16 +74,12 @@ class TestAIMatching(TestCase): unmatched_names = extract_unmatched_names(llm_names, matched_objects) self.assertEqual(unmatched_names, ["Nonexistent Tag"]) - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_tags_by_name_with_empty_names(self, mock_get_objects) -> None: - mock_get_objects.return_value = Tag.objects.all() + def test_match_tags_by_name_with_empty_names(self) -> None: names = [None, "", " "] result = match_tags_by_name(names, user=None) self.assertEqual(result, []) - @patch("paperless_ai.matching.get_objects_for_user_owner_aware") - def test_match_tags_with_fuzzy_matching(self, mock_get_objects) -> None: - mock_get_objects.return_value = Tag.objects.all() + def test_match_tags_with_fuzzy_matching(self) -> None: names = ["Test Taag 1", "Teest Tag 2"] result = match_tags_by_name(names, user=None) self.assertEqual(len(result), 2)