Merge branch 'dev' into feature-remote-ocr-2

This commit is contained in:
shamoon 2025-10-27 21:22:42 -07:00 committed by GitHub
commit f5525bbdff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 158 additions and 32 deletions

View File

@ -51,5 +51,5 @@ body:
id: logs id: logs
attributes: attributes:
label: Relevant logs or output label: Relevant logs or output
description: If you have logs, errors that might help, paste it here. description: If you have logs, errors that might help, paste it here. For example other containers or services (database, redis, etc).
render: bash render: bash

View File

@ -6,8 +6,8 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
### ⚠️ Please remember: issues are for *bugs* ### ⚠️ Please remember: issues are for *bugs* only! ⚠️
That is, something you believe affects every single user of Paperless-ngx, not just you. If you're not sure, start with one of the other options below. That is, something you believe affects every single user of Paperless-ngx (and the demo, for example), not just you. If you are not sure, start with one of the other options below.
Also, note that **Paperless-ngx does not perform OCR or archive file creation itself**, those are handled by other tools. Problems with OCR or archive versions of specific files should likely be raised 'upstream', see https://github.com/ocrmypdf/OCRmyPDF/issues or https://github.com/tesseract-ocr/tesseract/issues Also, note that **Paperless-ngx does not perform OCR or archive file creation itself**, those are handled by other tools. Problems with OCR or archive versions of specific files should likely be raised 'upstream', see https://github.com/ocrmypdf/OCRmyPDF/issues or https://github.com/tesseract-ocr/tesseract/issues
- type: markdown - type: markdown
@ -59,6 +59,12 @@ body:
label: Browser logs label: Browser logs
description: Logs from the web browser related to your issue, if needed description: Logs from the web browser related to your issue, if needed
render: bash render: bash
- type: textarea
id: logs_services
attributes:
label: Services logs
description: Logs from other services (or containers) related to your issue, if needed. For example, the database or redis logs.
render: bash
- type: input - type: input
id: version id: version
attributes: attributes:

View File

@ -7,6 +7,8 @@ from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from filelock import FileLock from filelock import FileLock
from documents.data_models import ConsumableDocument
if TYPE_CHECKING: if TYPE_CHECKING:
from documents.models import Document from documents.models import Document
@ -15,7 +17,7 @@ def send_email(
subject: str, subject: str,
body: str, body: str,
to: list[str], to: list[str],
attachments: list[Document], attachments: list[Document | ConsumableDocument],
*, *,
use_archive: bool, use_archive: bool,
) -> int: ) -> int:
@ -45,17 +47,20 @@ def send_email(
# Something could be renaming the file concurrently so it can't be attached # Something could be renaming the file concurrently so it can't be attached
with FileLock(settings.MEDIA_LOCK): with FileLock(settings.MEDIA_LOCK):
for document in attachments: for document in attachments:
attachment_path = ( if isinstance(document, ConsumableDocument):
document.archive_path attachment_path = document.original_file
if use_archive and document.has_archive_version friendly_filename = document.original_file.name
else document.source_path else:
) attachment_path = (
document.archive_path
friendly_filename = _get_unique_filename( if use_archive and document.has_archive_version
document, else document.source_path
used_filenames, )
archive=use_archive and document.has_archive_version, friendly_filename = _get_unique_filename(
) document,
used_filenames,
archive=use_archive and document.has_archive_version,
)
used_filenames.add(friendly_filename) used_filenames.add(friendly_filename)
with attachment_path.open("rb") as f: with attachment_path.open("rb") as f:

View File

@ -3,7 +3,6 @@ import logging
from django.db import migrations from django.db import migrations
from django.db import models from django.db import models
from django.db import transaction
from documents.templating.utils import convert_format_str_to_template_format from documents.templating.utils import convert_format_str_to_template_format
@ -11,21 +10,34 @@ logger = logging.getLogger("paperless.migrations")
def convert_from_format_to_template(apps, schema_editor): def convert_from_format_to_template(apps, schema_editor):
WorkflowActions = apps.get_model("documents", "WorkflowAction") WorkflowAction = apps.get_model("documents", "WorkflowAction")
with transaction.atomic(): batch_size = 500
for WorkflowAction in WorkflowActions.objects.all(): actions_to_update = []
if not WorkflowAction.assign_title:
continue queryset = (
WorkflowAction.assign_title = convert_format_str_to_template_format( WorkflowAction.objects.filter(assign_title__isnull=False)
WorkflowAction.assign_title, .exclude(assign_title="")
) .only("id", "assign_title")
logger.debug( )
"Converted WorkflowAction id %d title to template format: %s",
WorkflowAction.id, for action in queryset:
WorkflowAction.assign_title, action.assign_title = convert_format_str_to_template_format(
) action.assign_title,
WorkflowAction.save() )
logger.debug(
"Converted WorkflowAction id %d title to template format: %s",
action.id,
action.assign_title,
)
actions_to_update.append(action)
if actions_to_update:
WorkflowAction.objects.bulk_update(
actions_to_update,
["assign_title"],
batch_size=batch_size,
)
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.6 on 2025-10-27 15:11
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1073_migrate_workflow_title_jinja"),
]
operations = [
migrations.AddField(
model_name="workflowrun",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="workflowrun",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="workflowrun",
name="transaction_id",
field=models.UUIDField(blank=True, null=True),
),
]

