Compare commits

...

8 Commits

Author SHA1 Message Date
Trenton H
89a9e7f190 Performance: Increases workflow related M2M prefetching (#12618) 2026-04-21 22:01:51 +00:00
GitHub Actions
c669c3416e Auto translate strings 2026-04-21 21:50:36 +00:00
shamoon
88430c8ab7 Tweak: remove 'stale' indicator for index in system status (#12616) 2026-04-21 21:49:04 +00:00
GitHub Actions
edfebcbe44 Auto translate strings 2026-04-21 18:02:57 +00:00
Trenton H
a89cd2d5d9 Fix: Exact custom field monetary exact searching (#12592) 2026-04-21 18:01:27 +00:00
GitHub Actions
02e913b475 Auto translate strings 2026-04-21 17:26:25 +00:00
Trenton H
6017b11c42 Fix: Prefetches the custom field instance and the custom field all at once (#12617) 2026-04-21 10:24:51 -07:00
shamoon
ffaa2bb77a Fix: prevent sidebar animation at startup (#12615) 2026-04-20 23:17:16 -07:00
9 changed files with 272 additions and 64 deletions

View File

@@ -4428,23 +4428,23 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">176</context> <context context-type="linenumber">172</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">210</context> <context context-type="linenumber">206</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">244</context> <context context-type="linenumber">240</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">254</context> <context context-type="linenumber">250</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">292</context> <context context-type="linenumber">288</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/toast/toast.component.html</context> <context context-type="sourcefile">src/app/components/common/toast/toast.component.html</context>
@@ -6256,7 +6256,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">311</context> <context context-type="linenumber">307</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context> <context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
@@ -6983,75 +6983,75 @@
<source>Last Updated</source> <source>Last Updated</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">174</context> <context context-type="linenumber">170</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="46628344485199198" datatype="html"> <trans-unit id="46628344485199198" datatype="html">
<source>Classifier</source> <source>Classifier</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">179</context> <context context-type="linenumber">175</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9127131074422113272" datatype="html"> <trans-unit id="9127131074422113272" datatype="html">
<source>Run Task</source> <source>Run Task</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">201</context> <context context-type="linenumber">197</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">231</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">283</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6096684179126491743" datatype="html"> <trans-unit id="6096684179126491743" datatype="html">
<source>Last Trained</source> <source>Last Trained</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">208</context> <context context-type="linenumber">204</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6427836860962380759" datatype="html"> <trans-unit id="6427836860962380759" datatype="html">
<source>Sanity Checker</source> <source>Sanity Checker</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">213</context> <context context-type="linenumber">209</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6578747070254776938" datatype="html"> <trans-unit id="6578747070254776938" datatype="html">
<source>Last Run</source> <source>Last Run</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">242</context> <context context-type="linenumber">238</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">290</context> <context context-type="linenumber">286</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5921685253729220446" datatype="html"> <trans-unit id="5921685253729220446" datatype="html">
<source>WebSocket Connection</source> <source>WebSocket Connection</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">247</context> <context context-type="linenumber">243</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8998179362936748717" datatype="html"> <trans-unit id="8998179362936748717" datatype="html">
<source>OK</source> <source>OK</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">251</context> <context context-type="linenumber">247</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3804349597565969872" datatype="html"> <trans-unit id="3804349597565969872" datatype="html">
<source>AI Index</source> <source>AI Index</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
<context context-type="linenumber">260</context> <context context-type="linenumber">256</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6732151329960766506" datatype="html"> <trans-unit id="6732151329960766506" datatype="html">

View File

@@ -94,12 +94,18 @@ main {
} }
.sidebar.slim:not(.animating) { .sidebar.slim:not(.animating) {
transition: none;
li.nav-item span, li.nav-item span,
.sidebar-heading span { .sidebar-heading span {
display: none; display: none;
} }
} }
.sidebar.slim:not(.animating) ~ main.col-slim {
transition: none;
}
.sidebar.animating { .sidebar.animating {
li.nav-item span, li.nav-item span,
.sidebar-heading span { .sidebar-heading span {

View File

@@ -159,11 +159,7 @@
<button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="indexStatus" triggers="click mouseenter:mouseleave"> <button class="btn btn-sm d-flex align-items-center btn-dark text-uppercase small" [ngbPopover]="indexStatus" triggers="click mouseenter:mouseleave">
{{status.tasks.index_status}} {{status.tasks.index_status}}
@if (status.tasks.index_status === 'OK') { @if (status.tasks.index_status === 'OK') {
@if (isStale(status.tasks.index_last_modified)) {
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1"></i-bs>
} @else {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs> <i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
}
} @else { } @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs> <i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
} }

View File

@@ -6,6 +6,8 @@ import json
import logging import logging
import operator import operator
from contextlib import contextmanager from contextlib import contextmanager
from decimal import Decimal
from decimal import InvalidOperation
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Any from typing import Any
@@ -291,6 +293,34 @@ class MimeTypeFilter(Filter):
return qs return qs
class MonetaryAmountField(serializers.Field):
"""
Accepts either a plain decimal string ("100", "100.00") or a currency-prefixed
string ("USD100.00") and returns the numeric amount as a Decimal.
Mirrors the logic of the value_monetary_amount generated field: if the value
starts with a non-digit, the first 3 characters are treated as a currency code
(ISO 4217) and stripped before parsing. This preserves backwards compatibility
with saved views that stored a currency-prefixed string as the filter value.
"""
default_error_messages = {"invalid": "A valid number is required."}
def to_internal_value(self, data):
if not isinstance(data, str | int | float):
self.fail("invalid")
value = str(data).strip()
if value and not value[0].isdigit() and value[0] != "-":
value = value[3:] # strip 3-char ISO 4217 currency code
try:
return Decimal(value)
except InvalidOperation:
self.fail("invalid")
def to_representation(self, value):
return str(value)
class SelectField(serializers.CharField): class SelectField(serializers.CharField):
def __init__(self, custom_field: CustomField) -> None: def __init__(self, custom_field: CustomField) -> None:
self._options = custom_field.extra_data["select_options"] self._options = custom_field.extra_data["select_options"]
@@ -516,9 +546,8 @@ class CustomFieldQueryParser:
value_field_name = CustomFieldInstance.get_value_field_name( value_field_name = CustomFieldInstance.get_value_field_name(
custom_field.data_type, custom_field.data_type,
) )
if ( if custom_field.data_type == CustomField.FieldDataType.MONETARY and (
custom_field.data_type == CustomField.FieldDataType.MONETARY op in self.EXPR_BY_CATEGORY["arithmetic"] or op in {"exact", "in"}
and op in self.EXPR_BY_CATEGORY["arithmetic"]
): ):
value_field_name = "value_monetary_amount" value_field_name = "value_monetary_amount"
has_field = Q(custom_fields__field=custom_field) has_field = Q(custom_fields__field=custom_field)
@@ -628,6 +657,13 @@ class CustomFieldQueryParser:
elif custom_field.data_type == CustomField.FieldDataType.URL: elif custom_field.data_type == CustomField.FieldDataType.URL:
# For URL fields we don't need to be strict about validation (e.g., for istartswith). # For URL fields we don't need to be strict about validation (e.g., for istartswith).
field = serializers.CharField() field = serializers.CharField()
elif custom_field.data_type == CustomField.FieldDataType.MONETARY and (
op in self.EXPR_BY_CATEGORY["arithmetic"] or op in {"exact", "in"}
):
# These ops compare against value_monetary_amount (a DecimalField).
# MonetaryAmountField accepts both "100" and "USD100.00" for backwards
# compatibility with saved views that stored currency-prefixed values.
field = MonetaryAmountField()
else: else:
# The general case: inferred from the corresponding field in CustomFieldInstance. # The general case: inferred from the corresponding field in CustomFieldInstance.
value_field_name = CustomFieldInstance.get_value_field_name( value_field_name = CustomFieldInstance.get_value_field_name(

View File

@@ -3352,13 +3352,13 @@ class WorkflowSerializer(serializers.ModelSerializer[Workflow]):
ManyToMany fields dont support e.g. on_delete so we need to discard unattached ManyToMany fields dont support e.g. on_delete so we need to discard unattached
triggers and actions manually triggers and actions manually
""" """
for trigger in WorkflowTrigger.objects.all(): WorkflowTrigger.objects.annotate(
if trigger.workflows.all().count() == 0: workflow_count=Count("workflows"),
trigger.delete() ).filter(workflow_count=0).delete()
for action in WorkflowAction.objects.all(): WorkflowAction.objects.annotate(
if action.workflows.all().count() == 0: workflow_count=Count("workflows"),
action.delete() ).filter(workflow_count=0).delete()
WorkflowActionEmail.objects.filter(action=None).delete() WorkflowActionEmail.objects.filter(action=None).delete()
WorkflowActionWebhook.objects.filter(action=None).delete() WorkflowActionWebhook.objects.filter(action=None).delete()
@@ -3387,16 +3387,6 @@ class WorkflowSerializer(serializers.ModelSerializer[Workflow]):
return instance return instance
def to_representation(self, instance: Workflow) -> dict[str, Any]:
data = super().to_representation(instance)
actions = instance.actions.order_by("order", "pk")
data["actions"] = WorkflowActionSerializer(
actions,
many=True,
context=self.context,
).data
return data
class TrashSerializer(SerializerWithPerms): class TrashSerializer(SerializerWithPerms):
documents = serializers.ListField( documents = serializers.ListField(

View File

@@ -9,6 +9,8 @@ from rest_framework.test import APITestCase
from documents.models import CustomField from documents.models import CustomField
from documents.models import Document from documents.models import Document
from documents.models import SavedView
from documents.models import SavedViewFilterRule
from documents.serialisers import DocumentSerializer from documents.serialisers import DocumentSerializer
from documents.tests.utils import DirectoriesMixin from documents.tests.utils import DirectoriesMixin
@@ -453,6 +455,111 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
), ),
) )
def test_exact_monetary(self) -> None:
# "exact" should match by numeric amount, ignoring currency code prefix.
self._assert_query_match_predicate(
["monetary_field", "exact", "100"],
lambda document: (
"monetary_field" in document
and document["monetary_field"] == "USD100.00"
),
)
self._assert_query_match_predicate(
["monetary_field", "exact", "101"],
lambda document: (
"monetary_field" in document and document["monetary_field"] == "101.00"
),
)
def test_in_monetary(self) -> None:
# "in" should match by numeric amount, ignoring currency code prefix.
self._assert_query_match_predicate(
["monetary_field", "in", ["100", "50"]],
lambda document: (
"monetary_field" in document
and document["monetary_field"] in {"USD100.00", "EUR50.00"}
),
)
def test_exact_monetary_with_currency_prefix(self) -> None:
# Providing a currency-prefixed string like "USD100.00" for an exact monetary
# filter should work for backwards compatibility with saved views. The currency
# code is stripped and the numeric amount is used for comparison.
self._assert_query_match_predicate(
["monetary_field", "exact", "USD100.00"],
lambda document: (
"monetary_field" in document
and document["monetary_field"] == "USD100.00"
),
)
self._assert_query_match_predicate(
["monetary_field", "in", ["USD100.00", "EUR50.00"]],
lambda document: (
"monetary_field" in document
and document["monetary_field"] in {"USD100.00", "EUR50.00"}
),
)
self._assert_query_match_predicate(
["monetary_field", "gt", "USD99.00"],
lambda document: (
"monetary_field" in document
and document["monetary_field"] is not None
and (
document["monetary_field"] == "USD100.00"
or document["monetary_field"] == "101.00"
)
),
)
def test_saved_view_with_currency_prefixed_monetary_filter(self) -> None:
"""
A saved view created before the exact-monetary fix stored currency-prefixed
values like '["monetary_field", "exact", "USD100.00"]' as the filter rule value
(rule_type=42). Those saved views must continue to return correct results.
"""
saved_view = SavedView.objects.create(name="test view", owner=self.user)
SavedViewFilterRule.objects.create(
saved_view=saved_view,
rule_type=42, # FILTER_CUSTOM_FIELDS_QUERY
value=json.dumps(["monetary_field", "exact", "USD100.00"]),
)
# The frontend translates rule_type=42 to the custom_field_query URL param;
# simulate that here using the stored filter rule value directly.
rule = saved_view.filter_rules.get(rule_type=42)
query_string = quote(rule.value, safe="")
response = self.client.get(
"/api/documents/?"
+ "&".join(
(
f"custom_field_query={query_string}",
"ordering=archive_serial_number",
"page=1",
f"page_size={len(self.documents)}",
"truncate_content=true",
),
),
)
self.assertEqual(response.status_code, 200, msg=str(response.json()))
result_ids = {doc["id"] for doc in response.json()["results"]}
# Should match the single document with monetary_field = "USD100.00"
expected_ids = {
doc.id
for doc in self.documents
if doc.custom_fields.filter(
field__name="monetary_field",
value_monetary="USD100.00",
).exists()
}
self.assertEqual(result_ids, expected_ids)
def test_monetary_amount_with_invalid_value(self) -> None:
# A value that has a currency prefix but no valid number after it should fail.
self._assert_validation_error(
json.dumps(["monetary_field", "exact", "USDnotanumber"]),
["custom_field_query", "2"],
"valid number",
)
# ==========================================================# # ==========================================================#
# Subset check (document link field only) # # Subset check (document link field only) #
# ==========================================================# # ==========================================================#

View File

@@ -99,6 +99,40 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
self.action.assign_correspondent.pk, self.action.assign_correspondent.pk,
) )
def test_api_get_workflow_actions_ordered(self) -> None:
"""
GIVEN:
- A workflow with two actions added in reverse order (order=1 before order=0)
WHEN:
- API is called to get workflows
THEN:
- Actions are returned sorted by order ascending
"""
# Created before action_first so its pk is lower — ensures pk order
# disagrees with the order field, catching regressions if order_by is removed.
action_second = WorkflowAction.objects.create(
assign_title="Second action",
order=1,
)
action_first = WorkflowAction.objects.create(
assign_title="First action",
order=0,
)
self.workflow.actions.add(action_second)
self.workflow.actions.add(action_first)
response = self.client.get(self.ENDPOINT, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
resp_actions = response.data["results"][0]["actions"]
action_ids = [a["id"] for a in resp_actions]
self.assertIn(action_first.id, action_ids)
self.assertIn(action_second.id, action_ids)
self.assertLess(
action_ids.index(action_first.id),
action_ids.index(action_second.id),
)
def test_api_create_workflow(self) -> None: def test_api_create_workflow(self) -> None:
""" """
GIVEN: GIVEN:

View File

@@ -968,7 +968,10 @@ class DocumentViewSet(
), ),
), ),
"tags", "tags",
Prefetch(
"custom_fields", "custom_fields",
queryset=CustomFieldInstance.objects.select_related("field"),
),
"notes", "notes",
) )
) )
@@ -4482,8 +4485,44 @@ class WorkflowViewSet(ModelViewSet[Workflow]):
Workflow.objects.all() Workflow.objects.all()
.order_by("order") .order_by("order")
.prefetch_related( .prefetch_related(
Prefetch(
"triggers", "triggers",
queryset=WorkflowTrigger.objects.prefetch_related(
"filter_has_tags",
"filter_has_all_tags",
"filter_has_not_tags",
"filter_has_any_correspondents",
"filter_has_not_correspondents",
"filter_has_any_document_types",
"filter_has_not_document_types",
"filter_has_any_storage_paths",
"filter_has_not_storage_paths",
),
),
Prefetch(
"actions", "actions",
queryset=WorkflowAction.objects.order_by(
"order",
"pk",
).prefetch_related(
"assign_tags",
"assign_view_users",
"assign_view_groups",
"assign_change_users",
"assign_change_groups",
"assign_custom_fields",
"remove_tags",
"remove_correspondents",
"remove_document_types",
"remove_storage_paths",
"remove_custom_fields",
"remove_owners",
"remove_view_users",
"remove_view_groups",
"remove_change_users",
"remove_change_groups",
),
),
) )
) )

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-20 20:20+0000\n" "POT-Creation-Date: 2026-04-21 18:02+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n" "PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
@@ -21,39 +21,39 @@ msgstr ""
msgid "Documents" msgid "Documents"
msgstr "" msgstr ""
#: documents/filters.py:433 #: documents/filters.py:463
msgid "Value must be valid JSON." msgid "Value must be valid JSON."
msgstr "" msgstr ""
#: documents/filters.py:452 #: documents/filters.py:482
msgid "Invalid custom field query expression" msgid "Invalid custom field query expression"
msgstr "" msgstr ""
#: documents/filters.py:462 #: documents/filters.py:492
msgid "Invalid expression list. Must be nonempty." msgid "Invalid expression list. Must be nonempty."
msgstr "" msgstr ""
#: documents/filters.py:483 #: documents/filters.py:513
msgid "Invalid logical operator {op!r}" msgid "Invalid logical operator {op!r}"
msgstr "" msgstr ""
#: documents/filters.py:497 #: documents/filters.py:527
msgid "Maximum number of query conditions exceeded." msgid "Maximum number of query conditions exceeded."
msgstr "" msgstr ""
#: documents/filters.py:562 #: documents/filters.py:591
msgid "{name!r} is not a valid custom field." msgid "{name!r} is not a valid custom field."
msgstr "" msgstr ""
#: documents/filters.py:599 #: documents/filters.py:628
msgid "{data_type} does not support query expr {expr!r}." msgid "{data_type} does not support query expr {expr!r}."
msgstr "" msgstr ""
#: documents/filters.py:707 documents/models.py:136 #: documents/filters.py:743 documents/models.py:136
msgid "Maximum nesting depth exceeded." msgid "Maximum nesting depth exceeded."
msgstr "" msgstr ""
#: documents/filters.py:954 #: documents/filters.py:990
msgid "Custom field not found" msgid "Custom field not found"
msgstr "" msgstr ""
@@ -1352,8 +1352,8 @@ msgid "workflow runs"
msgstr "" msgstr ""
#: documents/serialisers.py:463 documents/serialisers.py:815 #: documents/serialisers.py:463 documents/serialisers.py:815
#: documents/serialisers.py:2681 documents/views.py:2255 #: documents/serialisers.py:2681 documents/views.py:2258
#: documents/views.py:2324 paperless_mail/serialisers.py:143 #: documents/views.py:2327 paperless_mail/serialisers.py:143
msgid "Insufficient permissions." msgid "Insufficient permissions."
msgstr "" msgstr ""
@@ -1393,7 +1393,7 @@ msgstr ""
msgid "Duplicate document identifiers are not allowed." msgid "Duplicate document identifiers are not allowed."
msgstr "" msgstr ""
#: documents/serialisers.py:2767 documents/views.py:4104 #: documents/serialisers.py:2767 documents/views.py:4107
#, python-format #, python-format
msgid "Documents not found: %(ids)s" msgid "Documents not found: %(ids)s"
msgstr "" msgstr ""
@@ -1661,28 +1661,28 @@ msgstr ""
msgid "Unable to parse URI {value}" msgid "Unable to parse URI {value}"
msgstr "" msgstr ""
#: documents/views.py:2135 #: documents/views.py:2138
msgid "Specify only one of text, title_search, query, or more_like_id." msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr "" msgstr ""
#: documents/views.py:2248 documents/views.py:2321 #: documents/views.py:2251 documents/views.py:2324
msgid "Invalid more_like_id" msgid "Invalid more_like_id"
msgstr "" msgstr ""
#: documents/views.py:4116 #: documents/views.py:4119
#, python-format #, python-format
msgid "Insufficient permissions to share document %(id)s." msgid "Insufficient permissions to share document %(id)s."
msgstr "" msgstr ""
#: documents/views.py:4162 #: documents/views.py:4165
msgid "Bundle is already being processed." msgid "Bundle is already being processed."
msgstr "" msgstr ""
#: documents/views.py:4222 #: documents/views.py:4225
msgid "The share link bundle is still being prepared. Please try again later." msgid "The share link bundle is still being prepared. Please try again later."
msgstr "" msgstr ""
#: documents/views.py:4232 #: documents/views.py:4235
msgid "The share link bundle is unavailable." msgid "The share link bundle is unavailable."
msgstr "" msgstr ""