mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-09 11:59:27 -05:00
Feat: Add pdf layout switch for email
This commit is contained in:
parent
e4e906ce2b
commit
218f7ad876
@ -41,6 +41,7 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<pngx-input-select [horizontal]="true" i18n-title title="Consumption scope" [items]="consumptionScopeOptions" formControlName="consumption_scope" i18n-hint hint="See docs for .eml processing requirements"></pngx-input-select>
|
<pngx-input-select [horizontal]="true" i18n-title title="Consumption scope" [items]="consumptionScopeOptions" formControlName="consumption_scope" i18n-hint hint="See docs for .eml processing requirements"></pngx-input-select>
|
||||||
<pngx-input-select [horizontal]="true" i18n-title title="Attachment type" [items]="attachmentTypeOptions" formControlName="attachment_type"></pngx-input-select>
|
<pngx-input-select [horizontal]="true" i18n-title title="Attachment type" [items]="attachmentTypeOptions" formControlName="attachment_type"></pngx-input-select>
|
||||||
|
<pngx-input-select [horizontal]="true" i18n-title title="PDF layout" [items]="pdfLayoutOptions" formControlName="pdf_layout"></pngx-input-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<pngx-input-text [horizontal]="true" i18n-title title="Include only files matching" formControlName="filter_attachment_filename_include" i18n-hint hint="Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive." [error]="error?.filter_attachment_filename_include"></pngx-input-text>
|
<pngx-input-text [horizontal]="true" i18n-title title="Include only files matching" formControlName="filter_attachment_filename_include" i18n-hint hint="Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive." [error]="error?.filter_attachment_filename_include"></pngx-input-text>
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
MailMetadataTitleOption,
|
MailMetadataTitleOption,
|
||||||
MailRule,
|
MailRule,
|
||||||
MailRuleConsumptionScope,
|
MailRuleConsumptionScope,
|
||||||
|
MailRulePdfLayout,
|
||||||
} from 'src/app/data/mail-rule'
|
} from 'src/app/data/mail-rule'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
@ -58,6 +59,25 @@ const CONSUMPTION_SCOPE_OPTIONS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const PDF_LAYOUT_OPTIONS = [
|
||||||
|
{
|
||||||
|
id: MailRulePdfLayout.Text_Html,
|
||||||
|
name: $localize`Text, then HTML`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: MailRulePdfLayout.Html_Text,
|
||||||
|
name: $localize`HTML, then text`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: MailRulePdfLayout.Html_only,
|
||||||
|
name: $localize`HTML only`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: MailRulePdfLayout.Text_only,
|
||||||
|
name: $localize`Text only`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const ACTION_OPTIONS = [
|
const ACTION_OPTIONS = [
|
||||||
{
|
{
|
||||||
id: MailAction.Delete,
|
id: MailAction.Delete,
|
||||||
@ -184,6 +204,7 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
|
|||||||
filter_attachment_filename_exclude: new FormControl(null),
|
filter_attachment_filename_exclude: new FormControl(null),
|
||||||
maximum_age: new FormControl(null),
|
maximum_age: new FormControl(null),
|
||||||
attachment_type: new FormControl(MailFilterAttachmentType.Attachments),
|
attachment_type: new FormControl(MailFilterAttachmentType.Attachments),
|
||||||
|
pdf_layout: new FormControl(MailRulePdfLayout.Text_Html),
|
||||||
consumption_scope: new FormControl(MailRuleConsumptionScope.Attachments),
|
consumption_scope: new FormControl(MailRuleConsumptionScope.Attachments),
|
||||||
order: new FormControl(null),
|
order: new FormControl(null),
|
||||||
action: new FormControl(MailAction.MarkRead),
|
action: new FormControl(MailAction.MarkRead),
|
||||||
@ -232,4 +253,8 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
|
|||||||
get consumptionScopeOptions() {
|
get consumptionScopeOptions() {
|
||||||
return CONSUMPTION_SCOPE_OPTIONS
|
return CONSUMPTION_SCOPE_OPTIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get pdfLayoutOptions() {
|
||||||
|
return PDF_LAYOUT_OPTIONS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,13 @@ export enum MailRuleConsumptionScope {
|
|||||||
Everything = 3,
|
Everything = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MailRulePdfLayout {
|
||||||
|
Text_Html = 1,
|
||||||
|
Html_Text = 2,
|
||||||
|
Html_only = 3,
|
||||||
|
Text_only = 4,
|
||||||
|
}
|
||||||
|
|
||||||
export enum MailAction {
|
export enum MailAction {
|
||||||
Delete = 1,
|
Delete = 1,
|
||||||
Move = 2,
|
Move = 2,
|
||||||
@ -59,6 +66,8 @@ export interface MailRule extends ObjectWithPermissions {
|
|||||||
|
|
||||||
attachment_type: MailFilterAttachmentType
|
attachment_type: MailFilterAttachmentType
|
||||||
|
|
||||||
|
pdf_layout: MailRulePdfLayout
|
||||||
|
|
||||||
action: MailAction
|
action: MailAction
|
||||||
|
|
||||||
action_parameter?: string
|
action_parameter?: string
|
||||||
|
@ -47,6 +47,7 @@ from documents.templating.workflows import parse_w_workflow_placeholders
|
|||||||
from documents.utils import copy_basic_file_stats
|
from documents.utils import copy_basic_file_stats
|
||||||
from documents.utils import copy_file_with_basic_stats
|
from documents.utils import copy_file_with_basic_stats
|
||||||
from documents.utils import run_subprocess
|
from documents.utils import run_subprocess
|
||||||
|
from paperless_mail.parsers import MailDocumentParser
|
||||||
|
|
||||||
|
|
||||||
class WorkflowTriggerPlugin(
|
class WorkflowTriggerPlugin(
|
||||||
@ -474,6 +475,17 @@ class ConsumerPlugin(
|
|||||||
ConsumerStatusShortMessage.PARSING_DOCUMENT,
|
ConsumerStatusShortMessage.PARSING_DOCUMENT,
|
||||||
)
|
)
|
||||||
self.log.debug(f"Parsing {self.filename}...")
|
self.log.debug(f"Parsing {self.filename}...")
|
||||||
|
if (
|
||||||
|
isinstance(document_parser, MailDocumentParser)
|
||||||
|
and self.input_doc.mailrule_id
|
||||||
|
):
|
||||||
|
document_parser.parse(
|
||||||
|
self.working_copy,
|
||||||
|
mime_type,
|
||||||
|
self.filename,
|
||||||
|
self.input_doc.mailrule_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
document_parser.parse(self.working_copy, mime_type, self.filename)
|
document_parser.parse(self.working_copy, mime_type, self.filename)
|
||||||
|
|
||||||
self.log.debug(f"Generating thumbnail for {self.filename}...")
|
self.log.debug(f"Generating thumbnail for {self.filename}...")
|
||||||
|
27
src/paperless_mail/migrations/0029_mailrule_pdf_layout.py
Normal file
27
src/paperless_mail/migrations/0029_mailrule_pdf_layout.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2024-11-24 12:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("paperless_mail", "0028_alter_mailaccount_password_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="mailrule",
|
||||||
|
name="pdf_layout",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
choices=[
|
||||||
|
(1, "Text, then HTML"),
|
||||||
|
(2, "HTML, then text"),
|
||||||
|
(3, "HTML only"),
|
||||||
|
(4, "Text only"),
|
||||||
|
],
|
||||||
|
default=1,
|
||||||
|
verbose_name="pdf layout",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -115,6 +115,12 @@ class MailRule(document_models.ModelWithOwner):
|
|||||||
ATTACHMENTS_ONLY = 1, _("Only process attachments.")
|
ATTACHMENTS_ONLY = 1, _("Only process attachments.")
|
||||||
EVERYTHING = 2, _("Process all files, including 'inline' attachments.")
|
EVERYTHING = 2, _("Process all files, including 'inline' attachments.")
|
||||||
|
|
||||||
|
class PdfLayout(models.IntegerChoices):
|
||||||
|
TEXT_HTML = 1, _("Text, then HTML")
|
||||||
|
HTML_TEXT = 2, _("HTML, then text")
|
||||||
|
HTML_ONLY = 3, _("HTML only")
|
||||||
|
TEXT_ONLY = 4, _("Text only")
|
||||||
|
|
||||||
class MailAction(models.IntegerChoices):
|
class MailAction(models.IntegerChoices):
|
||||||
DELETE = 1, _("Delete")
|
DELETE = 1, _("Delete")
|
||||||
MOVE = 2, _("Move to specified folder")
|
MOVE = 2, _("Move to specified folder")
|
||||||
@ -230,6 +236,12 @@ class MailRule(document_models.ModelWithOwner):
|
|||||||
default=ConsumptionScope.ATTACHMENTS_ONLY,
|
default=ConsumptionScope.ATTACHMENTS_ONLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pdf_layout = models.PositiveIntegerField(
|
||||||
|
_("pdf layout"),
|
||||||
|
choices=PdfLayout.choices,
|
||||||
|
default=PdfLayout.TEXT_HTML,
|
||||||
|
)
|
||||||
|
|
||||||
action = models.PositiveIntegerField(
|
action = models.PositiveIntegerField(
|
||||||
_("action"),
|
_("action"),
|
||||||
choices=MailAction.choices,
|
choices=MailAction.choices,
|
||||||
|
@ -22,6 +22,7 @@ from documents.parsers import DocumentParser
|
|||||||
from documents.parsers import ParseError
|
from documents.parsers import ParseError
|
||||||
from documents.parsers import make_thumbnail_from_pdf
|
from documents.parsers import make_thumbnail_from_pdf
|
||||||
from paperless.models import OutputTypeChoices
|
from paperless.models import OutputTypeChoices
|
||||||
|
from paperless_mail.models import MailRule
|
||||||
|
|
||||||
|
|
||||||
class MailDocumentParser(DocumentParser):
|
class MailDocumentParser(DocumentParser):
|
||||||
@ -121,7 +122,13 @@ class MailDocumentParser(DocumentParser):
|
|||||||
result.sort(key=lambda item: (item["prefix"], item["key"]))
|
result.sort(key=lambda item: (item["prefix"], item["key"]))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def parse(self, document_path: Path, mime_type: str, file_name=None):
|
def parse(
|
||||||
|
self,
|
||||||
|
document_path: Path,
|
||||||
|
mime_type: str,
|
||||||
|
file_name=None,
|
||||||
|
mailrule: int | None = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Parses the given .eml into formatted text, based on the decoded email.
|
Parses the given .eml into formatted text, based on the decoded email.
|
||||||
|
|
||||||
@ -180,6 +187,10 @@ class MailDocumentParser(DocumentParser):
|
|||||||
self.date = mail.date
|
self.date = mail.date
|
||||||
|
|
||||||
self.log.debug("Creating a PDF from the email")
|
self.log.debug("Creating a PDF from the email")
|
||||||
|
if mailrule:
|
||||||
|
rule = MailRule.objects.get(pk=mailrule)
|
||||||
|
self.archive_path = self.generate_pdf(mail, rule.pdf_layout)
|
||||||
|
else:
|
||||||
self.archive_path = self.generate_pdf(mail)
|
self.archive_path = self.generate_pdf(mail)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -217,7 +228,11 @@ class MailDocumentParser(DocumentParser):
|
|||||||
f"{settings.TIKA_ENDPOINT}: {err}",
|
f"{settings.TIKA_ENDPOINT}: {err}",
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
def generate_pdf(self, mail_message: MailMessage) -> Path:
|
def generate_pdf(
|
||||||
|
self,
|
||||||
|
mail_message: MailMessage,
|
||||||
|
pdf_layout: MailRule.PdfLayout = MailRule.PdfLayout.TEXT_HTML,
|
||||||
|
) -> Path:
|
||||||
archive_path = Path(self.tempdir) / "merged.pdf"
|
archive_path = Path(self.tempdir) / "merged.pdf"
|
||||||
|
|
||||||
mail_pdf_file = self.generate_pdf_from_mail(mail_message)
|
mail_pdf_file = self.generate_pdf_from_mail(mail_message)
|
||||||
@ -246,6 +261,16 @@ class MailDocumentParser(DocumentParser):
|
|||||||
if pdf_a_format is not None:
|
if pdf_a_format is not None:
|
||||||
route.pdf_format(pdf_a_format)
|
route.pdf_format(pdf_a_format)
|
||||||
|
|
||||||
|
match pdf_layout:
|
||||||
|
case MailRule.PdfLayout.TEXT_HTML:
|
||||||
|
route.merge([mail_pdf_file, pdf_of_html_content])
|
||||||
|
case MailRule.PdfLayout.HTML_TEXT:
|
||||||
|
route.merge([pdf_of_html_content, mail_pdf_file])
|
||||||
|
case MailRule.PdfLayout.HTML_ONLY:
|
||||||
|
route.merge([pdf_of_html_content])
|
||||||
|
case MailRule.PdfLayout.TEXT_ONLY:
|
||||||
|
route.merge([mail_pdf_file])
|
||||||
|
case _:
|
||||||
route.merge([mail_pdf_file, pdf_of_html_content])
|
route.merge([mail_pdf_file, pdf_of_html_content])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -96,6 +96,7 @@ class MailRuleSerializer(OwnedObjectSerializer):
|
|||||||
"order",
|
"order",
|
||||||
"attachment_type",
|
"attachment_type",
|
||||||
"consumption_scope",
|
"consumption_scope",
|
||||||
|
"pdf_layout",
|
||||||
"owner",
|
"owner",
|
||||||
"user_can_change",
|
"user_can_change",
|
||||||
"permissions",
|
"permissions",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user