View File

@ -1547,7 +1547,7 @@ class Workflow(models.Model):
return f"Workflow: {self.name}" return f"Workflow: {self.name}"
class WorkflowRun(models.Model): class WorkflowRun(SoftDeleteModel):
workflow = models.ForeignKey( workflow = models.ForeignKey(
Workflow, Workflow,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View File

@ -80,7 +80,7 @@ def parse_w_workflow_placeholders(
if doc_url is not None: if doc_url is not None:
formatting.update({"doc_url": doc_url}) formatting.update({"doc_url": doc_url})
logger.debug(f"Jinja Template is : {text}") logger.debug(f"Parsing Workflow Jinja template: {text}")
try: try:
template = _template_environment.from_string( template = _template_environment.from_string(
text, text,

View File

@ -30,6 +30,7 @@ from pytest_django.fixtures import SettingsWrapper
from documents import tasks from documents import tasks
from documents.data_models import ConsumableDocument from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource from documents.data_models import DocumentSource
from documents.matching import document_matches_workflow from documents.matching import document_matches_workflow
from documents.matching import existing_document_matches_workflow from documents.matching import existing_document_matches_workflow
@ -2788,6 +2789,80 @@ class TestWorkflows(
self.assertEqual(doc.tags.all().count(), 1) self.assertEqual(doc.tags.all().count(), 1)
self.assertIn(self.t2, doc.tags.all()) self.assertIn(self.t2, doc.tags.all())
@override_settings(
PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True,
PAPERLESS_URL="http://localhost:8000",
)
@mock.patch("django.core.mail.message.EmailMessage.send")
def test_workflow_assignment_then_email_includes_attachment(self, mock_email_send):
"""
GIVEN:
- Workflow with assignment and email actions
- Email action configured to include the document
WHEN:
- Workflow is run on a newly created document
THEN:
- Email action sends the document as an attachment
"""
storage_path = StoragePath.objects.create(
name="sp2",
path="workflow/{{ document.pk }}",
)
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
)
assignment_action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.ASSIGNMENT,
assign_storage_path=storage_path,
assign_owner=self.user2,
)
assignment_action.assign_tags.add(self.t1)
email_action_config = WorkflowActionEmail.objects.create(
subject="Doc ready {doc_title}",
body="Document URL: {doc_url}",
to="owner@example.com",
include_document=True,
)
email_action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.EMAIL,
email=email_action_config,
)
workflow = Workflow.objects.create(name="Assignment then email", order=0)
workflow.triggers.add(trigger)
workflow.actions.set([assignment_action, email_action])
temp_working_copy = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.dirs.scratch_dir / "working-copy.pdf",
)
Document.objects.create(
title="workflow doc",
correspondent=self.c,
checksum="wf-assignment-email",
mime_type="application/pdf",
)
consumable_document = ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=temp_working_copy,
)
mock_email_send.return_value = 1
with self.assertNoLogs("paperless.handlers", level="ERROR"):
run_workflows(
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
consumable_document,
overrides=DocumentMetadataOverrides(),
)
mock_email_send.assert_called_once()
@override_settings( @override_settings(
PAPERLESS_EMAIL_HOST="localhost", PAPERLESS_EMAIL_HOST="localhost",
EMAIL_ENABLED=True, EMAIL_ENABLED=True,