mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	fixes #153, adds option for inline attachments and filename filters
This commit is contained in:
		| @@ -12,6 +12,7 @@ class MailAccountAdmin(admin.ModelAdmin): | ||||
| class MailRuleAdmin(admin.ModelAdmin): | ||||
|  | ||||
|     radio_fields = { | ||||
|         "attachment_type": admin.VERTICAL, | ||||
|         "action": admin.VERTICAL, | ||||
|         "assign_title_from": admin.VERTICAL, | ||||
|         "assign_correspondent_from": admin.VERTICAL | ||||
| @@ -29,7 +30,9 @@ class MailRuleAdmin(admin.ModelAdmin): | ||||
|                 ('filter_from', | ||||
|                  'filter_subject', | ||||
|                  'filter_body', | ||||
|                  'maximum_age') | ||||
|                  'filter_attachment_filename', | ||||
|                  'maximum_age', | ||||
|                  'attachment_type') | ||||
|         }), | ||||
|         (_("Actions"), { | ||||
|             'description': | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import os | ||||
| import tempfile | ||||
| from datetime import timedelta, date | ||||
| from fnmatch import fnmatch | ||||
|  | ||||
| import magic | ||||
| import pathvalidate | ||||
| @@ -263,7 +264,7 @@ class MailAccountHandler(LoggingMixin): | ||||
|  | ||||
|         for att in message.attachments: | ||||
|  | ||||
|             if not att.content_disposition == "attachment": | ||||
|             if not att.content_disposition == "attachment" and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY:  # NOQA: E501 | ||||
|                 self.log( | ||||
|                     'debug', | ||||
|                     f"Rule {rule}: " | ||||
| @@ -271,6 +272,10 @@ class MailAccountHandler(LoggingMixin): | ||||
|                     f"with content disposition {att.content_disposition}") | ||||
|                 continue | ||||
|  | ||||
|             if rule.filter_attachment_filename: | ||||
|                 if not fnmatch(att.filename, rule.filter_attachment_filename): | ||||
|                     continue | ||||
|  | ||||
|             title = self.get_title(message, att, rule) | ||||
|  | ||||
|             # don't trust the content type of the attachment. Could be | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/paperless_mail/migrations/0007_auto_20210106_0138.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/paperless_mail/migrations/0007_auto_20210106_0138.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # Generated by Django 3.1.5 on 2021-01-06 01:38 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('paperless_mail', '0006_auto_20210101_2340'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='mailrule', | ||||
|             name='attachment_type', | ||||
|             field=models.PositiveIntegerField(choices=[(1, 'Only process attachments.'), (2, "Process all files, including 'inline' attachments.")], default=1, help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.", verbose_name='attachment type'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='mailrule', | ||||
|             name='filter_attachment_filename', | ||||
|             field=models.CharField(blank=True, help_text='Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive.', max_length=256, null=True, verbose_name='filter attachment filename'), | ||||
|         ), | ||||
|     ] | ||||
| @@ -60,6 +60,15 @@ class MailRule(models.Model): | ||||
|         verbose_name = _("mail rule") | ||||
|         verbose_name_plural = _("mail rules") | ||||
|  | ||||
|     ATTACHMENT_TYPE_ATTACHMENTS_ONLY = 1 | ||||
|     ATTACHMENT_TYPE_EVERYTHING = 2 | ||||
|  | ||||
|     ATTACHMENT_TYPES = ( | ||||
|         (ATTACHMENT_TYPE_ATTACHMENTS_ONLY, _("Only process attachments.")), | ||||
|         (ATTACHMENT_TYPE_EVERYTHING, _("Process all files, including 'inline' " | ||||
|                                        "attachments.")) | ||||
|     ) | ||||
|  | ||||
|     ACTION_DELETE = 1 | ||||
|     ACTION_MOVE = 2 | ||||
|     ACTION_MARK_READ = 3 | ||||
| @@ -125,11 +134,27 @@ class MailRule(models.Model): | ||||
|         _("filter body"), | ||||
|         max_length=256, null=True, blank=True) | ||||
|  | ||||
|     filter_attachment_filename = models.CharField( | ||||
|         _("filter attachment filename"), | ||||
|         max_length=256, null=True, blank=True, | ||||
|         help_text=_("Only consume documents which entirely match this " | ||||
|                     "filename if specified. Wildcards such as *.pdf or " | ||||
|                     "*invoice* are allowed. Case insensitive.") | ||||
|     ) | ||||
|  | ||||
|     maximum_age = models.PositiveIntegerField( | ||||
|         _("maximum age"), | ||||
|         default=30, | ||||
|         help_text=_("Specified in days.")) | ||||
|  | ||||
|     attachment_type = models.PositiveIntegerField( | ||||
|         _("attachment type"), | ||||
|         choices=ATTACHMENT_TYPES, | ||||
|         default=ATTACHMENT_TYPE_ATTACHMENTS_ONLY, | ||||
|         help_text=_("Inline attachments include embedded images, so it's best " | ||||
|                     "to combine this option with a filename filter.") | ||||
|     ) | ||||
|  | ||||
|     action = models.PositiveIntegerField( | ||||
|         _("action"), | ||||
|         choices=ACTIONS, | ||||
|   | ||||
| @@ -273,6 +273,49 @@ class TestMail(TestCase): | ||||
|         args, kwargs = self.async_task.call_args | ||||
|         self.assertEqual(kwargs['override_filename'], "f2.pdf") | ||||
|  | ||||
|     def test_handle_inline_files(self): | ||||
|         message = create_message() | ||||
|         message.attachments = [ | ||||
|             create_attachment(filename="f1.pdf", content_disposition='inline'), | ||||
|             create_attachment(filename="f2.pdf", content_disposition='attachment') | ||||
|         ] | ||||
|  | ||||
|         account = MailAccount() | ||||
|         rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account, attachment_type=MailRule.ATTACHMENT_TYPE_EVERYTHING) | ||||
|  | ||||
|         result = self.mail_account_handler.handle_message(message, rule) | ||||
|  | ||||
|         self.assertEqual(result, 2) | ||||
|         self.assertEqual(self.async_task.call_count, 2) | ||||
|  | ||||
|     def test_filename_filter(self): | ||||
|         message = create_message() | ||||
|         message.attachments = [ | ||||
|             create_attachment(filename="f1.pdf"), | ||||
|             create_attachment(filename="f2.pdf"), | ||||
|             create_attachment(filename="f3.pdf"), | ||||
|             create_attachment(filename="f2.png"), | ||||
|         ] | ||||
|  | ||||
|         tests = [ | ||||
|             ("*.pdf", ["f1.pdf", "f2.pdf", "f3.pdf"]), | ||||
|             ("f1.pdf", ["f1.pdf"]), | ||||
|             ("f1", []), | ||||
|             ("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png"]), | ||||
|             ("*.png", ["f2.png"]), | ||||
|         ] | ||||
|  | ||||
|         for (pattern, matches) in tests: | ||||
|             self.async_task.reset_mock() | ||||
|             account = MailAccount() | ||||
|             rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account, filter_attachment_filename=pattern) | ||||
|  | ||||
|             result = self.mail_account_handler.handle_message(message, rule) | ||||
|  | ||||
|             self.assertEqual(result, len(matches)) | ||||
|             filenames = [a[1]['override_filename'] for a in self.async_task.call_args_list] | ||||
|             self.assertCountEqual(filenames, matches) | ||||
|  | ||||
|     def test_handle_mail_account_mark_read(self): | ||||
|  | ||||
|         account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="secret") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 jonaswinkler
					jonaswinkler