mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
commit
c4b7429e99
@ -733,7 +733,7 @@ they use underscores instead of dashes.
|
||||
Paperless has been tested to work with the OCR options provided
|
||||
above. There are many options that are incompatible with each other,
|
||||
so specifying invalid options may prevent paperless from consuming
|
||||
any documents.
|
||||
any documents. Use with caution!
|
||||
|
||||
Specify arguments as a JSON dictionary. Keep note of lower case
|
||||
booleans and double quoted parameter names and strings. Examples:
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="col-md-4">
|
||||
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
||||
<p class="small" i18n>Process documents that match <em>all</em> filters specified below.</p>
|
||||
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.filter_filename"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Filter sources" [items]="sourceOptions" [multiple]="true" formControlName="sources" [error]="error?.sources"></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Filter filename" formControlName="filter_filename" i18n-hint hint="Apply to documents that match this filename. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_filename"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter path" formControlName="filter_path" i18n-hint hint="Apply to documents that match this path. Wildcards specified as * are allowed. Case insensitive.</a>" [error]="error?.filter_path"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Filter mail rule" [items]="mailRules" [allowNull]="true" formControlName="filter_mailrule" i18n-hint hint="Apply to documents consumed via this mail rule." [error]="error?.filter_mailrule"></pngx-input-select>
|
||||
|
@ -9,7 +9,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div [class.col-md-9]="horizontal">
|
||||
<div [class.input-group]="allowCreateNew || showFilter">
|
||||
<div [class.input-group]="allowCreateNew || showFilter" [class.is-invalid]="error">
|
||||
<ng-select name="inputId" [(ngModel)]="value"
|
||||
[disabled]="disabled"
|
||||
[style.color]="textColor"
|
||||
@ -42,6 +42,9 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{error}}
|
||||
</div>
|
||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||
<small *ngIf="getSuggestions().length > 0">
|
||||
<span i18n>Suggestions:</span>
|
||||
|
@ -17,3 +17,12 @@
|
||||
font-style: italic;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
::ng-deep .is-invalid ng-select .ng-select-container input {
|
||||
// replicate bootstrap
|
||||
padding-right: calc(1.5em + 0.75rem) !important;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: right calc(0.375em + 0.1875rem) center !important;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export const environment = {
|
||||
apiBaseUrl: document.baseURI + 'api/',
|
||||
apiVersion: '3',
|
||||
appTitle: 'Paperless-ngx',
|
||||
version: '2.1.2',
|
||||
version: '2.1.2-dev',
|
||||
webSocketHost: window.location.host,
|
||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||
|
@ -10,13 +10,13 @@
|
||||
</context-group>
|
||||
<target state="final">Fermer</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.timepicker.HH" datatype="html">
|
||||
<trans-unit id="ngb.timepicker.HH" datatype="html" approved="yes">
|
||||
<source>HH</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<target state="translated">HH</target>
|
||||
<target state="final">HH</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.toast.close-aria" datatype="html" approved="yes">
|
||||
<source>Close</source>
|
||||
@ -100,13 +100,13 @@
|
||||
</context-group>
|
||||
<target state="final">Précédent</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.timepicker.MM" datatype="html">
|
||||
<trans-unit id="ngb.timepicker.MM" datatype="html" approved="yes">
|
||||
<source>MM</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<target state="translated">MM</target>
|
||||
<target state="final">MM</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="ngb.pagination.next" datatype="html" approved="yes">
|
||||
<source>»</source>
|
||||
|
@ -288,7 +288,7 @@
|
||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||
<context context-type="linenumber">90</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Document <x id="PH" equiv-text="status.filename"/> was added to Paperless-ngx.</target>
|
||||
<target state="translated">Dokument <x id="PH" equiv-text="status.filename"/> je dodan u Paperless-ngx.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1931214133925051574" datatype="html">
|
||||
<source>Open document</source>
|
||||
@ -316,7 +316,7 @@
|
||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||
<context context-type="linenumber">120</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Document <x id="PH" equiv-text="status.filename"/> is being processed by Paperless-ngx.</target>
|
||||
<target state="translated">Dokument <x id="PH" equiv-text="status.filename"/> je u fazi obrade.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2501522447884928778" datatype="html">
|
||||
<source>Prev</source>
|
||||
@ -476,7 +476,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">15</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Auto refresh</target>
|
||||
<target state="translated">Automatsko osvježavanje</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3894950702316166331" datatype="html">
|
||||
<source>Loading...</source>
|
||||
@ -596,7 +596,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">15</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">General</target>
|
||||
<target state="translated">Općenito</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8671234314555525900" datatype="html">
|
||||
<source>Appearance</source>
|
||||
@ -916,7 +916,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||
<context context-type="linenumber">178,180</context>
|
||||
</context-group>
|
||||
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI </target>
|
||||
<target state="translated"> Postavke ovog korisničkog računa za objekte (Oznake, Pravila za e-poštu, itd.) stvorene putem web sučelja </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4292903881380648974" datatype="html">
|
||||
<source>Default Owner</source>
|
||||
@ -2074,7 +2074,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||
<context context-type="linenumber">124</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Deleted user</target>
|
||||
<target state="translated">Izbrisani korisnik</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1942566571910298572" datatype="html">
|
||||
<source>Error deleting user.</source>
|
||||
@ -2479,7 +2479,7 @@
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||
<context context-type="linenumber">276</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">An error occurred while saving update checking settings.</target>
|
||||
<target state="translated">Došlo je do pogreške prilikom spremanja postavki ažuriranja.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8700121026680200191" datatype="html">
|
||||
<source>Clear</source>
|
||||
@ -3039,7 +3039,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">9</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Data type</target>
|
||||
<target state="translated">Tip podataka</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5933665691581884232" datatype="html">
|
||||
<source>Data type cannot be changed after a field is created</source>
|
||||
@ -3047,7 +3047,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">10</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Data type cannot be changed after a field is created</target>
|
||||
<target state="translated">Tip podataka ne može se promijeniti nakon što je polje stvoreno</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="528950215505228201" datatype="html">
|
||||
<source>Create new custom field</source>
|
||||
@ -3175,7 +3175,7 @@
|
||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
<target state="needs-translation">Character Set</target>
|
||||
<target state="translated">Skup znakova</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6563391987554512024" datatype="html">
|
||||
<source>Test</source>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,8 @@ from pikepdf import Pdf
|
||||
from PIL import Image
|
||||
|
||||
from documents.converters import convert_from_tiff_to_pdf
|
||||
from documents.data_models import ConsumableDocument
|
||||
from documents.data_models import DocumentMetadataOverrides
|
||||
from documents.data_models import DocumentSource
|
||||
from documents.utils import copy_basic_file_stats
|
||||
from documents.utils import copy_file_with_basic_stats
|
||||
@ -53,6 +55,7 @@ class BarcodeReader:
|
||||
self.mime: Final[str] = mime_type
|
||||
self.pdf_file: Path = self.file
|
||||
self.barcodes: list[Barcode] = []
|
||||
self._tiff_conversion_done = False
|
||||
self.temp_dir: Optional[tempfile.TemporaryDirectory] = None
|
||||
|
||||
if settings.CONSUMER_BARCODE_TIFF_SUPPORT:
|
||||
@ -150,12 +153,14 @@ class BarcodeReader:
|
||||
|
||||
def convert_from_tiff_to_pdf(self):
|
||||
"""
|
||||
May convert a TIFF image into a PDF, if the input is a TIFF
|
||||
May convert a TIFF image into a PDF, if the input is a TIFF and
|
||||
the TIFF has not been made into a PDF
|
||||
"""
|
||||
# Nothing to do, pdf_file is already assigned correctly
|
||||
if self.mime != "image/tiff":
|
||||
if self.mime != "image/tiff" or self._tiff_conversion_done:
|
||||
return
|
||||
|
||||
self._tiff_conversion_done = True
|
||||
self.pdf_file = convert_from_tiff_to_pdf(self.file, Path(self.temp_dir.name))
|
||||
|
||||
def detect(self) -> None:
|
||||
@ -167,6 +172,9 @@ class BarcodeReader:
|
||||
if self.barcodes:
|
||||
return
|
||||
|
||||
# No op if not a TIFF
|
||||
self.convert_from_tiff_to_pdf()
|
||||
|
||||
# Choose the library for reading
|
||||
if settings.CONSUMER_BARCODE_SCANNER == "PYZBAR":
|
||||
reader = self.read_barcodes_pyzbar
|
||||
@ -240,7 +248,7 @@ class BarcodeReader:
|
||||
"""
|
||||
|
||||
document_paths = []
|
||||
fname = self.file.with_suffix("").name
|
||||
fname = self.file.stem
|
||||
with Pdf.open(self.pdf_file) as input_pdf:
|
||||
# Start with an empty document
|
||||
current_document: list[Page] = []
|
||||
@ -290,7 +298,7 @@ class BarcodeReader:
|
||||
def separate(
|
||||
self,
|
||||
source: DocumentSource,
|
||||
override_name: Optional[str] = None,
|
||||
overrides: DocumentMetadataOverrides,
|
||||
) -> bool:
|
||||
"""
|
||||
Separates the document, based on barcodes and configuration, creating new
|
||||
@ -316,27 +324,23 @@ class BarcodeReader:
|
||||
logger.warning("No pages to split on!")
|
||||
return False
|
||||
|
||||
# Create the split documents
|
||||
doc_paths = self.separate_pages(separator_pages)
|
||||
tmp_dir = Path(tempfile.mkdtemp(prefix="paperless-barcode-split-")).resolve()
|
||||
|
||||
# Save the new documents to correct folder
|
||||
if source != DocumentSource.ConsumeFolder:
|
||||
# The given file is somewhere in SCRATCH_DIR,
|
||||
# and new documents must be moved to the CONSUMPTION_DIR
|
||||
# for the consumer to notice them
|
||||
save_to_dir = settings.CONSUMPTION_DIR
|
||||
else:
|
||||
# The given file is somewhere in CONSUMPTION_DIR,
|
||||
# and may be some levels down for recursive tagging
|
||||
# so use the file's parent to preserve any metadata
|
||||
save_to_dir = self.file.parent
|
||||
from documents import tasks
|
||||
|
||||
for idx, document_path in enumerate(doc_paths):
|
||||
if override_name is not None:
|
||||
newname = f"{idx}_{override_name}"
|
||||
dest = save_to_dir / newname
|
||||
else:
|
||||
dest = save_to_dir
|
||||
logger.info(f"Saving {document_path} to {dest}")
|
||||
copy_file_with_basic_stats(document_path, dest)
|
||||
# Create the split document tasks
|
||||
for new_document in self.separate_pages(separator_pages):
|
||||
copy_file_with_basic_stats(new_document, tmp_dir / new_document.name)
|
||||
|
||||
tasks.consume_file.delay(
|
||||
ConsumableDocument(
|
||||
# Same source, for templates
|
||||
source=source,
|
||||
# Can't use same folder or the consume might grab it again
|
||||
original_file=(tmp_dir / new_document.name).resolve(),
|
||||
),
|
||||
# All the same metadata
|
||||
overrides,
|
||||
)
|
||||
logger.info("Barcode splitting complete!")
|
||||
return True
|
||||
|
@ -3,6 +3,7 @@ import math
|
||||
import os
|
||||
from collections import Counter
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from dateutil.parser import isoparse
|
||||
@ -371,7 +372,7 @@ class LocalDateParser(English):
|
||||
if isinstance(d, timespan):
|
||||
d.start = self.reverse_timezone_offset(d.start)
|
||||
d.end = self.reverse_timezone_offset(d.end)
|
||||
else:
|
||||
elif isinstance(d, datetime):
|
||||
d = self.reverse_timezone_offset(d)
|
||||
return d
|
||||
|
||||
|
@ -238,18 +238,6 @@ class Command(BaseCommand):
|
||||
serializers.serialize("json", StoragePath.objects.all()),
|
||||
)
|
||||
|
||||
notes = json.loads(
|
||||
serializers.serialize("json", Note.objects.all()),
|
||||
)
|
||||
if not self.split_manifest:
|
||||
manifest += notes
|
||||
|
||||
documents = Document.objects.order_by("id")
|
||||
document_map = {d.pk: d for d in documents}
|
||||
document_manifest = json.loads(serializers.serialize("json", documents))
|
||||
if not self.split_manifest:
|
||||
manifest += document_manifest
|
||||
|
||||
manifest += json.loads(
|
||||
serializers.serialize("json", MailAccount.objects.all()),
|
||||
)
|
||||
@ -303,10 +291,24 @@ class Command(BaseCommand):
|
||||
serializers.serialize("json", CustomField.objects.all()),
|
||||
)
|
||||
|
||||
# These are treated specially and included in the per-document manifest
|
||||
# if that setting is enabled. Otherwise, they are just exported to the bulk
|
||||
# manifest
|
||||
documents = Document.objects.order_by("id")
|
||||
document_map: dict[int, Document] = {d.pk: d for d in documents}
|
||||
document_manifest = json.loads(serializers.serialize("json", documents))
|
||||
|
||||
notes = json.loads(
|
||||
serializers.serialize("json", Note.objects.all()),
|
||||
)
|
||||
|
||||
custom_field_instances = json.loads(
|
||||
serializers.serialize("json", CustomFieldInstance.objects.all()),
|
||||
)
|
||||
if not self.split_manifest:
|
||||
manifest += json.loads(
|
||||
serializers.serialize("json", CustomFieldInstance.objects.all()),
|
||||
)
|
||||
manifest += document_manifest
|
||||
manifest += notes
|
||||
manifest += custom_field_instances
|
||||
|
||||
# 3. Export files from each document
|
||||
for index, document_dict in tqdm.tqdm(
|
||||
@ -412,6 +414,12 @@ class Command(BaseCommand):
|
||||
notes,
|
||||
),
|
||||
)
|
||||
content += list(
|
||||
filter(
|
||||
lambda d: d["fields"]["document"] == document_dict["pk"],
|
||||
custom_field_instances,
|
||||
),
|
||||
)
|
||||
manifest_name.write_text(
|
||||
json.dumps(content, indent=2, ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
|
@ -140,7 +140,7 @@ def consume_file(
|
||||
with BarcodeReader(input_doc.original_file, input_doc.mime_type) as reader:
|
||||
if settings.CONSUMER_ENABLE_BARCODES and reader.separate(
|
||||
input_doc.source,
|
||||
overrides.filename,
|
||||
overrides,
|
||||
):
|
||||
# notify the sender, otherwise the progress bar
|
||||
# in the UI stays stuck
|
||||
|
@ -455,6 +455,31 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
||||
# Assert subset in results
|
||||
self.assertDictEqual(result, {**result, **subset})
|
||||
|
||||
def test_search_added_invalid_date(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- One document added right now
|
||||
WHEN:
|
||||
- Query with invalid added date
|
||||
THEN:
|
||||
- No documents returned
|
||||
"""
|
||||
d1 = Document.objects.create(
|
||||
title="invoice",
|
||||
content="the thing i bought at a shop and paid with bank account",
|
||||
checksum="A",
|
||||
pk=1,
|
||||
)
|
||||
|
||||
with index.open_index_writer() as writer:
|
||||
index.update_document(writer, d1)
|
||||
|
||||
response = self.client.get("/api/documents/?query=added:invalid-date")
|
||||
results = response.data["results"]
|
||||
|
||||
# Expect 0 document returned
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
@mock.patch("documents.index.autocomplete")
|
||||
def test_search_autocomplete_limits(self, m):
|
||||
"""
|
||||
|
@ -1,5 +1,4 @@
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
@ -11,10 +10,13 @@ from documents import tasks
|
||||
from documents.barcodes import BarcodeReader
|
||||
from documents.consumer import ConsumerError
|
||||
from documents.data_models import ConsumableDocument
|
||||
from documents.data_models import DocumentMetadataOverrides
|
||||
from documents.data_models import DocumentSource
|
||||
from documents.models import Document
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from documents.tests.utils import DocumentConsumeDelayMixin
|
||||
from documents.tests.utils import FileSystemAssertsMixin
|
||||
from documents.tests.utils import SampleDirMixin
|
||||
|
||||
try:
|
||||
import zxingcpp # noqa: F401
|
||||
@ -25,11 +27,7 @@ except ImportError:
|
||||
|
||||
|
||||
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||
class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
SAMPLE_DIR = Path(__file__).parent / "samples"
|
||||
|
||||
BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes"
|
||||
|
||||
class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, SampleDirMixin, TestCase):
|
||||
def test_scan_file_for_separating_barcodes(self):
|
||||
"""
|
||||
GIVEN:
|
||||
@ -48,6 +46,46 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
self.assertDictEqual(separator_page_numbers, {0: False})
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
)
|
||||
def test_scan_tiff_for_separating_barcodes(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- TIFF image containing barcodes
|
||||
WHEN:
|
||||
- Consume task returns
|
||||
THEN:
|
||||
- The file was split
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff"
|
||||
|
||||
with BarcodeReader(test_file, "image/tiff") as reader:
|
||||
reader.detect()
|
||||
separator_page_numbers = reader.get_separation_pages()
|
||||
|
||||
self.assertDictEqual(separator_page_numbers, {1: False})
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
)
|
||||
def test_scan_tiff_with_alpha_for_separating_barcodes(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- TIFF image containing barcodes
|
||||
WHEN:
|
||||
- Consume task returns
|
||||
THEN:
|
||||
- The file was split
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle-alpha.tiff"
|
||||
|
||||
with BarcodeReader(test_file, "image/tiff") as reader:
|
||||
reader.detect()
|
||||
separator_page_numbers = reader.get_separation_pages()
|
||||
|
||||
self.assertDictEqual(separator_page_numbers, {1: False})
|
||||
|
||||
def test_scan_file_for_separating_barcodes_none_present(self):
|
||||
"""
|
||||
GIVEN:
|
||||
@ -285,6 +323,28 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
self.assertGreater(len(reader.barcodes), 0)
|
||||
self.assertDictEqual(separator_page_numbers, {1: False})
|
||||
|
||||
def test_scan_file_for_separating_barcodes_password(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Password protected PDF
|
||||
WHEN:
|
||||
- File is scanned for barcode
|
||||
THEN:
|
||||
- Scanning handles the exception without crashing
|
||||
"""
|
||||
test_file = self.SAMPLE_DIR / "password-is-test.pdf"
|
||||
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.detect()
|
||||
warning = cm.output[0]
|
||||
expected_str = "WARNING:paperless.barcodes:File is likely password protected, not checking for barcodes"
|
||||
self.assertTrue(warning.startswith(expected_str))
|
||||
|
||||
separator_page_numbers = reader.get_separation_pages()
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
self.assertDictEqual(separator_page_numbers, {})
|
||||
|
||||
def test_separate_pages(self):
|
||||
"""
|
||||
GIVEN:
|
||||
@ -332,8 +392,12 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
|
||||
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
success = reader.separate(DocumentSource.ApiUpload)
|
||||
self.assertFalse(success)
|
||||
self.assertFalse(
|
||||
reader.separate(
|
||||
DocumentSource.ApiUpload,
|
||||
DocumentMetadataOverrides(),
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
cm.output,
|
||||
[
|
||||
@ -341,215 +405,6 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_save_to_dir_given_name(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- File to save to a directory
|
||||
- There is a name override
|
||||
WHEN:
|
||||
- The file is saved
|
||||
THEN:
|
||||
- The file exists
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.separate(DocumentSource.ApiUpload, "newname.pdf")
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
target_file1 = settings.CONSUMPTION_DIR / "0_newname.pdf"
|
||||
target_file2 = settings.CONSUMPTION_DIR / "1_newname.pdf"
|
||||
self.assertIsFile(target_file1)
|
||||
self.assertIsFile(target_file2)
|
||||
|
||||
def test_barcode_splitter_api_upload(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Input file containing barcodes
|
||||
WHEN:
|
||||
- Input file is split on barcodes
|
||||
THEN:
|
||||
- Correct number of files produced
|
||||
"""
|
||||
sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
test_file = settings.SCRATCH_DIR / "patch-code-t-middle.pdf"
|
||||
shutil.copy(sample_file, test_file)
|
||||
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.separate(DocumentSource.ApiUpload)
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
|
||||
target_file1 = (
|
||||
settings.CONSUMPTION_DIR / "patch-code-t-middle_document_0.pdf"
|
||||
)
|
||||
|
||||
target_file2 = (
|
||||
settings.CONSUMPTION_DIR / "patch-code-t-middle_document_1.pdf"
|
||||
)
|
||||
|
||||
self.assertIsFile(target_file1)
|
||||
self.assertIsFile(target_file2)
|
||||
|
||||
def test_barcode_splitter_consume_dir(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Input file containing barcodes
|
||||
WHEN:
|
||||
- Input file is split on barcodes
|
||||
THEN:
|
||||
- Correct number of files produced
|
||||
"""
|
||||
sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
test_file = settings.CONSUMPTION_DIR / "patch-code-t-middle.pdf"
|
||||
shutil.copy(sample_file, test_file)
|
||||
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.detect()
|
||||
reader.separate(DocumentSource.ConsumeFolder)
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
|
||||
target_file1 = (
|
||||
settings.CONSUMPTION_DIR / "patch-code-t-middle_document_0.pdf"
|
||||
)
|
||||
|
||||
target_file2 = (
|
||||
settings.CONSUMPTION_DIR / "patch-code-t-middle_document_1.pdf"
|
||||
)
|
||||
|
||||
self.assertIsFile(target_file1)
|
||||
self.assertIsFile(target_file2)
|
||||
|
||||
def test_barcode_splitter_consume_dir_recursive(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Input file containing barcodes
|
||||
- Input file is within a directory structure of the consume folder
|
||||
WHEN:
|
||||
- Input file is split on barcodes
|
||||
THEN:
|
||||
- Correct number of files produced
|
||||
- Output files are within the same directory structure
|
||||
"""
|
||||
sample_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
test_file = (
|
||||
settings.CONSUMPTION_DIR / "tag1" / "tag2" / "patch-code-t-middle.pdf"
|
||||
)
|
||||
test_file.parent.mkdir(parents=True)
|
||||
shutil.copy(sample_file, test_file)
|
||||
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.separate(DocumentSource.ConsumeFolder)
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
|
||||
target_file1 = (
|
||||
settings.CONSUMPTION_DIR
|
||||
/ "tag1"
|
||||
/ "tag2"
|
||||
/ "patch-code-t-middle_document_0.pdf"
|
||||
)
|
||||
|
||||
target_file2 = (
|
||||
settings.CONSUMPTION_DIR
|
||||
/ "tag1"
|
||||
/ "tag2"
|
||||
/ "patch-code-t-middle_document_1.pdf"
|
||||
)
|
||||
|
||||
self.assertIsFile(target_file1)
|
||||
self.assertIsFile(target_file2)
|
||||
|
||||
@override_settings(CONSUMER_ENABLE_BARCODES=True)
|
||||
def test_consume_barcode_file(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Input file with barcodes given to consume task
|
||||
WHEN:
|
||||
- Consume task returns
|
||||
THEN:
|
||||
- The file was split
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
|
||||
dst = settings.SCRATCH_DIR / "patch-code-t-middle.pdf"
|
||||
shutil.copy(test_file, dst)
|
||||
|
||||
with mock.patch("documents.tasks.async_to_sync"):
|
||||
self.assertEqual(
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=dst,
|
||||
),
|
||||
None,
|
||||
),
|
||||
"File successfully split",
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_ENABLE_BARCODES=True,
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
)
|
||||
def test_consume_barcode_tiff_file(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- TIFF image containing barcodes
|
||||
WHEN:
|
||||
- Consume task returns
|
||||
THEN:
|
||||
- The file was split
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff"
|
||||
|
||||
dst = settings.SCRATCH_DIR / "patch-code-t-middle.tiff"
|
||||
shutil.copy(test_file, dst)
|
||||
|
||||
with mock.patch("documents.tasks.async_to_sync"):
|
||||
self.assertEqual(
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=dst,
|
||||
),
|
||||
None,
|
||||
),
|
||||
"File successfully split",
|
||||
)
|
||||
self.assertIsNotFile(dst)
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_ENABLE_BARCODES=True,
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
)
|
||||
def test_consume_barcode_tiff_file_with_alpha(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- TIFF image containing barcodes
|
||||
- TIFF image has an alpha layer
|
||||
WHEN:
|
||||
- Consume task handles the alpha layer and returns
|
||||
THEN:
|
||||
- The file was split without issue
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle-alpha.tiff"
|
||||
|
||||
dst = settings.SCRATCH_DIR / "patch-code-t-middle.tiff"
|
||||
shutil.copy(test_file, dst)
|
||||
|
||||
with mock.patch("documents.tasks.async_to_sync"):
|
||||
self.assertEqual(
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=dst,
|
||||
),
|
||||
None,
|
||||
),
|
||||
"File successfully split",
|
||||
)
|
||||
self.assertIsNotFile(dst)
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_ENABLE_BARCODES=True,
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
@ -597,60 +452,6 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
self.assertIsNone(kwargs["override_document_type_id"])
|
||||
self.assertIsNone(kwargs["override_tag_ids"])
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_ENABLE_BARCODES=True,
|
||||
CONSUMER_BARCODE_TIFF_SUPPORT=True,
|
||||
)
|
||||
def test_consume_barcode_supported_no_extension_file(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- TIFF image containing barcodes
|
||||
- TIFF file is given without extension
|
||||
WHEN:
|
||||
- Consume task returns
|
||||
THEN:
|
||||
- The file was split
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.tiff"
|
||||
|
||||
dst = settings.SCRATCH_DIR / "patch-code-t-middle"
|
||||
shutil.copy(test_file, dst)
|
||||
|
||||
with mock.patch("documents.tasks.async_to_sync"):
|
||||
self.assertEqual(
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=dst,
|
||||
),
|
||||
None,
|
||||
),
|
||||
"File successfully split",
|
||||
)
|
||||
self.assertIsNotFile(dst)
|
||||
|
||||
def test_scan_file_for_separating_barcodes_password(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Password protected PDF
|
||||
WHEN:
|
||||
- File is scanned for barcode
|
||||
THEN:
|
||||
- Scanning handles the exception without crashing
|
||||
"""
|
||||
test_file = self.SAMPLE_DIR / "password-is-test.pdf"
|
||||
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||
reader.detect()
|
||||
warning = cm.output[0]
|
||||
expected_str = "WARNING:paperless.barcodes:File is likely password protected, not checking for barcodes"
|
||||
self.assertTrue(warning.startswith(expected_str))
|
||||
|
||||
separator_page_numbers = reader.get_separation_pages()
|
||||
|
||||
self.assertEqual(reader.pdf_file, test_file)
|
||||
self.assertDictEqual(separator_page_numbers, {})
|
||||
|
||||
@override_settings(
|
||||
CONSUMER_ENABLE_BARCODES=True,
|
||||
CONSUMER_ENABLE_ASN_BARCODE=True,
|
||||
@ -722,11 +523,64 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
self.assertEqual(len(document_list), 5)
|
||||
|
||||
|
||||
class TestAsnBarcode(DirectoriesMixin, TestCase):
|
||||
SAMPLE_DIR = Path(__file__).parent / "samples"
|
||||
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||
class TestBarcodeNewConsume(
|
||||
DirectoriesMixin,
|
||||
FileSystemAssertsMixin,
|
||||
SampleDirMixin,
|
||||
DocumentConsumeDelayMixin,
|
||||
TestCase,
|
||||
):
|
||||
@override_settings(CONSUMER_ENABLE_BARCODES=True)
|
||||
def test_consume_barcode_file(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Incoming file with at 1 barcode producing 2 documents
|
||||
- Document includes metadata override information
|
||||
WHEN:
|
||||
- The document is split
|
||||
THEN:
|
||||
- Two new consume tasks are created
|
||||
- Metadata overrides are preserved for the new consume
|
||||
- The document source is unchanged (for consume templates)
|
||||
"""
|
||||
test_file = self.BARCODE_SAMPLE_DIR / "patch-code-t-middle.pdf"
|
||||
temp_copy = self.dirs.scratch_dir / test_file.name
|
||||
shutil.copy(test_file, temp_copy)
|
||||
|
||||
BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes"
|
||||
overrides = DocumentMetadataOverrides(tag_ids=[1, 2, 9])
|
||||
|
||||
with mock.patch("documents.tasks.async_to_sync") as progress_mocker:
|
||||
self.assertEqual(
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
source=DocumentSource.ConsumeFolder,
|
||||
original_file=temp_copy,
|
||||
),
|
||||
overrides,
|
||||
),
|
||||
"File successfully split",
|
||||
)
|
||||
# We let the consumer know progress is done
|
||||
progress_mocker.assert_called_once()
|
||||
# 2 new document consume tasks created
|
||||
self.assertEqual(self.consume_file_mock.call_count, 2)
|
||||
|
||||
self.assertIsNotFile(temp_copy)
|
||||
|
||||
# Check the split files exist
|
||||
# Check the source is unchanged
|
||||
# Check the overrides are unchanged
|
||||
for (
|
||||
new_input_doc,
|
||||
new_doc_overrides,
|
||||
) in self.get_all_consume_delay_call_args():
|
||||
self.assertEqual(new_input_doc.source, DocumentSource.ConsumeFolder)
|
||||
self.assertIsFile(new_input_doc.original_file)
|
||||
self.assertEqual(overrides, new_doc_overrides)
|
||||
|
||||
|
||||
class TestAsnBarcode(DirectoriesMixin, SampleDirMixin, TestCase):
|
||||
@override_settings(CONSUMER_ASN_BARCODE_PREFIX="CUSTOM-PREFIX-")
|
||||
def test_scan_file_for_asn_custom_prefix(self):
|
||||
"""
|
||||
|
@ -646,10 +646,13 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
|
||||
with paperless_environment():
|
||||
self.assertEqual(Document.objects.count(), 4)
|
||||
self.assertEqual(CustomFieldInstance.objects.count(), 1)
|
||||
Document.objects.all().delete()
|
||||
CustomFieldInstance.objects.all().delete()
|
||||
self.assertEqual(Document.objects.count(), 0)
|
||||
call_command("document_importer", "--no-progress-bar", self.target)
|
||||
self.assertEqual(Document.objects.count(), 4)
|
||||
self.assertEqual(CustomFieldInstance.objects.count(), 1)
|
||||
|
||||
def test_folder_prefix(self):
|
||||
"""
|
||||
|
@ -235,8 +235,10 @@ class DocumentConsumeDelayMixin:
|
||||
"""
|
||||
Iterates over all calls to the async task and returns the arguments
|
||||
"""
|
||||
# Must be at least 1 call
|
||||
self.consume_file_mock.assert_called()
|
||||
|
||||
for args, _ in self.consume_file_mock.call_args_list:
|
||||
for args, kwargs in self.consume_file_mock.call_args_list:
|
||||
input_doc, overrides = args
|
||||
|
||||
yield (input_doc, overrides)
|
||||
@ -244,7 +246,7 @@ class DocumentConsumeDelayMixin:
|
||||
def get_specific_consume_delay_call_args(
|
||||
self,
|
||||
index: int,
|
||||
) -> Iterator[tuple[ConsumableDocument, DocumentMetadataOverrides]]:
|
||||
) -> tuple[ConsumableDocument, DocumentMetadataOverrides]:
|
||||
"""
|
||||
Returns the arguments of a specific call to the async task
|
||||
"""
|
||||
@ -299,3 +301,9 @@ class TestMigrations(TransactionTestCase):
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
pass
|
||||
|
||||
|
||||
class SampleDirMixin:
|
||||
SAMPLE_DIR = Path(__file__).parent / "samples"
|
||||
|
||||
BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes"
|
||||
|
@ -182,10 +182,14 @@ class PassUserMixin(CreateModelMixin):
|
||||
class CorrespondentViewSet(ModelViewSet, PassUserMixin):
|
||||
model = Correspondent
|
||||
|
||||
queryset = Correspondent.objects.annotate(
|
||||
document_count=Count("documents"),
|
||||
last_correspondence=Max("documents__created"),
|
||||
).order_by(Lower("name"))
|
||||
queryset = (
|
||||
Correspondent.objects.annotate(
|
||||
document_count=Count("documents"),
|
||||
last_correspondence=Max("documents__created"),
|
||||
)
|
||||
.select_related("owner")
|
||||
.order_by(Lower("name"))
|
||||
)
|
||||
|
||||
serializer_class = CorrespondentSerializer
|
||||
pagination_class = StandardPagination
|
||||
@ -208,8 +212,12 @@ class CorrespondentViewSet(ModelViewSet, PassUserMixin):
|
||||
class TagViewSet(ModelViewSet, PassUserMixin):
|
||||
model = Tag
|
||||
|
||||
queryset = Tag.objects.annotate(document_count=Count("documents")).order_by(
|
||||
Lower("name"),
|
||||
queryset = (
|
||||
Tag.objects.annotate(document_count=Count("documents"))
|
||||
.select_related("owner")
|
||||
.order_by(
|
||||
Lower("name"),
|
||||
)
|
||||
)
|
||||
|
||||
def get_serializer_class(self, *args, **kwargs):
|
||||
@ -232,9 +240,13 @@ class TagViewSet(ModelViewSet, PassUserMixin):
|
||||
class DocumentTypeViewSet(ModelViewSet, PassUserMixin):
|
||||
model = DocumentType
|
||||
|
||||
queryset = DocumentType.objects.annotate(
|
||||
document_count=Count("documents"),
|
||||
).order_by(Lower("name"))
|
||||
queryset = (
|
||||
DocumentType.objects.annotate(
|
||||
document_count=Count("documents"),
|
||||
)
|
||||
.select_related("owner")
|
||||
.order_by(Lower("name"))
|
||||
)
|
||||
|
||||
serializer_class = DocumentTypeSerializer
|
||||
pagination_class = StandardPagination
|
||||
@ -283,7 +295,12 @@ class DocumentViewSet(
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return Document.objects.distinct().annotate(num_notes=Count("notes"))
|
||||
return (
|
||||
Document.objects.distinct()
|
||||
.annotate(num_notes=Count("notes"))
|
||||
.select_related("correspondent", "storage_path", "document_type", "owner")
|
||||
.prefetch_related("tags", "custom_fields", "notes")
|
||||
)
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
fields_param = self.request.query_params.get("fields", None)
|
||||
@ -627,9 +644,18 @@ class DocumentViewSet(
|
||||
|
||||
class SearchResultSerializer(DocumentSerializer, PassUserMixin):
|
||||
def to_representation(self, instance):
|
||||
doc = Document.objects.get(id=instance["id"])
|
||||
doc = (
|
||||
Document.objects.select_related(
|
||||
"correspondent",
|
||||
"storage_path",
|
||||
"document_type",
|
||||
"owner",
|
||||
)
|
||||
.prefetch_related("tags", "custom_fields", "notes")
|
||||
.get(id=instance["id"])
|
||||
)
|
||||
notes = ",".join(
|
||||
[str(c.note) for c in Note.objects.filter(document=instance["id"])],
|
||||
[str(c.note) for c in doc.notes.all()],
|
||||
)
|
||||
r = super().to_representation(doc)
|
||||
r["__search_hit__"] = {
|
||||
@ -752,7 +778,11 @@ class SavedViewViewSet(ModelViewSet, PassUserMixin):
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return SavedView.objects.filter(owner=user)
|
||||
return (
|
||||
SavedView.objects.filter(owner=user)
|
||||
.select_related("owner")
|
||||
.prefetch_related("filter_rules")
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user)
|
||||
@ -1080,8 +1110,12 @@ class BulkDownloadView(GenericAPIView):
|
||||
class StoragePathViewSet(ModelViewSet, PassUserMixin):
|
||||
model = StoragePath
|
||||
|
||||
queryset = StoragePath.objects.annotate(document_count=Count("documents")).order_by(
|
||||
Lower("name"),
|
||||
queryset = (
|
||||
StoragePath.objects.annotate(document_count=Count("documents"))
|
||||
.select_related("owner")
|
||||
.order_by(
|
||||
Lower("name"),
|
||||
)
|
||||
)
|
||||
|
||||
serializer_class = StoragePathSerializer
|
||||
@ -1347,7 +1381,18 @@ class ConsumptionTemplateViewSet(ModelViewSet):
|
||||
|
||||
model = ConsumptionTemplate
|
||||
|
||||
queryset = ConsumptionTemplate.objects.all().order_by("order")
|
||||
queryset = (
|
||||
ConsumptionTemplate.objects.prefetch_related(
|
||||
"assign_tags",
|
||||
"assign_view_users",
|
||||
"assign_view_groups",
|
||||
"assign_change_users",
|
||||
"assign_change_groups",
|
||||
"assign_custom_fields",
|
||||
)
|
||||
.all()
|
||||
.order_by("order")
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldViewSet(ModelViewSet):
|
||||
|
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-05 08:26-0800\n"
|
||||
"PO-Revision-Date: 2023-12-12 00:24\n"
|
||||
"PO-Revision-Date: 2023-12-14 00:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
|
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-05 08:26-0800\n"
|
||||
"PO-Revision-Date: 2023-12-05 16:27\n"
|
||||
"PO-Revision-Date: 2023-12-13 12:09\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Croatian\n"
|
||||
"Language: hr_HR\n"
|
||||
|
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-05 08:26-0800\n"
|
||||
"PO-Revision-Date: 2023-12-05 16:27\n"
|
||||
"PO-Revision-Date: 2023-12-16 00:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"Language: ro_RO\n"
|
||||
@ -23,11 +23,11 @@ msgstr "Documente"
|
||||
|
||||
#: documents/models.py:36 documents/models.py:734
|
||||
msgid "owner"
|
||||
msgstr ""
|
||||
msgstr "proprietar"
|
||||
|
||||
#: documents/models.py:53
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
msgstr "Nimic"
|
||||
|
||||
#: documents/models.py:54
|
||||
msgid "Any word"
|
||||
@ -108,15 +108,15 @@ msgstr "tipuri de document"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "path"
|
||||
msgstr ""
|
||||
msgstr "cale"
|
||||
|
||||
#: documents/models.py:129 documents/models.py:156
|
||||
msgid "storage path"
|
||||
msgstr ""
|
||||
msgstr "cale de stocare"
|
||||
|
||||
#: documents/models.py:130
|
||||
msgid "storage paths"
|
||||
msgstr ""
|
||||
msgstr "căi de stocare"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "Unencrypted"
|
||||
@ -193,11 +193,11 @@ msgstr "Numele curent al arhivei stocate"
|
||||
|
||||
#: documents/models.py:250
|
||||
msgid "original filename"
|
||||
msgstr ""
|
||||
msgstr "numele original al fișierului"
|
||||
|
||||
#: documents/models.py:256
|
||||
msgid "The original name of the file when it was uploaded"
|
||||
msgstr ""
|
||||
msgstr "Numele original al fișierului când a fost încărcat"
|
||||
|
||||
#: documents/models.py:263
|
||||
msgid "archive serial number"
|
||||
@ -381,47 +381,47 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:447
|
||||
msgid "storage path is"
|
||||
msgstr ""
|
||||
msgstr "calea de stocare este"
|
||||
|
||||
#: documents/models.py:448
|
||||
msgid "has correspondent in"
|
||||
msgstr ""
|
||||
msgstr "are corespondent în"
|
||||
|
||||
#: documents/models.py:449
|
||||
msgid "does not have correspondent in"
|
||||
msgstr ""
|
||||
msgstr "nu are corespondent în"
|
||||
|
||||
#: documents/models.py:450
|
||||
msgid "has document type in"
|
||||
msgstr ""
|
||||
msgstr "are tip de document în"
|
||||
|
||||
#: documents/models.py:451
|
||||
msgid "does not have document type in"
|
||||
msgstr ""
|
||||
msgstr "nu are tip document în"
|
||||
|
||||
#: documents/models.py:452
|
||||
msgid "has storage path in"
|
||||
msgstr ""
|
||||
msgstr "are cale de stocare în"
|
||||
|
||||
#: documents/models.py:453
|
||||
msgid "does not have storage path in"
|
||||
msgstr ""
|
||||
msgstr "nu are cale de stocare în"
|
||||
|
||||
#: documents/models.py:454
|
||||
msgid "owner is"
|
||||
msgstr ""
|
||||
msgstr "proprietarul este"
|
||||
|
||||
#: documents/models.py:455
|
||||
msgid "has owner in"
|
||||
msgstr ""
|
||||
msgstr "are proprietar în"
|
||||
|
||||
#: documents/models.py:456
|
||||
msgid "does not have owner"
|
||||
msgstr ""
|
||||
msgstr "nu are proprietar"
|
||||
|
||||
#: documents/models.py:457
|
||||
msgid "does not have owner in"
|
||||
msgstr ""
|
||||
msgstr "nu are proprietar în"
|
||||
|
||||
#: documents/models.py:467
|
||||
msgid "rule type"
|
||||
@ -441,47 +441,47 @@ msgstr "reguli de filtrare"
|
||||
|
||||
#: documents/models.py:584
|
||||
msgid "Task ID"
|
||||
msgstr ""
|
||||
msgstr "ID Sarcină"
|
||||
|
||||
#: documents/models.py:585
|
||||
msgid "Celery ID for the Task that was run"
|
||||
msgstr ""
|
||||
msgstr "ID-ul sarcinii Celery care a fost rulată"
|
||||
|
||||
#: documents/models.py:590
|
||||
msgid "Acknowledged"
|
||||
msgstr ""
|
||||
msgstr "Confirmat"
|
||||
|
||||
#: documents/models.py:591
|
||||
msgid "If the task is acknowledged via the frontend or API"
|
||||
msgstr ""
|
||||
msgstr "Dacă sarcina este confirmată prin frontend sau API"
|
||||
|
||||
#: documents/models.py:597
|
||||
msgid "Task Filename"
|
||||
msgstr ""
|
||||
msgstr "Numele fișierului sarcină"
|
||||
|
||||
#: documents/models.py:598
|
||||
msgid "Name of the file which the Task was run for"
|
||||
msgstr ""
|
||||
msgstr "Numele fișierului pentru care sarcina a fost executată"
|
||||
|
||||
#: documents/models.py:604
|
||||
msgid "Task Name"
|
||||
msgstr ""
|
||||
msgstr "Nume sarcină"
|
||||
|
||||
#: documents/models.py:605
|
||||
msgid "Name of the Task which was run"
|
||||
msgstr ""
|
||||
msgstr "Numele sarcinii care a fost executată"
|
||||
|
||||
#: documents/models.py:612
|
||||
msgid "Task State"
|
||||
msgstr ""
|
||||
msgstr "Stare sarcină"
|
||||
|
||||
#: documents/models.py:613
|
||||
msgid "Current state of the task being run"
|
||||
msgstr ""
|
||||
msgstr "Stadiul actual al sarcinii în curs de desfășurare"
|
||||
|
||||
#: documents/models.py:618
|
||||
msgid "Created DateTime"
|
||||
msgstr ""
|
||||
msgstr "Data creării"
|
||||
|
||||
#: documents/models.py:619
|
||||
msgid "Datetime field when the task result was created in UTC"
|
||||
@ -489,7 +489,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:624
|
||||
msgid "Started DateTime"
|
||||
msgstr ""
|
||||
msgstr "Data începerii"
|
||||
|
||||
#: documents/models.py:625
|
||||
msgid "Datetime field when the task was started in UTC"
|
||||
@ -497,7 +497,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:630
|
||||
msgid "Completed DateTime"
|
||||
msgstr ""
|
||||
msgstr "Data finalizării"
|
||||
|
||||
#: documents/models.py:631
|
||||
msgid "Datetime field when the task was completed in UTC"
|
||||
@ -505,15 +505,15 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:636
|
||||
msgid "Result Data"
|
||||
msgstr ""
|
||||
msgstr "Datele rezultatului"
|
||||
|
||||
#: documents/models.py:638
|
||||
msgid "The data returned by the task"
|
||||
msgstr ""
|
||||
msgstr "Datele returnate de sarcină"
|
||||
|
||||
#: documents/models.py:650
|
||||
msgid "Note for the document"
|
||||
msgstr ""
|
||||
msgstr "Notă pentru document"
|
||||
|
||||
#: documents/models.py:674
|
||||
msgid "user"
|
||||
@ -521,23 +521,23 @@ msgstr "utilizator"
|
||||
|
||||
#: documents/models.py:679
|
||||
msgid "note"
|
||||
msgstr ""
|
||||
msgstr "notă"
|
||||
|
||||
#: documents/models.py:680
|
||||
msgid "notes"
|
||||
msgstr ""
|
||||
msgstr "note"
|
||||
|
||||
#: documents/models.py:688
|
||||
msgid "Archive"
|
||||
msgstr ""
|
||||
msgstr "Arhivă"
|
||||
|
||||
#: documents/models.py:689
|
||||
msgid "Original"
|
||||
msgstr ""
|
||||
msgstr "Original"
|
||||
|
||||
#: documents/models.py:700
|
||||
msgid "expiration"
|
||||
msgstr ""
|
||||
msgstr "expirare"
|
||||
|
||||
#: documents/models.py:707
|
||||
msgid "slug"
|
||||
@ -545,35 +545,35 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:739
|
||||
msgid "share link"
|
||||
msgstr ""
|
||||
msgstr "link de partajare"
|
||||
|
||||
#: documents/models.py:740
|
||||
msgid "share links"
|
||||
msgstr ""
|
||||
msgstr "link-uri de partajare"
|
||||
|
||||
#: documents/models.py:752
|
||||
msgid "String"
|
||||
msgstr ""
|
||||
msgstr "Şir de caractere"
|
||||
|
||||
#: documents/models.py:753
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
msgstr "Adresă URL"
|
||||
|
||||
#: documents/models.py:754
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
msgstr "Dată"
|
||||
|
||||
#: documents/models.py:755
|
||||
msgid "Boolean"
|
||||
msgstr ""
|
||||
msgstr "Boolean"
|
||||
|
||||
#: documents/models.py:756
|
||||
msgid "Integer"
|
||||
msgstr ""
|
||||
msgstr "Număr întreg"
|
||||
|
||||
#: documents/models.py:757
|
||||
msgid "Float"
|
||||
msgstr ""
|
||||
msgstr "Număr zecimal"
|
||||
|
||||
#: documents/models.py:758
|
||||
msgid "Monetary"
|
||||
@ -581,11 +581,11 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:759
|
||||
msgid "Document Link"
|
||||
msgstr ""
|
||||
msgstr "Link document"
|
||||
|
||||
#: documents/models.py:771
|
||||
msgid "data type"
|
||||
msgstr ""
|
||||
msgstr "tip date"
|
||||
|
||||
#: documents/models.py:779
|
||||
msgid "custom field"
|
||||
|
@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-12-05 08:26-0800\n"
|
||||
"PO-Revision-Date: 2023-12-05 16:27\n"
|
||||
"PO-Revision-Date: 2023-12-14 00:23\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Traditional\n"
|
||||
"Language: zh_TW\n"
|
||||
@ -47,7 +47,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:58
|
||||
msgid "Fuzzy word"
|
||||
msgstr ""
|
||||
msgstr "模糊詞"
|
||||
|
||||
#: documents/models.py:59
|
||||
msgid "Automatic"
|
||||
@ -68,15 +68,15 @@ msgstr "比對演算法"
|
||||
|
||||
#: documents/models.py:72
|
||||
msgid "is insensitive"
|
||||
msgstr ""
|
||||
msgstr "不區分大小寫"
|
||||
|
||||
#: documents/models.py:95 documents/models.py:147
|
||||
msgid "correspondent"
|
||||
msgstr ""
|
||||
msgstr "聯繫者"
|
||||
|
||||
#: documents/models.py:96
|
||||
msgid "correspondents"
|
||||
msgstr ""
|
||||
msgstr "聯繫者"
|
||||
|
||||
#: documents/models.py:100
|
||||
msgid "color"
|
||||
@ -84,47 +84,47 @@ msgstr "顏色"
|
||||
|
||||
#: documents/models.py:103
|
||||
msgid "is inbox tag"
|
||||
msgstr ""
|
||||
msgstr "收件匣標籤"
|
||||
|
||||
#: documents/models.py:106
|
||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||
msgstr ""
|
||||
msgstr "標記此標籤為收件匣標籤:所有新處理的文件將會以此收件匣標籤作標記。"
|
||||
|
||||
#: documents/models.py:112
|
||||
msgid "tag"
|
||||
msgstr ""
|
||||
msgstr "標籤"
|
||||
|
||||
#: documents/models.py:113 documents/models.py:185
|
||||
msgid "tags"
|
||||
msgstr ""
|
||||
msgstr "標籤"
|
||||
|
||||
#: documents/models.py:118 documents/models.py:167
|
||||
msgid "document type"
|
||||
msgstr ""
|
||||
msgstr "文件類型"
|
||||
|
||||
#: documents/models.py:119
|
||||
msgid "document types"
|
||||
msgstr ""
|
||||
msgstr "文件類型"
|
||||
|
||||
#: documents/models.py:124
|
||||
msgid "path"
|
||||
msgstr ""
|
||||
msgstr "位址"
|
||||
|
||||
#: documents/models.py:129 documents/models.py:156
|
||||
msgid "storage path"
|
||||
msgstr ""
|
||||
msgstr "儲存位址"
|
||||
|
||||
#: documents/models.py:130
|
||||
msgid "storage paths"
|
||||
msgstr ""
|
||||
msgstr "儲存位址"
|
||||
|
||||
#: documents/models.py:137
|
||||
msgid "Unencrypted"
|
||||
msgstr ""
|
||||
msgstr "未加密"
|
||||
|
||||
#: documents/models.py:138
|
||||
msgid "Encrypted with GNU Privacy Guard"
|
||||
msgstr ""
|
||||
msgstr "已使用 GNU Privacy Guard 進行加密"
|
||||
|
||||
#: documents/models.py:159
|
||||
msgid "title"
|
||||
@ -189,27 +189,27 @@ msgstr "存檔檔案名稱"
|
||||
|
||||
#: documents/models.py:246
|
||||
msgid "Current archive filename in storage"
|
||||
msgstr ""
|
||||
msgstr "現時儲存空間封存的檔案名稱"
|
||||
|
||||
#: documents/models.py:250
|
||||
msgid "original filename"
|
||||
msgstr ""
|
||||
msgstr "原先檔案名稱"
|
||||
|
||||
#: documents/models.py:256
|
||||
msgid "The original name of the file when it was uploaded"
|
||||
msgstr ""
|
||||
msgstr "檔案上傳時的檔案名稱"
|
||||
|
||||
#: documents/models.py:263
|
||||
msgid "archive serial number"
|
||||
msgstr ""
|
||||
msgstr "封存編號"
|
||||
|
||||
#: documents/models.py:273
|
||||
msgid "The position of this document in your physical document archive."
|
||||
msgstr ""
|
||||
msgstr "此檔案在你實體儲存空間的位置。"
|
||||
|
||||
#: documents/models.py:279 documents/models.py:665 documents/models.py:719
|
||||
msgid "document"
|
||||
msgstr ""
|
||||
msgstr "文件"
|
||||
|
||||
#: documents/models.py:280
|
||||
msgid "documents"
|
||||
@ -217,47 +217,47 @@ msgstr "文件"
|
||||
|
||||
#: documents/models.py:368
|
||||
msgid "debug"
|
||||
msgstr ""
|
||||
msgstr "偵錯"
|
||||
|
||||
#: documents/models.py:369
|
||||
msgid "information"
|
||||
msgstr ""
|
||||
msgstr "資訊"
|
||||
|
||||
#: documents/models.py:370
|
||||
msgid "warning"
|
||||
msgstr ""
|
||||
msgstr "警告"
|
||||
|
||||
#: documents/models.py:371 paperless_mail/models.py:305
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
msgstr "錯誤"
|
||||
|
||||
#: documents/models.py:372
|
||||
msgid "critical"
|
||||
msgstr ""
|
||||
msgstr "嚴重"
|
||||
|
||||
#: documents/models.py:375
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
msgstr "群組"
|
||||
|
||||
#: documents/models.py:377
|
||||
msgid "message"
|
||||
msgstr ""
|
||||
msgstr "訊息"
|
||||
|
||||
#: documents/models.py:380
|
||||
msgid "level"
|
||||
msgstr ""
|
||||
msgstr "程度"
|
||||
|
||||
#: documents/models.py:389
|
||||
msgid "log"
|
||||
msgstr ""
|
||||
msgstr "記錄"
|
||||
|
||||
#: documents/models.py:390
|
||||
msgid "logs"
|
||||
msgstr ""
|
||||
msgstr "記錄"
|
||||
|
||||
#: documents/models.py:399 documents/models.py:464
|
||||
msgid "saved view"
|
||||
msgstr ""
|
||||
msgstr "已儲存的檢視表"
|
||||
|
||||
#: documents/models.py:400
|
||||
msgid "saved views"
|
||||
@ -265,207 +265,207 @@ msgstr "保存視圖"
|
||||
|
||||
#: documents/models.py:405
|
||||
msgid "show on dashboard"
|
||||
msgstr ""
|
||||
msgstr "顯示在概覽"
|
||||
|
||||
#: documents/models.py:408
|
||||
msgid "show in sidebar"
|
||||
msgstr ""
|
||||
msgstr "顯示在側邊欄"
|
||||
|
||||
#: documents/models.py:412
|
||||
msgid "sort field"
|
||||
msgstr ""
|
||||
msgstr "排序欄位"
|
||||
|
||||
#: documents/models.py:417
|
||||
msgid "sort reverse"
|
||||
msgstr ""
|
||||
msgstr "倒轉排序"
|
||||
|
||||
#: documents/models.py:422
|
||||
msgid "title contains"
|
||||
msgstr ""
|
||||
msgstr "標題包含"
|
||||
|
||||
#: documents/models.py:423
|
||||
msgid "content contains"
|
||||
msgstr ""
|
||||
msgstr "內容包含"
|
||||
|
||||
#: documents/models.py:424
|
||||
msgid "ASN is"
|
||||
msgstr ""
|
||||
msgstr "ASN 為"
|
||||
|
||||
#: documents/models.py:425
|
||||
msgid "correspondent is"
|
||||
msgstr ""
|
||||
msgstr "聯繫者為"
|
||||
|
||||
#: documents/models.py:426
|
||||
msgid "document type is"
|
||||
msgstr ""
|
||||
msgstr "文件類型為"
|
||||
|
||||
#: documents/models.py:427
|
||||
msgid "is in inbox"
|
||||
msgstr ""
|
||||
msgstr "在收件匣內"
|
||||
|
||||
#: documents/models.py:428
|
||||
msgid "has tag"
|
||||
msgstr ""
|
||||
msgstr "包含標籤"
|
||||
|
||||
#: documents/models.py:429
|
||||
msgid "has any tag"
|
||||
msgstr ""
|
||||
msgstr "包含任何標籤"
|
||||
|
||||
#: documents/models.py:430
|
||||
msgid "created before"
|
||||
msgstr ""
|
||||
msgstr "建立時間之前"
|
||||
|
||||
#: documents/models.py:431
|
||||
msgid "created after"
|
||||
msgstr ""
|
||||
msgstr "建立時間之後"
|
||||
|
||||
#: documents/models.py:432
|
||||
msgid "created year is"
|
||||
msgstr ""
|
||||
msgstr "建立年份為"
|
||||
|
||||
#: documents/models.py:433
|
||||
msgid "created month is"
|
||||
msgstr ""
|
||||
msgstr "建立月份為"
|
||||
|
||||
#: documents/models.py:434
|
||||
msgid "created day is"
|
||||
msgstr ""
|
||||
msgstr "建立日期為"
|
||||
|
||||
#: documents/models.py:435
|
||||
msgid "added before"
|
||||
msgstr ""
|
||||
msgstr "加入時間之前"
|
||||
|
||||
#: documents/models.py:436
|
||||
msgid "added after"
|
||||
msgstr ""
|
||||
msgstr "加入時間之後"
|
||||
|
||||
#: documents/models.py:437
|
||||
msgid "modified before"
|
||||
msgstr ""
|
||||
msgstr "修改之前"
|
||||
|
||||
#: documents/models.py:438
|
||||
msgid "modified after"
|
||||
msgstr ""
|
||||
msgstr "修改之後"
|
||||
|
||||
#: documents/models.py:439
|
||||
msgid "does not have tag"
|
||||
msgstr ""
|
||||
msgstr "沒有包含標籤"
|
||||
|
||||
#: documents/models.py:440
|
||||
msgid "does not have ASN"
|
||||
msgstr ""
|
||||
msgstr "沒有包含 ASN"
|
||||
|
||||
#: documents/models.py:441
|
||||
msgid "title or content contains"
|
||||
msgstr ""
|
||||
msgstr "標題或內容包含"
|
||||
|
||||
#: documents/models.py:442
|
||||
msgid "fulltext query"
|
||||
msgstr ""
|
||||
msgstr "全文搜索"
|
||||
|
||||
#: documents/models.py:443
|
||||
msgid "more like this"
|
||||
msgstr ""
|
||||
msgstr "其他類似內容"
|
||||
|
||||
#: documents/models.py:444
|
||||
msgid "has tags in"
|
||||
msgstr ""
|
||||
msgstr "含有這個標籤"
|
||||
|
||||
#: documents/models.py:445
|
||||
msgid "ASN greater than"
|
||||
msgstr ""
|
||||
msgstr "ASN 大於"
|
||||
|
||||
#: documents/models.py:446
|
||||
msgid "ASN less than"
|
||||
msgstr ""
|
||||
msgstr "ASN 小於"
|
||||
|
||||
#: documents/models.py:447
|
||||
msgid "storage path is"
|
||||
msgstr ""
|
||||
msgstr "儲存位址為"
|
||||
|
||||
#: documents/models.py:448
|
||||
msgid "has correspondent in"
|
||||
msgstr ""
|
||||
msgstr "包含聯繫者"
|
||||
|
||||
#: documents/models.py:449
|
||||
msgid "does not have correspondent in"
|
||||
msgstr ""
|
||||
msgstr "沒有包含聯繫者"
|
||||
|
||||
#: documents/models.py:450
|
||||
msgid "has document type in"
|
||||
msgstr ""
|
||||
msgstr "文件類型包含"
|
||||
|
||||
#: documents/models.py:451
|
||||
msgid "does not have document type in"
|
||||
msgstr ""
|
||||
msgstr "沒有包含的文件類型"
|
||||
|
||||
#: documents/models.py:452
|
||||
msgid "has storage path in"
|
||||
msgstr ""
|
||||
msgstr "儲存位址包含"
|
||||
|
||||
#: documents/models.py:453
|
||||
msgid "does not have storage path in"
|
||||
msgstr ""
|
||||
msgstr "沒有包含的儲存位址"
|
||||
|
||||
#: documents/models.py:454
|
||||
msgid "owner is"
|
||||
msgstr ""
|
||||
msgstr "擁有者為"
|
||||
|
||||
#: documents/models.py:455
|
||||
msgid "has owner in"
|
||||
msgstr ""
|
||||
msgstr "擁有者包含"
|
||||
|
||||
#: documents/models.py:456
|
||||
msgid "does not have owner"
|
||||
msgstr ""
|
||||
msgstr "沒有包含的擁有者"
|
||||
|
||||
#: documents/models.py:457
|
||||
msgid "does not have owner in"
|
||||
msgstr ""
|
||||
msgstr "沒有包含的擁有者"
|
||||
|
||||
#: documents/models.py:467
|
||||
msgid "rule type"
|
||||
msgstr ""
|
||||
msgstr "規則類型"
|
||||
|
||||
#: documents/models.py:469
|
||||
msgid "value"
|
||||
msgstr ""
|
||||
msgstr "數值"
|
||||
|
||||
#: documents/models.py:472
|
||||
msgid "filter rule"
|
||||
msgstr ""
|
||||
msgstr "過濾規則"
|
||||
|
||||
#: documents/models.py:473
|
||||
msgid "filter rules"
|
||||
msgstr ""
|
||||
msgstr "過濾規則"
|
||||
|
||||
#: documents/models.py:584
|
||||
msgid "Task ID"
|
||||
msgstr ""
|
||||
msgstr "任務 ID"
|
||||
|
||||
#: documents/models.py:585
|
||||
msgid "Celery ID for the Task that was run"
|
||||
msgstr ""
|
||||
msgstr "已執行任務的 Celery ID"
|
||||
|
||||
#: documents/models.py:590
|
||||
msgid "Acknowledged"
|
||||
msgstr ""
|
||||
msgstr "已確認"
|
||||
|
||||
#: documents/models.py:591
|
||||
msgid "If the task is acknowledged via the frontend or API"
|
||||
msgstr ""
|
||||
msgstr "如果任務已由前端 / API 確認"
|
||||
|
||||
#: documents/models.py:597
|
||||
msgid "Task Filename"
|
||||
msgstr ""
|
||||
msgstr "任務檔案名稱"
|
||||
|
||||
#: documents/models.py:598
|
||||
msgid "Name of the file which the Task was run for"
|
||||
msgstr ""
|
||||
msgstr "執行任務的目標檔案名稱"
|
||||
|
||||
#: documents/models.py:604
|
||||
msgid "Task Name"
|
||||
msgstr ""
|
||||
msgstr "任務名稱"
|
||||
|
||||
#: documents/models.py:605
|
||||
msgid "Name of the Task which was run"
|
||||
@ -473,7 +473,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:612
|
||||
msgid "Task State"
|
||||
msgstr ""
|
||||
msgstr "任務狀態"
|
||||
|
||||
#: documents/models.py:613
|
||||
msgid "Current state of the task being run"
|
||||
@ -657,7 +657,7 @@ msgstr ""
|
||||
|
||||
#: documents/models.py:967 paperless_mail/models.py:238
|
||||
msgid "assign this correspondent"
|
||||
msgstr ""
|
||||
msgstr "指派這個聯繫者"
|
||||
|
||||
#: documents/models.py:975
|
||||
msgid "assign this storage path"
|
||||
@ -1128,7 +1128,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:88
|
||||
msgid "Do not assign a correspondent"
|
||||
msgstr ""
|
||||
msgstr "不要指派聯繫者"
|
||||
|
||||
#: paperless_mail/models.py:89
|
||||
msgid "Use mail address"
|
||||
@ -1140,7 +1140,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:91
|
||||
msgid "Use correspondent selected below"
|
||||
msgstr ""
|
||||
msgstr "使用以下已選擇的聯繫者"
|
||||
|
||||
#: paperless_mail/models.py:101
|
||||
msgid "account"
|
||||
@ -1220,7 +1220,7 @@ msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:228
|
||||
msgid "assign correspondent from"
|
||||
msgstr ""
|
||||
msgstr "指派聯繫者從"
|
||||
|
||||
#: paperless_mail/models.py:242
|
||||
msgid "Assign the rule owner to documents"
|
||||
|
@ -254,7 +254,7 @@ class RasterisedDocumentParser(DocumentParser):
|
||||
f"Image DPI of {ocrmypdf_args['image_dpi']} is low, OCR may fail",
|
||||
)
|
||||
|
||||
if settings.OCR_USER_ARGS and not safe_fallback:
|
||||
if settings.OCR_USER_ARGS:
|
||||
try:
|
||||
user_args = json.loads(settings.OCR_USER_ARGS)
|
||||
ocrmypdf_args = {**ocrmypdf_args, **user_args}
|
||||
|
Loading…
x
Reference in New Issue
Block a user