Messing around with notification action to start

This commit is contained in:
shamoon 2024-10-23 10:17:19 -07:00
parent cc93bc41df
commit c4d59e0e77
No known key found for this signature in database
9 changed files with 264 additions and 2 deletions

View File

@ -322,6 +322,17 @@
</div>
</div>
}
@case (WorkflowActionType.Notification) {
<div class="row">
<div class="col">
<pngx-input-text i18n-title title="Notification subject" formControlName="notification_subject" [error]="error?.actions?.[i]?.notification_subject"></pngx-input-text>
<pngx-input-text i18n-title title="Notification body" formControlName="notification_body" [error]="error?.actions?.[i]?.notification_body"></pngx-input-text>
<pngx-input-text i18n-title title="Notification emails" formControlName="notification_destination_emails" [error]="error?.actions?.[i]?.notification_destination_emails"></pngx-input-text>
<pngx-input-text i18n-title title="Notification url" formControlName="notification_destination_url" [error]="error?.actions?.[i]?.notification_destination_url"></pngx-input-text>
<pngx-input-switch i18n-title title="Notification include document" formControlName="notification_include_document"></pngx-input-switch>
</div>
</div>
}
}
</div>
</ng-template>

View File

@ -96,6 +96,10 @@ export const WORKFLOW_ACTION_OPTIONS = [
id: WorkflowActionType.Removal,
name: $localize`Removal`,
},
{
id: WorkflowActionType.Notification,
name: $localize`Notification`,
},
]
const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
@ -402,6 +406,17 @@ export class WorkflowEditDialogComponent
remove_all_custom_fields: new FormControl(
action.remove_all_custom_fields
),
notification_subject: new FormControl(action.notification_subject),
notification_body: new FormControl(action.notification_body),
notification_destination_emails: new FormControl(
action.notification_destination_emails
),
notification_destination_url: new FormControl(
action.notification_destination_url
),
notification_include_document: new FormControl(
action.notification_include_document
),
}),
{ emitEvent }
)
@ -503,6 +518,11 @@ export class WorkflowEditDialogComponent
remove_all_permissions: false,
remove_custom_fields: [],
remove_all_custom_fields: false,
notification_subject: null,
notification_body: null,
notification_destination_emails: null,
notification_destination_url: null,
notification_include_document: null,
}
this.object.actions.push(action)
this.createActionField(action)

View File

@ -3,6 +3,7 @@ import { ObjectWithId } from './object-with-id'
export enum WorkflowActionType {
Assignment = 1,
Removal = 2,
Notification = 3,
}
export interface WorkflowAction extends ObjectWithId {
type: WorkflowActionType
@ -62,4 +63,14 @@ export interface WorkflowAction extends ObjectWithId {
remove_custom_fields?: number[] // [CustomField.id]
remove_all_custom_fields?: boolean
notification_subject?: string
notification_body?: string
notification_destination_emails?: string
notification_destination_url?: string
notification_include_document?: boolean
}

View File

@ -18,8 +18,7 @@ def settings(request):
)
return {
"EMAIL_ENABLED": django_settings.EMAIL_HOST != "localhost"
or django_settings.EMAIL_HOST_USER != "",
"EMAIL_ENABLED": django_settings.EMAIL_ENABLED,
"DISABLE_REGULAR_LOGIN": django_settings.DISABLE_REGULAR_LOGIN,
"REDIRECT_LOGIN_TO_SSO": django_settings.REDIRECT_LOGIN_TO_SSO,
"ACCOUNT_ALLOW_SIGNUPS": django_settings.ACCOUNT_ALLOW_SIGNUPS,

View File

@ -0,0 +1,71 @@
# Generated by Django 5.1.1 on 2024-10-23 17:15
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1055_alter_storagepath_path"),
]
operations = [
migrations.AddField(
model_name="workflowaction",
name="notification_body",
field=models.TextField(
blank=True,
help_text="The body (message) of the notification, can include some placeholders, see documentation.",
null=True,
verbose_name="notification body",
),
),
migrations.AddField(
model_name="workflowaction",
name="notification_destination_emails",
field=models.TextField(
blank=True,
help_text="The destination email addresses for the notification, comma separated.",
null=True,
verbose_name="notification destination emails",
),
),
migrations.AddField(
model_name="workflowaction",
name="notification_destination_url",
field=models.URLField(
blank=True,
help_text="The destination URL for the notification.",
null=True,
verbose_name="notification destination url",
),
),
migrations.AddField(
model_name="workflowaction",
name="notification_include_document",
field=models.BooleanField(
default=False,
verbose_name="include document in notification",
),
),
migrations.AddField(
model_name="workflowaction",
name="notification_subject",
field=models.CharField(
blank=True,
help_text="The subject of the notification, can include some placeholders, see documentation.",
max_length=256,
null=True,
verbose_name="notification subject",
),
),
migrations.AlterField(
model_name="workflowaction",
name="type",
field=models.PositiveIntegerField(
choices=[(1, "Assignment"), (2, "Removal"), (3, "Notification")],
default=1,
verbose_name="Workflow Action Type",
),
),
]

View File

@ -1166,6 +1166,10 @@ class WorkflowAction(models.Model):
2,
_("Removal"),
)
NOTIFICATION = (
3,
_("Notification"),
)
type = models.PositiveIntegerField(
_("Workflow Action Type"),
@ -1367,6 +1371,48 @@ class WorkflowAction(models.Model):
verbose_name=_("remove all custom fields"),
)
notification_subject = models.CharField(
_("notification subject"),
max_length=256,
null=True,
blank=True,
help_text=_(
"The subject of the notification, can include some placeholders, "
"see documentation.",
),
)
notification_body = models.TextField(
_("notification body"),
null=True,
blank=True,
help_text=_(
"The body (message) of the notification, can include some placeholders, "
"see documentation.",
),
)
notification_destination_emails = models.TextField(
_("notification destination emails"),
null=True,
blank=True,
help_text=_(
"The destination email addresses for the notification, comma separated.",
),
)
notification_destination_url = models.URLField(
_("notification destination url"),
null=True,
blank=True,
help_text=_("The destination URL for the notification."),
)
notification_include_document = models.BooleanField(
default=False,
verbose_name=_("include document in notification"),
)
class Meta:
verbose_name = _("workflow action")
verbose_name_plural = _("workflow actions")

View File

@ -1847,6 +1847,11 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
"remove_view_groups",
"remove_change_users",
"remove_change_groups",
"notification_subject",
"notification_body",
"notification_destination_emails",
"notification_destination_url",
"notification_include_document",
]
def validate(self, attrs):
@ -1884,6 +1889,38 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
)
if (
"notification_subject" in attrs
and attrs["notification_subject"] is not None
and len(attrs["notification_subject"]) > 0
and not (
attrs["notification_destination_emails"]
or attrs["notification_destination_url"]
)
):
raise serializers.ValidationError(
"Notification subject requires destination emails or URL",
)
if (
(
(
"notification_destination_emails" in attrs
and attrs["notification_destination_emails"] is not None
and len(attrs["notification_destination_emails"]) > 0
)
or (
"notification_destination_url" in attrs
and attrs["notification_destination_url"] is not None
and len(attrs["notification_destination_url"]) > 0
)
)
and not attrs["notification_subject"]
and not attrs["notification_body"]
):
raise serializers.ValidationError(
"Notification subject and body required",
)
return attrs

View File

@ -2,6 +2,7 @@ import logging
import os
import shutil
import httpx
from celery import states
from celery.signals import before_task_publish
from celery.signals import task_failure
@ -12,6 +13,7 @@ from django.contrib.admin.models import ADDITION
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core.mail import EmailMessage
from django.db import DatabaseError
from django.db import close_old_connections
from django.db import models
@ -866,6 +868,68 @@ def run_workflows(
):
overrides.custom_field_ids.remove(field.pk)
def notification_action():
subject = parse_doc_title_w_placeholders(
action.notification_subject,
document.correspondent.name if document.correspondent else "",
document.document_type.name if document.document_type else "",
document.owner.username if document.owner else "",
timezone.localtime(document.added),
document.original_filename or "",
timezone.localtime(document.created),
)
body = action.notification_body.format(
title=subject,
document=document,
)
# TODO: if doc exists, construct URL
if action.notification_destination_emails:
if not settings.EMAIL_ENABLED:
logger.error(
"Email backend has not been configured, cannot send email notifications",
extra={"group": logging_group},
)
else:
try:
email = EmailMessage(
subject=subject,
body=body,
to=action.notification_destination_emails.split(","),
)
if action.notification_include_document:
email.attach_file(document.source_path)
email.send()
except Exception as e:
logger.exception(
f"Error occurred sending notification email: {e}",
extra={"group": logging_group},
)
if action.notification_destination_url:
try:
data = {
"title": subject,
"message": body,
}
files = None
if action.notification_include_document:
with open(document.source_path, "rb") as f:
files = {"document": f}
httpx.post(
action.notification_destination_url,
data=data,
files=files,
)
else:
httpx.post(
action.notification_destination_url,
data=data,
)
except Exception as e:
logger.exception(
f"Error occurred sending notification to destination URL: {e}",
extra={"group": logging_group},
)
use_overrides = overrides is not None
messages = []
@ -911,6 +975,8 @@ def run_workflows(
assignment_action()
elif action.type == WorkflowAction.WorkflowActionType.REMOVAL:
removal_action()
elif action.type == WorkflowAction.WorkflowActionType.NOTIFICATION:
notification_action()
if not use_overrides:
# save first before setting tags

View File

@ -1195,6 +1195,7 @@ DEFAULT_FROM_EMAIL: Final[str] = os.getenv("PAPERLESS_EMAIL_FROM", EMAIL_HOST_US
EMAIL_USE_TLS: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_TLS")
EMAIL_USE_SSL: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_SSL")
EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] "
EMAIL_ENABLED = EMAIL_HOST != "localhost" or EMAIL_HOST_USER != ""
if DEBUG: # pragma: no cover
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"