mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-28 03:46:06 -05:00 
			
		
		
		
	| @@ -63,8 +63,20 @@ class Command(BaseCommand): | |||||||
|             action="store_true", |             action="store_true", | ||||||
|             help="If set, the progress bar will not be shown" |             help="If set, the progress bar will not be shown" | ||||||
|         ) |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             "--suggest", | ||||||
|  |             default=False, | ||||||
|  |             action="store_true", | ||||||
|  |             help="Return the suggestion, don't change anything." | ||||||
|  |         ) | ||||||
|  |         parser.add_argument( | ||||||
|  |             "--base-url", | ||||||
|  |             help="The base URL to use to build the link to the documents." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|  |         # Detect if we support color | ||||||
|  |         color = self.style.ERROR("test") != "test" | ||||||
|  |  | ||||||
|         if options["inbox_only"]: |         if options["inbox_only"]: | ||||||
|             queryset = Document.objects.filter(tags__is_inbox_tag=True) |             queryset = Document.objects.filter(tags__is_inbox_tag=True) | ||||||
| @@ -85,18 +97,27 @@ class Command(BaseCommand): | |||||||
|                     document=document, |                     document=document, | ||||||
|                     classifier=classifier, |                     classifier=classifier, | ||||||
|                     replace=options['overwrite'], |                     replace=options['overwrite'], | ||||||
|                     use_first=options['use_first']) |                     use_first=options['use_first'], | ||||||
|  |                     suggest=options['suggest'], | ||||||
|  |                     base_url=options['base_url'], | ||||||
|  |                     color=color) | ||||||
|  |  | ||||||
|             if options['document_type']: |             if options['document_type']: | ||||||
|                 set_document_type(sender=None, |                 set_document_type(sender=None, | ||||||
|                                   document=document, |                                   document=document, | ||||||
|                                   classifier=classifier, |                                   classifier=classifier, | ||||||
|                                   replace=options['overwrite'], |                                   replace=options['overwrite'], | ||||||
|                                   use_first=options['use_first']) |                                   use_first=options['use_first'], | ||||||
|  |                                   suggest=options['suggest'], | ||||||
|  |                                   base_url=options['base_url'], | ||||||
|  |                                   color=color) | ||||||
|  |  | ||||||
|             if options['tags']: |             if options['tags']: | ||||||
|                 set_tags( |                 set_tags( | ||||||
|                     sender=None, |                     sender=None, | ||||||
|                     document=document, |                     document=document, | ||||||
|                     classifier=classifier, |                     classifier=classifier, | ||||||
|                     replace=options['overwrite']) |                     replace=options['overwrite'], | ||||||
|  |                     suggest=options['suggest'], | ||||||
|  |                     base_url=options['base_url'], | ||||||
|  |                     color=color) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
|  |  | ||||||
|  | from django.utils import termcolors | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.admin.models import ADDITION, LogEntry | from django.contrib.admin.models import ADDITION, LogEntry | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| @@ -8,14 +9,14 @@ from django.contrib.contenttypes.models import ContentType | |||||||
| from django.db import models, DatabaseError | from django.db import models, DatabaseError | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from django.dispatch import receiver | from django.dispatch import receiver | ||||||
| from django.utils import timezone | from django.utils import termcolors, timezone | ||||||
| from filelock import FileLock | from filelock import FileLock | ||||||
|  |  | ||||||
| from .. import matching | from .. import matching | ||||||
| from ..file_handling import delete_empty_directories, \ | from ..file_handling import delete_empty_directories, \ | ||||||
|     create_source_path_directory, \ |     create_source_path_directory, \ | ||||||
|     generate_unique_filename |     generate_unique_filename | ||||||
| from ..models import Document, Tag | from ..models import Document, Tag, MatchingModel | ||||||
|  |  | ||||||
|  |  | ||||||
| logger = logging.getLogger("paperless.handlers") | logger = logging.getLogger("paperless.handlers") | ||||||
| @@ -32,6 +33,9 @@ def set_correspondent(sender, | |||||||
|                       classifier=None, |                       classifier=None, | ||||||
|                       replace=False, |                       replace=False, | ||||||
|                       use_first=True, |                       use_first=True, | ||||||
|  |                       suggest=False, | ||||||
|  |                       base_url=None, | ||||||
|  |                       color=False, | ||||||
|                       **kwargs): |                       **kwargs): | ||||||
|     if document.correspondent and not replace: |     if document.correspondent and not replace: | ||||||
|         return |         return | ||||||
| @@ -60,6 +64,24 @@ def set_correspondent(sender, | |||||||
|             return |             return | ||||||
|  |  | ||||||
|     if selected or replace: |     if selected or replace: | ||||||
|  |         if suggest: | ||||||
|  |             if base_url: | ||||||
|  |                 print( | ||||||
|  |                     termcolors.colorize(str(document), fg='green') | ||||||
|  |                     if color | ||||||
|  |                     else str(document) | ||||||
|  |                 ) | ||||||
|  |                 print(f"{base_url}/documents/{document.pk}") | ||||||
|  |             else: | ||||||
|  |                 print( | ||||||
|  |                     ( | ||||||
|  |                         termcolors.colorize(str(document), fg='green') | ||||||
|  |                         if color | ||||||
|  |                         else str(document) | ||||||
|  |                     ) + f" [{document.pk}]" | ||||||
|  |                 ) | ||||||
|  |             print(f"Suggest correspondent {selected}") | ||||||
|  |         else: | ||||||
|             logger.info( |             logger.info( | ||||||
|                 f"Assigning correspondent {selected} to {document}", |                 f"Assigning correspondent {selected} to {document}", | ||||||
|                 extra={'group': logging_group} |                 extra={'group': logging_group} | ||||||
| @@ -75,6 +97,9 @@ def set_document_type(sender, | |||||||
|                       classifier=None, |                       classifier=None, | ||||||
|                       replace=False, |                       replace=False, | ||||||
|                       use_first=True, |                       use_first=True, | ||||||
|  |                       suggest=False, | ||||||
|  |                       base_url=None, | ||||||
|  |                       color=False, | ||||||
|                       **kwargs): |                       **kwargs): | ||||||
|     if document.document_type and not replace: |     if document.document_type and not replace: | ||||||
|         return |         return | ||||||
| @@ -104,6 +129,24 @@ def set_document_type(sender, | |||||||
|             return |             return | ||||||
|  |  | ||||||
|     if selected or replace: |     if selected or replace: | ||||||
|  |         if suggest: | ||||||
|  |             if base_url: | ||||||
|  |                 print( | ||||||
|  |                     termcolors.colorize(str(document), fg='green') | ||||||
|  |                     if color | ||||||
|  |                     else str(document) | ||||||
|  |                 ) | ||||||
|  |                 print(f"{base_url}/documents/{document.pk}") | ||||||
|  |             else: | ||||||
|  |                 print( | ||||||
|  |                     ( | ||||||
|  |                         termcolors.colorize(str(document), fg='green') | ||||||
|  |                         if color | ||||||
|  |                         else str(document) | ||||||
|  |                     ) + f" [{document.pk}]" | ||||||
|  |                 ) | ||||||
|  |             print(f"Sugest document type {selected}") | ||||||
|  |         else: | ||||||
|             logger.info( |             logger.info( | ||||||
|                 f"Assigning document type {selected} to {document}", |                 f"Assigning document type {selected} to {document}", | ||||||
|                 extra={'group': logging_group} |                 extra={'group': logging_group} | ||||||
| @@ -118,6 +161,9 @@ def set_tags(sender, | |||||||
|              logging_group=None, |              logging_group=None, | ||||||
|              classifier=None, |              classifier=None, | ||||||
|              replace=False, |              replace=False, | ||||||
|  |              suggest=False, | ||||||
|  |              base_url=None, | ||||||
|  |              color=False, | ||||||
|              **kwargs): |              **kwargs): | ||||||
|  |  | ||||||
|     if replace: |     if replace: | ||||||
| @@ -132,12 +178,44 @@ def set_tags(sender, | |||||||
|  |  | ||||||
|     relevant_tags = set(matched_tags) - current_tags |     relevant_tags = set(matched_tags) - current_tags | ||||||
|  |  | ||||||
|  |     if suggest: | ||||||
|  |         extra_tags = current_tags - set(matched_tags) | ||||||
|  |         extra_tags = [ | ||||||
|  |             t for t in extra_tags | ||||||
|  |             if t.matching_algorithm == MatchingModel.MATCH_AUTO | ||||||
|  |         ] | ||||||
|  |         if not relevant_tags and not extra_tags: | ||||||
|  |             return | ||||||
|  |         if base_url: | ||||||
|  |             print( | ||||||
|  |                 termcolors.colorize(str(document), fg='green') | ||||||
|  |                 if color | ||||||
|  |                 else str(document) | ||||||
|  |             ) | ||||||
|  |             print(f"{base_url}/documents/{document.pk}") | ||||||
|  |         else: | ||||||
|  |             print( | ||||||
|  |                 ( | ||||||
|  |                     termcolors.colorize(str(document), fg='green') | ||||||
|  |                     if color | ||||||
|  |                     else str(document) | ||||||
|  |                 ) + f" [{document.pk}]" | ||||||
|  |             ) | ||||||
|  |         if relevant_tags: | ||||||
|  |             print( | ||||||
|  |                 "Suggest tags: " + ", ".join([t.name for t in relevant_tags]) | ||||||
|  |             ) | ||||||
|  |         if extra_tags: | ||||||
|  |             print("Extra tags: " + ", ".join([t.name for t in extra_tags])) | ||||||
|  |     else: | ||||||
|         if not relevant_tags: |         if not relevant_tags: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         message = 'Tagging "{}" with "{}"' |         message = 'Tagging "{}" with "{}"' | ||||||
|         logger.info( |         logger.info( | ||||||
|         message.format(document, ", ".join([t.name for t in relevant_tags])), |             message.format( | ||||||
|  |                 document, ", ".join([t.name for t in relevant_tags]) | ||||||
|  |             ), | ||||||
|             extra={'group': logging_group} |             extra={'group': logging_group} | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,14 +11,17 @@ class TestRetagger(DirectoriesMixin, TestCase): | |||||||
|         self.d1 = Document.objects.create(checksum="A", title="A", content="first document") |         self.d1 = Document.objects.create(checksum="A", title="A", content="first document") | ||||||
|         self.d2 = Document.objects.create(checksum="B", title="B", content="second document") |         self.d2 = Document.objects.create(checksum="B", title="B", content="second document") | ||||||
|         self.d3 = Document.objects.create(checksum="C", title="C", content="unrelated document") |         self.d3 = Document.objects.create(checksum="C", title="C", content="unrelated document") | ||||||
|  |         self.d4 = Document.objects.create(checksum="D", title="D", content="auto document") | ||||||
|  |  | ||||||
|         self.tag_first = Tag.objects.create(name="tag1", match="first", matching_algorithm=Tag.MATCH_ANY) |         self.tag_first = Tag.objects.create(name="tag1", match="first", matching_algorithm=Tag.MATCH_ANY) | ||||||
|         self.tag_second = Tag.objects.create(name="tag2", match="second", matching_algorithm=Tag.MATCH_ANY) |         self.tag_second = Tag.objects.create(name="tag2", match="second", matching_algorithm=Tag.MATCH_ANY) | ||||||
|         self.tag_inbox = Tag.objects.create(name="test", is_inbox_tag=True) |         self.tag_inbox = Tag.objects.create(name="test", is_inbox_tag=True) | ||||||
|         self.tag_no_match = Tag.objects.create(name="test2") |         self.tag_no_match = Tag.objects.create(name="test2") | ||||||
|  |         self.tag_auto = Tag.objects.create(name="tagauto", matching_algorithm=Tag.MATCH_AUTO) | ||||||
|  |  | ||||||
|         self.d3.tags.add(self.tag_inbox) |         self.d3.tags.add(self.tag_inbox) | ||||||
|         self.d3.tags.add(self.tag_no_match) |         self.d3.tags.add(self.tag_no_match) | ||||||
|  |         self.d4.tags.add(self.tag_auto) | ||||||
|  |  | ||||||
|  |  | ||||||
|         self.correspondent_first = Correspondent.objects.create( |         self.correspondent_first = Correspondent.objects.create( | ||||||
| @@ -32,7 +35,8 @@ class TestRetagger(DirectoriesMixin, TestCase): | |||||||
|             name="dt2", match="second", matching_algorithm=DocumentType.MATCH_ANY) |             name="dt2", match="second", matching_algorithm=DocumentType.MATCH_ANY) | ||||||
|  |  | ||||||
|     def get_updated_docs(self): |     def get_updated_docs(self): | ||||||
|         return Document.objects.get(title="A"), Document.objects.get(title="B"), Document.objects.get(title="C") |         return Document.objects.get(title="A"), Document.objects.get(title="B"), \ | ||||||
|  |             Document.objects.get(title="C"), Document.objects.get(title="D") | ||||||
|  |  | ||||||
|     def setUp(self) -> None: |     def setUp(self) -> None: | ||||||
|         super(TestRetagger, self).setUp() |         super(TestRetagger, self).setUp() | ||||||
| @@ -40,25 +44,26 @@ class TestRetagger(DirectoriesMixin, TestCase): | |||||||
|  |  | ||||||
|     def test_add_tags(self): |     def test_add_tags(self): | ||||||
|         call_command('document_retagger', '--tags') |         call_command('document_retagger', '--tags') | ||||||
|         d_first, d_second, d_unrelated = self.get_updated_docs() |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|         self.assertEqual(d_first.tags.count(), 1) |         self.assertEqual(d_first.tags.count(), 1) | ||||||
|         self.assertEqual(d_second.tags.count(), 1) |         self.assertEqual(d_second.tags.count(), 1) | ||||||
|         self.assertEqual(d_unrelated.tags.count(), 2) |         self.assertEqual(d_unrelated.tags.count(), 2) | ||||||
|  |         self.assertEqual(d_auto.tags.count(), 1) | ||||||
|  |  | ||||||
|         self.assertEqual(d_first.tags.first(), self.tag_first) |         self.assertEqual(d_first.tags.first(), self.tag_first) | ||||||
|         self.assertEqual(d_second.tags.first(), self.tag_second) |         self.assertEqual(d_second.tags.first(), self.tag_second) | ||||||
|  |  | ||||||
|     def test_add_type(self): |     def test_add_type(self): | ||||||
|         call_command('document_retagger', '--document_type') |         call_command('document_retagger', '--document_type') | ||||||
|         d_first, d_second, d_unrelated = self.get_updated_docs() |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|         self.assertEqual(d_first.document_type, self.doctype_first) |         self.assertEqual(d_first.document_type, self.doctype_first) | ||||||
|         self.assertEqual(d_second.document_type, self.doctype_second) |         self.assertEqual(d_second.document_type, self.doctype_second) | ||||||
|  |  | ||||||
|     def test_add_correspondent(self): |     def test_add_correspondent(self): | ||||||
|         call_command('document_retagger', '--correspondent') |         call_command('document_retagger', '--correspondent') | ||||||
|         d_first, d_second, d_unrelated = self.get_updated_docs() |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|         self.assertEqual(d_first.correspondent, self.correspondent_first) |         self.assertEqual(d_first.correspondent, self.correspondent_first) | ||||||
|         self.assertEqual(d_second.correspondent, self.correspondent_second) |         self.assertEqual(d_second.correspondent, self.correspondent_second) | ||||||
| @@ -68,11 +73,55 @@ class TestRetagger(DirectoriesMixin, TestCase): | |||||||
|  |  | ||||||
|         call_command('document_retagger', '--tags', '--overwrite') |         call_command('document_retagger', '--tags', '--overwrite') | ||||||
|  |  | ||||||
|         d_first, d_second, d_unrelated = self.get_updated_docs() |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|         self.assertIsNotNone(Tag.objects.get(id=self.tag_second.id)) |         self.assertIsNotNone(Tag.objects.get(id=self.tag_second.id)) | ||||||
|  |  | ||||||
|         self.assertCountEqual([tag.id for tag in d_first.tags.all()], [self.tag_first.id]) |         self.assertCountEqual([tag.id for tag in d_first.tags.all()], [self.tag_first.id]) | ||||||
|         self.assertCountEqual([tag.id for tag in d_second.tags.all()], [self.tag_second.id]) |         self.assertCountEqual([tag.id for tag in d_second.tags.all()], [self.tag_second.id]) | ||||||
|         self.assertCountEqual([tag.id for tag in d_unrelated.tags.all()], [self.tag_inbox.id, self.tag_no_match.id]) |         self.assertCountEqual([tag.id for tag in d_unrelated.tags.all()], [self.tag_inbox.id, self.tag_no_match.id]) | ||||||
|  |         self.assertEqual(d_auto.tags.count(), 0) | ||||||
|  |  | ||||||
|  |     def test_add_tags_suggest(self): | ||||||
|  |         call_command('document_retagger', '--tags', '--suggest') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.tags.count(), 0) | ||||||
|  |         self.assertEqual(d_second.tags.count(), 0) | ||||||
|  |         self.assertEqual(d_auto.tags.count(), 1) | ||||||
|  |  | ||||||
|  |     def test_add_type_suggest(self): | ||||||
|  |         call_command('document_retagger', '--document_type', '--suggest') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.document_type, None) | ||||||
|  |         self.assertEqual(d_second.document_type, None) | ||||||
|  |  | ||||||
|  |     def test_add_correspondent_suggest(self): | ||||||
|  |         call_command('document_retagger', '--correspondent', '--suggest') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.correspondent, None) | ||||||
|  |         self.assertEqual(d_second.correspondent, None) | ||||||
|  |  | ||||||
|  |     def test_add_tags_suggest_url(self): | ||||||
|  |         call_command('document_retagger', '--tags', '--suggest', '--base-url=http://localhost') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.tags.count(), 0) | ||||||
|  |         self.assertEqual(d_second.tags.count(), 0) | ||||||
|  |         self.assertEqual(d_auto.tags.count(), 1) | ||||||
|  |  | ||||||
|  |     def test_add_type_suggest_url(self): | ||||||
|  |         call_command('document_retagger', '--document_type', '--suggest', '--base-url=http://localhost') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.document_type, None) | ||||||
|  |         self.assertEqual(d_second.document_type, None) | ||||||
|  |  | ||||||
|  |     def test_add_correspondent_suggest_url(self): | ||||||
|  |         call_command('document_retagger', '--correspondent', '--suggest', '--base-url=http://localhost') | ||||||
|  |         d_first, d_second, d_unrelated, d_auto = self.get_updated_docs() | ||||||
|  |  | ||||||
|  |         self.assertEqual(d_first.correspondent, None) | ||||||
|  |         self.assertEqual(d_second.correspondent, None) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonas Winkler
					Jonas Winkler