Feature: store barcode contents, list and search them (#14276)

* Feature: store barcode contents, list and search them

New setting PAPERLESS_CONSUMER_STORE_BARCODE_VALUES (off by default)
stores all barcodes found during consumption with the document: page,
type and content. They are listed on the metadata tab with a copy
button, returned by the documents API and searchable with barcodes:
in the advanced search. Versions keep their own barcodes, reprocessing
reads them again. Refs #9898

* Tests: cover the remaining barcode branches

Covers unchanged and failing barcode reads on reprocessing, unsupported files and DocumentBarcode.__str__, and uses toHaveLength in the barcode list spec as suggested by SonarCloud.

* Address review: keep barcodes when they can't be read, format choices

- Reprocessing and new versions keep the stored barcodes when the file
  can't be scanned or the scan fails, and replace them atomically.
- Format is a TextChoices of the zxing-cpp formats, with a test.
- Shared scan code, latest_version helper, TypedDict, serializer reuse.
- Barcodes in the split manifest, export/import tests, pytest-style tests.

* Barcode tests: TIFF reprocess, format check both ways, fixtures

- Reprocessing a TIFF with TIFF support off keeps the stored barcodes.
- The format test also fails when zxing-cpp drops a format.
- Fixtures in place of the sample dir mixin, plugin-level disabled test.
- Format labels aren't translated, OpenAPI enum named BarcodeFormatEnum.

* Review: module-level zxing reader, shorter barcode docs

- read_barcodes_zxing is a module-level function used by scan_pdf.
- Drop the trivial __str__ test, mark it no cover.
- Shorten the barcode docs and remove the duplicate in configuration.md.

* Fix header

* Use utility class

* Return barcodes from the metadata endpoint only

Drop the barcodes field and its prefetches from the document serializer,
as agreed in the review. Also remove the now empty component stylesheet.

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
jurassicparkicecreamandshamoon authored and GitHub committed 2026-10-05 16:25:19 +00:00
1 parent 312684aef3
commit 62cc31fbdb
37 files changed
+1196 -80

No files matched your search

+38
View File
@@ -18,6 +18,7 @@ from django.utils import timezone
from filelock import FileLock
from rest_framework.reverse import reverse
from documents.barcodes import read_barcode_values
from documents.classifier import load_classifier
from documents.data_models import ConsumableDocument
from documents.data_models import ConsumeFileSuccessResult
@@ -31,6 +32,7 @@ from documents.models import Correspondent
from documents.models import CustomField
from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentBarcode
from documents.models import DocumentType
from documents.models import StoragePath
from documents.models import Tag
@@ -53,6 +55,7 @@ from documents.utils import compute_checksum
from documents.utils import copy_basic_file_stats
from documents.utils import copy_file_with_basic_stats
from documents.utils import run_subprocess
from paperless.config import BarcodeConfig
from paperless.config import OcrConfig
from paperless.config import RemoteOCRConfig
from paperless.models import ArchiveFileGenerationChoices
@@ -504,6 +507,13 @@ class ConsumerPlugin(
f"Parser: {document_parser.name} v{document_parser.version}",
)
# New versions skip the barcode plugin, so read their barcodes here
if (
self.input_doc.root_document_id is not None
and self.metadata.barcodes is None
):
self._read_version_barcodes(mime_type, Path(tmpdir))
# Parse the document. This may take some time.
text = None
@@ -631,6 +641,8 @@ class ConsumerPlugin(
else:
original_document.save()
self._store_barcodes(original_document)
# Adding a version changes the effective document, so update root modified
Document.objects.filter(pk=root_doc.pk).update(
modified=timezone.now(),
@@ -962,6 +974,32 @@ class ConsumerPlugin(
}
CustomFieldInstance.objects.create(**args) # adds to document
self._store_barcodes(document)
def _read_version_barcodes(self, mime_type: str, work_dir: Path) -> None:
barcode_settings = BarcodeConfig()
if not barcode_settings.barcode_store_values:
return
try:
self.metadata.barcodes = (
read_barcode_values(
self.working_copy,
mime_type,
barcode_settings,
work_dir,
)
or None
)
except Exception as e:
self.log.warning(f"Could not read barcodes of {self.filename}: {e}")
def _store_barcodes(self, document: Document) -> None:
if self.metadata.barcodes:
DocumentBarcode.objects.bulk_create(
DocumentBarcode(document=document, **barcode)
for barcode in self.metadata.barcodes
)
def _write(self, source, target) -> None:
with (
Path(source).open("rb") as read_file,