Compare commits

..
Author SHA1 Message Date
Trenton Holmes 34f2385df4 Fix: resolve silent duplication from rebase onto fix/bulk_edit
Rebasing onto origin/fix/bulk_edit (PR #14083, which independently
bounds the edit_pdf doc index via manual checks) applied cleanly with
no reported conflicts, but left two copies of the same "doc index is
out of bounds" check back to back in both EditPdfDocumentsSerializer
.validate and BulkEditSerializer._validate_parameters_edit_pdf --
#14083's own `< 0 or >= len(operations)` check is now fully redundant
here since PdfEditOperationSerializer.doc already has min_value=0.
Removed the redundant second check in both methods.

Also split the merged test_edit_pdf_invalid_params subtest: #14083's
loop asserted the same "doc index is out of bounds" message for both
doc=-1 and doc=2**32, but with min_value=0 in place, -1 is now
rejected earlier by the field itself with a different message.
2026-09-13 15:06:52 -07:00
Trenton HolmesandClaude Sonnet 5 1ff18656b7 Fix: type edit_pdf operations via a nested serializer
operations was a plain ListField(required=True) with no child=, so
each element was untyped, and both EditPdfDocumentsSerializer.validate
and BulkEditSerializer._validate_parameters_edit_pdf hand-checked
page/rotate/doc with isinstance(). This let a negative doc index
through silently (used as a wrapping Python list index instead of
being rejected), an empty operations list crashed
`max(op.get("doc", 0) for op in operations)` with update_document=True,
and an out-of-range doc index could drive pikepdf.new() to allocate an
unbounded number of objects.

