diff --git a/src/documents/management/commands/document_fuzzy_match.py b/src/documents/management/commands/document_fuzzy_match.py index 15f3f689b..eb1f77a73 100644 --- a/src/documents/management/commands/document_fuzzy_match.py +++ b/src/documents/management/commands/document_fuzzy_match.py @@ -66,6 +66,12 @@ class Command(PaperlessCommand): action="store_true", help="If set, one document of matches above the ratio WILL BE DELETED", ) + parser.add_argument( + "--yes", + default=False, + action="store_true", + help="Skip the confirmation prompt when used with --delete", + ) def _render_results( self, @@ -199,8 +205,21 @@ class Command(PaperlessCommand): ) if options["delete"] and maybe_delete_ids: - self.console.print( - f"[red]Deleting {len(maybe_delete_ids)} document(s)...[/red]", - ) - Document.objects.filter(pk__in=maybe_delete_ids).delete() - self.console.print("[green]Done.[/green]") + confirmed = options["yes"] + if not confirmed: + self.console.print( + f"\nDelete [bold]{len(maybe_delete_ids)}[/bold] document(s)? " + "[bold]\\[y/N][/bold] ", + end="", + ) + answer = input().strip().lower() + confirmed = answer in {"y", "yes"} + + if confirmed: + self.console.print( + f"[red]Deleting {len(maybe_delete_ids)} document(s)...[/red]", + ) + Document.objects.filter(pk__in=maybe_delete_ids).delete() + self.console.print("[green]Done.[/green]") + else: + self.console.print("[yellow]Deletion cancelled.[/yellow]") diff --git a/src/documents/tests/test_management_fuzzy.py b/src/documents/tests/test_management_fuzzy.py index 47dbbbad8..bcd102db6 100644 --- a/src/documents/tests/test_management_fuzzy.py +++ b/src/documents/tests/test_management_fuzzy.py @@ -1,4 +1,5 @@ from io import StringIO +from unittest.mock import patch import pytest from django.core.management import CommandError @@ -182,6 +183,7 @@ class TestFuzzyMatchCommand(TestCase): stdout, _ = self.call_command( "--delete", + "--yes", "--no-progress-bar", "--processes", "1", @@ -195,6 +197,51 @@ class TestFuzzyMatchCommand(TestCase): self.assertIsNotNone(Document.objects.get(pk=1)) self.assertIsNotNone(Document.objects.get(pk=2)) + def test_document_deletion_cancelled(self) -> None: + """ + GIVEN: + - 3 documents exist + - Document 1 to document 3 has a similarity over 85.0 + WHEN: + - Command is called with --delete but user answers "n" at the prompt + THEN: + - No documents are deleted + """ + Document.objects.create( + checksum="BEEFCAFE", + title="A", + content="first document scanned by bob", + mime_type="application/pdf", + filename="test.pdf", + ) + Document.objects.create( + checksum="DEADBEAF", + title="A", + content="second document scanned by alice", + mime_type="application/pdf", + filename="other_test.pdf", + ) + Document.objects.create( + checksum="CATTLE", + title="A", + content="first document scanned by pete", + mime_type="application/pdf", + filename="final_test.pdf", + ) + + self.assertEqual(Document.objects.count(), 3) + + with patch("builtins.input", return_value="n"): + stdout, _ = self.call_command( + "--delete", + "--no-progress-bar", + "--processes", + "1", + ) + + self.assertIn("Deletion cancelled", stdout) + self.assertEqual(Document.objects.count(), 3) + def test_empty_content(self) -> None: """ GIVEN: