Compare commits

...

8 Commits

Author SHA1 Message Date
shamoon
28261ac51c Bump version to 2.14.3 2025-01-15 21:01:02 -08:00
Trenton H
2f96cc0050 Adds a default 30s timeout for emails, instead of no timeout (#8757) 2025-01-15 21:46:37 +00:00
Max Mehl
d36e8254f3 Enhancement: set autofocus on MFA code field (#8756) 2025-01-15 21:32:55 +00:00
shamoon
405fab8514 Update handlers.py 2025-01-15 12:18:02 -08:00
shamoon
ee4f62a1b3 Fix: import forms modules for entries component (#8752) 2025-01-15 08:46:11 -08:00
shamoon
d97e4a9a95 Fix: fix email/wh actions on consume started (#8750) 2025-01-15 15:48:10 +00:00
shamoon
1cfba87114 Fix: import date picker module in cf query dropdown (#8749) 2025-01-15 06:46:36 -08:00
github-actions[bot]
b145ed315a Documentation: Add v2.14.2 changelog (#8743)
* Changelog v2.14.2 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-14 23:42:18 -08:00
10 changed files with 238 additions and 45 deletions

View File

@@ -1,5 +1,35 @@
# Changelog
## paperless-ngx 2.14.2
### Bug Fixes
- Fix: dont try to parse empty webhook params [@shamoon](https://github.com/shamoon) ([#8742](https://github.com/paperless-ngx/paperless-ngx/pull/8742))
- Fix: pass working file to workflows, pickle file bytes [@shamoon](https://github.com/shamoon) ([#8741](https://github.com/paperless-ngx/paperless-ngx/pull/8741))
- Fix: use hard delete when bulk editing custom fields [@shamoon](https://github.com/shamoon) ([#8740](https://github.com/paperless-ngx/paperless-ngx/pull/8740))
- Fix: Ensure email attachments use the latest document path for attachments [@stumpylog](https://github.com/stumpylog) ([#8737](https://github.com/paperless-ngx/paperless-ngx/pull/8737))
- Fix: include tooltip module for custom fields display [@shamoon](https://github.com/shamoon) ([#8739](https://github.com/paperless-ngx/paperless-ngx/pull/8739))
- Fix: remove id of webhook/email actions on copy [@shamoon](https://github.com/shamoon) ([#8729](https://github.com/paperless-ngx/paperless-ngx/pull/8729))
- Fix: import dnd module for merge confirm dialog [@shamoon](https://github.com/shamoon) ([#8727](https://github.com/paperless-ngx/paperless-ngx/pull/8727))
### Dependencies
- Chore(deps): Bump django from 5.1.4 to 5.1.5 [@dependabot](https://github.com/dependabot) ([#8738](https://github.com/paperless-ngx/paperless-ngx/pull/8738))
### All App Changes
<details>
<summary>7 changes</summary>
- Fix: dont try to parse empty webhook params [@shamoon](https://github.com/shamoon) ([#8742](https://github.com/paperless-ngx/paperless-ngx/pull/8742))
- Fix: pass working file to workflows, pickle file bytes [@shamoon](https://github.com/shamoon) ([#8741](https://github.com/paperless-ngx/paperless-ngx/pull/8741))
- Fix: use hard delete when bulk editing custom fields [@shamoon](https://github.com/shamoon) ([#8740](https://github.com/paperless-ngx/paperless-ngx/pull/8740))
- Fix: Ensure email attachments use the latest document path for attachments [@stumpylog](https://github.com/stumpylog) ([#8737](https://github.com/paperless-ngx/paperless-ngx/pull/8737))
- Fix: include tooltip module for custom fields display [@shamoon](https://github.com/shamoon) ([#8739](https://github.com/paperless-ngx/paperless-ngx/pull/8739))
- Fix: remove id of webhook/email actions on copy [@shamoon](https://github.com/shamoon) ([#8729](https://github.com/paperless-ngx/paperless-ngx/pull/8729))
- Fix: import dnd module for merge confirm dialog [@shamoon](https://github.com/shamoon) ([#8727](https://github.com/paperless-ngx/paperless-ngx/pull/8727))
</details>
## paperless-ngx 2.14.1
### Bug Fixes

View File

@@ -9,7 +9,11 @@ import {
ViewChildren,
} from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import {
NgbDatepickerModule,
NgbDropdown,
NgbDropdownModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first, Subject, takeUntil } from 'rxjs'
@@ -164,6 +168,7 @@ export class CustomFieldQueriesModel {
ClearableBadgeComponent,
FormsModule,
ReactiveFormsModule,
NgbDatepickerModule,
NgTemplateOutlet,
NgSelectModule,
NgxBootstrapIconsModule,

View File

@@ -1,5 +1,9 @@
import { Component, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { AbstractInputComponent } from '../abstract-input'
@@ -14,7 +18,7 @@ import { AbstractInputComponent } from '../abstract-input'
selector: 'pngx-input-entries',
templateUrl: './entries.component.html',
styleUrl: './entries.component.scss',
imports: [NgxBootstrapIconsModule],
imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule],
})
export class EntriesComponent extends AbstractInputComponent<object> {
entries = []

View File

@@ -5,7 +5,7 @@ export const environment = {
apiBaseUrl: document.baseURI + 'api/',
apiVersion: '6',
appTitle: 'Paperless-ngx',
version: '2.14.2',
version: '2.14.3',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',

View File

@@ -62,10 +62,10 @@ class WorkflowTriggerPlugin(
Get overrides from matching workflows
"""
overrides, msg = run_workflows(
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
self.input_doc,
None,
DocumentMetadataOverrides(),
trigger_type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
document=self.input_doc,
logging_group=None,
overrides=DocumentMetadataOverrides(),
)
if overrides:
self.metadata.update(overrides)

View File

@@ -11,6 +11,7 @@ from celery.signals import task_failure
from celery.signals import task_postrun
from celery.signals import task_prerun
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import EmailMessage
from django.db import DatabaseError
from django.db import close_old_connections
@@ -29,9 +30,11 @@ from documents.data_models import DocumentMetadataOverrides
from documents.file_handling import create_source_path_directory
from documents.file_handling import delete_empty_directories
from documents.file_handling import generate_unique_filename
from documents.models import Correspondent
from documents.models import CustomField
from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import MatchingModel
from documents.models import PaperlessTask
from documents.models import Tag
@@ -558,9 +561,9 @@ def run_workflows_added(
def run_workflows_updated(sender, document: Document, logging_group=None, **kwargs):
run_workflows(
WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
document,
logging_group,
trigger_type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
document=document,
logging_group=logging_group,
)
@@ -907,21 +910,43 @@ def run_workflows(
)
return
title = (
document.title
if isinstance(document, Document)
else str(document.original_file)
)
doc_url = None
if isinstance(document, Document):
if not use_overrides:
title = document.title
doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
correspondent = document.correspondent.name if document.correspondent else ""
document_type = document.document_type.name if document.document_type else ""
owner_username = document.owner.username if document.owner else ""
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
created = timezone.localtime(document.created)
correspondent = (
document.correspondent.name if document.correspondent else ""
)
document_type = (
document.document_type.name if document.document_type else ""
)
owner_username = document.owner.username if document.owner else ""
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
created = timezone.localtime(document.created)
else:
title = overrides.title if overrides.title else str(document.original_file)
doc_url = ""
correspondent = (
Correspondent.objects.filter(pk=overrides.correspondent_id).first()
if overrides.correspondent_id
else ""
)
document_type = (
DocumentType.objects.filter(pk=overrides.document_type_id).first().name
if overrides.document_type_id
else ""
)
owner_username = (
User.objects.filter(pk=overrides.owner_id).first().username
if overrides.owner_id
else ""
)
filename = document.original_file if document.original_file else ""
current_filename = filename
added = timezone.localtime(timezone.now())
created = timezone.localtime(overrides.created)
subject = parse_w_workflow_placeholders(
action.email.subject,
correspondent,
@@ -973,21 +998,42 @@ def run_workflows(
)
def webhook_action():
title = (
document.title
if isinstance(document, Document)
else str(document.original_file)
)
doc_url = None
if isinstance(document, Document):
if not use_overrides:
title = document.title
doc_url = f"{settings.PAPERLESS_URL}/documents/{document.pk}/"
correspondent = document.correspondent.name if document.correspondent else ""
document_type = document.document_type.name if document.document_type else ""
owner_username = document.owner.username if document.owner else ""
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
created = timezone.localtime(document.created)
correspondent = (
document.correspondent.name if document.correspondent else ""
)
document_type = (
document.document_type.name if document.document_type else ""
)
owner_username = document.owner.username if document.owner else ""
filename = document.original_filename or ""
current_filename = document.filename or ""
added = timezone.localtime(document.added)
created = timezone.localtime(document.created)
else:
title = overrides.title if overrides.title else str(document.original_file)
doc_url = ""
correspondent = (
Correspondent.objects.filter(pk=overrides.correspondent_id).first()
if overrides.correspondent_id
else ""
)
document_type = (
DocumentType.objects.filter(pk=overrides.document_type_id).first().name
if overrides.document_type_id
else ""
)
owner_username = (
User.objects.filter(pk=overrides.owner_id).first().username
if overrides.owner_id
else ""
)
filename = document.original_file if document.original_file else ""
current_filename = filename
added = timezone.localtime(timezone.now())
created = timezone.localtime(overrides.created)
try:
data = {}

View File

@@ -16,7 +16,7 @@
{% block form_content %}
{% translate "Code" as i18n_code %}
<div class="form-floating">
<input type="code" name="code" id="inputCode" autocomplete="one-time-code" placeholder="{{ i18n_code }}" class="form-control" required>
<input type="code" name="code" id="inputCode" autocomplete="one-time-code" placeholder="{{ i18n_code }}" class="form-control" required autofocus>
<label for="inputCode">{{ i18n_code }}</label>
</div>
<div class="d-grid mt-3">

View File

@@ -2296,6 +2296,64 @@ class TestWorkflows(
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("httpx.post")
@mock.patch("django.core.mail.message.EmailMessage.send")
def test_workflow_email_consumption_started(self, mock_email_send, mock_post):
"""
GIVEN:
- Workflow with email action and consumption trigger
WHEN:
- Document is consumed
THEN:
- Email is sent
"""
mock_post.return_value = mock.Mock(
status_code=200,
json=mock.Mock(return_value={"status": "ok"}),
)
mock_email_send.return_value = 1
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
)
email_action = WorkflowActionEmail.objects.create(
subject="Test Notification: {doc_title}",
body="Test message: {doc_url}",
to="user@example.com",
include_document=False,
)
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL,
email=email_action,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.dirs.scratch_dir / "simple.pdf",
)
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
with self.assertLogs("paperless.matching", level="INFO"):
tasks.consume_file(
ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=test_file,
),
None,
)
mock_email_send.assert_called_once()
@override_settings(
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("documents.signals.handlers.send_webhook.delay")
def test_workflow_webhook_action_body(self, mock_post):
"""
@@ -2352,8 +2410,6 @@ class TestWorkflows(
)
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("documents.signals.handlers.send_webhook.delay")
@@ -2415,8 +2471,6 @@ class TestWorkflows(
)
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
def test_workflow_webhook_action_fail(self):
@@ -2562,3 +2616,56 @@ class TestWorkflows(
"Failed attempt sending webhook to http://paperless-ngx.com"
)
self.assertIn(expected_str, cm.output[0])
@mock.patch("documents.signals.handlers.send_webhook.delay")
def test_workflow_webhook_action_consumption(self, mock_post):
"""
GIVEN:
- Workflow with webhook action and consumption trigger
WHEN:
- Document is consumed
THEN:
- Webhook is sent
"""
mock_post.return_value = mock.Mock(
status_code=200,
json=mock.Mock(return_value={"status": "ok"}),
)
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
)
webhook_action = WorkflowActionWebhook.objects.create(
use_params=False,
body="Test message: {doc_url}",
url="http://paperless-ngx.com",
include_document=False,
)
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.WEBHOOK,
webhook=webhook_action,
)
w = Workflow.objects.create(
name="Workflow 1",
order=0,
)
w.triggers.add(trigger)
w.actions.add(action)
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.dirs.scratch_dir / "simple.pdf",
)
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
with self.assertLogs("paperless.matching", level="INFO"):
tasks.consume_file(
ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=test_file,
),
None,
)
mock_post.assert_called_once()

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_TIMEOUT = 30.0
EMAIL_ENABLED = EMAIL_HOST != "localhost" or EMAIL_HOST_USER != ""
if DEBUG: # pragma: no cover
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"

View File

@@ -1,6 +1,6 @@
from typing import Final
__version__: Final[tuple[int, int, int]] = (2, 14, 2)
__version__: Final[tuple[int, int, int]] = (2, 14, 3)
# Version string like X.Y.Z
__full_version_str__: Final[str] = ".".join(map(str, __version__))
# Version string like X.Y