Compare commits

..

5 Commits

Author SHA1 Message Date
Crowdin Bot
b3fa40267c New Crowdin translations by GitHub Action 2025-12-22 00:40:43 +00:00
shamoon
7604a0b583 Fix: prevent ASN collisions for merge operations (#11634) 2025-12-19 20:05:34 -08:00
shamoon
4e789acf2d Chore: mark test_error_skip_rule as flaky 2025-12-18 10:15:04 -08:00
shamoon
d9459d04ea Chore: refactor preview URL variable naming and safeUrl usage 2025-12-18 09:59:14 -08:00
github-actions[bot]
305d764805 Changelog v2.20.3 - GHA (#11623) 2025-12-18 08:12:05 -08:00
20 changed files with 306 additions and 287 deletions

View File

@@ -1,5 +1,7 @@
# Changelog # Changelog
## paperless-ngx 2.20.3
## paperless-ngx 2.20.2 ## paperless-ngx 2.20.2
### Features / Enhancements ### Features / Enhancements

View File

@@ -14,7 +14,7 @@
@if (previewText) { @if (previewText) {
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div> <div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
} @else { } @else {
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object> <object [data]="previewUrl | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
} }
} @else { } @else {
@if (requiresPassword) { @if (requiresPassword) {
@@ -24,7 +24,7 @@
} }
@if (!requiresPassword) { @if (!requiresPassword) {
<pdf-viewer <pdf-viewer
[src]="previewURL" [src]="previewUrl"
[original-size]="false" [original-size]="false"
[show-borders]="false" [show-borders]="false"
[show-all]="true" [show-all]="true"

View File

@@ -71,7 +71,7 @@ export class PreviewPopupComponent implements OnDestroy {
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
} }
get previewURL() { get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id) return this.documentService.getPreviewUrl(this.document.id)
} }
@@ -93,7 +93,7 @@ export class PreviewPopupComponent implements OnDestroy {
init() { init() {
if (this.document.mime_type?.includes('text')) { if (this.document.mime_type?.includes('text')) {
this.http this.http
.get(this.previewURL, { responseType: 'text' }) .get(this.previewUrl, { responseType: 'text' })
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: (res) => { next: (res) => {
@@ -126,10 +126,6 @@ export class PreviewPopupComponent implements OnDestroy {
} }
} }
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
mouseEnterPreview() { mouseEnterPreview() {
this.mouseOnPreview = true this.mouseOnPreview = true
if (!this.popover.isOpen()) { if (!this.popover.isOpen()) {

View File

@@ -379,7 +379,7 @@
<ng-template #previewContent> <ng-template #previewContent>
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded"> <div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
@if (showThumbnailOverlay) { @if (showThumbnailOverlay) {
<img [src]="thumbUrl | safeUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt /> <img [src]="thumbUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
} }
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center"> <div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
<div> <div>
@@ -414,7 +414,7 @@
} }
@case (ContentRenderType.Image) { @case (ContentRenderType.Image) {
<div class="preview-sticky"> <div class="preview-sticky">
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" /> <img [src]="previewUrl" width="100%" height="100%" alt="{{title}}" />
</div> </div>
} }
@case (ContentRenderType.TIFF) { @case (ContentRenderType.TIFF) {

View File

@@ -5236,7 +5236,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">164</context> <context context-type="linenumber">164</context>
</context-group> </context-group>
<target state="needs-translation">Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</target> <target state="translated">Aplica als documents que coincideixin amb aquesta ruta. Es permeten comodins especificats com a *. Normalitzat per majúscules i minúscules.</target>
</trans-unit> </trans-unit>
<trans-unit id="7468453896129193641" datatype="html"> <trans-unit id="7468453896129193641" datatype="html">
<source>Filter mail rule</source> <source>Filter mail rule</source>

File diff suppressed because it is too large Load Diff

View File

@@ -5236,7 +5236,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">164</context> <context context-type="linenumber">164</context>
</context-group> </context-group>
<target state="needs-translation">Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</target> <target state="translated">Uporabi za dokumente, ki se ujemajo s to potjo. Dovoljeni so nadomestni znaki, navedeni kot *. Razlikuje se med velikimi in malimi črkami.</target>
</trans-unit> </trans-unit>
<trans-unit id="7468453896129193641" datatype="html"> <trans-unit id="7468453896129193641" datatype="html">
<source>Filter mail rule</source> <source>Filter mail rule</source>

View File

@@ -5236,7 +5236,7 @@
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
<context context-type="linenumber">164</context> <context context-type="linenumber">164</context>
</context-group> </context-group>
<target state="needs-translation">Apply to documents that match this path. Wildcards specified as * are allowed. Case-normalized.</target> <target state="translated">Primeni na dokumente koji odgovaraju ovoj putanji. Džokerski znakovi navedeni kao * su dozvoljeni. Normalizuje se veličina slova.</target>
</trans-unit> </trans-unit>
<trans-unit id="7468453896129193641" datatype="html"> <trans-unit id="7468453896129193641" datatype="html">
<source>Filter mail rule</source> <source>Filter mail rule</source>

View File

@@ -186,7 +186,11 @@ class BarcodePlugin(ConsumeTaskPlugin):
# Update/overwrite an ASN if possible # Update/overwrite an ASN if possible
# After splitting, as otherwise each split document gets the same ASN # After splitting, as otherwise each split document gets the same ASN
if self.settings.barcode_enable_asn and (located_asn := self.asn) is not None: if (
self.settings.barcode_enable_asn
and not self.metadata.skip_asn
and (located_asn := self.asn) is not None
):
logger.info(f"Found ASN in barcode: {located_asn}") logger.info(f"Found ASN in barcode: {located_asn}")
self.metadata.asn = located_asn self.metadata.asn = located_asn

View File

@@ -433,6 +433,8 @@ def merge(
if user is not None: if user is not None:
overrides.owner_id = user.id overrides.owner_id = user.id
# Avoid copying or detecting ASN from merged PDFs to prevent collision
overrides.skip_asn = True
logger.info("Adding merged document to the task queue.") logger.info("Adding merged document to the task queue.")

View File

@@ -696,7 +696,7 @@ class ConsumerPlugin(
pk=self.metadata.storage_path_id, pk=self.metadata.storage_path_id,
) )
if self.metadata.asn is not None: if self.metadata.asn is not None and not self.metadata.skip_asn:
document.archive_serial_number = self.metadata.asn document.archive_serial_number = self.metadata.asn
if self.metadata.owner_id: if self.metadata.owner_id:
@@ -812,8 +812,8 @@ class ConsumerPreflightPlugin(
""" """
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
""" """
if self.metadata.asn is None: if self.metadata.skip_asn or self.metadata.asn is None:
# check not necessary in case no ASN gets set # if skip is set or ASN is None
return return
# Validate the range is above zero and less than uint32_t max # Validate the range is above zero and less than uint32_t max
# otherwise, Whoosh can't handle it in the index # otherwise, Whoosh can't handle it in the index

View File

@@ -30,6 +30,7 @@ class DocumentMetadataOverrides:
change_users: list[int] | None = None change_users: list[int] | None = None
change_groups: list[int] | None = None change_groups: list[int] | None = None
custom_fields: dict | None = None custom_fields: dict | None = None
skip_asn: bool = False
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides": def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
""" """
@@ -49,6 +50,8 @@ class DocumentMetadataOverrides:
self.storage_path_id = other.storage_path_id self.storage_path_id = other.storage_path_id
if other.owner_id is not None: if other.owner_id is not None:
self.owner_id = other.owner_id self.owner_id = other.owner_id
if other.skip_asn:
self.skip_asn = True
# merge # merge
if self.tag_ids is None: if self.tag_ids is None:

View File

@@ -602,11 +602,13 @@ class TestPDFActions(DirectoriesMixin, TestCase):
expected_filename, expected_filename,
) )
self.assertEqual(consume_file_args[1].title, None) self.assertEqual(consume_file_args[1].title, None)
self.assertTrue(consume_file_args[1].skip_asn)
# With metadata_document_id overrides # With metadata_document_id overrides
result = bulk_edit.merge(doc_ids, metadata_document_id=metadata_document_id) result = bulk_edit.merge(doc_ids, metadata_document_id=metadata_document_id)
consume_file_args, _ = mock_consume_file.call_args consume_file_args, _ = mock_consume_file.call_args
self.assertEqual(consume_file_args[1].title, "A (merged)") self.assertEqual(consume_file_args[1].title, "A (merged)")
self.assertTrue(consume_file_args[1].skip_asn)
self.assertEqual(result, "OK") self.assertEqual(result, "OK")
@@ -647,6 +649,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
expected_filename, expected_filename,
) )
self.assertEqual(consume_file_args[1].title, None) self.assertEqual(consume_file_args[1].title, None)
self.assertTrue(consume_file_args[1].skip_asn)
delete_documents_args, _ = mock_delete_documents.call_args delete_documents_args, _ = mock_delete_documents.call_args
self.assertEqual( self.assertEqual(

View File

@@ -412,6 +412,14 @@ class TestConsumer(
self.assertEqual(document.archive_serial_number, 123) self.assertEqual(document.archive_serial_number, 123)
self._assert_first_last_send_progress() self._assert_first_last_send_progress()
def testMetadataOverridesSkipAsnPropagation(self):
overrides = DocumentMetadataOverrides()
incoming = DocumentMetadataOverrides(skip_asn=True)
overrides.update(incoming)
self.assertTrue(overrides.skip_asn)
def testOverrideTitlePlaceholders(self): def testOverrideTitlePlaceholders(self):
c = Correspondent.objects.create(name="Correspondent Name") c = Correspondent.objects.create(name="Correspondent Name")
dt = DocumentType.objects.create(name="DocType Name") dt = DocumentType.objects.create(name="DocType Name")

View File

@@ -3,7 +3,7 @@ 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: 2025-12-12 17:41+0000\n" "POT-Creation-Date: 2025-12-12 17:41+0000\n"
"PO-Revision-Date: 2025-12-12 17:44\n" "PO-Revision-Date: 2025-12-21 00:41\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
"Language: ar_SA\n" "Language: ar_SA\n"
@@ -27,7 +27,7 @@ msgstr "يجب أن تكون القيمة JSON."
#: documents/filters.py:414 #: documents/filters.py:414
msgid "Invalid custom field query expression" msgid "Invalid custom field query expression"
msgstr "" msgstr "تعبير استعلام غير صالح للحقول المخصصة"
#: documents/filters.py:424 #: documents/filters.py:424
msgid "Invalid expression list. Must be nonempty." msgid "Invalid expression list. Must be nonempty."
@@ -47,7 +47,7 @@ msgstr "{name!r} حقل مخصص غير صالح."
#: documents/filters.py:561 #: documents/filters.py:561
msgid "{data_type} does not support query expr {expr!r}." msgid "{data_type} does not support query expr {expr!r}."
msgstr "" msgstr "{data_type} لا يدعم تعبير الاستعلام {expr!r}."
#: documents/filters.py:669 documents/models.py:135 #: documents/filters.py:669 documents/models.py:135
msgid "Maximum nesting depth exceeded." msgid "Maximum nesting depth exceeded."
@@ -962,7 +962,7 @@ msgstr ""
#: documents/models.py:1209 #: documents/models.py:1209
msgid "The destination email addresses, comma separated." msgid "The destination email addresses, comma separated."
msgstr "" msgstr "عناوين البريد الإلكتروني المستهدفة، مفصولة بفواصل."
#: documents/models.py:1215 #: documents/models.py:1215
msgid "include document in email" msgid "include document in email"
@@ -970,7 +970,7 @@ msgstr "تضمين المستند في البريد الإلكتروني"
#: documents/models.py:1226 #: documents/models.py:1226
msgid "webhook url" msgid "webhook url"
msgstr "" msgstr "عنوان Url الخاص بـ webhook"
#: documents/models.py:1229 #: documents/models.py:1229
msgid "The destination URL for the notification." msgid "The destination URL for the notification."
@@ -978,19 +978,19 @@ msgstr "عنوان URL وجهة الإشعار."
#: documents/models.py:1234 #: documents/models.py:1234
msgid "use parameters" msgid "use parameters"
msgstr "" msgstr "استخدام المعلمات"
#: documents/models.py:1239 #: documents/models.py:1239
msgid "send as JSON" msgid "send as JSON"
msgstr "" msgstr "إرسال بصيغة JSON"
#: documents/models.py:1243 #: documents/models.py:1243
msgid "webhook parameters" msgid "webhook parameters"
msgstr "" msgstr "معلمات webhook"
#: documents/models.py:1246 #: documents/models.py:1246
msgid "The parameters to send with the webhook URL if body not used." msgid "The parameters to send with the webhook URL if body not used."
msgstr "" msgstr "المعلمات التي يتم إرسالها مع عنوان URL الخاص بـ webhook في حالة عدم استخدام نص الطلب."
#: documents/models.py:1250 #: documents/models.py:1250
msgid "webhook body" msgid "webhook body"
@@ -998,7 +998,7 @@ msgstr ""
#: documents/models.py:1253 #: documents/models.py:1253
msgid "The body to send with the webhook URL if parameters not used." msgid "The body to send with the webhook URL if parameters not used."
msgstr "" msgstr "نص الطلب الذي سيتم إرساله مع عنوان URL الخاص بـ webhook في حالة عدم استخدام المعلمات."
#: documents/models.py:1257 #: documents/models.py:1257
msgid "webhook headers" msgid "webhook headers"

View File

@@ -3,7 +3,7 @@ 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: 2025-12-12 17:41+0000\n" "POT-Creation-Date: 2025-12-12 17:41+0000\n"
"PO-Revision-Date: 2025-12-12 17:44\n" "PO-Revision-Date: 2025-12-19 00:38\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Catalan\n" "Language-Team: Catalan\n"
"Language: ca_ES\n" "Language: ca_ES\n"

View File

@@ -3,7 +3,7 @@ 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: 2025-12-12 17:41+0000\n" "POT-Creation-Date: 2025-12-12 17:41+0000\n"
"PO-Revision-Date: 2025-12-12 17:44\n" "PO-Revision-Date: 2025-12-21 12:13\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Indonesian\n" "Language-Team: Indonesian\n"
"Language: id_ID\n" "Language: id_ID\n"
@@ -890,11 +890,11 @@ msgstr "tidak memiliki jalur penyimpanan ini"
#: documents/models.py:1128 #: documents/models.py:1128
msgid "filter custom field query" msgid "filter custom field query"
msgstr "" msgstr "filter kueri kolom kustom"
#: documents/models.py:1131 #: documents/models.py:1131
msgid "JSON-encoded custom field query expression." msgid "JSON-encoded custom field query expression."
msgstr "" msgstr "Ekspresi kueri kolom kustom yang dienkode JSON."
#: documents/models.py:1135 #: documents/models.py:1135
msgid "schedule offset days" msgid "schedule offset days"
@@ -1038,7 +1038,7 @@ msgstr "tetapkan judul"
#: documents/models.py:1302 #: documents/models.py:1302
msgid "Assign a document title, must be a Jinja2 template, see documentation." msgid "Assign a document title, must be a Jinja2 template, see documentation."
msgstr "" msgstr "Tetapkan judul dokumen, harus berupa templat Jinja2, lihat dokumentasi."
#: documents/models.py:1310 paperless_mail/models.py:274 #: documents/models.py:1310 paperless_mail/models.py:274
msgid "assign this tag" msgid "assign this tag"
@@ -1220,20 +1220,20 @@ msgstr "Jenis berkas %(type)s tidak didukung"
#: documents/serialisers.py:1870 #: documents/serialisers.py:1870
#, python-format #, python-format
msgid "Custom field id must be an integer: %(id)s" msgid "Custom field id must be an integer: %(id)s"
msgstr "" msgstr "Id kolom kustom harus berupa bilangan bulat: %(id)s"
#: documents/serialisers.py:1877 #: documents/serialisers.py:1877
#, python-format #, python-format
msgid "Custom field with id %(id)s does not exist" msgid "Custom field with id %(id)s does not exist"
msgstr "" msgstr "Kolom kustom dengan id %(id)s tidak ada"
#: documents/serialisers.py:1894 documents/serialisers.py:1904 #: documents/serialisers.py:1894 documents/serialisers.py:1904
msgid "Custom fields must be a list of integers or an object mapping ids to values." msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr "" msgstr "Kolom kustom harus berupa daftar bilangan bulat atau objek yang memetakan id ke nilai."
#: documents/serialisers.py:1899 #: documents/serialisers.py:1899
msgid "Some custom fields don't exist or were specified twice." msgid "Some custom fields don't exist or were specified twice."
msgstr "" msgstr "Beberapa kolom kustom tidak ada atau ditentukan dua kali."
#: documents/serialisers.py:2014 #: documents/serialisers.py:2014
msgid "Invalid variable detected." msgid "Invalid variable detected."

View File

@@ -3,7 +3,7 @@ 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: 2025-12-12 17:41+0000\n" "POT-Creation-Date: 2025-12-12 17:41+0000\n"
"PO-Revision-Date: 2025-12-12 17:44\n" "PO-Revision-Date: 2025-12-19 00:38\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Slovenian\n" "Language-Team: Slovenian\n"
"Language: sl_SI\n" "Language: sl_SI\n"

View File

@@ -3,7 +3,7 @@ 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: 2025-12-12 17:41+0000\n" "POT-Creation-Date: 2025-12-12 17:41+0000\n"
"PO-Revision-Date: 2025-12-12 17:44\n" "PO-Revision-Date: 2025-12-19 12:15\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Serbian (Latin)\n" "Language-Team: Serbian (Latin)\n"
"Language: sr_CS\n" "Language: sr_CS\n"

View File

@@ -1108,6 +1108,7 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 2) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 2)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages_spam), 1) self.assertEqual(len(self.mailMocker.bogus_mailbox.messages_spam), 1)
@pytest.mark.flaky(reruns=4)
def test_error_skip_rule(self): def test_error_skip_rule(self):
account = MailAccount.objects.create( account = MailAccount.objects.create(
name="test2", name="test2",