Compare commits

..

7 Commits

Author SHA1 Message Date
shamoon
bd7752e4d6 Happy Sonar 2026-01-29 11:47:21 -08:00
shamoon
fbcaaf2e9b Also in the tests 2026-01-29 11:45:14 -08:00
shamoon
2229479499 Refactor to separate AsnCheckPlugin 2026-01-29 11:32:13 -08:00
shamoon
7a044763dd Merge branch 'dev' into fix-11679 2026-01-29 11:25:07 -08:00
shamoon
f23433bd57 Merge branch 'dev' into fix-11679 2026-01-09 20:45:38 -08:00
shamoon
c968505f64 Fix: run pre-flight ASN check after barcode detection 2025-12-30 10:02:32 -08:00
shamoon
fbb5864757 Add test to reproduce asn in trash error 2025-12-30 10:02:31 -08:00
20 changed files with 110 additions and 128 deletions

View File

@@ -7,11 +7,6 @@ cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py management_command "$@" python3 manage.py management_command "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py management_command "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py management_command "$@" s6-setuidgid paperless python3 manage.py management_command "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py convert_mariadb_uuid "$@" python3 manage.py convert_mariadb_uuid "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py convert_mariadb_uuid "$@" s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py createsuperuser "$@" python3 manage.py createsuperuser "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py createsuperuser "$@" s6-setuidgid paperless python3 manage.py createsuperuser "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_archiver "$@" python3 manage.py document_archiver "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_archiver "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_archiver "$@" s6-setuidgid paperless python3 manage.py document_archiver "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,16 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_create_classifier "$@" python3 manage.py document_create_classifier "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_create_classifier "$@"
else
echo "Unknown user."
exit 1
fi
er "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
s6-setuidgid paperless python3 manage.py document_create_classifier "$@" s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_exporter "$@" python3 manage.py document_exporter "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_exporter "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_exporter "$@" s6-setuidgid paperless python3 manage.py document_exporter "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_fuzzy_match "$@" python3 manage.py document_fuzzy_match "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_fuzzy_match "$@" s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_importer "$@" python3 manage.py document_importer "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_importer "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_importer "$@" s6-setuidgid paperless python3 manage.py document_importer "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_index "$@" python3 manage.py document_index "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_index "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_index "$@" s6-setuidgid paperless python3 manage.py document_index "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_renamer "$@" python3 manage.py document_renamer "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_renamer "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_renamer "$@" s6-setuidgid paperless python3 manage.py document_renamer "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_retagger "$@" python3 manage.py document_retagger "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_retagger "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_retagger "$@" s6-setuidgid paperless python3 manage.py document_retagger "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_sanity_checker "$@" python3 manage.py document_sanity_checker "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_sanity_checker "$@" s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py document_thumbnails "$@" python3 manage.py document_thumbnails "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py document_thumbnails "$@" s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py mail_fetcher "$@" python3 manage.py mail_fetcher "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py mail_fetcher "$@" s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py manage_superuser "$@" python3 manage.py manage_superuser "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py manage_superuser "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py manage_superuser "$@" s6-setuidgid paperless python3 manage.py manage_superuser "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -6,12 +6,7 @@ set -e
cd "${PAPERLESS_SRC_DIR}" cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py prune_audit_logs "$@" python3 manage.py prune_audit_logs "$@"
elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
elif [[ $(id -un) == "paperless" ]]; then elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py prune_audit_logs "$@" s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
else
echo "Unknown user."
exit 1
fi fi

View File

@@ -5,6 +5,7 @@ import tempfile
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Final
import magic import magic
from django.conf import settings from django.conf import settings
@@ -49,6 +50,8 @@ 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 from paperless_mail.parsers import MailDocumentParser
LOGGING_NAME: Final[str] = "paperless.consumer"
class WorkflowTriggerPlugin( class WorkflowTriggerPlugin(
NoCleanupPluginMixin, NoCleanupPluginMixin,
@@ -156,7 +159,7 @@ class ConsumerPlugin(
ConsumerPluginMixin, ConsumerPluginMixin,
ConsumeTaskPlugin, ConsumeTaskPlugin,
): ):
logging_name = "paperless.consumer" logging_name = LOGGING_NAME
def run_pre_consume_script(self): def run_pre_consume_script(self):
""" """
@@ -753,7 +756,7 @@ class ConsumerPreflightPlugin(
ConsumeTaskPlugin, ConsumeTaskPlugin,
): ):
NAME: str = "ConsumerPreflightPlugin" NAME: str = "ConsumerPreflightPlugin"
logging_name = "paperless.consumer" logging_name = LOGGING_NAME
def pre_check_file_exists(self): def pre_check_file_exists(self):
""" """
@@ -828,6 +831,32 @@ class ConsumerPreflightPlugin(
settings.ORIGINALS_DIR.mkdir(parents=True, exist_ok=True) settings.ORIGINALS_DIR.mkdir(parents=True, exist_ok=True)
settings.ARCHIVE_DIR.mkdir(parents=True, exist_ok=True) settings.ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
def run(self) -> None:
self._send_progress(
0,
100,
ProgressStatusOptions.STARTED,
ConsumerStatusShortMessage.NEW_FILE,
)
# Make sure that preconditions for consuming the file are met.
self.pre_check_file_exists()
self.pre_check_duplicate()
self.pre_check_directories()
class AsnCheckPlugin(
NoCleanupPluginMixin,
NoSetupPluginMixin,
AlwaysRunPluginMixin,
LoggingMixin,
ConsumerPluginMixin,
ConsumeTaskPlugin,
):
NAME: str = "AsnCheckPlugin"
logging_name = LOGGING_NAME
def pre_check_asn_value(self): def pre_check_asn_value(self):
""" """
Check that if override_asn is given, it is unique and within a valid range Check that if override_asn is given, it is unique and within a valid range
@@ -865,16 +894,4 @@ class ConsumerPreflightPlugin(
) )
def run(self) -> None: def run(self) -> None:
self._send_progress(
0,
100,
ProgressStatusOptions.STARTED,
ConsumerStatusShortMessage.NEW_FILE,
)
# Make sure that preconditions for consuming the file are met.
self.pre_check_file_exists()
self.pre_check_duplicate()
self.pre_check_directories()
self.pre_check_asn_value() self.pre_check_asn_value()

