mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-30 18:27:45 -05:00
Format Python code with black
This commit is contained in:
@@ -15,9 +15,9 @@ class MailAccountAdminForm(forms.ModelForm):
|
||||
|
||||
model = MailAccount
|
||||
widgets = {
|
||||
'password': forms.PasswordInput(),
|
||||
"password": forms.PasswordInput(),
|
||||
}
|
||||
fields = '__all__'
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class MailAccountAdmin(admin.ModelAdmin):
|
||||
@@ -25,15 +25,9 @@ class MailAccountAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "imap_server", "username")
|
||||
|
||||
fieldsets = [
|
||||
(None, {
|
||||
'fields': ['name', 'imap_server', 'imap_port']
|
||||
}),
|
||||
(_("Authentication"), {
|
||||
'fields': ['imap_security', 'username', 'password']
|
||||
}),
|
||||
(_("Advanced settings"), {
|
||||
'fields': ['character_set']
|
||||
})
|
||||
(None, {"fields": ["name", "imap_server", "imap_port"]}),
|
||||
(_("Authentication"), {"fields": ["imap_security", "username", "password"]}),
|
||||
(_("Advanced settings"), {"fields": ["character_set"]}),
|
||||
]
|
||||
form = MailAccountAdminForm
|
||||
|
||||
@@ -44,56 +38,66 @@ class MailRuleAdmin(admin.ModelAdmin):
|
||||
"attachment_type": admin.VERTICAL,
|
||||
"action": admin.VERTICAL,
|
||||
"assign_title_from": admin.VERTICAL,
|
||||
"assign_correspondent_from": admin.VERTICAL
|
||||
"assign_correspondent_from": admin.VERTICAL,
|
||||
}
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('name', 'order', 'account', 'folder')
|
||||
}),
|
||||
(_("Filter"), {
|
||||
'description':
|
||||
_("Paperless will only process mails that match ALL of the "
|
||||
"filters given below."),
|
||||
'fields':
|
||||
('filter_from',
|
||||
'filter_subject',
|
||||
'filter_body',
|
||||
'filter_attachment_filename',
|
||||
'maximum_age',
|
||||
'attachment_type')
|
||||
}),
|
||||
(_("Actions"), {
|
||||
'description':
|
||||
_("The action applied to the mail. This action is only "
|
||||
"performed when documents were consumed from the mail. "
|
||||
"Mails without attachments will remain entirely untouched."),
|
||||
'fields': (
|
||||
'action',
|
||||
'action_parameter')
|
||||
}),
|
||||
(_("Metadata"), {
|
||||
'description':
|
||||
_("Assign metadata to documents consumed from this rule "
|
||||
"automatically. If you do not assign tags, types or "
|
||||
"correspondents here, paperless will still process all "
|
||||
"matching rules that you have defined."),
|
||||
"fields": (
|
||||
'assign_title_from',
|
||||
'assign_tag',
|
||||
'assign_document_type',
|
||||
'assign_correspondent_from',
|
||||
'assign_correspondent')
|
||||
})
|
||||
(None, {"fields": ("name", "order", "account", "folder")}),
|
||||
(
|
||||
_("Filter"),
|
||||
{
|
||||
"description": _(
|
||||
"Paperless will only process mails that match ALL of the "
|
||||
"filters given below."
|
||||
),
|
||||
"fields": (
|
||||
"filter_from",
|
||||
"filter_subject",
|
||||
"filter_body",
|
||||
"filter_attachment_filename",
|
||||
"maximum_age",
|
||||
"attachment_type",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Actions"),
|
||||
{
|
||||
"description": _(
|
||||
"The action applied to the mail. This action is only "
|
||||
"performed when documents were consumed from the mail. "
|
||||
"Mails without attachments will remain entirely untouched."
|
||||
),
|
||||
"fields": ("action", "action_parameter"),
|
||||
},
|
||||
),
|
||||
(
|
||||
_("Metadata"),
|
||||
{
|
||||
"description": _(
|
||||
"Assign metadata to documents consumed from this rule "
|
||||
"automatically. If you do not assign tags, types or "
|
||||
"correspondents here, paperless will still process all "
|
||||
"matching rules that you have defined."
|
||||
),
|
||||
"fields": (
|
||||
"assign_title_from",
|
||||
"assign_tag",
|
||||
"assign_document_type",
|
||||
"assign_correspondent_from",
|
||||
"assign_correspondent",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
list_filter = ("account",)
|
||||
|
||||
list_display = ("order", "name", "account", "folder", "action")
|
||||
|
||||
list_editable = ("order", )
|
||||
list_editable = ("order",)
|
||||
|
||||
list_display_links = ("name", )
|
||||
list_display_links = ("name",)
|
||||
|
||||
sortable_by = []
|
||||
|
||||
|
@@ -4,6 +4,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class PaperlessMailConfig(AppConfig):
|
||||
name = 'paperless_mail'
|
||||
name = "paperless_mail"
|
||||
|
||||
verbose_name = _('Paperless mail')
|
||||
verbose_name = _("Paperless mail")
|
||||
|
@@ -8,8 +8,13 @@ import pathvalidate
|
||||
from django.conf import settings
|
||||
from django.db import DatabaseError
|
||||
from django_q.tasks import async_task
|
||||
from imap_tools import MailBox, MailBoxUnencrypted, AND, MailMessageFlags, \
|
||||
MailboxFolderSelectError
|
||||
from imap_tools import (
|
||||
MailBox,
|
||||
MailBoxUnencrypted,
|
||||
AND,
|
||||
MailMessageFlags,
|
||||
MailboxFolderSelectError,
|
||||
)
|
||||
|
||||
from documents.loggers import LoggingMixin
|
||||
from documents.models import Correspondent
|
||||
@@ -22,7 +27,6 @@ class MailError(Exception):
|
||||
|
||||
|
||||
class BaseMailAction:
|
||||
|
||||
def get_criteria(self):
|
||||
return {}
|
||||
|
||||
@@ -31,30 +35,26 @@ class BaseMailAction:
|
||||
|
||||
|
||||
class DeleteMailAction(BaseMailAction):
|
||||
|
||||
def post_consume(self, M, message_uids, parameter):
|
||||
M.delete(message_uids)
|
||||
|
||||
|
||||
class MarkReadMailAction(BaseMailAction):
|
||||
|
||||
def get_criteria(self):
|
||||
return {'seen': False}
|
||||
return {"seen": False}
|
||||
|
||||
def post_consume(self, M, message_uids, parameter):
|
||||
M.seen(message_uids, True)
|
||||
|
||||
|
||||
class MoveMailAction(BaseMailAction):
|
||||
|
||||
def post_consume(self, M, message_uids, parameter):
|
||||
M.move(message_uids, parameter)
|
||||
|
||||
|
||||
class FlagMailAction(BaseMailAction):
|
||||
|
||||
def get_criteria(self):
|
||||
return {'flagged': False}
|
||||
return {"flagged": False}
|
||||
|
||||
def post_consume(self, M, message_uids, parameter):
|
||||
M.flag(message_uids, [MailMessageFlags.FLAGGED], True)
|
||||
@@ -108,10 +108,7 @@ class MailAccountHandler(LoggingMixin):
|
||||
try:
|
||||
return Correspondent.objects.get_or_create(name=name)[0]
|
||||
except DatabaseError as e:
|
||||
self.log(
|
||||
"error",
|
||||
f"Error while retrieving correspondent {name}: {e}"
|
||||
)
|
||||
self.log("error", f"Error while retrieving correspondent {name}: {e}")
|
||||
return None
|
||||
|
||||
def get_title(self, message, att, rule):
|
||||
@@ -122,7 +119,9 @@ class MailAccountHandler(LoggingMixin):
|
||||
return os.path.splitext(os.path.basename(att.filename))[0]
|
||||
|
||||
else:
|
||||
raise NotImplementedError("Unknown title selector.") # pragma: nocover # NOQA: E501
|
||||
raise NotImplementedError(
|
||||
"Unknown title selector."
|
||||
) # pragma: nocover # NOQA: E501
|
||||
|
||||
def get_correspondent(self, message, rule):
|
||||
c_from = rule.assign_correspondent_from
|
||||
@@ -134,9 +133,12 @@ class MailAccountHandler(LoggingMixin):
|
||||
return self._correspondent_from_name(message.from_)
|
||||
|
||||
elif c_from == MailRule.CORRESPONDENT_FROM_NAME:
|
||||
if message.from_values and 'name' in message.from_values and message.from_values['name']: # NOQA: E501
|
||||
return self._correspondent_from_name(
|
||||
message.from_values['name'])
|
||||
if (
|
||||
message.from_values
|
||||
and "name" in message.from_values
|
||||
and message.from_values["name"]
|
||||
): # NOQA: E501
|
||||
return self._correspondent_from_name(message.from_values["name"])
|
||||
else:
|
||||
return self._correspondent_from_name(message.from_)
|
||||
|
||||
@@ -144,69 +146,71 @@ class MailAccountHandler(LoggingMixin):
|
||||
return rule.assign_correspondent
|
||||
|
||||
else:
|
||||
raise NotImplementedError("Unknwown correspondent selector") # pragma: nocover # NOQA: E501
|
||||
raise NotImplementedError(
|
||||
"Unknwown correspondent selector"
|
||||
) # pragma: nocover # NOQA: E501
|
||||
|
||||
def handle_mail_account(self, account):
|
||||
|
||||
self.renew_logging_group()
|
||||
|
||||
self.log('debug', f"Processing mail account {account}")
|
||||
self.log("debug", f"Processing mail account {account}")
|
||||
|
||||
total_processed_files = 0
|
||||
|
||||
with get_mailbox(account.imap_server,
|
||||
account.imap_port,
|
||||
account.imap_security) as M:
|
||||
with get_mailbox(
|
||||
account.imap_server, account.imap_port, account.imap_security
|
||||
) as M:
|
||||
|
||||
try:
|
||||
M.login(account.username, account.password)
|
||||
except Exception:
|
||||
raise MailError(
|
||||
f"Error while authenticating account {account}")
|
||||
raise MailError(f"Error while authenticating account {account}")
|
||||
|
||||
self.log('debug', f"Account {account}: Processing "
|
||||
f"{account.rules.count()} rule(s)")
|
||||
self.log(
|
||||
"debug",
|
||||
f"Account {account}: Processing " f"{account.rules.count()} rule(s)",
|
||||
)
|
||||
|
||||
for rule in account.rules.order_by('order'):
|
||||
for rule in account.rules.order_by("order"):
|
||||
try:
|
||||
total_processed_files += self.handle_mail_rule(M, rule)
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"error",
|
||||
f"Rule {rule}: Error while processing rule: {e}",
|
||||
exc_info=True
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
return total_processed_files
|
||||
|
||||
def handle_mail_rule(self, M, rule):
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
f"Rule {rule}: Selecting folder {rule.folder}")
|
||||
self.log("debug", f"Rule {rule}: Selecting folder {rule.folder}")
|
||||
|
||||
try:
|
||||
M.folder.set(rule.folder)
|
||||
except MailboxFolderSelectError:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Folder {rule.folder} "
|
||||
f"does not exist in account {rule.account}")
|
||||
f"does not exist in account {rule.account}"
|
||||
)
|
||||
|
||||
criterias = make_criterias(rule)
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
f"Rule {rule}: Searching folder with criteria "
|
||||
f"{str(AND(**criterias))}")
|
||||
"debug",
|
||||
f"Rule {rule}: Searching folder with criteria " f"{str(AND(**criterias))}",
|
||||
)
|
||||
|
||||
try:
|
||||
messages = M.fetch(
|
||||
criteria=AND(**criterias),
|
||||
mark_seen=False,
|
||||
charset=rule.account.character_set)
|
||||
charset=rule.account.character_set,
|
||||
)
|
||||
except Exception:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Error while fetching folder {rule.folder}")
|
||||
raise MailError(f"Rule {rule}: Error while fetching folder {rule.folder}")
|
||||
|
||||
post_consume_messages = []
|
||||
|
||||
@@ -224,29 +228,27 @@ class MailAccountHandler(LoggingMixin):
|
||||
except Exception as e:
|
||||
self.log(
|
||||
"error",
|
||||
f"Rule {rule}: Error while processing mail "
|
||||
f"{message.uid}: {e}",
|
||||
exc_info=True)
|
||||
f"Rule {rule}: Error while processing mail " f"{message.uid}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
self.log("debug", f"Rule {rule}: Processed {mails_processed} matching mail(s)")
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
f"Rule {rule}: Processed {mails_processed} matching mail(s)")
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
"debug",
|
||||
f"Rule {rule}: Running mail actions on "
|
||||
f"{len(post_consume_messages)} mails")
|
||||
f"{len(post_consume_messages)} mails",
|
||||
)
|
||||
|
||||
try:
|
||||
get_rule_action(rule).post_consume(
|
||||
M,
|
||||
post_consume_messages,
|
||||
rule.action_parameter)
|
||||
M, post_consume_messages, rule.action_parameter
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise MailError(
|
||||
f"Rule {rule}: Error while processing post-consume actions: "
|
||||
f"{e}")
|
||||
f"Rule {rule}: Error while processing post-consume actions: " f"{e}"
|
||||
)
|
||||
|
||||
return total_processed_files
|
||||
|
||||
@@ -255,10 +257,11 @@ class MailAccountHandler(LoggingMixin):
|
||||
return 0
|
||||
|
||||
self.log(
|
||||
'debug',
|
||||
"debug",
|
||||
f"Rule {rule}: "
|
||||
f"Processing mail {message.subject} from {message.from_} with "
|
||||
f"{len(message.attachments)} attachment(s)")
|
||||
f"{len(message.attachments)} attachment(s)",
|
||||
)
|
||||
|
||||
correspondent = self.get_correspondent(message, rule)
|
||||
tag = rule.assign_tag
|
||||
@@ -268,12 +271,16 @@ class MailAccountHandler(LoggingMixin):
|
||||
|
||||
for att in message.attachments:
|
||||
|
||||
if not att.content_disposition == "attachment" and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY: # NOQA: E501
|
||||
if (
|
||||
not att.content_disposition == "attachment"
|
||||
and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY
|
||||
): # NOQA: E501
|
||||
self.log(
|
||||
'debug',
|
||||
"debug",
|
||||
f"Rule {rule}: "
|
||||
f"Skipping attachment {att.filename} "
|
||||
f"with content disposition {att.content_disposition}")
|
||||
f"with content disposition {att.content_disposition}",
|
||||
)
|
||||
continue
|
||||
|
||||
if rule.filter_attachment_filename:
|
||||
@@ -289,35 +296,44 @@ class MailAccountHandler(LoggingMixin):
|
||||
if is_mime_type_supported(mime_type):
|
||||
|
||||
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
|
||||
_, temp_filename = tempfile.mkstemp(prefix="paperless-mail-",
|
||||
dir=settings.SCRATCH_DIR)
|
||||
with open(temp_filename, 'wb') as f:
|
||||
_, temp_filename = tempfile.mkstemp(
|
||||
prefix="paperless-mail-", dir=settings.SCRATCH_DIR
|
||||
)
|
||||
with open(temp_filename, "wb") as f:
|
||||
f.write(att.payload)
|
||||
|
||||
self.log(
|
||||
'info',
|
||||
"info",
|
||||
f"Rule {rule}: "
|
||||
f"Consuming attachment {att.filename} from mail "
|
||||
f"{message.subject} from {message.from_}")
|
||||
f"{message.subject} from {message.from_}",
|
||||
)
|
||||
|
||||
async_task(
|
||||
"documents.tasks.consume_file",
|
||||
path=temp_filename,
|
||||
override_filename=pathvalidate.sanitize_filename(att.filename), # NOQA: E501
|
||||
override_filename=pathvalidate.sanitize_filename(
|
||||
att.filename
|
||||
), # NOQA: E501
|
||||
override_title=title,
|
||||
override_correspondent_id=correspondent.id if correspondent else None, # NOQA: E501
|
||||
override_document_type_id=doc_type.id if doc_type else None, # NOQA: E501
|
||||
override_correspondent_id=correspondent.id
|
||||
if correspondent
|
||||
else None, # NOQA: E501
|
||||
override_document_type_id=doc_type.id
|
||||
if doc_type
|
||||
else None, # NOQA: E501
|
||||
override_tag_ids=[tag.id] if tag else None,
|
||||
task_name=att.filename[:100]
|
||||
task_name=att.filename[:100],
|
||||
)
|
||||
|
||||
processed_attachments += 1
|
||||
else:
|
||||
self.log(
|
||||
'debug',
|
||||
"debug",
|
||||
f"Rule {rule}: "
|
||||
f"Skipping attachment {att.filename} "
|
||||
f"since guessed mime type {mime_type} is not supported "
|
||||
f"by paperless")
|
||||
f"by paperless",
|
||||
)
|
||||
|
||||
return processed_attachments
|
||||
|
@@ -6,7 +6,9 @@ from paperless_mail import tasks
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = """
|
||||
""".replace(" ", "")
|
||||
""".replace(
|
||||
" ", ""
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
|
@@ -9,40 +9,146 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('documents', '1002_auto_20201111_1105'),
|
||||
("documents", "1002_auto_20201111_1105"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MailAccount',
|
||||
name="MailAccount",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256, unique=True)),
|
||||
('imap_server', models.CharField(max_length=256)),
|
||||
('imap_port', models.IntegerField(blank=True, null=True)),
|
||||
('imap_security', models.PositiveIntegerField(choices=[(1, 'No encryption'), (2, 'Use SSL'), (3, 'Use STARTTLS')], default=2)),
|
||||
('username', models.CharField(max_length=256)),
|
||||
('password', models.CharField(max_length=256)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=256, unique=True)),
|
||||
("imap_server", models.CharField(max_length=256)),
|
||||
("imap_port", models.IntegerField(blank=True, null=True)),
|
||||
(
|
||||
"imap_security",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "No encryption"),
|
||||
(2, "Use SSL"),
|
||||
(3, "Use STARTTLS"),
|
||||
],
|
||||
default=2,
|
||||
),
|
||||
),
|
||||
("username", models.CharField(max_length=256)),
|
||||
("password", models.CharField(max_length=256)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MailRule',
|
||||
name="MailRule",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256)),
|
||||
('folder', models.CharField(default='INBOX', max_length=256)),
|
||||
('filter_from', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('filter_subject', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('filter_body', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('maximum_age', models.PositiveIntegerField(default=30)),
|
||||
('action', models.PositiveIntegerField(choices=[(1, 'Delete'), (2, 'Move to specified folder'), (3, "Mark as read, don't process read mails"), (4, "Flag the mail, don't process flagged mails")], default=3, help_text='The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched.')),
|
||||
('action_parameter', models.CharField(blank=True, help_text='Additional parameter for the action selected above, i.e., the target folder of the move to folder action.', max_length=256, null=True)),
|
||||
('assign_title_from', models.PositiveIntegerField(choices=[(1, 'Use subject as title'), (2, 'Use attachment filename as title')], default=1)),
|
||||
('assign_correspondent_from', models.PositiveIntegerField(choices=[(1, 'Do not assign a correspondent'), (2, 'Use mail address'), (3, 'Use name (or mail address if not available)'), (4, 'Use correspondent selected below')], default=1)),
|
||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='paperless_mail.mailaccount')),
|
||||
('assign_correspondent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.correspondent')),
|
||||
('assign_document_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.documenttype')),
|
||||
('assign_tag', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.tag')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=256)),
|
||||
("folder", models.CharField(default="INBOX", max_length=256)),
|
||||
(
|
||||
"filter_from",
|
||||
models.CharField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
(
|
||||
"filter_subject",
|
||||
models.CharField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
(
|
||||
"filter_body",
|
||||
models.CharField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
("maximum_age", models.PositiveIntegerField(default=30)),
|
||||
(
|
||||
"action",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Delete"),
|
||||
(2, "Move to specified folder"),
|
||||
(3, "Mark as read, don't process read mails"),
|
||||
(4, "Flag the mail, don't process flagged mails"),
|
||||
],
|
||||
default=3,
|
||||
help_text="The action applied to the mail. This action is only performed when documents were consumed from the mail. Mails without attachments will remain entirely untouched.",
|
||||
),
|
||||
),
|
||||
(
|
||||
"action_parameter",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_title_from",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Use subject as title"),
|
||||
(2, "Use attachment filename as title"),
|
||||
],
|
||||
default=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_correspondent_from",
|
||||
models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Do not assign a correspondent"),
|
||||
(2, "Use mail address"),
|
||||
(3, "Use name (or mail address if not available)"),
|
||||
(4, "Use correspondent selected below"),
|
||||
],
|
||||
default=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"account",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="rules",
|
||||
to="paperless_mail.mailaccount",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_correspondent",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.correspondent",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_document_type",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.documenttype",
|
||||
),
|
||||
),
|
||||
(
|
||||
"assign_tag",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.tag",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
@@ -7,26 +7,23 @@ from django_q.tasks import schedule
|
||||
|
||||
|
||||
def add_schedules(apps, schema_editor):
|
||||
schedule('paperless_mail.tasks.process_mail_accounts',
|
||||
name="Check all e-mail accounts",
|
||||
schedule_type=Schedule.MINUTES,
|
||||
minutes=10)
|
||||
schedule(
|
||||
"paperless_mail.tasks.process_mail_accounts",
|
||||
name="Check all e-mail accounts",
|
||||
schedule_type=Schedule.MINUTES,
|
||||
minutes=10,
|
||||
)
|
||||
|
||||
|
||||
def remove_schedules(apps, schema_editor):
|
||||
Schedule.objects.filter(
|
||||
func='paperless_mail.tasks.process_mail_accounts').delete()
|
||||
Schedule.objects.filter(func="paperless_mail.tasks.process_mail_accounts").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0001_initial'),
|
||||
('django_q', '0013_task_attempt_count'),
|
||||
("paperless_mail", "0001_initial"),
|
||||
("django_q", "0013_task_attempt_count"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
RunPython(add_schedules, remove_schedules)
|
||||
]
|
||||
|
||||
|
||||
operations = [RunPython(add_schedules, remove_schedules)]
|
||||
|
@@ -6,18 +6,22 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0002_auto_20201117_1334'),
|
||||
("paperless_mail", "0002_auto_20201117_1334"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='imap_port',
|
||||
field=models.IntegerField(blank=True, help_text='This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.', null=True),
|
||||
model_name="mailaccount",
|
||||
name="imap_port",
|
||||
field=models.IntegerField(
|
||||
blank=True,
|
||||
help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='name',
|
||||
model_name="mailrule",
|
||||
name="name",
|
||||
field=models.CharField(max_length=256, unique=True),
|
||||
),
|
||||
]
|
||||
|
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0003_auto_20201118_1940'),
|
||||
("paperless_mail", "0003_auto_20201118_1940"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mailrule',
|
||||
name='order',
|
||||
model_name="mailrule",
|
||||
name="order",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,28 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0004_mailrule_order'),
|
||||
("paperless_mail", "0004_mailrule_order"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='action',
|
||||
field=models.PositiveIntegerField(choices=[(3, "Mark as read, don't process read mails"), (4, "Flag the mail, don't process flagged mails"), (2, 'Move to specified folder'), (1, 'Delete')], default=3),
|
||||
model_name="mailrule",
|
||||
name="action",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(3, "Mark as read, don't process read mails"),
|
||||
(4, "Flag the mail, don't process flagged mails"),
|
||||
(2, "Move to specified folder"),
|
||||
(1, "Delete"),
|
||||
],
|
||||
default=3,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='maximum_age',
|
||||
field=models.PositiveIntegerField(default=30, help_text='Specified in days.'),
|
||||
model_name="mailrule",
|
||||
name="maximum_age",
|
||||
field=models.PositiveIntegerField(
|
||||
default=30, help_text="Specified in days."
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -7,122 +7,198 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '1011_auto_20210101_2340'),
|
||||
('paperless_mail', '0005_help_texts'),
|
||||
("documents", "1011_auto_20210101_2340"),
|
||||
("paperless_mail", "0005_help_texts"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='mailaccount',
|
||||
options={'verbose_name': 'mail account', 'verbose_name_plural': 'mail accounts'},
|
||||
name="mailaccount",
|
||||
options={
|
||||
"verbose_name": "mail account",
|
||||
"verbose_name_plural": "mail accounts",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='mailrule',
|
||||
options={'verbose_name': 'mail rule', 'verbose_name_plural': 'mail rules'},
|
||||
name="mailrule",
|
||||
options={"verbose_name": "mail rule", "verbose_name_plural": "mail rules"},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='imap_port',
|
||||
field=models.IntegerField(blank=True, help_text='This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.', null=True, verbose_name='IMAP port'),
|
||||
model_name="mailaccount",
|
||||
name="imap_port",
|
||||
field=models.IntegerField(
|
||||
blank=True,
|
||||
help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.",
|
||||
null=True,
|
||||
verbose_name="IMAP port",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='imap_security',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'No encryption'), (2, 'Use SSL'), (3, 'Use STARTTLS')], default=2, verbose_name='IMAP security'),
|
||||
model_name="mailaccount",
|
||||
name="imap_security",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[(1, "No encryption"), (2, "Use SSL"), (3, "Use STARTTLS")],
|
||||
default=2,
|
||||
verbose_name="IMAP security",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='imap_server',
|
||||
field=models.CharField(max_length=256, verbose_name='IMAP server'),
|
||||
model_name="mailaccount",
|
||||
name="imap_server",
|
||||
field=models.CharField(max_length=256, verbose_name="IMAP server"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='name',
|
||||
field=models.CharField(max_length=256, unique=True, verbose_name='name'),
|
||||
model_name="mailaccount",
|
||||
name="name",
|
||||
field=models.CharField(max_length=256, unique=True, verbose_name="name"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='password',
|
||||
field=models.CharField(max_length=256, verbose_name='password'),
|
||||
model_name="mailaccount",
|
||||
name="password",
|
||||
field=models.CharField(max_length=256, verbose_name="password"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailaccount',
|
||||
name='username',
|
||||
field=models.CharField(max_length=256, verbose_name='username'),
|
||||
model_name="mailaccount",
|
||||
name="username",
|
||||
field=models.CharField(max_length=256, verbose_name="username"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='account',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='paperless_mail.mailaccount', verbose_name='account'),
|
||||
model_name="mailrule",
|
||||
name="account",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="rules",
|
||||
to="paperless_mail.mailaccount",
|
||||
verbose_name="account",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='action',
|
||||
field=models.PositiveIntegerField(choices=[(3, "Mark as read, don't process read mails"), (4, "Flag the mail, don't process flagged mails"), (2, 'Move to specified folder'), (1, 'Delete')], default=3, verbose_name='action'),
|
||||
model_name="mailrule",
|
||||
name="action",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(3, "Mark as read, don't process read mails"),
|
||||
(4, "Flag the mail, don't process flagged mails"),
|
||||
(2, "Move to specified folder"),
|
||||
(1, "Delete"),
|
||||
],
|
||||
default=3,
|
||||
verbose_name="action",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='action_parameter',
|
||||
field=models.CharField(blank=True, help_text='Additional parameter for the action selected above, i.e., the target folder of the move to folder action.', max_length=256, null=True, verbose_name='action parameter'),
|
||||
model_name="mailrule",
|
||||
name="action_parameter",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="action parameter",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='assign_correspondent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.correspondent', verbose_name='assign this correspondent'),
|
||||
model_name="mailrule",
|
||||
name="assign_correspondent",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.correspondent",
|
||||
verbose_name="assign this correspondent",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='assign_correspondent_from',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Do not assign a correspondent'), (2, 'Use mail address'), (3, 'Use name (or mail address if not available)'), (4, 'Use correspondent selected below')], default=1, verbose_name='assign correspondent from'),
|
||||
model_name="mailrule",
|
||||
name="assign_correspondent_from",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Do not assign a correspondent"),
|
||||
(2, "Use mail address"),
|
||||
(3, "Use name (or mail address if not available)"),
|
||||
(4, "Use correspondent selected below"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="assign correspondent from",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='assign_document_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.documenttype', verbose_name='assign this document type'),
|
||||
model_name="mailrule",
|
||||
name="assign_document_type",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.documenttype",
|
||||
verbose_name="assign this document type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='assign_tag',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='documents.tag', verbose_name='assign this tag'),
|
||||
model_name="mailrule",
|
||||
name="assign_tag",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="documents.tag",
|
||||
verbose_name="assign this tag",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='assign_title_from',
|
||||
field=models.PositiveIntegerField(choices=[(1, 'Use subject as title'), (2, 'Use attachment filename as title')], default=1, verbose_name='assign title from'),
|
||||
model_name="mailrule",
|
||||
name="assign_title_from",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(1, "Use subject as title"),
|
||||
(2, "Use attachment filename as title"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="assign title from",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='filter_body',
|
||||
field=models.CharField(blank=True, max_length=256, null=True, verbose_name='filter body'),
|
||||
model_name="mailrule",
|
||||
name="filter_body",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=256, null=True, verbose_name="filter body"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='filter_from',
|
||||
field=models.CharField(blank=True, max_length=256, null=True, verbose_name='filter from'),
|
||||
model_name="mailrule",
|
||||
name="filter_from",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=256, null=True, verbose_name="filter from"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='filter_subject',
|
||||
field=models.CharField(blank=True, max_length=256, null=True, verbose_name='filter subject'),
|
||||
model_name="mailrule",
|
||||
name="filter_subject",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=256, null=True, verbose_name="filter subject"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='folder',
|
||||
field=models.CharField(default='INBOX', max_length=256, verbose_name='folder'),
|
||||
model_name="mailrule",
|
||||
name="folder",
|
||||
field=models.CharField(
|
||||
default="INBOX", max_length=256, verbose_name="folder"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='maximum_age',
|
||||
field=models.PositiveIntegerField(default=30, help_text='Specified in days.', verbose_name='maximum age'),
|
||||
model_name="mailrule",
|
||||
name="maximum_age",
|
||||
field=models.PositiveIntegerField(
|
||||
default=30, help_text="Specified in days.", verbose_name="maximum age"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='name',
|
||||
field=models.CharField(max_length=256, unique=True, verbose_name='name'),
|
||||
model_name="mailrule",
|
||||
name="name",
|
||||
field=models.CharField(max_length=256, unique=True, verbose_name="name"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='order',
|
||||
field=models.IntegerField(default=0, verbose_name='order'),
|
||||
model_name="mailrule",
|
||||
name="order",
|
||||
field=models.IntegerField(default=0, verbose_name="order"),
|
||||
),
|
||||
]
|
||||
|
@@ -6,18 +6,32 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0006_auto_20210101_2340'),
|
||||
("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'),
|
||||
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'),
|
||||
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",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -6,23 +6,39 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('paperless_mail', '0007_auto_20210106_0138'),
|
||||
("paperless_mail", "0007_auto_20210106_0138"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mailaccount',
|
||||
name='character_set',
|
||||
field=models.CharField(default='UTF-8', help_text="The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'.", max_length=256, verbose_name='character set'),
|
||||
model_name="mailaccount",
|
||||
name="character_set",
|
||||
field=models.CharField(
|
||||
default="UTF-8",
|
||||
help_text="The character set to use when communicating with the mail server, such as 'UTF-8' or 'US-ASCII'.",
|
||||
max_length=256,
|
||||
verbose_name="character set",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='action_parameter',
|
||||
field=models.CharField(blank=True, help_text='Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots.', max_length=256, null=True, verbose_name='action parameter'),
|
||||
model_name="mailrule",
|
||||
name="action_parameter",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Additional parameter for the action selected above, i.e., the target folder of the move to folder action. Subfolders must be separated by dots.",
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="action parameter",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailrule',
|
||||
name='folder',
|
||||
field=models.CharField(default='INBOX', help_text='Subfolders must be separated by dots.', max_length=256, verbose_name='folder'),
|
||||
model_name="mailrule",
|
||||
name="folder",
|
||||
field=models.CharField(
|
||||
default="INBOX",
|
||||
help_text="Subfolders must be separated by dots.",
|
||||
max_length=256,
|
||||
verbose_name="folder",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
@@ -6,7 +6,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class MailAccount(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("mail account")
|
||||
verbose_name_plural = _("mail accounts")
|
||||
@@ -21,41 +20,36 @@ class MailAccount(models.Model):
|
||||
(IMAP_SECURITY_STARTTLS, _("Use STARTTLS")),
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
_("name"),
|
||||
max_length=256, unique=True)
|
||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||
|
||||
imap_server = models.CharField(
|
||||
_("IMAP server"),
|
||||
max_length=256)
|
||||
imap_server = models.CharField(_("IMAP server"), max_length=256)
|
||||
|
||||
imap_port = models.IntegerField(
|
||||
_("IMAP port"),
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_("This is usually 143 for unencrypted and STARTTLS "
|
||||
"connections, and 993 for SSL connections."))
|
||||
|
||||
imap_security = models.PositiveIntegerField(
|
||||
_("IMAP security"),
|
||||
choices=IMAP_SECURITY_OPTIONS,
|
||||
default=IMAP_SECURITY_SSL
|
||||
help_text=_(
|
||||
"This is usually 143 for unencrypted and STARTTLS "
|
||||
"connections, and 993 for SSL connections."
|
||||
),
|
||||
)
|
||||
|
||||
username = models.CharField(
|
||||
_("username"),
|
||||
max_length=256)
|
||||
imap_security = models.PositiveIntegerField(
|
||||
_("IMAP security"), choices=IMAP_SECURITY_OPTIONS, default=IMAP_SECURITY_SSL
|
||||
)
|
||||
|
||||
password = models.CharField(
|
||||
_("password"),
|
||||
max_length=256)
|
||||
username = models.CharField(_("username"), max_length=256)
|
||||
|
||||
password = models.CharField(_("password"), max_length=256)
|
||||
|
||||
character_set = models.CharField(
|
||||
_("character set"),
|
||||
max_length=256,
|
||||
default="UTF-8",
|
||||
help_text=_("The character set to use when communicating with the "
|
||||
"mail server, such as 'UTF-8' or 'US-ASCII'.")
|
||||
help_text=_(
|
||||
"The character set to use when communicating with the "
|
||||
"mail server, such as 'UTF-8' or 'US-ASCII'."
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@@ -63,7 +57,6 @@ class MailAccount(models.Model):
|
||||
|
||||
|
||||
class MailRule(models.Model):
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("mail rule")
|
||||
verbose_name_plural = _("mail rules")
|
||||
@@ -73,8 +66,10 @@ class MailRule(models.Model):
|
||||
|
||||
ATTACHMENT_TYPES = (
|
||||
(ATTACHMENT_TYPE_ATTACHMENTS_ONLY, _("Only process attachments.")),
|
||||
(ATTACHMENT_TYPE_EVERYTHING, _("Process all files, including 'inline' "
|
||||
"attachments."))
|
||||
(
|
||||
ATTACHMENT_TYPE_EVERYTHING,
|
||||
_("Process all files, including 'inline' " "attachments."),
|
||||
),
|
||||
)
|
||||
|
||||
ACTION_DELETE = 1
|
||||
@@ -94,7 +89,7 @@ class MailRule(models.Model):
|
||||
|
||||
TITLE_SELECTOR = (
|
||||
(TITLE_FROM_SUBJECT, _("Use subject as title")),
|
||||
(TITLE_FROM_FILENAME, _("Use attachment filename as title"))
|
||||
(TITLE_FROM_FILENAME, _("Use attachment filename as title")),
|
||||
)
|
||||
|
||||
CORRESPONDENT_FROM_NOTHING = 1
|
||||
@@ -103,66 +98,64 @@ class MailRule(models.Model):
|
||||
CORRESPONDENT_FROM_CUSTOM = 4
|
||||
|
||||
CORRESPONDENT_SELECTOR = (
|
||||
(CORRESPONDENT_FROM_NOTHING,
|
||||
_("Do not assign a correspondent")),
|
||||
(CORRESPONDENT_FROM_EMAIL,
|
||||
_("Use mail address")),
|
||||
(CORRESPONDENT_FROM_NAME,
|
||||
_("Use name (or mail address if not available)")),
|
||||
(CORRESPONDENT_FROM_CUSTOM,
|
||||
_("Use correspondent selected below"))
|
||||
(CORRESPONDENT_FROM_NOTHING, _("Do not assign a correspondent")),
|
||||
(CORRESPONDENT_FROM_EMAIL, _("Use mail address")),
|
||||
(CORRESPONDENT_FROM_NAME, _("Use name (or mail address if not available)")),
|
||||
(CORRESPONDENT_FROM_CUSTOM, _("Use correspondent selected below")),
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
_("name"),
|
||||
max_length=256, unique=True)
|
||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||
|
||||
order = models.IntegerField(
|
||||
_("order"),
|
||||
default=0)
|
||||
order = models.IntegerField(_("order"), default=0)
|
||||
|
||||
account = models.ForeignKey(
|
||||
MailAccount,
|
||||
related_name="rules",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("account")
|
||||
verbose_name=_("account"),
|
||||
)
|
||||
|
||||
folder = models.CharField(
|
||||
_("folder"),
|
||||
default='INBOX', max_length=256,
|
||||
help_text=_("Subfolders must be separated by dots.")
|
||||
default="INBOX",
|
||||
max_length=256,
|
||||
help_text=_("Subfolders must be separated by dots."),
|
||||
)
|
||||
|
||||
filter_from = models.CharField(
|
||||
_("filter from"),
|
||||
max_length=256, null=True, blank=True)
|
||||
_("filter from"), max_length=256, null=True, blank=True
|
||||
)
|
||||
filter_subject = models.CharField(
|
||||
_("filter subject"),
|
||||
max_length=256, null=True, blank=True)
|
||||
_("filter subject"), max_length=256, null=True, blank=True
|
||||
)
|
||||
filter_body = models.CharField(
|
||||
_("filter body"),
|
||||
max_length=256, null=True, blank=True)
|
||||
_("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.")
|
||||
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."))
|
||||
_("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.")
|
||||
help_text=_(
|
||||
"Inline attachments include embedded images, so it's best "
|
||||
"to combine this option with a filename filter."
|
||||
),
|
||||
)
|
||||
|
||||
action = models.PositiveIntegerField(
|
||||
@@ -173,17 +166,19 @@ class MailRule(models.Model):
|
||||
|
||||
action_parameter = models.CharField(
|
||||
_("action parameter"),
|
||||
max_length=256, blank=True, null=True,
|
||||
help_text=_("Additional parameter for the action selected above, "
|
||||
"i.e., "
|
||||
"the target folder of the move to folder action. "
|
||||
"Subfolders must be separated by dots.")
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_(
|
||||
"Additional parameter for the action selected above, "
|
||||
"i.e., "
|
||||
"the target folder of the move to folder action. "
|
||||
"Subfolders must be separated by dots."
|
||||
),
|
||||
)
|
||||
|
||||
assign_title_from = models.PositiveIntegerField(
|
||||
_("assign title from"),
|
||||
choices=TITLE_SELECTOR,
|
||||
default=TITLE_FROM_SUBJECT
|
||||
_("assign title from"), choices=TITLE_SELECTOR, default=TITLE_FROM_SUBJECT
|
||||
)
|
||||
|
||||
assign_tag = models.ForeignKey(
|
||||
@@ -205,7 +200,7 @@ class MailRule(models.Model):
|
||||
assign_correspondent_from = models.PositiveIntegerField(
|
||||
_("assign correspondent from"),
|
||||
choices=CORRESPONDENT_SELECTOR,
|
||||
default=CORRESPONDENT_FROM_NOTHING
|
||||
default=CORRESPONDENT_FROM_NOTHING,
|
||||
)
|
||||
|
||||
assign_correspondent = models.ForeignKey(
|
||||
@@ -213,7 +208,7 @@ class MailRule(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("assign this correspondent")
|
||||
verbose_name=_("assign this correspondent"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
@@ -11,8 +11,7 @@ def process_mail_accounts():
|
||||
total_new_documents = 0
|
||||
for account in MailAccount.objects.all():
|
||||
try:
|
||||
total_new_documents += MailAccountHandler().handle_mail_account(
|
||||
account)
|
||||
total_new_documents += MailAccountHandler().handle_mail_account(account)
|
||||
except MailError:
|
||||
logger.exception(f"Error while processing mail account {account}")
|
||||
|
||||
|
@@ -38,7 +38,7 @@ class BogusMailBox(ContextManager):
|
||||
self.messages_spam = []
|
||||
|
||||
def login(self, username, password):
|
||||
if not (username == 'admin' and password == 'secret'):
|
||||
if not (username == "admin" and password == "secret"):
|
||||
raise Exception()
|
||||
|
||||
folder = BogusFolderManager()
|
||||
@@ -46,24 +46,24 @@ class BogusMailBox(ContextManager):
|
||||
def fetch(self, criteria, mark_seen, charset=""):
|
||||
msg = self.messages
|
||||
|
||||
criteria = str(criteria).strip('()').split(" ")
|
||||
criteria = str(criteria).strip("()").split(" ")
|
||||
|
||||
if 'UNSEEN' in criteria:
|
||||
if "UNSEEN" in criteria:
|
||||
msg = filter(lambda m: not m.seen, msg)
|
||||
|
||||
if 'SUBJECT' in criteria:
|
||||
subject = criteria[criteria.index('SUBJECT') + 1].strip('"')
|
||||
if "SUBJECT" in criteria:
|
||||
subject = criteria[criteria.index("SUBJECT") + 1].strip('"')
|
||||
msg = filter(lambda m: subject in m.subject, msg)
|
||||
|
||||
if 'BODY' in criteria:
|
||||
body = criteria[criteria.index('BODY') + 1].strip('"')
|
||||
if "BODY" in criteria:
|
||||
body = criteria[criteria.index("BODY") + 1].strip('"')
|
||||
msg = filter(lambda m: body in m.body, msg)
|
||||
|
||||
if 'FROM' in criteria:
|
||||
from_ = criteria[criteria.index('FROM') + 1].strip('"')
|
||||
if "FROM" in criteria:
|
||||
from_ = criteria[criteria.index("FROM") + 1].strip('"')
|
||||
msg = filter(lambda m: from_ in m.from_, msg)
|
||||
|
||||
if 'UNFLAGGED' in criteria:
|
||||
if "UNFLAGGED" in criteria:
|
||||
msg = filter(lambda m: not m.flagged, msg)
|
||||
|
||||
return list(msg)
|
||||
@@ -88,15 +88,20 @@ class BogusMailBox(ContextManager):
|
||||
self.messages_spam.append(
|
||||
filter(lambda m: m.uid in uid_list, self.messages)
|
||||
)
|
||||
self.messages = list(
|
||||
filter(lambda m: m.uid not in uid_list, self.messages)
|
||||
)
|
||||
self.messages = list(filter(lambda m: m.uid not in uid_list, self.messages))
|
||||
else:
|
||||
raise Exception()
|
||||
|
||||
|
||||
def create_message(num_attachments=1, body="", subject="the suject", from_="noone@mail.com", seen=False, flagged=False):
|
||||
message = namedtuple('MailMessage', [])
|
||||
def create_message(
|
||||
num_attachments=1,
|
||||
body="",
|
||||
subject="the suject",
|
||||
from_="noone@mail.com",
|
||||
seen=False,
|
||||
flagged=False,
|
||||
):
|
||||
message = namedtuple("MailMessage", [])
|
||||
|
||||
message.uid = uuid.uuid4()
|
||||
message.subject = subject
|
||||
@@ -112,8 +117,10 @@ def create_message(num_attachments=1, body="", subject="the suject", from_="noon
|
||||
return message
|
||||
|
||||
|
||||
def create_attachment(filename="the_file.pdf", content_disposition="attachment", payload=b"a PDF document"):
|
||||
attachment = namedtuple('Attachment', [])
|
||||
def create_attachment(
|
||||
filename="the_file.pdf", content_disposition="attachment", payload=b"a PDF document"
|
||||
):
|
||||
attachment = namedtuple("Attachment", [])
|
||||
attachment.filename = filename
|
||||
attachment.content_disposition = content_disposition
|
||||
attachment.payload = payload
|
||||
@@ -123,25 +130,24 @@ def create_attachment(filename="the_file.pdf", content_disposition="attachment",
|
||||
def fake_magic_from_buffer(buffer, mime=False):
|
||||
|
||||
if mime:
|
||||
if 'PDF' in str(buffer):
|
||||
return 'application/pdf'
|
||||
if "PDF" in str(buffer):
|
||||
return "application/pdf"
|
||||
else:
|
||||
return 'unknown/type'
|
||||
return "unknown/type"
|
||||
else:
|
||||
return 'Some verbose file description'
|
||||
return "Some verbose file description"
|
||||
|
||||
|
||||
@mock.patch('paperless_mail.mail.magic.from_buffer', fake_magic_from_buffer)
|
||||
@mock.patch("paperless_mail.mail.magic.from_buffer", fake_magic_from_buffer)
|
||||
class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
def setUp(self):
|
||||
patcher = mock.patch('paperless_mail.mail.MailBox')
|
||||
patcher = mock.patch("paperless_mail.mail.MailBox")
|
||||
m = patcher.start()
|
||||
self.bogus_mailbox = BogusMailBox()
|
||||
m.return_value = self.bogus_mailbox
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
patcher = mock.patch('paperless_mail.mail.async_task')
|
||||
patcher = mock.patch("paperless_mail.mail.async_task")
|
||||
self.async_task = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@@ -153,28 +159,53 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
def reset_bogus_mailbox(self):
|
||||
self.bogus_mailbox.messages = []
|
||||
self.bogus_mailbox.messages_spam = []
|
||||
self.bogus_mailbox.messages.append(create_message(subject="Invoice 1", from_="amazon@amazon.de", body="cables", seen=True, flagged=False))
|
||||
self.bogus_mailbox.messages.append(create_message(subject="Invoice 2", body="from my favorite electronic store", seen=False, flagged=True))
|
||||
self.bogus_mailbox.messages.append(create_message(subject="Claim your $10M price now!", from_="amazon@amazon-some-indian-site.org", seen=False))
|
||||
self.bogus_mailbox.messages.append(
|
||||
create_message(
|
||||
subject="Invoice 1",
|
||||
from_="amazon@amazon.de",
|
||||
body="cables",
|
||||
seen=True,
|
||||
flagged=False,
|
||||
)
|
||||
)
|
||||
self.bogus_mailbox.messages.append(
|
||||
create_message(
|
||||
subject="Invoice 2",
|
||||
body="from my favorite electronic store",
|
||||
seen=False,
|
||||
flagged=True,
|
||||
)
|
||||
)
|
||||
self.bogus_mailbox.messages.append(
|
||||
create_message(
|
||||
subject="Claim your $10M price now!",
|
||||
from_="amazon@amazon-some-indian-site.org",
|
||||
seen=False,
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_correspondent(self):
|
||||
message = namedtuple('MailMessage', [])
|
||||
message = namedtuple("MailMessage", [])
|
||||
message.from_ = "someone@somewhere.com"
|
||||
message.from_values = {'name': "Someone!", 'email': "someone@somewhere.com"}
|
||||
message.from_values = {"name": "Someone!", "email": "someone@somewhere.com"}
|
||||
|
||||
message2 = namedtuple('MailMessage', [])
|
||||
message2 = namedtuple("MailMessage", [])
|
||||
message2.from_ = "me@localhost.com"
|
||||
message2.from_values = {'name': "", 'email': "fake@localhost.com"}
|
||||
message2.from_values = {"name": "", "email": "fake@localhost.com"}
|
||||
|
||||
me_localhost = Correspondent.objects.create(name=message2.from_)
|
||||
someone_else = Correspondent.objects.create(name="someone else")
|
||||
|
||||
handler = MailAccountHandler()
|
||||
|
||||
rule = MailRule(name="a", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NOTHING)
|
||||
rule = MailRule(
|
||||
name="a", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NOTHING
|
||||
)
|
||||
self.assertIsNone(handler.get_correspondent(message, rule))
|
||||
|
||||
rule = MailRule(name="b", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL)
|
||||
rule = MailRule(
|
||||
name="b", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
self.assertIsNotNone(c)
|
||||
self.assertEqual(c.name, "someone@somewhere.com")
|
||||
@@ -183,7 +214,9 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(c.name, "me@localhost.com")
|
||||
self.assertEqual(c.id, me_localhost.id)
|
||||
|
||||
rule = MailRule(name="c", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NAME)
|
||||
rule = MailRule(
|
||||
name="c", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NAME
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
self.assertIsNotNone(c)
|
||||
self.assertEqual(c.name, "Someone!")
|
||||
@@ -191,14 +224,18 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertIsNotNone(c)
|
||||
self.assertEqual(c.id, me_localhost.id)
|
||||
|
||||
rule = MailRule(name="d", assign_correspondent_from=MailRule.CORRESPONDENT_FROM_CUSTOM, assign_correspondent=someone_else)
|
||||
rule = MailRule(
|
||||
name="d",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_CUSTOM,
|
||||
assign_correspondent=someone_else,
|
||||
)
|
||||
c = handler.get_correspondent(message, rule)
|
||||
self.assertEqual(c, someone_else)
|
||||
|
||||
def test_get_title(self):
|
||||
message = namedtuple('MailMessage', [])
|
||||
message = namedtuple("MailMessage", [])
|
||||
message.subject = "the message title"
|
||||
att = namedtuple('Attachment', [])
|
||||
att = namedtuple("Attachment", [])
|
||||
att.filename = "this_is_the_file.pdf"
|
||||
|
||||
handler = MailAccountHandler()
|
||||
@@ -209,7 +246,9 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(handler.get_title(message, att, rule), "the message title")
|
||||
|
||||
def test_handle_message(self):
|
||||
message = create_message(subject="the message title", from_="Myself", num_attachments=2)
|
||||
message = create_message(
|
||||
subject="the message title", from_="Myself", num_attachments=2
|
||||
)
|
||||
|
||||
account = MailAccount()
|
||||
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
|
||||
@@ -223,18 +262,18 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
args1, kwargs1 = self.async_task.call_args_list[0]
|
||||
args2, kwargs2 = self.async_task.call_args_list[1]
|
||||
|
||||
self.assertTrue(os.path.isfile(kwargs1['path']), kwargs1['path'])
|
||||
self.assertTrue(os.path.isfile(kwargs1["path"]), kwargs1["path"])
|
||||
|
||||
self.assertEqual(kwargs1['override_title'], "file_0")
|
||||
self.assertEqual(kwargs1['override_filename'], "file_0.pdf")
|
||||
self.assertEqual(kwargs1["override_title"], "file_0")
|
||||
self.assertEqual(kwargs1["override_filename"], "file_0.pdf")
|
||||
|
||||
self.assertTrue(os.path.isfile(kwargs2['path']), kwargs1['path'])
|
||||
self.assertTrue(os.path.isfile(kwargs2["path"]), kwargs1["path"])
|
||||
|
||||
self.assertEqual(kwargs2['override_title'], "file_1")
|
||||
self.assertEqual(kwargs2['override_filename'], "file_1.pdf")
|
||||
self.assertEqual(kwargs2["override_title"], "file_1")
|
||||
self.assertEqual(kwargs2["override_filename"], "file_1.pdf")
|
||||
|
||||
def test_handle_empty_message(self):
|
||||
message = namedtuple('MailMessage', [])
|
||||
message = namedtuple("MailMessage", [])
|
||||
|
||||
message.attachments = []
|
||||
rule = MailRule()
|
||||
@@ -248,7 +287,10 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
message = create_message()
|
||||
message.attachments = [
|
||||
create_attachment(filename="f1.pdf"),
|
||||
create_attachment(filename="f2.json", payload=b"{'much': 'payload.', 'so': 'json', 'wow': true}")
|
||||
create_attachment(
|
||||
filename="f2.json",
|
||||
payload=b"{'much': 'payload.', 'so': 'json', 'wow': true}",
|
||||
),
|
||||
]
|
||||
|
||||
account = MailAccount()
|
||||
@@ -260,14 +302,14 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(self.async_task.call_count, 1)
|
||||
|
||||
args, kwargs = self.async_task.call_args
|
||||
self.assertTrue(os.path.isfile(kwargs['path']), kwargs['path'])
|
||||
self.assertEqual(kwargs['override_filename'], "f1.pdf")
|
||||
self.assertTrue(os.path.isfile(kwargs["path"]), kwargs["path"])
|
||||
self.assertEqual(kwargs["override_filename"], "f1.pdf")
|
||||
|
||||
def test_handle_disposition(self):
|
||||
message = create_message()
|
||||
message.attachments = [
|
||||
create_attachment(filename="f1.pdf", content_disposition='inline'),
|
||||
create_attachment(filename="f2.pdf", content_disposition='attachment')
|
||||
create_attachment(filename="f1.pdf", content_disposition="inline"),
|
||||
create_attachment(filename="f2.pdf", content_disposition="attachment"),
|
||||
]
|
||||
|
||||
account = MailAccount()
|
||||
@@ -279,17 +321,21 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(self.async_task.call_count, 1)
|
||||
|
||||
args, kwargs = self.async_task.call_args
|
||||
self.assertEqual(kwargs['override_filename'], "f2.pdf")
|
||||
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')
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -316,19 +362,29 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
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)
|
||||
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]
|
||||
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")
|
||||
account = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_MARK_READ)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule", account=account, action=MailRule.ACTION_MARK_READ
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
@@ -340,9 +396,16 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
def test_handle_mail_account_delete(self):
|
||||
|
||||
account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="secret")
|
||||
account = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_DELETE, filter_subject="Invoice")
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_DELETE,
|
||||
filter_subject="Invoice",
|
||||
)
|
||||
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
@@ -351,9 +414,16 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 1)
|
||||
|
||||
def test_handle_mail_account_flag(self):
|
||||
account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="secret")
|
||||
account = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_FLAG, filter_subject="Invoice")
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_FLAG,
|
||||
filter_subject="Invoice",
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
@@ -364,9 +434,17 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
|
||||
def test_handle_mail_account_move(self):
|
||||
account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="secret")
|
||||
account = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_MOVE, action_parameter="spam", filter_subject="Claim")
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
)
|
||||
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 3)
|
||||
@@ -377,7 +455,9 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(len(self.bogus_mailbox.messages_spam), 1)
|
||||
|
||||
def test_error_login(self):
|
||||
account = MailAccount.objects.create(name="test", imap_server="", username="admin", password="wrong")
|
||||
account = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="wrong"
|
||||
)
|
||||
|
||||
try:
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
@@ -387,11 +467,20 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.fail("Should raise exception")
|
||||
|
||||
def test_error_skip_account(self):
|
||||
account_faulty = MailAccount.objects.create(name="test", imap_server="", username="admin", password="wroasdng")
|
||||
account_faulty = MailAccount.objects.create(
|
||||
name="test", imap_server="", username="admin", password="wroasdng"
|
||||
)
|
||||
|
||||
account = MailAccount.objects.create(name="test2", imap_server="", username="admin", password="secret")
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam", filter_subject="Claim")
|
||||
account = MailAccount.objects.create(
|
||||
name="test2", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
)
|
||||
|
||||
tasks.process_mail_accounts()
|
||||
self.assertEqual(self.async_task.call_count, 1)
|
||||
@@ -400,31 +489,51 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
def test_error_skip_rule(self):
|
||||
|
||||
account = MailAccount.objects.create(name="test2", imap_server="", username="admin", password="secret")
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam", filter_subject="Claim", order=1, folder="uuuhhhh")
|
||||
rule2 = MailRule.objects.create(name="testrule2", account=account, action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam", filter_subject="Claim", order=2)
|
||||
account = MailAccount.objects.create(
|
||||
name="test2", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
order=1,
|
||||
folder="uuuhhhh",
|
||||
)
|
||||
rule2 = MailRule.objects.create(
|
||||
name="testrule2",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
filter_subject="Claim",
|
||||
order=2,
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
self.assertEqual(self.async_task.call_count, 1)
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 2)
|
||||
self.assertEqual(len(self.bogus_mailbox.messages_spam), 1)
|
||||
|
||||
|
||||
@mock.patch("paperless_mail.mail.MailAccountHandler.get_correspondent")
|
||||
def test_error_skip_mail(self, m):
|
||||
|
||||
def get_correspondent_fake(message, rule):
|
||||
if message.from_ == 'amazon@amazon.de':
|
||||
if message.from_ == "amazon@amazon.de":
|
||||
raise ValueError("Does not compute.")
|
||||
else:
|
||||
return None
|
||||
|
||||
m.side_effect = get_correspondent_fake
|
||||
|
||||
account = MailAccount.objects.create(name="test2", imap_server="", username="admin", password="secret")
|
||||
rule = MailRule.objects.create(name="testrule", account=account, action=MailRule.ACTION_MOVE, action_parameter="spam")
|
||||
account = MailAccount.objects.create(
|
||||
name="test2", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
@@ -433,15 +542,21 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
# faulty mail still in inbox, untouched
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 1)
|
||||
self.assertEqual(self.bogus_mailbox.messages[0].from_, 'amazon@amazon.de')
|
||||
self.assertEqual(self.bogus_mailbox.messages[0].from_, "amazon@amazon.de")
|
||||
|
||||
def test_error_create_correspondent(self):
|
||||
|
||||
account = MailAccount.objects.create(name="test2", imap_server="", username="admin", password="secret")
|
||||
account = MailAccount.objects.create(
|
||||
name="test2", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule", filter_from="amazon@amazon.de",
|
||||
account=account, action=MailRule.ACTION_MOVE, action_parameter="spam",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL)
|
||||
name="testrule",
|
||||
filter_from="amazon@amazon.de",
|
||||
account=account,
|
||||
action=MailRule.ACTION_MOVE,
|
||||
action_parameter="spam",
|
||||
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL,
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
@@ -450,7 +565,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
c = Correspondent.objects.get(name="amazon@amazon.de")
|
||||
# should work
|
||||
self.assertEqual(kwargs['override_correspondent_id'], c.id)
|
||||
self.assertEqual(kwargs["override_correspondent_id"], c.id)
|
||||
|
||||
self.async_task.reset_mock()
|
||||
self.reset_bogus_mailbox()
|
||||
@@ -462,13 +577,19 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
|
||||
args, kwargs = self.async_task.call_args
|
||||
self.async_task.assert_called_once()
|
||||
self.assertEqual(kwargs['override_correspondent_id'], None)
|
||||
|
||||
self.assertEqual(kwargs["override_correspondent_id"], None)
|
||||
|
||||
def test_filters(self):
|
||||
|
||||
account = MailAccount.objects.create(name="test3", imap_server="", username="admin", password="secret")
|
||||
rule = MailRule.objects.create(name="testrule3", account=account, action=MailRule.ACTION_DELETE, filter_subject="Claim")
|
||||
account = MailAccount.objects.create(
|
||||
name="test3", imap_server="", username="admin", password="secret"
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule3",
|
||||
account=account,
|
||||
action=MailRule.ACTION_DELETE,
|
||||
filter_subject="Claim",
|
||||
)
|
||||
|
||||
self.assertEqual(self.async_task.call_count, 0)
|
||||
|
||||
@@ -508,23 +629,29 @@ class TestMail(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(len(self.bogus_mailbox.messages), 2)
|
||||
self.assertEqual(self.async_task.call_count, 5)
|
||||
|
||||
class TestManagementCommand(TestCase):
|
||||
|
||||
@mock.patch("paperless_mail.management.commands.mail_fetcher.tasks.process_mail_accounts")
|
||||
class TestManagementCommand(TestCase):
|
||||
@mock.patch(
|
||||
"paperless_mail.management.commands.mail_fetcher.tasks.process_mail_accounts"
|
||||
)
|
||||
def test_mail_fetcher(self, m):
|
||||
|
||||
call_command("mail_fetcher")
|
||||
|
||||
m.assert_called_once()
|
||||
|
||||
class TestTasks(TestCase):
|
||||
|
||||
class TestTasks(TestCase):
|
||||
@mock.patch("paperless_mail.tasks.MailAccountHandler.handle_mail_account")
|
||||
def test_all_accounts(self, m):
|
||||
m.side_effect = lambda account: 6
|
||||
|
||||
MailAccount.objects.create(name="A", imap_server="A", username="A", password="A")
|
||||
MailAccount.objects.create(name="B", imap_server="A", username="A", password="A")
|
||||
MailAccount.objects.create(
|
||||
name="A", imap_server="A", username="A", password="A"
|
||||
)
|
||||
MailAccount.objects.create(
|
||||
name="B", imap_server="A", username="A", password="A"
|
||||
)
|
||||
|
||||
result = tasks.process_mail_accounts()
|
||||
|
||||
@@ -538,7 +665,9 @@ class TestTasks(TestCase):
|
||||
@mock.patch("paperless_mail.tasks.MailAccountHandler.handle_mail_account")
|
||||
def test_single_accounts(self, m):
|
||||
|
||||
MailAccount.objects.create(name="A", imap_server="A", username="A", password="A")
|
||||
MailAccount.objects.create(
|
||||
name="A", imap_server="A", username="A", password="A"
|
||||
)
|
||||
|
||||
tasks.process_mail_account("A")
|
||||
|
||||
|
Reference in New Issue
Block a user