mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Allows filtering email by the TO value(s) as well
This commit is contained in:
		| @@ -9,6 +9,7 @@ | |||||||
|             "account": 2, |             "account": 2, | ||||||
|             "folder": "INBOX", |             "folder": "INBOX", | ||||||
|             "filter_from": null, |             "filter_from": null, | ||||||
|  |             "filter_to": null, | ||||||
|             "filter_subject": "[paperless]", |             "filter_subject": "[paperless]", | ||||||
|             "filter_body": null, |             "filter_body": null, | ||||||
|             "filter_attachment_filename": null, |             "filter_attachment_filename": null, | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ | |||||||
|       <div class="col"> |       <div class="col"> | ||||||
|         <p class="small" i18n>Paperless will only process mails that match <em>all</em> of the filters specified below.</p> |         <p class="small" i18n>Paperless will only process mails that match <em>all</em> of the filters specified below.</p> | ||||||
|         <app-input-text i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></app-input-text> |         <app-input-text i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></app-input-text> | ||||||
|  |         <app-input-text i18n-title title="Filter to" formControlName="filter_to" [error]="error?.filter_to"></app-input-text> | ||||||
|         <app-input-text i18n-title title="Filter subject" formControlName="filter_subject" [error]="error?.filter_subject"></app-input-text> |         <app-input-text i18n-title title="Filter subject" formControlName="filter_subject" [error]="error?.filter_subject"></app-input-text> | ||||||
|         <app-input-text i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></app-input-text> |         <app-input-text i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></app-input-text> | ||||||
|         <app-input-text i18n-title title="Filter attachment filename" formControlName="filter_attachment_filename" i18n-hint hint="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename"></app-input-text> |         <app-input-text i18n-title title="Filter attachment filename" formControlName="filter_attachment_filename" i18n-hint hint="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename"></app-input-text> | ||||||
|   | |||||||
| @@ -149,6 +149,7 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<PaperlessMa | |||||||
|       account: new FormControl(null), |       account: new FormControl(null), | ||||||
|       folder: new FormControl('INBOX'), |       folder: new FormControl('INBOX'), | ||||||
|       filter_from: new FormControl(null), |       filter_from: new FormControl(null), | ||||||
|  |       filter_to: new FormControl(null), | ||||||
|       filter_subject: new FormControl(null), |       filter_subject: new FormControl(null), | ||||||
|       filter_body: new FormControl(null), |       filter_body: new FormControl(null), | ||||||
|       filter_attachment_filename: new FormControl(null), |       filter_attachment_filename: new FormControl(null), | ||||||
|   | |||||||
| @@ -386,6 +386,7 @@ export class SettingsComponent | |||||||
|           account: rule.account, |           account: rule.account, | ||||||
|           folder: rule.folder, |           folder: rule.folder, | ||||||
|           filter_from: rule.filter_from, |           filter_from: rule.filter_from, | ||||||
|  |           filter_to: rule.filter_to, | ||||||
|           filter_subject: rule.filter_subject, |           filter_subject: rule.filter_subject, | ||||||
|           filter_body: rule.filter_body, |           filter_body: rule.filter_body, | ||||||
|           filter_attachment_filename: rule.filter_attachment_filename, |           filter_attachment_filename: rule.filter_attachment_filename, | ||||||
| @@ -406,6 +407,7 @@ export class SettingsComponent | |||||||
|             account: new FormControl(null), |             account: new FormControl(null), | ||||||
|             folder: new FormControl(null), |             folder: new FormControl(null), | ||||||
|             filter_from: new FormControl(null), |             filter_from: new FormControl(null), | ||||||
|  |             filter_to: new FormControl(null), | ||||||
|             filter_subject: new FormControl(null), |             filter_subject: new FormControl(null), | ||||||
|             filter_body: new FormControl(null), |             filter_body: new FormControl(null), | ||||||
|             filter_attachment_filename: new FormControl(null), |             filter_attachment_filename: new FormControl(null), | ||||||
|   | |||||||
| @@ -42,6 +42,8 @@ export interface PaperlessMailRule extends ObjectWithId { | |||||||
|  |  | ||||||
|   filter_from: string |   filter_from: string | ||||||
|  |  | ||||||
|  |   filter_to: string | ||||||
|  |  | ||||||
|   filter_subject: string |   filter_subject: string | ||||||
|  |  | ||||||
|   filter_body: string |   filter_body: string | ||||||
|   | |||||||
| @@ -53,6 +53,7 @@ class MailRuleAdmin(admin.ModelAdmin): | |||||||
|                 ), |                 ), | ||||||
|                 "fields": ( |                 "fields": ( | ||||||
|                     "filter_from", |                     "filter_from", | ||||||
|  |                     "filter_to", | ||||||
|                     "filter_subject", |                     "filter_subject", | ||||||
|                     "filter_body", |                     "filter_body", | ||||||
|                     "filter_attachment_filename", |                     "filter_attachment_filename", | ||||||
|   | |||||||
| @@ -82,7 +82,12 @@ class BaseMailAction: | |||||||
|         """ |         """ | ||||||
|         return {} |         return {} | ||||||
|  |  | ||||||
|     def post_consume(self, M: MailBox, message_uid: str, parameter: str): |     def post_consume( | ||||||
|  |         self, | ||||||
|  |         M: MailBox, | ||||||
|  |         message_uid: str, | ||||||
|  |         parameter: str, | ||||||
|  |     ):  # pragma: nocover | ||||||
|         """ |         """ | ||||||
|         Perform mail action on the given mail uid in the mailbox. |         Perform mail action on the given mail uid in the mailbox. | ||||||
|         """ |         """ | ||||||
| @@ -160,7 +165,7 @@ class TagMailAction(BaseMailAction): | |||||||
|             return {"flagged": False} |             return {"flagged": False} | ||||||
|         elif self.keyword: |         elif self.keyword: | ||||||
|             return AND(NOT(gmail_label=self.keyword), no_keyword=self.keyword) |             return AND(NOT(gmail_label=self.keyword), no_keyword=self.keyword) | ||||||
|         else: |         else:  # pragma: nocover | ||||||
|             raise ValueError("This should never happen.") |             raise ValueError("This should never happen.") | ||||||
|  |  | ||||||
|     def post_consume(self, M: MailBox, message_uid: str, parameter: str): |     def post_consume(self, M: MailBox, message_uid: str, parameter: str): | ||||||
| @@ -363,6 +368,8 @@ def make_criterias(rule): | |||||||
|         criterias["date_gte"] = maximum_age |         criterias["date_gte"] = maximum_age | ||||||
|     if rule.filter_from: |     if rule.filter_from: | ||||||
|         criterias["from_"] = rule.filter_from |         criterias["from_"] = rule.filter_from | ||||||
|  |     if rule.filter_to: | ||||||
|  |         criterias["to"] = rule.filter_to | ||||||
|     if rule.filter_subject: |     if rule.filter_subject: | ||||||
|         criterias["subject"] = rule.filter_subject |         criterias["subject"] = rule.filter_subject | ||||||
|     if rule.filter_body: |     if rule.filter_body: | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								src/paperless_mail/migrations/0019_mailrule_filter_to.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/paperless_mail/migrations/0019_mailrule_filter_to.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # Generated by Django 4.1.7 on 2023-03-11 21:08 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |     dependencies = [ | ||||||
|  |         ("paperless_mail", "0018_processedmail"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="mailrule", | ||||||
|  |             name="filter_to", | ||||||
|  |             field=models.CharField( | ||||||
|  |                 blank=True, max_length=256, null=True, verbose_name="filter to" | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -113,12 +113,21 @@ class MailRule(document_models.ModelWithOwner): | |||||||
|         null=True, |         null=True, | ||||||
|         blank=True, |         blank=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     filter_to = models.CharField( | ||||||
|  |         _("filter to"), | ||||||
|  |         max_length=256, | ||||||
|  |         null=True, | ||||||
|  |         blank=True, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     filter_subject = models.CharField( |     filter_subject = models.CharField( | ||||||
|         _("filter subject"), |         _("filter subject"), | ||||||
|         max_length=256, |         max_length=256, | ||||||
|         null=True, |         null=True, | ||||||
|         blank=True, |         blank=True, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     filter_body = models.CharField( |     filter_body = models.CharField( | ||||||
|         _("filter body"), |         _("filter body"), | ||||||
|         max_length=256, |         max_length=256, | ||||||
|   | |||||||
| @@ -70,6 +70,7 @@ class MailRuleSerializer(OwnedObjectSerializer): | |||||||
|             "account", |             "account", | ||||||
|             "folder", |             "folder", | ||||||
|             "filter_from", |             "filter_from", | ||||||
|  |             "filter_to", | ||||||
|             "filter_subject", |             "filter_subject", | ||||||
|             "filter_body", |             "filter_body", | ||||||
|             "filter_attachment_filename", |             "filter_attachment_filename", | ||||||
|   | |||||||
| @@ -201,6 +201,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase): | |||||||
|             account=account1, |             account=account1, | ||||||
|             folder="INBOX", |             folder="INBOX", | ||||||
|             filter_from="from@example.com", |             filter_from="from@example.com", | ||||||
|  |             filter_to="someone@somewhere.com", | ||||||
|             filter_subject="subject", |             filter_subject="subject", | ||||||
|             filter_body="body", |             filter_body="body", | ||||||
|             filter_attachment_filename="file.pdf", |             filter_attachment_filename="file.pdf", | ||||||
| @@ -222,6 +223,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase): | |||||||
|         self.assertEqual(returned_rule1["account"], account1.pk) |         self.assertEqual(returned_rule1["account"], account1.pk) | ||||||
|         self.assertEqual(returned_rule1["folder"], rule1.folder) |         self.assertEqual(returned_rule1["folder"], rule1.folder) | ||||||
|         self.assertEqual(returned_rule1["filter_from"], rule1.filter_from) |         self.assertEqual(returned_rule1["filter_from"], rule1.filter_from) | ||||||
|  |         self.assertEqual(returned_rule1["filter_to"], rule1.filter_to) | ||||||
|         self.assertEqual(returned_rule1["filter_subject"], rule1.filter_subject) |         self.assertEqual(returned_rule1["filter_subject"], rule1.filter_subject) | ||||||
|         self.assertEqual(returned_rule1["filter_body"], rule1.filter_body) |         self.assertEqual(returned_rule1["filter_body"], rule1.filter_body) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
| @@ -275,6 +277,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase): | |||||||
|             "account": account1.pk, |             "account": account1.pk, | ||||||
|             "folder": "INBOX", |             "folder": "INBOX", | ||||||
|             "filter_from": "from@example.com", |             "filter_from": "from@example.com", | ||||||
|  |             "filter_to": "aperson@aplace.com", | ||||||
|             "filter_subject": "subject", |             "filter_subject": "subject", | ||||||
|             "filter_body": "body", |             "filter_body": "body", | ||||||
|             "filter_attachment_filename": "file.pdf", |             "filter_attachment_filename": "file.pdf", | ||||||
| @@ -307,6 +310,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase): | |||||||
|         self.assertEqual(returned_rule1["account"], account1.pk) |         self.assertEqual(returned_rule1["account"], account1.pk) | ||||||
|         self.assertEqual(returned_rule1["folder"], rule1["folder"]) |         self.assertEqual(returned_rule1["folder"], rule1["folder"]) | ||||||
|         self.assertEqual(returned_rule1["filter_from"], rule1["filter_from"]) |         self.assertEqual(returned_rule1["filter_from"], rule1["filter_from"]) | ||||||
|  |         self.assertEqual(returned_rule1["filter_to"], rule1["filter_to"]) | ||||||
|         self.assertEqual(returned_rule1["filter_subject"], rule1["filter_subject"]) |         self.assertEqual(returned_rule1["filter_subject"], rule1["filter_subject"]) | ||||||
|         self.assertEqual(returned_rule1["filter_body"], rule1["filter_body"]) |         self.assertEqual(returned_rule1["filter_body"], rule1["filter_body"]) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import uuid | |||||||
| from collections import namedtuple | from collections import namedtuple | ||||||
| from typing import ContextManager | from typing import ContextManager | ||||||
| from typing import List | from typing import List | ||||||
|  | from typing import Optional | ||||||
| from typing import Union | from typing import Union | ||||||
| from unittest import mock | from unittest import mock | ||||||
|  |  | ||||||
| @@ -131,12 +132,20 @@ class BogusMailBox(ContextManager): | |||||||
|             from_ = criteria[criteria.index("FROM") + 1].strip('"') |             from_ = criteria[criteria.index("FROM") + 1].strip('"') | ||||||
|             msg = filter(lambda m: from_ in m.from_, msg) |             msg = filter(lambda m: from_ in m.from_, msg) | ||||||
|  |  | ||||||
|  |         if "TO" in criteria: | ||||||
|  |             to_ = criteria[criteria.index("TO") + 1].strip('"') | ||||||
|  |             msg = [] | ||||||
|  |             for m in self.messages: | ||||||
|  |                 for to_addrs in m.to: | ||||||
|  |                     if to_ in to_addrs: | ||||||
|  |                         msg.append(m) | ||||||
|  |  | ||||||
|         if "UNFLAGGED" in criteria: |         if "UNFLAGGED" in criteria: | ||||||
|             msg = filter(lambda m: not m.flagged, msg) |             msg = filter(lambda m: not m.flagged, msg) | ||||||
|  |  | ||||||
|         if "UNKEYWORD" in criteria: |         if "UNKEYWORD" in criteria: | ||||||
|             tag = criteria[criteria.index("UNKEYWORD") + 1].strip("'") |             tag = criteria[criteria.index("UNKEYWORD") + 1].strip("'") | ||||||
|             msg = filter(lambda m: "processed" not in m.flags, msg) |             msg = filter(lambda m: tag not in m.flags, msg) | ||||||
|  |  | ||||||
|         if "(X-GM-LABELS" in criteria:  # ['NOT', '(X-GM-LABELS', '"processed"'] |         if "(X-GM-LABELS" in criteria:  # ['NOT', '(X-GM-LABELS', '"processed"'] | ||||||
|             msg = filter(lambda m: "processed" not in m.flags, msg) |             msg = filter(lambda m: "processed" not in m.flags, msg) | ||||||
| @@ -205,15 +214,21 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | |||||||
|         body: str = "", |         body: str = "", | ||||||
|         subject: str = "the suject", |         subject: str = "the suject", | ||||||
|         from_: str = "noone@mail.com", |         from_: str = "noone@mail.com", | ||||||
|  |         to: Optional[List[str]] = None, | ||||||
|         seen: bool = False, |         seen: bool = False, | ||||||
|         flagged: bool = False, |         flagged: bool = False, | ||||||
|         processed: bool = False, |         processed: bool = False, | ||||||
|     ) -> MailMessage: |     ) -> MailMessage: | ||||||
|  |  | ||||||
|  |         if to is None: | ||||||
|  |             to = ["tosomeone@somewhere.com"] | ||||||
|  |  | ||||||
|         email_msg = email.message.EmailMessage() |         email_msg = email.message.EmailMessage() | ||||||
|         # TODO: This does NOT set the UID |         # TODO: This does NOT set the UID | ||||||
|         email_msg["Message-ID"] = str(uuid.uuid4()) |         email_msg["Message-ID"] = str(uuid.uuid4()) | ||||||
|         email_msg["Subject"] = subject |         email_msg["Subject"] = subject | ||||||
|         email_msg["From"] = from_ |         email_msg["From"] = from_ | ||||||
|  |         email_msg["To"] = str(" ,".join(to)) | ||||||
|         email_msg.set_content(body) |         email_msg.set_content(body) | ||||||
|  |  | ||||||
|         # Either add some default number of attachments |         # Either add some default number of attachments | ||||||
| @@ -266,6 +281,7 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | |||||||
|             self.create_message( |             self.create_message( | ||||||
|                 subject="Invoice 1", |                 subject="Invoice 1", | ||||||
|                 from_="amazon@amazon.de", |                 from_="amazon@amazon.de", | ||||||
|  |                 to=["me@myselfandi.com", "helpdesk@mydomain.com"], | ||||||
|                 body="cables", |                 body="cables", | ||||||
|                 seen=True, |                 seen=True, | ||||||
|                 flagged=False, |                 flagged=False, | ||||||
| @@ -276,6 +292,7 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | |||||||
|             self.create_message( |             self.create_message( | ||||||
|                 subject="Invoice 2", |                 subject="Invoice 2", | ||||||
|                 body="from my favorite electronic store", |                 body="from my favorite electronic store", | ||||||
|  |                 to=["invoices@mycompany.com"], | ||||||
|                 seen=False, |                 seen=False, | ||||||
|                 flagged=True, |                 flagged=True, | ||||||
|                 processed=True, |                 processed=True, | ||||||
| @@ -285,6 +302,7 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | |||||||
|             self.create_message( |             self.create_message( | ||||||
|                 subject="Claim your $10M price now!", |                 subject="Claim your $10M price now!", | ||||||
|                 from_="amazon@amazon-some-indian-site.org", |                 from_="amazon@amazon-some-indian-site.org", | ||||||
|  |                 to="special@me.me", | ||||||
|                 seen=False, |                 seen=False, | ||||||
|             ), |             ), | ||||||
|         ) |         ) | ||||||
| @@ -946,21 +964,26 @@ class TestMail(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | |||||||
|             password="secret", |             password="secret", | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         for (f_body, f_from, f_subject, expected_mail_count) in [ |         for (f_body, f_from, f_to, f_subject, expected_mail_count) in [ | ||||||
|             (None, None, "Claim", 1), |             (None, None, None, "Claim", 1), | ||||||
|             ("electronic", None, None, 1), |             ("electronic", None, None, None, 1), | ||||||
|             (None, "amazon", None, 2), |             (None, "amazon", None, None, 2), | ||||||
|             ("cables", "amazon", "Invoice", 1), |             ("cables", "amazon", None, "Invoice", 1), | ||||||
|  |             (None, None, "test@email.com", None, 0), | ||||||
|  |             (None, None, "invoices@mycompany.com", None, 1), | ||||||
|  |             ("electronic", None, "invoices@mycompany.com", None, 1), | ||||||
|  |             (None, "amazon", "me@myselfandi.com", None, 1), | ||||||
|         ]: |         ]: | ||||||
|             with self.subTest(f_body=f_body, f_from=f_from, f_subject=f_subject): |             with self.subTest(f_body=f_body, f_from=f_from, f_subject=f_subject): | ||||||
|                 MailRule.objects.all().delete() |                 MailRule.objects.all().delete() | ||||||
|                 rule = MailRule.objects.create( |                 _ = MailRule.objects.create( | ||||||
|                     name="testrule3", |                     name="testrule3", | ||||||
|                     account=account, |                     account=account, | ||||||
|                     action=MailRule.MailAction.DELETE, |                     action=MailRule.MailAction.DELETE, | ||||||
|                     filter_subject=f_subject, |                     filter_subject=f_subject, | ||||||
|                     filter_body=f_body, |                     filter_body=f_body, | ||||||
|                     filter_from=f_from, |                     filter_from=f_from, | ||||||
|  |                     filter_to=f_to, | ||||||
|                 ) |                 ) | ||||||
|                 self.reset_bogus_mailbox() |                 self.reset_bogus_mailbox() | ||||||
|                 self._queue_consumption_tasks_mock.reset_mock() |                 self._queue_consumption_tasks_mock.reset_mock() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Trenton H
					Trenton H