View File

@@ -29,6 +29,7 @@ from documents.bulk_download import OriginalsOnlyStrategy
from documents.caching import clear_document_caches from documents.caching import clear_document_caches
from documents.classifier import DocumentClassifier from documents.classifier import DocumentClassifier
from documents.classifier import load_classifier from documents.classifier import load_classifier
from documents.consumer import AsnCheckPlugin
from documents.consumer import ConsumerPlugin from documents.consumer import ConsumerPlugin
from documents.consumer import ConsumerPreflightPlugin from documents.consumer import ConsumerPreflightPlugin
from documents.consumer import WorkflowTriggerPlugin from documents.consumer import WorkflowTriggerPlugin
@@ -157,8 +158,10 @@ def consume_file(
plugins: list[type[ConsumeTaskPlugin]] = [ plugins: list[type[ConsumeTaskPlugin]] = [
ConsumerPreflightPlugin, ConsumerPreflightPlugin,
AsnCheckPlugin,
CollatePlugin, CollatePlugin,
BarcodePlugin, BarcodePlugin,
AsnCheckPlugin, # Re-run ASN check after barcode reading
WorkflowTriggerPlugin, WorkflowTriggerPlugin,
ConsumerPlugin, ConsumerPlugin,
] ]

View File

@@ -11,6 +11,7 @@ from django.test import override_settings
from documents import tasks from documents import tasks
from documents.barcodes import BarcodePlugin from documents.barcodes import BarcodePlugin
from documents.consumer import ConsumerError
from documents.data_models import ConsumableDocument from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource from documents.data_models import DocumentSource
@@ -93,6 +94,41 @@ class TestBarcode(
self.assertDictEqual(separator_page_numbers, {1: False}) self.assertDictEqual(separator_page_numbers, {1: False})
@override_settings(CONSUMER_ENABLE_ASN_BARCODE=True)
def test_asn_barcode_duplicate_in_trash_fails(self):
"""
GIVEN:
- A document with ASN barcode 123 is in the trash
WHEN:
- A file with the same barcode ASN is consumed
THEN:
- The ASN check is re-run and consumption fails
"""
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-123.pdf"
first_doc = Document.objects.create(
title="First ASN 123",
content="",
checksum="asn123first",
mime_type="application/pdf",
archive_serial_number=123,
)
first_doc.delete()
dupe_asn = settings.SCRATCH_DIR / "barcode-39-asn-123-second.pdf"
shutil.copy(test_file, dupe_asn)
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
with self.assertRaisesRegex(ConsumerError, r"ASN 123.*trash"):
tasks.consume_file(
ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=dupe_asn,
),
None,
)
@override_settings( @override_settings(
CONSUMER_BARCODE_TIFF_SUPPORT=True, CONSUMER_BARCODE_TIFF_SUPPORT=True,
) )

View File

@@ -20,6 +20,7 @@ from django.db.migrations.executor import MigrationExecutor
from django.test import TransactionTestCase from django.test import TransactionTestCase
from django.test import override_settings from django.test import override_settings
from documents.consumer import AsnCheckPlugin
from documents.consumer import ConsumerPlugin from documents.consumer import ConsumerPlugin
from documents.consumer import ConsumerPreflightPlugin from documents.consumer import ConsumerPreflightPlugin
from documents.data_models import ConsumableDocument from documents.data_models import ConsumableDocument
@@ -371,6 +372,14 @@ class GetConsumerMixin:
"task-id", "task-id",
) )
preflight_plugin.setup() preflight_plugin.setup()
asncheck_plugin = AsnCheckPlugin(
doc,
overrides or DocumentMetadataOverrides(),
self.status, # type: ignore
self.dirs.scratch_dir,
"task-id",
)
asncheck_plugin.setup()
reader = ConsumerPlugin( reader = ConsumerPlugin(
doc, doc,
overrides or DocumentMetadataOverrides(), overrides or DocumentMetadataOverrides(),
@@ -381,6 +390,7 @@ class GetConsumerMixin:
reader.setup() reader.setup()
try: try:
preflight_plugin.run() preflight_plugin.run()
asncheck_plugin.run()
yield reader yield reader
finally: finally:
reader.cleanup() reader.cleanup()