Added PdfEditOperationSerializer (page: IntegerField(min_value=1),
rotate/doc: IntegerField, doc: min_value=0) and used it as
ListField(child=..., allow_empty=False) in both places, plus a
doc-index bound of len(operations) shared by both validate() methods.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 15:02:31 -07:00
GitHub Actions 57e0a17571 Auto translate strings 2026-09-13 15:02:31 -07:00
shamoon 3fc85c82e1 Chore: include Apply AI Suggestions in the tasks UI filter dropdown (#14093) 2026-09-13 15:02:31 -07:00
GitHub Actions 54ce5f9f61 Auto translate strings 2026-09-13 15:02:31 -07:00
shamoon f5a7ab062e Fix: update some api global perms inconsistencies (#14086) 2026-09-13 15:02:31 -07:00
GitHub Actions 9a317844c2 Auto translate strings 2026-09-13 15:02:31 -07:00
shamoon fc4107d390 Fix: ignore nested action IDs on WF create (#14084) 2026-09-13 15:02:31 -07:00
shamoon 4acc0bed54 Chore: read-only deleted_at 2026-09-13 15:02:31 -07:00
shamoon 9425f53d5c fix: validate PDF output doc indexes in bulk edit 2026-09-12 15:20:40 -07:00
10 changed files with 224 additions and 170 deletions
-8
View File
@@ -1209,14 +1209,6 @@ left unassigned, preventing low-confidence guesses from being applied.
Defaults to 0.6.
#### [`PAPERLESS_MATCH_REGEX_TIMEOUT_SECONDS=<float>`](#PAPERLESS_MATCH_REGEX_TIMEOUT_SECONDS) {#PAPERLESS_MATCH_REGEX_TIMEOUT_SECONDS}
: Sets the timeout, in seconds, for regular expression matching. Increase this
value if date parsing or user-defined matching rules time out when processing
long documents, especially on slower hardware.
Defaults to 0.1 seconds.
#### [`PAPERLESS_DATE_PARSER_LANGUAGES=<lang>`](#PAPERLESS_DATE_PARSER_LANGUAGES) {#PAPERLESS_DATE_PARSER_LANGUAGES}
: Specifies which language Paperless should use when parsing dates from documents.
+1 -5
View File
@@ -21,7 +21,6 @@ from typing import ClassVar
from typing import Generic
from typing import TypeVar
import django
from django import db
from django.core.management import CommandError
from django.db.models import QuerySet
@@ -535,10 +534,7 @@ class PaperlessCommand(RichCommand):
with self._create_progress(description) as progress:
task_id = progress.add_task(description, total=total)
with ProcessPoolExecutor(
max_workers=self.process_count,
initializer=django.setup,
) as executor:
with ProcessPoolExecutor(max_workers=self.process_count) as executor:
# Submit all tasks and map futures back to items
future_to_item = {executor.submit(fn, item): item for item in items}
+29 -41
View File
@@ -1749,8 +1749,18 @@ class MergeDocumentsAsVersionsSerializer(DocumentListSerializer):
return attrs
class PdfEditOperationSerializer(serializers.Serializer[dict[str, int]]):
page = serializers.IntegerField(min_value=1)
rotate = serializers.IntegerField(required=False)
doc = serializers.IntegerField(required=False, min_value=0)
class EditPdfDocumentsSerializer(DocumentListSerializer, SourceModeValidationMixin):
operations = serializers.ListField(required=True, allow_empty=False)
operations = serializers.ListField(
child=PdfEditOperationSerializer(),
required=True,
allow_empty=False,
)
delete_original = serializers.BooleanField(required=False, default=False)
update_document = serializers.BooleanField(required=False, default=False)
include_metadata = serializers.BooleanField(required=False, default=True)
@@ -1768,18 +1778,9 @@ class EditPdfDocumentsSerializer(DocumentListSerializer, SourceModeValidationMix
)
operations = attrs["operations"]
if not isinstance(operations, list):
raise serializers.ValidationError("operations must be a list")
for op in operations:
if not isinstance(op, dict):
raise serializers.ValidationError("invalid operation entry")
if "page" not in op or not isinstance(op["page"], int):
raise serializers.ValidationError("page must be an integer")
if "rotate" in op and not isinstance(op["rotate"], int):
raise serializers.ValidationError("rotate must be an integer")
if "doc" in op and not isinstance(op["doc"], int):
raise serializers.ValidationError("doc must be an integer")
if any(op.get("doc", 0) >= len(operations) for op in operations):
raise serializers.ValidationError("doc index is out of bounds")
if attrs["update_document"]:
max_idx = max(op.get("doc", 0) for op in operations)
@@ -1788,16 +1789,10 @@ class EditPdfDocumentsSerializer(DocumentListSerializer, SourceModeValidationMix
"update_document only allowed with a single output document",
)
if any(
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(operations)
for op in operations
):
raise serializers.ValidationError("doc index is out of bounds")
doc = Document.objects.get(id=documents[0])
if doc.page_count:
for op in operations:
if op["page"] < 1 or op["page"] > doc.page_count:
if op["page"] > doc.page_count:
raise serializers.ValidationError(
f"Page {op['page']} is out of bounds for document with {doc.page_count} pages.",
)
@@ -2128,19 +2123,15 @@ class BulkEditSerializer(
def _validate_parameters_edit_pdf(self, parameters, document_id) -> None:
if "operations" not in parameters:
raise serializers.ValidationError("operations not specified")
if not isinstance(parameters["operations"], list):
raise serializers.ValidationError("operations must be a list")
if not parameters["operations"]:
raise serializers.ValidationError("operations must not be empty")
for op in parameters["operations"]:
if not isinstance(op, dict):
raise serializers.ValidationError("invalid operation entry")
if "page" not in op or not isinstance(op["page"], int):
raise serializers.ValidationError("page must be an integer")
if "rotate" in op and not isinstance(op["rotate"], int):
raise serializers.ValidationError("rotate must be an integer")
if "doc" in op and not isinstance(op["doc"], int):
raise serializers.ValidationError("doc must be an integer")
operations_field = serializers.ListField(
child=PdfEditOperationSerializer(),
allow_empty=False,
)
parameters["operations"] = operations_field.run_validation(
parameters["operations"],
)
operations = parameters["operations"]
if "update_document" in parameters:
if not isinstance(parameters["update_document"], bool):
raise serializers.ValidationError("update_document must be a boolean")
@@ -2152,24 +2143,21 @@ class BulkEditSerializer(
else:
parameters["include_metadata"] = True
if any(op.get("doc", 0) >= len(operations) for op in operations):
raise serializers.ValidationError("doc index is out of bounds")
if parameters["update_document"]:
max_idx = max(op.get("doc", 0) for op in parameters["operations"])
max_idx = max(op.get("doc", 0) for op in operations)
if max_idx > 0:
raise serializers.ValidationError(
"update_document only allowed with a single output document",
)
if any(
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(parameters["operations"])
for op in parameters["operations"]
):
raise serializers.ValidationError("doc index is out of bounds")
doc = Document.objects.get(id=document_id)
# doc existence is already validated
if doc.page_count:
for op in parameters["operations"]:
if op["page"] < 1 or op["page"] > doc.page_count:
for op in operations:
if op["page"] > doc.page_count:
raise serializers.ValidationError(
f"Page {op['page']} is out of bounds for document with {doc.page_count} pages.",
)
+6 -11
View File
@@ -1189,18 +1189,13 @@ def before_task_publish_handler(
trigger_source = _determine_trigger_source(headers)
owner_id = _extract_owner_id(task_type, task_kwargs)
# A retried task is republished with the same task_id, so this fires
# again for it; get_or_create keeps the original PENDING record
# instead of raising a duplicate-key IntegrityError on the retry.
PaperlessTask.objects.get_or_create(
PaperlessTask.objects.create(
task_id=task_id,
defaults={
"task_type": task_type,
"trigger_source": trigger_source,
"status": PaperlessTask.Status.PENDING,
"input_data": input_data,
"owner_id": owner_id,
},
task_type=task_type,
trigger_source=trigger_source,
status=PaperlessTask.Status.PENDING,
input_data=input_data,
owner_id=owner_id,
)
except Exception: # pragma: no cover
logger.exception("Creating PaperlessTask failed")
+134 -30
View File
@@ -1667,22 +1667,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"doc index is out of bounds", response.content)
def test_legacy_bulk_edit_rejects_empty_pdf_operations(self) -> None:
response = self.client.post(
"/api/documents/bulk_edit/",
json.dumps(
{
"documents": [self.doc2.id],
"method": "edit_pdf",
"parameters": {"operations": []},
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"operations must not be empty", response.content)
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf(self, m) -> None:
self.setup_mock(m, "edit_pdf")
@@ -1733,13 +1717,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"Expected a list of items", response.content)
response = self.client.post(
"/api/documents/edit_pdf/",
{"documents": [self.doc2.id], "operations": []},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
response = self.client.post(
"/api/documents/edit_pdf/",
json.dumps(
@@ -1751,7 +1728,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"invalid operation entry", response.content)
self.assertIn(b"Expected a dictionary", response.content)
response = self.client.post(
"/api/documents/edit_pdf/",
@@ -1764,7 +1741,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"page must be an integer", response.content)
self.assertIn(b"valid integer is required", response.content)
response = self.client.post(
"/api/documents/edit_pdf/",
@@ -1777,7 +1754,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"rotate must be an integer", response.content)
self.assertIn(b"valid integer is required", response.content)
response = self.client.post(
"/api/documents/edit_pdf/",
@@ -1790,9 +1767,16 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"doc must be an integer", response.content)
self.assertIn(b"valid integer is required", response.content)
for doc_index in (-1, 2**32):
# A negative doc index is rejected by PdfEditOperationSerializer's
# own min_value=0 field constraint, before the "doc index is out
# of bounds" object-level check (against len(operations)) ever
# runs -- hence the different expected message per case.
for doc_index, expected_message in (
(-1, b"greater than or equal to 0"),
(2**32, b"doc index is out of bounds"),
):
with self.subTest(doc_index=doc_index):
response = self.client.post(
"/api/documents/edit_pdf/",
@@ -1805,7 +1789,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"doc index is out of bounds", response.content)
self.assertIn(expected_message, response.content)
response = self.client.post(
"/api/documents/edit_pdf/",
@@ -1813,7 +1797,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
{
"documents": [self.doc2.id],
"update_document": True,
"operations": [{"page": 1, "doc": 1}, {"page": 2, "doc": 2}],
"operations": [{"page": 1, "doc": 0}, {"page": 2, "doc": 1}],
},
),
content_type="application/json",
@@ -1838,6 +1822,86 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"Invalid source_mode", response.content)
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_rejects_empty_operations(self, m) -> None:
"""
An empty operations list previously reached bulk_edit.edit_pdf()
and crashed with `ValueError: max() iterable argument is empty`
(via `max(op.get("doc", 0) for op in operations)`) whenever
update_document was true. Must now be rejected up front.
"""
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/edit_pdf/",
json.dumps(
{
"documents": [self.doc2.id],
"operations": [],
"update_document": True,
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_rejects_negative_doc_index(self, m) -> None:
"""
A negative `doc` index was previously silently accepted and used
as a wrapping Python list index instead of being rejected.
"""
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/edit_pdf/",
json.dumps(
{
"documents": [self.doc2.id],
"operations": [{"page": 1, "doc": -1}],
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_rejects_out_of_bounds_doc_index(self, m) -> None:
"""
A `doc` index far larger than the number of operations previously
drove `pdf_docs = [pikepdf.new() for _ in range(max_idx + 1)]` to
attempt allocating an enormous number of real objects.
"""
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/edit_pdf/",
json.dumps(
{
"documents": [self.doc2.id],
"operations": [{"page": 1, "doc": 2**33}],
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_rejects_non_positive_page(self, m) -> None:
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/edit_pdf/",
json.dumps(
{
"documents": [self.doc2.id],
"operations": [{"page": 0}],
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_page_out_of_bounds(self, m) -> None:
self.setup_mock(m, "edit_pdf")
@@ -1855,6 +1919,46 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
self.assertIn(b"out of bounds", response.content)
m.assert_not_called()
@mock.patch("documents.serialisers.bulk_edit.edit_pdf")
def test_bulk_edit_edit_pdf_rejects_empty_operations(self, m) -> None:
"""
Same validation gap as test_edit_pdf_rejects_empty_operations, but
via the legacy generic /api/documents/bulk_edit/ method="edit_pdf"
path, which hand-parses `parameters["operations"]` independently
in BulkEditSerializer._validate_parameters_edit_pdf.
"""
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/bulk_edit/",
json.dumps(
{
"documents": [self.doc2.id],
"method": "edit_pdf",
"parameters": {"operations": [], "update_document": True},
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.serialisers.bulk_edit.edit_pdf")
def test_bulk_edit_edit_pdf_rejects_out_of_bounds_doc_index(self, m) -> None:
self.setup_mock(m, "edit_pdf")
response = self.client.post(
"/api/documents/bulk_edit/",
json.dumps(
{
"documents": [self.doc2.id],
"method": "edit_pdf",
"parameters": {"operations": [{"page": 1, "doc": 2**33}]},
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
m.assert_not_called()
@mock.patch("documents.views.bulk_edit.edit_pdf")
def test_edit_pdf_insufficient_permissions(self, m) -> None:
self.doc1.owner = User.objects.get(username="temp_admin")
+7 -6
View File
@@ -1643,12 +1643,13 @@ class TestPDFActions(DirectoriesMixin, TestCase):
mock_consume_file.assert_not_called()
@mock.patch("pikepdf.open")
def test_edit_pdf_rejects_invalid_operations(self, mock_open) -> None:
for operations in ([], [{"page": 1, "doc": 2**32}]):
with self.subTest(operations=operations):
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
with self.assertRaisesRegex(ValueError, "index is out of bounds"):
bulk_edit.edit_pdf([self.doc2.id], operations)
def test_edit_pdf_rejects_out_of_bounds_output_index(self, mock_open) -> None:
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
with self.assertRaisesRegex(ValueError, "index is out of bounds"):
bulk_edit.edit_pdf(
[self.doc2.id],
[{"page": 1, "doc": 2**32}],
)
mock_open.assert_not_called()
-7
View File
@@ -1,6 +1,5 @@
import pytest
import regex
from django.conf import settings
from pytest_mock import MockerFixture
from documents.regex import safe_regex_finditer
@@ -10,12 +9,6 @@ from documents.regex import safe_regex_sub
from documents.regex import validate_regex_pattern
def test_regex_timeout_uses_configured_setting() -> None:
from documents.regex import REGEX_TIMEOUT_SECONDS
assert REGEX_TIMEOUT_SECONDS == settings.MATCH_REGEX_TIMEOUT_SECONDS
class TestValidateRegexPattern:
def test_valid_pattern(self) -> None:
validate_regex_pattern(r"\d+")
-11
View File
@@ -106,17 +106,6 @@ class TestBeforeTaskPublishHandler:
assert task.task_type == PaperlessTask.TaskType.TRAIN_CLASSIFIER
assert task.trigger_source == PaperlessTask.TriggerSource.MANUAL
# A Celery retry republishes with the same task_id; this must not
# raise a duplicate-key IntegrityError, and must leave the original
# PENDING record alone.
send_publish(
"documents.tasks.train_classifier",
(),
{},
headers={"id": task_id},
)
assert PaperlessTask.objects.filter(task_id=task_id).count() == 1
def test_creates_task_for_sanity_check(self) -> None:
task_id = send_publish("documents.tasks.sanity_check", (), {})
task = PaperlessTask.objects.get(task_id=task_id)
+47 -47
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-13 22:13+0000\n"
"POT-Creation-Date: 2026-09-12 23:18+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -1632,7 +1632,7 @@ msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:525 documents/serialisers.py:882
#: documents/serialisers.py:2868 documents/views.py:319 documents/views.py:2694
#: documents/serialisers.py:2854 documents/views.py:319 documents/views.py:2694
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
@@ -1641,39 +1641,39 @@ msgstr ""
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:2341
#: documents/serialisers.py:2327
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:2385
#: documents/serialisers.py:2371
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2392
#: documents/serialisers.py:2378
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2409 documents/serialisers.py:2419
#: documents/serialisers.py:2395 documents/serialisers.py:2405
msgid ""
"Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2414
#: documents/serialisers.py:2400
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2561
#: documents/serialisers.py:2547
msgid "Invalid variable detected."
msgstr ""
#: documents/serialisers.py:2924
#: documents/serialisers.py:2910
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2954 documents/views.py:4707
#: documents/serialisers.py:2940 documents/views.py:4707
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -2258,151 +2258,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:560
#: paperless/settings/__init__.py:556
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:561
#: paperless/settings/__init__.py:557
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:558
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:559
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:560
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:561
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:562
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:563
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:564
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:565
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:566
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:567
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:568
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:569
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:570
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:571
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:572
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:573
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:574
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:575
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:576
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:577
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:578
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:579
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:580
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:581
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:582
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:583
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:584
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:585
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:586
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:587
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:588
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:589
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:590
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:591
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:592
msgid "Chinese Traditional"
msgstr ""
-4
View File
@@ -102,10 +102,6 @@ CLASSIFIER_MATCH_THRESHOLD: Final[float] = get_float_from_env(
"PAPERLESS_CLASSIFIER_MATCH_THRESHOLD",
0.6,
)
MATCH_REGEX_TIMEOUT_SECONDS: Final[float] = get_float_from_env(
"PAPERLESS_MATCH_REGEX_TIMEOUT_SECONDS",
0.1,
)
LLM_INDEX_DIR = DATA_DIR / "llm_index"
LLM_INDEX_LOCK = LLM_INDEX_DIR / "index.lock"
# Cross-process read/write lock guarding the LLM index compaction/migration