mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-19 10:19:27 -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
|
Paperless has been tested to work with the OCR options provided
|
||||||
above. There are many options that are incompatible with each other,
|
above. There are many options that are incompatible with each other,
|
||||||
so specifying invalid options may prevent paperless from consuming
|
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
|
Specify arguments as a JSON dictionary. Keep note of lower case
|
||||||
booleans and double quoted parameter names and strings. Examples:
|
booleans and double quoted parameter names and strings. Examples:
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
<h5 class="border-bottom pb-2" i18n>Filters</h5>
|
||||||
<p class="small" i18n>Process documents that match <em>all</em> filters specified below.</p>
|
<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 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-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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div [class.col-md-9]="horizontal">
|
<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"
|
<ng-select name="inputId" [(ngModel)]="value"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[style.color]="textColor"
|
[style.color]="textColor"
|
||||||
@ -42,6 +42,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{{error}}
|
||||||
|
</div>
|
||||||
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
|
||||||
<small *ngIf="getSuggestions().length > 0">
|
<small *ngIf="getSuggestions().length > 0">
|
||||||
<span i18n>Suggestions:</span>
|
<span i18n>Suggestions:</span>
|
||||||
|
@ -17,3 +17,12 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
opacity: .75;
|
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/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '3',
|
apiVersion: '3',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '2.1.2',
|
version: '2.1.2-dev',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<target state="final">Fermer</target>
|
<target state="final">Fermer</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.HH" datatype="html">
|
<trans-unit id="ngb.timepicker.HH" datatype="html" approved="yes">
|
||||||
<source>HH</source>
|
<source>HH</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
||||||
<context context-type="linenumber">13</context>
|
<context context-type="linenumber">13</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">HH</target>
|
<target state="final">HH</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.toast.close-aria" datatype="html" approved="yes">
|
<trans-unit id="ngb.toast.close-aria" datatype="html" approved="yes">
|
||||||
<source>Close</source>
|
<source>Close</source>
|
||||||
@ -100,13 +100,13 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<target state="final">Précédent</target>
|
<target state="final">Précédent</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.timepicker.MM" datatype="html">
|
<trans-unit id="ngb.timepicker.MM" datatype="html" approved="yes">
|
||||||
<source>MM</source>
|
<source>MM</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
<context context-type="sourcefile">node_modules/src/ngb-config.ts</context>
|
||||||
<context context-type="linenumber">13</context>
|
<context context-type="linenumber">13</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">MM</target>
|
<target state="final">MM</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.pagination.next" datatype="html" approved="yes">
|
<trans-unit id="ngb.pagination.next" datatype="html" approved="yes">
|
||||||
<source>»</source>
|
<source>»</source>
|
||||||
|
@ -288,7 +288,7 @@
|
|||||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||||
<context context-type="linenumber">90</context>
|
<context context-type="linenumber">90</context>
|
||||||
</context-group>
|
</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>
|
||||||
<trans-unit id="1931214133925051574" datatype="html">
|
<trans-unit id="1931214133925051574" datatype="html">
|
||||||
<source>Open document</source>
|
<source>Open document</source>
|
||||||
@ -316,7 +316,7 @@
|
|||||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||||
<context context-type="linenumber">120</context>
|
<context context-type="linenumber">120</context>
|
||||||
</context-group>
|
</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>
|
||||||
<trans-unit id="2501522447884928778" datatype="html">
|
<trans-unit id="2501522447884928778" datatype="html">
|
||||||
<source>Prev</source>
|
<source>Prev</source>
|
||||||
@ -476,7 +476,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">15</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Auto refresh</target>
|
<target state="translated">Automatsko osvježavanje</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3894950702316166331" datatype="html">
|
<trans-unit id="3894950702316166331" datatype="html">
|
||||||
<source>Loading...</source>
|
<source>Loading...</source>
|
||||||
@ -596,7 +596,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">15</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">General</target>
|
<target state="translated">Općenito</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8671234314555525900" datatype="html">
|
<trans-unit id="8671234314555525900" datatype="html">
|
||||||
<source>Appearance</source>
|
<source>Appearance</source>
|
||||||
@ -916,7 +916,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">178,180</context>
|
<context context-type="linenumber">178,180</context>
|
||||||
</context-group>
|
</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>
|
||||||
<trans-unit id="4292903881380648974" datatype="html">
|
<trans-unit id="4292903881380648974" datatype="html">
|
||||||
<source>Default Owner</source>
|
<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="sourcefile">src/app/components/admin/users-groups/users-groups.component.ts</context>
|
||||||
<context context-type="linenumber">124</context>
|
<context context-type="linenumber">124</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Deleted user</target>
|
<target state="translated">Izbrisani korisnik</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1942566571910298572" datatype="html">
|
<trans-unit id="1942566571910298572" datatype="html">
|
||||||
<source>Error deleting user.</source>
|
<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="sourcefile">src/app/components/app-frame/app-frame.component.ts</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">276</context>
|
||||||
</context-group>
|
</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>
|
||||||
<trans-unit id="8700121026680200191" datatype="html">
|
<trans-unit id="8700121026680200191" datatype="html">
|
||||||
<source>Clear</source>
|
<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="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 context-type="linenumber">9</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Data type</target>
|
<target state="translated">Tip podataka</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5933665691581884232" datatype="html">
|
<trans-unit id="5933665691581884232" datatype="html">
|
||||||
<source>Data type cannot be changed after a field is created</source>
|
<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="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 context-type="linenumber">10</context>
|
||||||
</context-group>
|
</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>
|
||||||
<trans-unit id="528950215505228201" datatype="html">
|
<trans-unit id="528950215505228201" datatype="html">
|
||||||
<source>Create new custom field</source>
|
<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="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 context-type="linenumber">19</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="needs-translation">Character Set</target>
|
<target state="translated">Skup znakova</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6563391987554512024" datatype="html">
|
<trans-unit id="6563391987554512024" datatype="html">
|
||||||
<source>Test</source>
|
<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 PIL import Image
|
||||||
|
|
||||||
from documents.converters import convert_from_tiff_to_pdf
|
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.data_models import DocumentSource
|
||||||
from documents.utils import copy_basic_file_stats
|
from documents.utils import copy_basic_file_stats
|
||||||
from documents.utils import copy_file_with_basic_stats
|
from documents.utils import copy_file_with_basic_stats
|
||||||
@ -53,6 +55,7 @@ class BarcodeReader:
|
|||||||
self.mime: Final[str] = mime_type
|
self.mime: Final[str] = mime_type
|
||||||
self.pdf_file: Path = self.file
|
self.pdf_file: Path = self.file
|
||||||
self.barcodes: list[Barcode] = []
|
self.barcodes: list[Barcode] = []
|
||||||
|
self._tiff_conversion_done = False
|
||||||
self.temp_dir: Optional[tempfile.TemporaryDirectory] = None
|
self.temp_dir: Optional[tempfile.TemporaryDirectory] = None
|
||||||
|
|
||||||
if settings.CONSUMER_BARCODE_TIFF_SUPPORT:
|
if settings.CONSUMER_BARCODE_TIFF_SUPPORT:
|
||||||
@ -150,12 +153,14 @@ class BarcodeReader:
|
|||||||
|
|
||||||
def convert_from_tiff_to_pdf(self):
|
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
|
# 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
|
return
|
||||||
|
|
||||||
|
self._tiff_conversion_done = True
|
||||||
self.pdf_file = convert_from_tiff_to_pdf(self.file, Path(self.temp_dir.name))
|
self.pdf_file = convert_from_tiff_to_pdf(self.file, Path(self.temp_dir.name))
|
||||||
|
|
||||||
def detect(self) -> None:
|
def detect(self) -> None:
|
||||||
@ -167,6 +172,9 @@ class BarcodeReader:
|
|||||||
if self.barcodes:
|
if self.barcodes:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# No op if not a TIFF
|
||||||
|
self.convert_from_tiff_to_pdf()
|
||||||
|
|
||||||
# Choose the library for reading
|
# Choose the library for reading
|
||||||
if settings.CONSUMER_BARCODE_SCANNER == "PYZBAR":
|
if settings.CONSUMER_BARCODE_SCANNER == "PYZBAR":
|
||||||
reader = self.read_barcodes_pyzbar
|
reader = self.read_barcodes_pyzbar
|
||||||
@ -240,7 +248,7 @@ class BarcodeReader:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
document_paths = []
|
document_paths = []
|
||||||
fname = self.file.with_suffix("").name
|
fname = self.file.stem
|
||||||
with Pdf.open(self.pdf_file) as input_pdf:
|
with Pdf.open(self.pdf_file) as input_pdf:
|
||||||
# Start with an empty document
|
# Start with an empty document
|
||||||
current_document: list[Page] = []
|
current_document: list[Page] = []
|
||||||
@ -290,7 +298,7 @@ class BarcodeReader:
|
|||||||
def separate(
|
def separate(
|
||||||
self,
|
self,
|
||||||
source: DocumentSource,
|
source: DocumentSource,
|
||||||
override_name: Optional[str] = None,
|
overrides: DocumentMetadataOverrides,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Separates the document, based on barcodes and configuration, creating new
|
Separates the document, based on barcodes and configuration, creating new
|
||||||
@ -316,27 +324,23 @@ class BarcodeReader:
|
|||||||
logger.warning("No pages to split on!")
|
logger.warning("No pages to split on!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create the split documents
|
tmp_dir = Path(tempfile.mkdtemp(prefix="paperless-barcode-split-")).resolve()
|
||||||
doc_paths = self.separate_pages(separator_pages)
|
|
||||||
|
|
||||||
# Save the new documents to correct folder
|
from documents import tasks
|
||||||
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
|
|
||||||
|
|
||||||
for idx, document_path in enumerate(doc_paths):
|
# Create the split document tasks
|
||||||
if override_name is not None:
|
for new_document in self.separate_pages(separator_pages):
|
||||||
newname = f"{idx}_{override_name}"
|
copy_file_with_basic_stats(new_document, tmp_dir / new_document.name)
|
||||||
dest = save_to_dir / newname
|
|
||||||
else:
|
tasks.consume_file.delay(
|
||||||
dest = save_to_dir
|
ConsumableDocument(
|
||||||
logger.info(f"Saving {document_path} to {dest}")
|
# Same source, for templates
|
||||||
copy_file_with_basic_stats(document_path, dest)
|
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
|
return True
|
||||||
|
@ -3,6 +3,7 @@ import math
|
|||||||
import os
|
import os
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from dateutil.parser import isoparse
|
from dateutil.parser import isoparse
|
||||||
@ -371,7 +372,7 @@ class LocalDateParser(English):
|
|||||||
if isinstance(d, timespan):
|
if isinstance(d, timespan):
|
||||||
d.start = self.reverse_timezone_offset(d.start)
|
d.start = self.reverse_timezone_offset(d.start)
|
||||||
d.end = self.reverse_timezone_offset(d.end)
|
d.end = self.reverse_timezone_offset(d.end)
|
||||||
else:
|
elif isinstance(d, datetime):
|
||||||
d = self.reverse_timezone_offset(d)
|
d = self.reverse_timezone_offset(d)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@ -238,18 +238,6 @@ class Command(BaseCommand):
|
|||||||
serializers.serialize("json", StoragePath.objects.all()),
|
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(
|
manifest += json.loads(
|
||||||
serializers.serialize("json", MailAccount.objects.all()),
|
serializers.serialize("json", MailAccount.objects.all()),
|
||||||
)
|
)
|
||||||
@ -303,10 +291,24 @@ class Command(BaseCommand):
|
|||||||
serializers.serialize("json", CustomField.objects.all()),
|
serializers.serialize("json", CustomField.objects.all()),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.split_manifest:
|
# These are treated specially and included in the per-document manifest
|
||||||
manifest += json.loads(
|
# 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()),
|
serializers.serialize("json", CustomFieldInstance.objects.all()),
|
||||||
)
|
)
|
||||||
|
if not self.split_manifest:
|
||||||
|
manifest += document_manifest
|
||||||
|
manifest += notes
|
||||||
|
manifest += custom_field_instances
|
||||||
|
|
||||||
# 3. Export files from each document
|
# 3. Export files from each document
|
||||||
for index, document_dict in tqdm.tqdm(
|
for index, document_dict in tqdm.tqdm(
|
||||||
@ -412,6 +414,12 @@ class Command(BaseCommand):
|
|||||||
notes,
|
notes,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
content += list(
|
||||||
|
filter(
|
||||||
|
lambda d: d["fields"]["document"] == document_dict["pk"],
|
||||||
|
custom_field_instances,
|
||||||
|
),
|
||||||
|
)
|
||||||
manifest_name.write_text(
|
manifest_name.write_text(
|
||||||
json.dumps(content, indent=2, ensure_ascii=False),
|
json.dumps(content, indent=2, ensure_ascii=False),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
|
@ -140,7 +140,7 @@ def consume_file(
|
|||||||
with BarcodeReader(input_doc.original_file, input_doc.mime_type) as reader:
|
with BarcodeReader(input_doc.original_file, input_doc.mime_type) as reader:
|
||||||
if settings.CONSUMER_ENABLE_BARCODES and reader.separate(
|
if settings.CONSUMER_ENABLE_BARCODES and reader.separate(
|
||||||
input_doc.source,
|
input_doc.source,
|
||||||
overrides.filename,
|
overrides,
|
||||||
):
|
):
|
||||||
# notify the sender, otherwise the progress bar
|
# notify the sender, otherwise the progress bar
|
||||||
# in the UI stays stuck
|
# in the UI stays stuck
|
||||||
|
@ -455,6 +455,31 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
|||||||
# Assert subset in results
|
# Assert subset in results
|
||||||
self.assertDictEqual(result, {**result, **subset})
|
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")
|
@mock.patch("documents.index.autocomplete")
|
||||||
def test_search_autocomplete_limits(self, m):
|
def test_search_autocomplete_limits(self, m):
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -11,10 +10,13 @@ from documents import tasks
|
|||||||
from documents.barcodes import BarcodeReader
|
from documents.barcodes import BarcodeReader
|
||||||
from documents.consumer import ConsumerError
|
from documents.consumer import ConsumerError
|
||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
|
from documents.data_models import DocumentMetadataOverrides
|
||||||
from documents.data_models import DocumentSource
|
from documents.data_models import DocumentSource
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.tests.utils import DirectoriesMixin
|
from documents.tests.utils import DirectoriesMixin
|
||||||
|
from documents.tests.utils import DocumentConsumeDelayMixin
|
||||||
from documents.tests.utils import FileSystemAssertsMixin
|
from documents.tests.utils import FileSystemAssertsMixin
|
||||||
|
from documents.tests.utils import SampleDirMixin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zxingcpp # noqa: F401
|
import zxingcpp # noqa: F401
|
||||||
@ -25,11 +27,7 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||||
class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, SampleDirMixin, TestCase):
|
||||||
SAMPLE_DIR = Path(__file__).parent / "samples"
|
|
||||||
|
|
||||||
BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes"
|
|
||||||
|
|
||||||
def test_scan_file_for_separating_barcodes(self):
|
def test_scan_file_for_separating_barcodes(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@ -48,6 +46,46 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
self.assertEqual(reader.pdf_file, test_file)
|
self.assertEqual(reader.pdf_file, test_file)
|
||||||
self.assertDictEqual(separator_page_numbers, {0: False})
|
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):
|
def test_scan_file_for_separating_barcodes_none_present(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@ -285,6 +323,28 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
self.assertGreater(len(reader.barcodes), 0)
|
self.assertGreater(len(reader.barcodes), 0)
|
||||||
self.assertDictEqual(separator_page_numbers, {1: False})
|
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):
|
def test_separate_pages(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@ -332,8 +392,12 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
|
|
||||||
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
with self.assertLogs("paperless.barcodes", level="WARNING") as cm:
|
||||||
with BarcodeReader(test_file, "application/pdf") as reader:
|
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||||
success = reader.separate(DocumentSource.ApiUpload)
|
self.assertFalse(
|
||||||
self.assertFalse(success)
|
reader.separate(
|
||||||
|
DocumentSource.ApiUpload,
|
||||||
|
DocumentMetadataOverrides(),
|
||||||
|
),
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cm.output,
|
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(
|
@override_settings(
|
||||||
CONSUMER_ENABLE_BARCODES=True,
|
CONSUMER_ENABLE_BARCODES=True,
|
||||||
CONSUMER_BARCODE_TIFF_SUPPORT=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_document_type_id"])
|
||||||
self.assertIsNone(kwargs["override_tag_ids"])
|
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(
|
@override_settings(
|
||||||
CONSUMER_ENABLE_BARCODES=True,
|
CONSUMER_ENABLE_BARCODES=True,
|
||||||
CONSUMER_ENABLE_ASN_BARCODE=True,
|
CONSUMER_ENABLE_ASN_BARCODE=True,
|
||||||
@ -722,11 +523,64 @@ class TestBarcode(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
self.assertEqual(len(document_list), 5)
|
self.assertEqual(len(document_list), 5)
|
||||||
|
|
||||||
|
|
||||||
class TestAsnBarcode(DirectoriesMixin, TestCase):
|
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||||
SAMPLE_DIR = Path(__file__).parent / "samples"
|
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-")
|
@override_settings(CONSUMER_ASN_BARCODE_PREFIX="CUSTOM-PREFIX-")
|
||||||
def test_scan_file_for_asn_custom_prefix(self):
|
def test_scan_file_for_asn_custom_prefix(self):
|
||||||
"""
|
"""
|
||||||
|
@ -646,10 +646,13 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
|
|
||||||
with paperless_environment():
|
with paperless_environment():
|
||||||
self.assertEqual(Document.objects.count(), 4)
|
self.assertEqual(Document.objects.count(), 4)
|
||||||
|
self.assertEqual(CustomFieldInstance.objects.count(), 1)
|
||||||
Document.objects.all().delete()
|
Document.objects.all().delete()
|
||||||
|
CustomFieldInstance.objects.all().delete()
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
call_command("document_importer", "--no-progress-bar", self.target)
|
call_command("document_importer", "--no-progress-bar", self.target)
|
||||||
self.assertEqual(Document.objects.count(), 4)
|
self.assertEqual(Document.objects.count(), 4)
|
||||||
|
self.assertEqual(CustomFieldInstance.objects.count(), 1)
|
||||||
|
|
||||||
def test_folder_prefix(self):
|
def test_folder_prefix(self):
|
||||||
"""
|
"""
|
||||||
|
@ -235,8 +235,10 @@ class DocumentConsumeDelayMixin:
|
|||||||
"""
|
"""
|
||||||
Iterates over all calls to the async task and returns the arguments
|
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
|
input_doc, overrides = args
|
||||||
|
|
||||||
yield (input_doc, overrides)
|
yield (input_doc, overrides)
|
||||||
@ -244,7 +246,7 @@ class DocumentConsumeDelayMixin:
|
|||||||
def get_specific_consume_delay_call_args(
|
def get_specific_consume_delay_call_args(
|
||||||
self,
|
self,
|
||||||
index: int,
|
index: int,
|
||||||
) -> Iterator[tuple[ConsumableDocument, DocumentMetadataOverrides]]:
|
) -> tuple[ConsumableDocument, DocumentMetadataOverrides]:
|
||||||
"""
|
"""
|
||||||
Returns the arguments of a specific call to the async task
|
Returns the arguments of a specific call to the async task
|
||||||
"""
|
"""
|
||||||
@ -299,3 +301,9 @@ class TestMigrations(TransactionTestCase):
|
|||||||
|
|
||||||
def setUpBeforeMigration(self, apps):
|
def setUpBeforeMigration(self, apps):
|
||||||
pass
|
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):
|
class CorrespondentViewSet(ModelViewSet, PassUserMixin):
|
||||||
model = Correspondent
|
model = Correspondent
|
||||||
|
|
||||||
queryset = Correspondent.objects.annotate(
|
queryset = (
|
||||||
|
Correspondent.objects.annotate(
|
||||||
document_count=Count("documents"),
|
document_count=Count("documents"),
|
||||||
last_correspondence=Max("documents__created"),
|
last_correspondence=Max("documents__created"),
|
||||||
).order_by(Lower("name"))
|
)
|
||||||
|
.select_related("owner")
|
||||||
|
.order_by(Lower("name"))
|
||||||
|
)
|
||||||
|
|
||||||
serializer_class = CorrespondentSerializer
|
serializer_class = CorrespondentSerializer
|
||||||
pagination_class = StandardPagination
|
pagination_class = StandardPagination
|
||||||
@ -208,9 +212,13 @@ class CorrespondentViewSet(ModelViewSet, PassUserMixin):
|
|||||||
class TagViewSet(ModelViewSet, PassUserMixin):
|
class TagViewSet(ModelViewSet, PassUserMixin):
|
||||||
model = Tag
|
model = Tag
|
||||||
|
|
||||||
queryset = Tag.objects.annotate(document_count=Count("documents")).order_by(
|
queryset = (
|
||||||
|
Tag.objects.annotate(document_count=Count("documents"))
|
||||||
|
.select_related("owner")
|
||||||
|
.order_by(
|
||||||
Lower("name"),
|
Lower("name"),
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_serializer_class(self, *args, **kwargs):
|
def get_serializer_class(self, *args, **kwargs):
|
||||||
if int(self.request.version) == 1:
|
if int(self.request.version) == 1:
|
||||||
@ -232,9 +240,13 @@ class TagViewSet(ModelViewSet, PassUserMixin):
|
|||||||
class DocumentTypeViewSet(ModelViewSet, PassUserMixin):
|
class DocumentTypeViewSet(ModelViewSet, PassUserMixin):
|
||||||
model = DocumentType
|
model = DocumentType
|
||||||
|
|
||||||
queryset = DocumentType.objects.annotate(
|
queryset = (
|
||||||
|
DocumentType.objects.annotate(
|
||||||
document_count=Count("documents"),
|
document_count=Count("documents"),
|
||||||
).order_by(Lower("name"))
|
)
|
||||||
|
.select_related("owner")
|
||||||
|
.order_by(Lower("name"))
|
||||||
|
)
|
||||||
|
|
||||||
serializer_class = DocumentTypeSerializer
|
serializer_class = DocumentTypeSerializer
|
||||||
pagination_class = StandardPagination
|
pagination_class = StandardPagination
|
||||||
@ -283,7 +295,12 @@ class DocumentViewSet(
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
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):
|
def get_serializer(self, *args, **kwargs):
|
||||||
fields_param = self.request.query_params.get("fields", None)
|
fields_param = self.request.query_params.get("fields", None)
|
||||||
@ -627,9 +644,18 @@ class DocumentViewSet(
|
|||||||
|
|
||||||
class SearchResultSerializer(DocumentSerializer, PassUserMixin):
|
class SearchResultSerializer(DocumentSerializer, PassUserMixin):
|
||||||
def to_representation(self, instance):
|
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(
|
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 = super().to_representation(doc)
|
||||||
r["__search_hit__"] = {
|
r["__search_hit__"] = {
|
||||||
@ -752,7 +778,11 @@ class SavedViewViewSet(ModelViewSet, PassUserMixin):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
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):
|
def perform_create(self, serializer):
|
||||||
serializer.save(owner=self.request.user)
|
serializer.save(owner=self.request.user)
|
||||||
@ -1080,9 +1110,13 @@ class BulkDownloadView(GenericAPIView):
|
|||||||
class StoragePathViewSet(ModelViewSet, PassUserMixin):
|
class StoragePathViewSet(ModelViewSet, PassUserMixin):
|
||||||
model = StoragePath
|
model = StoragePath
|
||||||
|
|
||||||
queryset = StoragePath.objects.annotate(document_count=Count("documents")).order_by(
|
queryset = (
|
||||||
|
StoragePath.objects.annotate(document_count=Count("documents"))
|
||||||
|
.select_related("owner")
|
||||||
|
.order_by(
|
||||||
Lower("name"),
|
Lower("name"),
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
serializer_class = StoragePathSerializer
|
serializer_class = StoragePathSerializer
|
||||||
pagination_class = StandardPagination
|
pagination_class = StandardPagination
|
||||||
@ -1347,7 +1381,18 @@ class ConsumptionTemplateViewSet(ModelViewSet):
|
|||||||
|
|
||||||
model = ConsumptionTemplate
|
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):
|
class CustomFieldViewSet(ModelViewSet):
|
||||||
|
@ -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: 2023-12-05 08:26-0800\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"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
|
@ -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: 2023-12-05 08:26-0800\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"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Croatian\n"
|
"Language-Team: Croatian\n"
|
||||||
"Language: hr_HR\n"
|
"Language: hr_HR\n"
|
||||||
|
@ -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: 2023-12-05 08:26-0800\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"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Language: ro_RO\n"
|
"Language: ro_RO\n"
|
||||||
@ -23,11 +23,11 @@ msgstr "Documente"
|
|||||||
|
|
||||||
#: documents/models.py:36 documents/models.py:734
|
#: documents/models.py:36 documents/models.py:734
|
||||||
msgid "owner"
|
msgid "owner"
|
||||||
msgstr ""
|
msgstr "proprietar"
|
||||||
|
|
||||||
#: documents/models.py:53
|
#: documents/models.py:53
|
||||||
msgid "None"
|
msgid "None"
|
||||||
msgstr ""
|
msgstr "Nimic"
|
||||||
|
|
||||||
#: documents/models.py:54
|
#: documents/models.py:54
|
||||||
msgid "Any word"
|
msgid "Any word"
|
||||||
@ -108,15 +108,15 @@ msgstr "tipuri de document"
|
|||||||
|
|
||||||
#: documents/models.py:124
|
#: documents/models.py:124
|
||||||
msgid "path"
|
msgid "path"
|
||||||
msgstr ""
|
msgstr "cale"
|
||||||
|
|
||||||
#: documents/models.py:129 documents/models.py:156
|
#: documents/models.py:129 documents/models.py:156
|
||||||
msgid "storage path"
|
msgid "storage path"
|
||||||
msgstr ""
|
msgstr "cale de stocare"
|
||||||
|
|
||||||
#: documents/models.py:130
|
#: documents/models.py:130
|
||||||
msgid "storage paths"
|
msgid "storage paths"
|
||||||
msgstr ""
|
msgstr "căi de stocare"
|
||||||
|
|
||||||
#: documents/models.py:137
|
#: documents/models.py:137
|
||||||
msgid "Unencrypted"
|
msgid "Unencrypted"
|
||||||
@ -193,11 +193,11 @@ msgstr "Numele curent al arhivei stocate"
|
|||||||
|
|
||||||
#: documents/models.py:250
|
#: documents/models.py:250
|
||||||
msgid "original filename"
|
msgid "original filename"
|
||||||
msgstr ""
|
msgstr "numele original al fișierului"
|
||||||
|
|
||||||
#: documents/models.py:256
|
#: documents/models.py:256
|
||||||
msgid "The original name of the file when it was uploaded"
|
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
|
#: documents/models.py:263
|
||||||
msgid "archive serial number"
|
msgid "archive serial number"
|
||||||
@ -381,47 +381,47 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:447
|
#: documents/models.py:447
|
||||||
msgid "storage path is"
|
msgid "storage path is"
|
||||||
msgstr ""
|
msgstr "calea de stocare este"
|
||||||
|
|
||||||
#: documents/models.py:448
|
#: documents/models.py:448
|
||||||
msgid "has correspondent in"
|
msgid "has correspondent in"
|
||||||
msgstr ""
|
msgstr "are corespondent în"
|
||||||
|
|
||||||
#: documents/models.py:449
|
#: documents/models.py:449
|
||||||
msgid "does not have correspondent in"
|
msgid "does not have correspondent in"
|
||||||
msgstr ""
|
msgstr "nu are corespondent în"
|
||||||
|
|
||||||
#: documents/models.py:450
|
#: documents/models.py:450
|
||||||
msgid "has document type in"
|
msgid "has document type in"
|
||||||
msgstr ""
|
msgstr "are tip de document în"
|
||||||
|
|
||||||
#: documents/models.py:451
|
#: documents/models.py:451
|
||||||
msgid "does not have document type in"
|
msgid "does not have document type in"
|
||||||
msgstr ""
|
msgstr "nu are tip document în"
|
||||||
|
|
||||||
#: documents/models.py:452
|
#: documents/models.py:452
|
||||||
msgid "has storage path in"
|
msgid "has storage path in"
|
||||||
msgstr ""
|
msgstr "are cale de stocare în"
|
||||||
|
|
||||||
#: documents/models.py:453
|
#: documents/models.py:453
|
||||||
msgid "does not have storage path in"
|
msgid "does not have storage path in"
|
||||||
msgstr ""
|
msgstr "nu are cale de stocare în"
|
||||||
|
|
||||||
#: documents/models.py:454
|
#: documents/models.py:454
|
||||||
msgid "owner is"
|
msgid "owner is"
|
||||||
msgstr ""
|
msgstr "proprietarul este"
|
||||||
|
|
||||||
#: documents/models.py:455
|
#: documents/models.py:455
|
||||||
msgid "has owner in"
|
msgid "has owner in"
|
||||||
msgstr ""
|
msgstr "are proprietar în"
|
||||||
|
|
||||||
#: documents/models.py:456
|
#: documents/models.py:456
|
||||||
msgid "does not have owner"
|
msgid "does not have owner"
|
||||||
msgstr ""
|
msgstr "nu are proprietar"
|
||||||
|
|
||||||
#: documents/models.py:457
|
#: documents/models.py:457
|
||||||
msgid "does not have owner in"
|
msgid "does not have owner in"
|
||||||
msgstr ""
|
msgstr "nu are proprietar în"
|
||||||
|
|
||||||
#: documents/models.py:467
|
#: documents/models.py:467
|
||||||
msgid "rule type"
|
msgid "rule type"
|
||||||
@ -441,47 +441,47 @@ msgstr "reguli de filtrare"
|
|||||||
|
|
||||||
#: documents/models.py:584
|
#: documents/models.py:584
|
||||||
msgid "Task ID"
|
msgid "Task ID"
|
||||||
msgstr ""
|
msgstr "ID Sarcină"
|
||||||
|
|
||||||
#: documents/models.py:585
|
#: documents/models.py:585
|
||||||
msgid "Celery ID for the Task that was run"
|
msgid "Celery ID for the Task that was run"
|
||||||
msgstr ""
|
msgstr "ID-ul sarcinii Celery care a fost rulată"
|
||||||
|
|
||||||
#: documents/models.py:590
|
#: documents/models.py:590
|
||||||
msgid "Acknowledged"
|
msgid "Acknowledged"
|
||||||
msgstr ""
|
msgstr "Confirmat"
|
||||||
|
|
||||||
#: documents/models.py:591
|
#: documents/models.py:591
|
||||||
msgid "If the task is acknowledged via the frontend or API"
|
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
|
#: documents/models.py:597
|
||||||
msgid "Task Filename"
|
msgid "Task Filename"
|
||||||
msgstr ""
|
msgstr "Numele fișierului sarcină"
|
||||||
|
|
||||||
#: documents/models.py:598
|
#: documents/models.py:598
|
||||||
msgid "Name of the file which the Task was run for"
|
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
|
#: documents/models.py:604
|
||||||
msgid "Task Name"
|
msgid "Task Name"
|
||||||
msgstr ""
|
msgstr "Nume sarcină"
|
||||||
|
|
||||||
#: documents/models.py:605
|
#: documents/models.py:605
|
||||||
msgid "Name of the Task which was run"
|
msgid "Name of the Task which was run"
|
||||||
msgstr ""
|
msgstr "Numele sarcinii care a fost executată"
|
||||||
|
|
||||||
#: documents/models.py:612
|
#: documents/models.py:612
|
||||||
msgid "Task State"
|
msgid "Task State"
|
||||||
msgstr ""
|
msgstr "Stare sarcină"
|
||||||
|
|
||||||
#: documents/models.py:613
|
#: documents/models.py:613
|
||||||
msgid "Current state of the task being run"
|
msgid "Current state of the task being run"
|
||||||
msgstr ""
|
msgstr "Stadiul actual al sarcinii în curs de desfășurare"
|
||||||
|
|
||||||
#: documents/models.py:618
|
#: documents/models.py:618
|
||||||
msgid "Created DateTime"
|
msgid "Created DateTime"
|
||||||
msgstr ""
|
msgstr "Data creării"
|
||||||
|
|
||||||
#: documents/models.py:619
|
#: documents/models.py:619
|
||||||
msgid "Datetime field when the task result was created in UTC"
|
msgid "Datetime field when the task result was created in UTC"
|
||||||
@ -489,7 +489,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:624
|
#: documents/models.py:624
|
||||||
msgid "Started DateTime"
|
msgid "Started DateTime"
|
||||||
msgstr ""
|
msgstr "Data începerii"
|
||||||
|
|
||||||
#: documents/models.py:625
|
#: documents/models.py:625
|
||||||
msgid "Datetime field when the task was started in UTC"
|
msgid "Datetime field when the task was started in UTC"
|
||||||
@ -497,7 +497,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:630
|
#: documents/models.py:630
|
||||||
msgid "Completed DateTime"
|
msgid "Completed DateTime"
|
||||||
msgstr ""
|
msgstr "Data finalizării"
|
||||||
|
|
||||||
#: documents/models.py:631
|
#: documents/models.py:631
|
||||||
msgid "Datetime field when the task was completed in UTC"
|
msgid "Datetime field when the task was completed in UTC"
|
||||||
@ -505,15 +505,15 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:636
|
#: documents/models.py:636
|
||||||
msgid "Result Data"
|
msgid "Result Data"
|
||||||
msgstr ""
|
msgstr "Datele rezultatului"
|
||||||
|
|
||||||
#: documents/models.py:638
|
#: documents/models.py:638
|
||||||
msgid "The data returned by the task"
|
msgid "The data returned by the task"
|
||||||
msgstr ""
|
msgstr "Datele returnate de sarcină"
|
||||||
|
|
||||||
#: documents/models.py:650
|
#: documents/models.py:650
|
||||||
msgid "Note for the document"
|
msgid "Note for the document"
|
||||||
msgstr ""
|
msgstr "Notă pentru document"
|
||||||
|
|
||||||
#: documents/models.py:674
|
#: documents/models.py:674
|
||||||
msgid "user"
|
msgid "user"
|
||||||
@ -521,23 +521,23 @@ msgstr "utilizator"
|
|||||||
|
|
||||||
#: documents/models.py:679
|
#: documents/models.py:679
|
||||||
msgid "note"
|
msgid "note"
|
||||||
msgstr ""
|
msgstr "notă"
|
||||||
|
|
||||||
#: documents/models.py:680
|
#: documents/models.py:680
|
||||||
msgid "notes"
|
msgid "notes"
|
||||||
msgstr ""
|
msgstr "note"
|
||||||
|
|
||||||
#: documents/models.py:688
|
#: documents/models.py:688
|
||||||
msgid "Archive"
|
msgid "Archive"
|
||||||
msgstr ""
|
msgstr "Arhivă"
|
||||||
|
|
||||||
#: documents/models.py:689
|
#: documents/models.py:689
|
||||||
msgid "Original"
|
msgid "Original"
|
||||||
msgstr ""
|
msgstr "Original"
|
||||||
|
|
||||||
#: documents/models.py:700
|
#: documents/models.py:700
|
||||||
msgid "expiration"
|
msgid "expiration"
|
||||||
msgstr ""
|
msgstr "expirare"
|
||||||
|
|
||||||
#: documents/models.py:707
|
#: documents/models.py:707
|
||||||
msgid "slug"
|
msgid "slug"
|
||||||
@ -545,35 +545,35 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:739
|
#: documents/models.py:739
|
||||||
msgid "share link"
|
msgid "share link"
|
||||||
msgstr ""
|
msgstr "link de partajare"
|
||||||
|
|
||||||
#: documents/models.py:740
|
#: documents/models.py:740
|
||||||
msgid "share links"
|
msgid "share links"
|
||||||
msgstr ""
|
msgstr "link-uri de partajare"
|
||||||
|
|
||||||
#: documents/models.py:752
|
#: documents/models.py:752
|
||||||
msgid "String"
|
msgid "String"
|
||||||
msgstr ""
|
msgstr "Şir de caractere"
|
||||||
|
|
||||||
#: documents/models.py:753
|
#: documents/models.py:753
|
||||||
msgid "URL"
|
msgid "URL"
|
||||||
msgstr ""
|
msgstr "Adresă URL"
|
||||||
|
|
||||||
#: documents/models.py:754
|
#: documents/models.py:754
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr ""
|
msgstr "Dată"
|
||||||
|
|
||||||
#: documents/models.py:755
|
#: documents/models.py:755
|
||||||
msgid "Boolean"
|
msgid "Boolean"
|
||||||
msgstr ""
|
msgstr "Boolean"
|
||||||
|
|
||||||
#: documents/models.py:756
|
#: documents/models.py:756
|
||||||
msgid "Integer"
|
msgid "Integer"
|
||||||
msgstr ""
|
msgstr "Număr întreg"
|
||||||
|
|
||||||
#: documents/models.py:757
|
#: documents/models.py:757
|
||||||
msgid "Float"
|
msgid "Float"
|
||||||
msgstr ""
|
msgstr "Număr zecimal"
|
||||||
|
|
||||||
#: documents/models.py:758
|
#: documents/models.py:758
|
||||||
msgid "Monetary"
|
msgid "Monetary"
|
||||||
@ -581,11 +581,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:759
|
#: documents/models.py:759
|
||||||
msgid "Document Link"
|
msgid "Document Link"
|
||||||
msgstr ""
|
msgstr "Link document"
|
||||||
|
|
||||||
#: documents/models.py:771
|
#: documents/models.py:771
|
||||||
msgid "data type"
|
msgid "data type"
|
||||||
msgstr ""
|
msgstr "tip date"
|
||||||
|
|
||||||
#: documents/models.py:779
|
#: documents/models.py:779
|
||||||
msgid "custom field"
|
msgid "custom field"
|
||||||
|
@ -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: 2023-12-05 08:26-0800\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"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_TW\n"
|
||||||
@ -47,7 +47,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:58
|
#: documents/models.py:58
|
||||||
msgid "Fuzzy word"
|
msgid "Fuzzy word"
|
||||||
msgstr ""
|
msgstr "模糊詞"
|
||||||
|
|
||||||
#: documents/models.py:59
|
#: documents/models.py:59
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
@ -68,15 +68,15 @@ msgstr "比對演算法"
|
|||||||
|
|
||||||
#: documents/models.py:72
|
#: documents/models.py:72
|
||||||
msgid "is insensitive"
|
msgid "is insensitive"
|
||||||
msgstr ""
|
msgstr "不區分大小寫"
|
||||||
|
|
||||||
#: documents/models.py:95 documents/models.py:147
|
#: documents/models.py:95 documents/models.py:147
|
||||||
msgid "correspondent"
|
msgid "correspondent"
|
||||||
msgstr ""
|
msgstr "聯繫者"
|
||||||
|
|
||||||
#: documents/models.py:96
|
#: documents/models.py:96
|
||||||
msgid "correspondents"
|
msgid "correspondents"
|
||||||
msgstr ""
|
msgstr "聯繫者"
|
||||||
|
|
||||||
#: documents/models.py:100
|
#: documents/models.py:100
|
||||||
msgid "color"
|
msgid "color"
|
||||||
@ -84,47 +84,47 @@ msgstr "顏色"
|
|||||||
|
|
||||||
#: documents/models.py:103
|
#: documents/models.py:103
|
||||||
msgid "is inbox tag"
|
msgid "is inbox tag"
|
||||||
msgstr ""
|
msgstr "收件匣標籤"
|
||||||
|
|
||||||
#: documents/models.py:106
|
#: documents/models.py:106
|
||||||
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
msgid "Marks this tag as an inbox tag: All newly consumed documents will be tagged with inbox tags."
|
||||||
msgstr ""
|
msgstr "標記此標籤為收件匣標籤:所有新處理的文件將會以此收件匣標籤作標記。"
|
||||||
|
|
||||||
#: documents/models.py:112
|
#: documents/models.py:112
|
||||||
msgid "tag"
|
msgid "tag"
|
||||||
msgstr ""
|
msgstr "標籤"
|
||||||
|
|
||||||
#: documents/models.py:113 documents/models.py:185
|
#: documents/models.py:113 documents/models.py:185
|
||||||
msgid "tags"
|
msgid "tags"
|
||||||
msgstr ""
|
msgstr "標籤"
|
||||||
|
|
||||||
#: documents/models.py:118 documents/models.py:167
|
#: documents/models.py:118 documents/models.py:167
|
||||||
msgid "document type"
|
msgid "document type"
|
||||||
msgstr ""
|
msgstr "文件類型"
|
||||||
|
|
||||||
#: documents/models.py:119
|
#: documents/models.py:119
|
||||||
msgid "document types"
|
msgid "document types"
|
||||||
msgstr ""
|
msgstr "文件類型"
|
||||||
|
|
||||||
#: documents/models.py:124
|
#: documents/models.py:124
|
||||||
msgid "path"
|
msgid "path"
|
||||||
msgstr ""
|
msgstr "位址"
|
||||||
|
|
||||||
#: documents/models.py:129 documents/models.py:156
|
#: documents/models.py:129 documents/models.py:156
|
||||||
msgid "storage path"
|
msgid "storage path"
|
||||||
msgstr ""
|
msgstr "儲存位址"
|
||||||
|
|
||||||
#: documents/models.py:130
|
#: documents/models.py:130
|
||||||
msgid "storage paths"
|
msgid "storage paths"
|
||||||
msgstr ""
|
msgstr "儲存位址"
|
||||||
|
|
||||||
#: documents/models.py:137
|
#: documents/models.py:137
|
||||||
msgid "Unencrypted"
|
msgid "Unencrypted"
|
||||||
msgstr ""
|
msgstr "未加密"
|
||||||
|
|
||||||
#: documents/models.py:138
|
#: documents/models.py:138
|
||||||
msgid "Encrypted with GNU Privacy Guard"
|
msgid "Encrypted with GNU Privacy Guard"
|
||||||
msgstr ""
|
msgstr "已使用 GNU Privacy Guard 進行加密"
|
||||||
|
|
||||||
#: documents/models.py:159
|
#: documents/models.py:159
|
||||||
msgid "title"
|
msgid "title"
|
||||||
@ -189,27 +189,27 @@ msgstr "存檔檔案名稱"
|
|||||||
|
|
||||||
#: documents/models.py:246
|
#: documents/models.py:246
|
||||||
msgid "Current archive filename in storage"
|
msgid "Current archive filename in storage"
|
||||||
msgstr ""
|
msgstr "現時儲存空間封存的檔案名稱"
|
||||||
|
|
||||||
#: documents/models.py:250
|
#: documents/models.py:250
|
||||||
msgid "original filename"
|
msgid "original filename"
|
||||||
msgstr ""
|
msgstr "原先檔案名稱"
|
||||||
|
|
||||||
#: documents/models.py:256
|
#: documents/models.py:256
|
||||||
msgid "The original name of the file when it was uploaded"
|
msgid "The original name of the file when it was uploaded"
|
||||||
msgstr ""
|
msgstr "檔案上傳時的檔案名稱"
|
||||||
|
|
||||||
#: documents/models.py:263
|
#: documents/models.py:263
|
||||||
msgid "archive serial number"
|
msgid "archive serial number"
|
||||||
msgstr ""
|
msgstr "封存編號"
|
||||||
|
|
||||||
#: documents/models.py:273
|
#: documents/models.py:273
|
||||||
msgid "The position of this document in your physical document archive."
|
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
|
#: documents/models.py:279 documents/models.py:665 documents/models.py:719
|
||||||
msgid "document"
|
msgid "document"
|
||||||
msgstr ""
|
msgstr "文件"
|
||||||
|
|
||||||
#: documents/models.py:280
|
#: documents/models.py:280
|
||||||
msgid "documents"
|
msgid "documents"
|
||||||
@ -217,47 +217,47 @@ msgstr "文件"
|
|||||||
|
|
||||||
#: documents/models.py:368
|
#: documents/models.py:368
|
||||||
msgid "debug"
|
msgid "debug"
|
||||||
msgstr ""
|
msgstr "偵錯"
|
||||||
|
|
||||||
#: documents/models.py:369
|
#: documents/models.py:369
|
||||||
msgid "information"
|
msgid "information"
|
||||||
msgstr ""
|
msgstr "資訊"
|
||||||
|
|
||||||
#: documents/models.py:370
|
#: documents/models.py:370
|
||||||
msgid "warning"
|
msgid "warning"
|
||||||
msgstr ""
|
msgstr "警告"
|
||||||
|
|
||||||
#: documents/models.py:371 paperless_mail/models.py:305
|
#: documents/models.py:371 paperless_mail/models.py:305
|
||||||
msgid "error"
|
msgid "error"
|
||||||
msgstr ""
|
msgstr "錯誤"
|
||||||
|
|
||||||
#: documents/models.py:372
|
#: documents/models.py:372
|
||||||
msgid "critical"
|
msgid "critical"
|
||||||
msgstr ""
|
msgstr "嚴重"
|
||||||
|
|
||||||
#: documents/models.py:375
|
#: documents/models.py:375
|
||||||
msgid "group"
|
msgid "group"
|
||||||
msgstr ""
|
msgstr "群組"
|
||||||
|
|
||||||
#: documents/models.py:377
|
#: documents/models.py:377
|
||||||
msgid "message"
|
msgid "message"
|
||||||
msgstr ""
|
msgstr "訊息"
|
||||||
|
|
||||||
#: documents/models.py:380
|
#: documents/models.py:380
|
||||||
msgid "level"
|
msgid "level"
|
||||||
msgstr ""
|
msgstr "程度"
|
||||||
|
|
||||||
#: documents/models.py:389
|
#: documents/models.py:389
|
||||||
msgid "log"
|
msgid "log"
|
||||||
msgstr ""
|
msgstr "記錄"
|
||||||
|
|
||||||
#: documents/models.py:390
|
#: documents/models.py:390
|
||||||
msgid "logs"
|
msgid "logs"
|
||||||
msgstr ""
|
msgstr "記錄"
|
||||||
|
|
||||||
#: documents/models.py:399 documents/models.py:464
|
#: documents/models.py:399 documents/models.py:464
|
||||||
msgid "saved view"
|
msgid "saved view"
|
||||||
msgstr ""
|
msgstr "已儲存的檢視表"
|
||||||
|
|
||||||
#: documents/models.py:400
|
#: documents/models.py:400
|
||||||
msgid "saved views"
|
msgid "saved views"
|
||||||
@ -265,207 +265,207 @@ msgstr "保存視圖"
|
|||||||
|
|
||||||
#: documents/models.py:405
|
#: documents/models.py:405
|
||||||
msgid "show on dashboard"
|
msgid "show on dashboard"
|
||||||
msgstr ""
|
msgstr "顯示在概覽"
|
||||||
|
|
||||||
#: documents/models.py:408
|
#: documents/models.py:408
|
||||||
msgid "show in sidebar"
|
msgid "show in sidebar"
|
||||||
msgstr ""
|
msgstr "顯示在側邊欄"
|
||||||
|
|
||||||
#: documents/models.py:412
|
#: documents/models.py:412
|
||||||
msgid "sort field"
|
msgid "sort field"
|
||||||
msgstr ""
|
msgstr "排序欄位"
|
||||||
|
|
||||||
#: documents/models.py:417
|
#: documents/models.py:417
|
||||||
msgid "sort reverse"
|
msgid "sort reverse"
|
||||||
msgstr ""
|
msgstr "倒轉排序"
|
||||||
|
|
||||||
#: documents/models.py:422
|
#: documents/models.py:422
|
||||||
msgid "title contains"
|
msgid "title contains"
|
||||||
msgstr ""
|
msgstr "標題包含"
|
||||||
|
|
||||||
#: documents/models.py:423
|
#: documents/models.py:423
|
||||||
msgid "content contains"
|
msgid "content contains"
|
||||||
msgstr ""
|
msgstr "內容包含"
|
||||||
|
|
||||||
#: documents/models.py:424
|
#: documents/models.py:424
|
||||||
msgid "ASN is"
|
msgid "ASN is"
|
||||||
msgstr ""
|
msgstr "ASN 為"
|
||||||
|
|
||||||
#: documents/models.py:425
|
#: documents/models.py:425
|
||||||
msgid "correspondent is"
|
msgid "correspondent is"
|
||||||
msgstr ""
|
msgstr "聯繫者為"
|
||||||
|
|
||||||
#: documents/models.py:426
|
#: documents/models.py:426
|
||||||
msgid "document type is"
|
msgid "document type is"
|
||||||
msgstr ""
|
msgstr "文件類型為"
|
||||||
|
|
||||||
#: documents/models.py:427
|
#: documents/models.py:427
|
||||||
msgid "is in inbox"
|
msgid "is in inbox"
|
||||||
msgstr ""
|
msgstr "在收件匣內"
|
||||||
|
|
||||||
#: documents/models.py:428
|
#: documents/models.py:428
|
||||||
msgid "has tag"
|
msgid "has tag"
|
||||||
msgstr ""
|
msgstr "包含標籤"
|
||||||
|
|
||||||
#: documents/models.py:429
|
#: documents/models.py:429
|
||||||
msgid "has any tag"
|
msgid "has any tag"
|
||||||
msgstr ""
|
msgstr "包含任何標籤"
|
||||||
|
|
||||||
#: documents/models.py:430
|
#: documents/models.py:430
|
||||||
msgid "created before"
|
msgid "created before"
|
||||||
msgstr ""
|
msgstr "建立時間之前"
|
||||||
|
|
||||||
#: documents/models.py:431
|
#: documents/models.py:431
|
||||||
msgid "created after"
|
msgid "created after"
|
||||||
msgstr ""
|
msgstr "建立時間之後"
|
||||||
|
|
||||||
#: documents/models.py:432
|
#: documents/models.py:432
|
||||||
msgid "created year is"
|
msgid "created year is"
|
||||||
msgstr ""
|
msgstr "建立年份為"
|
||||||
|
|
||||||
#: documents/models.py:433
|
#: documents/models.py:433
|
||||||
msgid "created month is"
|
msgid "created month is"
|
||||||
msgstr ""
|
msgstr "建立月份為"
|
||||||
|
|
||||||
#: documents/models.py:434
|
#: documents/models.py:434
|
||||||
msgid "created day is"
|
msgid "created day is"
|
||||||
msgstr ""
|
msgstr "建立日期為"
|
||||||
|
|
||||||
#: documents/models.py:435
|
#: documents/models.py:435
|
||||||
msgid "added before"
|
msgid "added before"
|
||||||
msgstr ""
|
msgstr "加入時間之前"
|
||||||
|
|
||||||
#: documents/models.py:436
|
#: documents/models.py:436
|
||||||
msgid "added after"
|
msgid "added after"
|
||||||
msgstr ""
|
msgstr "加入時間之後"
|
||||||
|
|
||||||
#: documents/models.py:437
|
#: documents/models.py:437
|
||||||
msgid "modified before"
|
msgid "modified before"
|
||||||
msgstr ""
|
msgstr "修改之前"
|
||||||
|
|
||||||
#: documents/models.py:438
|
#: documents/models.py:438
|
||||||
msgid "modified after"
|
msgid "modified after"
|
||||||
msgstr ""
|
msgstr "修改之後"
|
||||||
|
|
||||||
#: documents/models.py:439
|
#: documents/models.py:439
|
||||||
msgid "does not have tag"
|
msgid "does not have tag"
|
||||||
msgstr ""
|
msgstr "沒有包含標籤"
|
||||||
|
|
||||||
#: documents/models.py:440
|
#: documents/models.py:440
|
||||||
msgid "does not have ASN"
|
msgid "does not have ASN"
|
||||||
msgstr ""
|
msgstr "沒有包含 ASN"
|
||||||
|
|
||||||
#: documents/models.py:441
|
#: documents/models.py:441
|
||||||
msgid "title or content contains"
|
msgid "title or content contains"
|
||||||
msgstr ""
|
msgstr "標題或內容包含"
|
||||||
|
|
||||||
#: documents/models.py:442
|
#: documents/models.py:442
|
||||||
msgid "fulltext query"
|
msgid "fulltext query"
|
||||||
msgstr ""
|
msgstr "全文搜索"
|
||||||
|
|
||||||
#: documents/models.py:443
|
#: documents/models.py:443
|
||||||
msgid "more like this"
|
msgid "more like this"
|
||||||
msgstr ""
|
msgstr "其他類似內容"
|
||||||
|
|
||||||
#: documents/models.py:444
|
#: documents/models.py:444
|
||||||
msgid "has tags in"
|
msgid "has tags in"
|
||||||
msgstr ""
|
msgstr "含有這個標籤"
|
||||||
|
|
||||||
#: documents/models.py:445
|
#: documents/models.py:445
|
||||||
msgid "ASN greater than"
|
msgid "ASN greater than"
|
||||||
msgstr ""
|
msgstr "ASN 大於"
|
||||||
|
|
||||||
#: documents/models.py:446
|
#: documents/models.py:446
|
||||||
msgid "ASN less than"
|
msgid "ASN less than"
|
||||||
msgstr ""
|
msgstr "ASN 小於"
|
||||||
|
|
||||||
#: documents/models.py:447
|
#: documents/models.py:447
|
||||||
msgid "storage path is"
|
msgid "storage path is"
|
||||||
msgstr ""
|
msgstr "儲存位址為"
|
||||||
|
|
||||||
#: documents/models.py:448
|
#: documents/models.py:448
|
||||||
msgid "has correspondent in"
|
msgid "has correspondent in"
|
||||||
msgstr ""
|
msgstr "包含聯繫者"
|
||||||
|
|
||||||
#: documents/models.py:449
|
#: documents/models.py:449
|
||||||
msgid "does not have correspondent in"
|
msgid "does not have correspondent in"
|
||||||
msgstr ""
|
msgstr "沒有包含聯繫者"
|
||||||
|
|
||||||
#: documents/models.py:450
|
#: documents/models.py:450
|
||||||
msgid "has document type in"
|
msgid "has document type in"
|
||||||
msgstr ""
|
msgstr "文件類型包含"
|
||||||
|
|
||||||
#: documents/models.py:451
|
#: documents/models.py:451
|
||||||
msgid "does not have document type in"
|
msgid "does not have document type in"
|
||||||
msgstr ""
|
msgstr "沒有包含的文件類型"
|
||||||
|
|
||||||
#: documents/models.py:452
|
#: documents/models.py:452
|
||||||
msgid "has storage path in"
|
msgid "has storage path in"
|
||||||
msgstr ""
|
msgstr "儲存位址包含"
|
||||||
|
|
||||||
#: documents/models.py:453
|
#: documents/models.py:453
|
||||||
msgid "does not have storage path in"
|
msgid "does not have storage path in"
|
||||||
msgstr ""
|
msgstr "沒有包含的儲存位址"
|
||||||
|
|
||||||
#: documents/models.py:454
|
#: documents/models.py:454
|
||||||
msgid "owner is"
|
msgid "owner is"
|
||||||
msgstr ""
|
msgstr "擁有者為"
|
||||||
|
|
||||||
#: documents/models.py:455
|
#: documents/models.py:455
|
||||||
msgid "has owner in"
|
msgid "has owner in"
|
||||||
msgstr ""
|
msgstr "擁有者包含"
|
||||||
|
|
||||||
#: documents/models.py:456
|
#: documents/models.py:456
|
||||||
msgid "does not have owner"
|
msgid "does not have owner"
|
||||||
msgstr ""
|
msgstr "沒有包含的擁有者"
|
||||||
|
|
||||||
#: documents/models.py:457
|
#: documents/models.py:457
|
||||||
msgid "does not have owner in"
|
msgid "does not have owner in"
|
||||||
msgstr ""
|
msgstr "沒有包含的擁有者"
|
||||||
|
|
||||||
#: documents/models.py:467
|
#: documents/models.py:467
|
||||||
msgid "rule type"
|
msgid "rule type"
|
||||||
msgstr ""
|
msgstr "規則類型"
|
||||||
|
|
||||||
#: documents/models.py:469
|
#: documents/models.py:469
|
||||||
msgid "value"
|
msgid "value"
|
||||||
msgstr ""
|
msgstr "數值"
|
||||||
|
|
||||||
#: documents/models.py:472
|
#: documents/models.py:472
|
||||||
msgid "filter rule"
|
msgid "filter rule"
|
||||||
msgstr ""
|
msgstr "過濾規則"
|
||||||
|
|
||||||
#: documents/models.py:473
|
#: documents/models.py:473
|
||||||
msgid "filter rules"
|
msgid "filter rules"
|
||||||
msgstr ""
|
msgstr "過濾規則"
|
||||||
|
|
||||||
#: documents/models.py:584
|
#: documents/models.py:584
|
||||||
msgid "Task ID"
|
msgid "Task ID"
|
||||||
msgstr ""
|
msgstr "任務 ID"
|
||||||
|
|
||||||
#: documents/models.py:585
|
#: documents/models.py:585
|
||||||
msgid "Celery ID for the Task that was run"
|
msgid "Celery ID for the Task that was run"
|
||||||
msgstr ""
|
msgstr "已執行任務的 Celery ID"
|
||||||
|
|
||||||
#: documents/models.py:590
|
#: documents/models.py:590
|
||||||
msgid "Acknowledged"
|
msgid "Acknowledged"
|
||||||
msgstr ""
|
msgstr "已確認"
|
||||||
|
|
||||||
#: documents/models.py:591
|
#: documents/models.py:591
|
||||||
msgid "If the task is acknowledged via the frontend or API"
|
msgid "If the task is acknowledged via the frontend or API"
|
||||||
msgstr ""
|
msgstr "如果任務已由前端 / API 確認"
|
||||||
|
|
||||||
#: documents/models.py:597
|
#: documents/models.py:597
|
||||||
msgid "Task Filename"
|
msgid "Task Filename"
|
||||||
msgstr ""
|
msgstr "任務檔案名稱"
|
||||||
|
|
||||||
#: documents/models.py:598
|
#: documents/models.py:598
|
||||||
msgid "Name of the file which the Task was run for"
|
msgid "Name of the file which the Task was run for"
|
||||||
msgstr ""
|
msgstr "執行任務的目標檔案名稱"
|
||||||
|
|
||||||
#: documents/models.py:604
|
#: documents/models.py:604
|
||||||
msgid "Task Name"
|
msgid "Task Name"
|
||||||
msgstr ""
|
msgstr "任務名稱"
|
||||||
|
|
||||||
#: documents/models.py:605
|
#: documents/models.py:605
|
||||||
msgid "Name of the Task which was run"
|
msgid "Name of the Task which was run"
|
||||||
@ -473,7 +473,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:612
|
#: documents/models.py:612
|
||||||
msgid "Task State"
|
msgid "Task State"
|
||||||
msgstr ""
|
msgstr "任務狀態"
|
||||||
|
|
||||||
#: documents/models.py:613
|
#: documents/models.py:613
|
||||||
msgid "Current state of the task being run"
|
msgid "Current state of the task being run"
|
||||||
@ -657,7 +657,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:967 paperless_mail/models.py:238
|
#: documents/models.py:967 paperless_mail/models.py:238
|
||||||
msgid "assign this correspondent"
|
msgid "assign this correspondent"
|
||||||
msgstr ""
|
msgstr "指派這個聯繫者"
|
||||||
|
|
||||||
#: documents/models.py:975
|
#: documents/models.py:975
|
||||||
msgid "assign this storage path"
|
msgid "assign this storage path"
|
||||||
@ -1128,7 +1128,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: paperless_mail/models.py:88
|
#: paperless_mail/models.py:88
|
||||||
msgid "Do not assign a correspondent"
|
msgid "Do not assign a correspondent"
|
||||||
msgstr ""
|
msgstr "不要指派聯繫者"
|
||||||
|
|
||||||
#: paperless_mail/models.py:89
|
#: paperless_mail/models.py:89
|
||||||
msgid "Use mail address"
|
msgid "Use mail address"
|
||||||
@ -1140,7 +1140,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: paperless_mail/models.py:91
|
#: paperless_mail/models.py:91
|
||||||
msgid "Use correspondent selected below"
|
msgid "Use correspondent selected below"
|
||||||
msgstr ""
|
msgstr "使用以下已選擇的聯繫者"
|
||||||
|
|
||||||
#: paperless_mail/models.py:101
|
#: paperless_mail/models.py:101
|
||||||
msgid "account"
|
msgid "account"
|
||||||
@ -1220,7 +1220,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: paperless_mail/models.py:228
|
#: paperless_mail/models.py:228
|
||||||
msgid "assign correspondent from"
|
msgid "assign correspondent from"
|
||||||
msgstr ""
|
msgstr "指派聯繫者從"
|
||||||
|
|
||||||
#: paperless_mail/models.py:242
|
#: paperless_mail/models.py:242
|
||||||
msgid "Assign the rule owner to documents"
|
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",
|
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:
|
try:
|
||||||
user_args = json.loads(settings.OCR_USER_ARGS)
|
user_args = json.loads(settings.OCR_USER_ARGS)
|
||||||
ocrmypdf_args = {**ocrmypdf_args, **user_args}
|
ocrmypdf_args = {**ocrmypdf_args, **user_args}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user