mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-14 23:21:18 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28261ac51c | ||
|
|
2f96cc0050 | ||
|
|
d36e8254f3 | ||
|
|
405fab8514 | ||
|
|
ee4f62a1b3 | ||
|
|
d97e4a9a95 | ||
|
|
1cfba87114 | ||
|
|
b145ed315a |
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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/',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user