mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-12 13:53:20 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
793374158f | ||
|
|
6a02b87dde | ||
|
|
59a2651804 | ||
|
|
a99f63e059 | ||
|
|
939cb52f6e | ||
|
|
855669ddf9 |
+1
-2
@@ -301,8 +301,7 @@ The following methods are supported:
|
|||||||
- `delete`
|
- `delete`
|
||||||
- No `parameters` required
|
- No `parameters` required
|
||||||
- `reprocess`
|
- `reprocess`
|
||||||
- Optional `parameters`: `{ "remote_ocr": true }` to send the documents to the
|
- No `parameters` required
|
||||||
remote OCR engine, see [Remote OCR](usage.md#remote-ocr). Defaults to false.
|
|
||||||
- `set_permissions`
|
- `set_permissions`
|
||||||
- Requires `parameters`:
|
- Requires `parameters`:
|
||||||
- `"set_permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
|
- `"set_permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
|
||||||
|
|||||||
+5
-16
@@ -948,10 +948,11 @@ for display in the web interface.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
The **remote OCR parser** (Azure AI) always produces a searchable
|
The **remote OCR parser** (Azure AI) also honors this setting: when
|
||||||
PDF and stores it as the archive copy, regardless of this setting.
|
no archive is requested (`never`, or `auto` with a born-digital PDF),
|
||||||
`ARCHIVE_FILE_GENERATION=never` has no effect when the remote
|
the remote engine is skipped entirely and locally-extracted text is
|
||||||
parser handles a document.
|
used instead, avoiding an unnecessary API call and a duplicate text
|
||||||
|
layer.
|
||||||
|
|
||||||
#### [`PAPERLESS_OCR_CLEAN=<mode>`](#PAPERLESS_OCR_CLEAN) {#PAPERLESS_OCR_CLEAN}
|
#### [`PAPERLESS_OCR_CLEAN=<mode>`](#PAPERLESS_OCR_CLEAN) {#PAPERLESS_OCR_CLEAN}
|
||||||
|
|
||||||
@@ -2047,18 +2048,6 @@ password. All of these options come from their similarly-named [Django settings]
|
|||||||
|
|
||||||
Defaults to None.
|
Defaults to None.
|
||||||
|
|
||||||
#### [`PAPERLESS_REMOTE_OCR_MODE=<str>`](#PAPERLESS_REMOTE_OCR_MODE) {#PAPERLESS_REMOTE_OCR_MODE}
|
|
||||||
|
|
||||||
: Which documents are sent to the remote OCR engine.
|
|
||||||
|
|
||||||
- `always`: every document of a supported file type is sent to the remote
|
|
||||||
engine, bypassing the local OCR engine.
|
|
||||||
- `workflow_only`: documents are processed locally unless a workflow
|
|
||||||
explicitly enables remote OCR for them, letting you use the remote engine
|
|
||||||
selectively.
|
|
||||||
|
|
||||||
Defaults to "always".
|
|
||||||
|
|
||||||
## AI {#ai}
|
## AI {#ai}
|
||||||
|
|
||||||
#### [`PAPERLESS_AI_ENABLED=<bool>`](#PAPERLESS_AI_ENABLED) {#PAPERLESS_AI_ENABLED}
|
#### [`PAPERLESS_AI_ENABLED=<bool>`](#PAPERLESS_AI_ENABLED) {#PAPERLESS_AI_ENABLED}
|
||||||
|
|||||||
@@ -456,20 +456,6 @@ def score(
|
|||||||
return 10
|
return 10
|
||||||
```
|
```
|
||||||
|
|
||||||
**Remote services**
|
|
||||||
|
|
||||||
If your parser sends document content to a remote service, declare it:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyCustomParser:
|
|
||||||
uses_remote_service = True
|
|
||||||
```
|
|
||||||
|
|
||||||
Paperless-ngx excludes such parsers when the document being consumed has not
|
|
||||||
been marked for remote processing, so users can keep remote OCR off by default
|
|
||||||
and enable it selectively with a workflow. Parsers that do not declare the
|
|
||||||
attribute are treated as fully local and are always considered.
|
|
||||||
|
|
||||||
**Archive and rendition flags**
|
**Archive and rendition flags**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -187,10 +187,11 @@ PAPERLESS_ARCHIVE_FILE_GENERATION=auto
|
|||||||
|
|
||||||
### Remote OCR parser
|
### Remote OCR parser
|
||||||
|
|
||||||
If you use the **remote OCR parser** (Azure AI), note that it always produces a
|
If you use the **remote OCR parser** (Azure AI), `ARCHIVE_FILE_GENERATION` is
|
||||||
searchable PDF and stores it as the archive copy. `ARCHIVE_FILE_GENERATION=never`
|
honored the same way as for the local engine: when no archive is requested
|
||||||
has no effect for documents handled by the remote parser - the archive is produced
|
(`never`, or `auto` with a born-digital PDF), the remote engine is skipped
|
||||||
unconditionally by the remote engine.
|
entirely and locally-extracted text is used instead, avoiding an unnecessary
|
||||||
|
API call and a duplicate text layer.
|
||||||
|
|
||||||
## Search Index (Whoosh -> Tantivy)
|
## Search Index (Whoosh -> Tantivy)
|
||||||
|
|
||||||
|
|||||||
+4
-9
@@ -576,7 +576,9 @@ The following workflow action types are available:
|
|||||||
- Tags, correspondent, document type and storage path
|
- Tags, correspondent, document type and storage path
|
||||||
- Document owner
|
- Document owner
|
||||||
- View and / or edit permissions to users or groups
|
- View and / or edit permissions to users or groups
|
||||||
- Custom fields. Note that no value for the field will be set
|
- Custom fields, optionally with a value. If no value is set, the field is only added to the
|
||||||
|
document and any value it may already have is left untouched. If a value is set, it will
|
||||||
|
overwrite an existing value of that field on the document.
|
||||||
|
|
||||||
##### Removal {#workflow-action-removal}
|
##### Removal {#workflow-action-removal}
|
||||||
|
|
||||||
@@ -1084,18 +1086,11 @@ Paperless-ngx supports performing OCR on documents using remote services. At the
|
|||||||
[Microsoft's Azure "Document Intelligence" service](https://azure.microsoft.com/en-us/products/ai-services/ai-document-intelligence).
|
[Microsoft's Azure "Document Intelligence" service](https://azure.microsoft.com/en-us/products/ai-services/ai-document-intelligence).
|
||||||
This is of course a paid service (with a free tier) which requires an Azure account and subscription. Azure AI is not affiliated with
|
This is of course a paid service (with a free tier) which requires an Azure account and subscription. Azure AI is not affiliated with
|
||||||
Paperless-ngx in any way. When enabled, Paperless-ngx will automatically send appropriate documents to Azure for OCR processing, bypassing
|
Paperless-ngx in any way. When enabled, Paperless-ngx will automatically send appropriate documents to Azure for OCR processing, bypassing
|
||||||
the local OCR engine. See the [configuration](configuration.md#PAPERLESS_REMOTE_OCR_ENGINE) options for more details. These
|
the local OCR engine. See the [configuration](configuration.md#PAPERLESS_REMOTE_OCR_ENGINE) options for more details.
|
||||||
settings can be supplied as environment variables or via **Application Configuration**.
|
|
||||||
|
|
||||||
Additionally, when using a commercial service with this feature, consider both potential costs as well as any associated file size
|
Additionally, when using a commercial service with this feature, consider both potential costs as well as any associated file size
|
||||||
or page limitations (e.g. with a free tier).
|
or page limitations (e.g. with a free tier).
|
||||||
|
|
||||||
By default, every document of a supported file type is sent to the remote engine. To use it more selectively, set the
|
|
||||||
[remote OCR mode](configuration.md#PAPERLESS_REMOTE_OCR_MODE) to `workflow_only`. Documents are then processed locally
|
|
||||||
unless a workflow explicitly enables remote OCR for them, so you can limit the remote engine to particular documents.
|
|
||||||
|
|
||||||
Setting the mode to `workflow_only` also allows the **Reprocess** actions to selectively use remote OCR for individual documents.
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Paperless-ngx consists of the following components:
|
Paperless-ngx consists of the following components:
|
||||||
|
|||||||
+8
-8
@@ -599,7 +599,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
||||||
<context context-type="linenumber">84,85</context>
|
<context context-type="linenumber">85,86</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3768927257183755959" datatype="html">
|
<trans-unit id="3768927257183755959" datatype="html">
|
||||||
@@ -670,7 +670,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
||||||
<context context-type="linenumber">85,86</context>
|
<context context-type="linenumber">86,87</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5079885666748292382" datatype="html">
|
<trans-unit id="5079885666748292382" datatype="html">
|
||||||
@@ -9907,7 +9907,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">296</context>
|
<context context-type="linenumber">314</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2620006875434695386" datatype="html">
|
<trans-unit id="2620006875434695386" datatype="html">
|
||||||
@@ -10234,7 +10234,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">290</context>
|
<context context-type="linenumber">308</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3501895737484542570" datatype="html">
|
<trans-unit id="3501895737484542570" datatype="html">
|
||||||
@@ -10346,28 +10346,28 @@
|
|||||||
<source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
|
<source>Saved view "<x id="PH" equiv-text="savedView.name"/>" deleted.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">160</context>
|
<context context-type="linenumber">178</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1660419335376265526" datatype="html">
|
<trans-unit id="1660419335376265526" datatype="html">
|
||||||
<source>Views saved successfully.</source>
|
<source>Views saved successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">237</context>
|
<context context-type="linenumber">255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1699877326523238632" datatype="html">
|
<trans-unit id="1699877326523238632" datatype="html">
|
||||||
<source>Error while saving views.</source>
|
<source>Error while saving views.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">242</context>
|
<context context-type="linenumber">260</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4919025779187821586" datatype="html">
|
<trans-unit id="4919025779187821586" datatype="html">
|
||||||
<source>Note: Sharing saved views does not share the underlying documents.</source>
|
<source>Note: Sharing saved views does not share the underlying documents.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.ts</context>
|
||||||
<context context-type="linenumber">278</context>
|
<context context-type="linenumber">296</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1229748338333965418" datatype="html">
|
<trans-unit id="1229748338333965418" datatype="html">
|
||||||
|
|||||||
@@ -14,48 +14,43 @@
|
|||||||
<a ngbNavLink>{{category}}</a>
|
<a ngbNavLink>{{category}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
@for (section of getCategorySections(category); track section) {
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
|
||||||
@if (section) {
|
@for (option of getCategoryOptions(category); track option.key) {
|
||||||
<h5 class="mt-4 mb-3">{{section}}</h5>
|
<div class="col">
|
||||||
}
|
<div class="card bg-light">
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
|
<div class="card-body">
|
||||||
@for (option of getCategoryOptions(category, section); track option.key) {
|
<div class="card-title d-flex align-items-center">
|
||||||
<div class="col">
|
<h6 class="mb-0">
|
||||||
<div class="card bg-light">
|
{{option.title}}
|
||||||
<div class="card-body">
|
</h6>
|
||||||
<div class="card-title d-flex align-items-center">
|
<a class="btn btn-sm btn-link" title="Read the documentation about this setting" i18n-title [href]="getDocsUrl(option.config_key)" target="_blank" referrerpolicy="no-referrer">
|
||||||
<h6 class="mb-0">
|
<i-bs name="info-circle"></i-bs>
|
||||||
{{option.title}}
|
</a>
|
||||||
</h6>
|
@if (isSet(option.key)) {
|
||||||
<a class="btn btn-sm btn-link" title="Read the documentation about this setting" i18n-title [href]="getDocsUrl(option.config_key)" target="_blank" referrerpolicy="no-referrer">
|
<button type="button" class="btn btn-sm btn-link text-danger ms-auto pe-0" title="Reset" i18n-title (click)="resetOption(option.key)">
|
||||||
<i-bs name="info-circle"></i-bs>
|
<i-bs class="me-1" name="x"></i-bs><ng-container i18n>Reset</ng-container>
|
||||||
</a>
|
</button>
|
||||||
@if (isSet(option.key)) {
|
|
||||||
<button type="button" class="btn btn-sm btn-link text-danger ms-auto pe-0" title="Reset" i18n-title (click)="resetOption(option.key)">
|
|
||||||
<i-bs class="me-1" name="x"></i-bs><ng-container i18n>Reset</ng-container>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="mb-n3">
|
|
||||||
@switch (option.type) {
|
|
||||||
@case (ConfigOptionType.Select) { <pngx-input-select [formControlName]="option.key" [error]="errors[option.key]" [items]="option.choices" [allowNull]="true"></pngx-input-select> }
|
|
||||||
@case (ConfigOptionType.Number) { <pngx-input-number [formControlName]="option.key" [error]="errors[option.key]" [showAdd]="false"></pngx-input-number> }
|
|
||||||
@case (ConfigOptionType.Boolean) { <pngx-input-switch [formControlName]="option.key" [error]="errors[option.key]" [showUnsetNote]="true" [horizontal]="true" title="Enable" i18n-title></pngx-input-switch> }
|
|
||||||
@case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
|
|
||||||
@case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
|
|
||||||
@case (ConfigOptionType.File) { <pngx-input-file [formControlName]="option.key" (upload)="uploadFile($event, option.key)" [error]="errors[option.key]"></pngx-input-file> }
|
|
||||||
@case (ConfigOptionType.Password) { <pngx-input-password [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-password> }
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@if (option.note) {
|
|
||||||
<div class="form-text fst-italic">{{option.note}}</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-n3">
|
||||||
|
@switch (option.type) {
|
||||||
|
@case (ConfigOptionType.Select) { <pngx-input-select [formControlName]="option.key" [error]="errors[option.key]" [items]="option.choices" [allowNull]="true"></pngx-input-select> }
|
||||||
|
@case (ConfigOptionType.Number) { <pngx-input-number [formControlName]="option.key" [error]="errors[option.key]" [showAdd]="false"></pngx-input-number> }
|
||||||
|
@case (ConfigOptionType.Boolean) { <pngx-input-switch [formControlName]="option.key" [error]="errors[option.key]" [showUnsetNote]="true" [horizontal]="true" title="Enable" i18n-title></pngx-input-switch> }
|
||||||
|
@case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
|
||||||
|
@case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
|
||||||
|
@case (ConfigOptionType.File) { <pngx-input-file [formControlName]="option.key" (upload)="uploadFile($event, option.key)" [error]="errors[option.key]"></pngx-input-file> }
|
||||||
|
@case (ConfigOptionType.Password) { <pngx-input-password [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-password> }
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (option.note) {
|
||||||
|
<div class="form-text fst-italic">{{option.note}}</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import {
|
import { OutputTypeConfig } from 'src/app/data/paperless-config'
|
||||||
ConfigCategory,
|
|
||||||
ConfigSection,
|
|
||||||
OutputTypeConfig,
|
|
||||||
} from 'src/app/data/paperless-config'
|
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@@ -162,24 +158,4 @@ describe('ConfigComponent', () => {
|
|||||||
component.resetOption('barcodes_enabled')
|
component.resetOption('barcodes_enabled')
|
||||||
expect(component.configForm.get('barcodes_enabled').value).toBeNull()
|
expect(component.configForm.get('barcodes_enabled').value).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should group options into sections within a category, or not', () => {
|
|
||||||
const sections = component.getCategorySections(ConfigCategory.OCR)
|
|
||||||
expect(sections).toEqual([null, ConfigSection.RemoteOCR])
|
|
||||||
expect(
|
|
||||||
component
|
|
||||||
.getCategoryOptions(ConfigCategory.OCR)
|
|
||||||
.map((option) => option.key)
|
|
||||||
).toContain('output_type')
|
|
||||||
expect(
|
|
||||||
component
|
|
||||||
.getCategoryOptions(ConfigCategory.OCR, ConfigSection.RemoteOCR)
|
|
||||||
.map((option) => option.key)
|
|
||||||
).toEqual([
|
|
||||||
'remote_ocr_engine',
|
|
||||||
'remote_ocr_api_key',
|
|
||||||
'remote_ocr_endpoint',
|
|
||||||
'remote_ocr_mode',
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -74,20 +74,8 @@ export class ConfigComponent
|
|||||||
return Object.values(ConfigCategory)
|
return Object.values(ConfigCategory)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategorySections(category: string): string[] {
|
getCategoryOptions(category: string): ConfigOption[] {
|
||||||
return [
|
return PaperlessConfigOptions.filter((o) => o.category === category)
|
||||||
...new Set(
|
|
||||||
PaperlessConfigOptions.filter((o) => o.category === category).map(
|
|
||||||
(o) => o.section ?? null // null means no section
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
getCategoryOptions(category: string, section: string = null): ConfigOption[] {
|
|
||||||
return PaperlessConfigOptions.filter(
|
|
||||||
(o) => o.category === category && (o.section ?? null) === section
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialConfig: PaperlessConfig
|
initialConfig: PaperlessConfig
|
||||||
|
|||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
|
||||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
@if (messageBold) {
|
|
||||||
<p class="text-break"><b>{{messageBold}}</b></p>
|
|
||||||
}
|
|
||||||
@if (message) {
|
|
||||||
<p class="mb-0 text-break" [innerHTML]="message"></p>
|
|
||||||
}
|
|
||||||
@if (showRemoteOcr) {
|
|
||||||
<div class="form-check mt-3">
|
|
||||||
<input class="form-check-input" type="checkbox" id="reprocessRemoteOcr" [(ngModel)]="remoteOcr" />
|
|
||||||
<label class="form-check-label" for="reprocessRemoteOcr" i18n>Use remote OCR</label>
|
|
||||||
<div class="form-text" i18n>Sends the document to the configured remote OCR service, which may incur costs.</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
|
||||||
{{btnCaption}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
|
||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { RemoteOCRModeConfig } from 'src/app/data/paperless-config'
|
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
|
||||||
import { ReprocessConfirmDialogComponent } from './reprocess-confirm-dialog.component'
|
|
||||||
|
|
||||||
describe('ReprocessConfirmDialogComponent', () => {
|
|
||||||
let component: ReprocessConfirmDialogComponent
|
|
||||||
let fixture: ComponentFixture<ReprocessConfirmDialogComponent>
|
|
||||||
let settingsService: SettingsService
|
|
||||||
|
|
||||||
const createComponent = (configured: boolean, mode: string) => {
|
|
||||||
settingsService.set(SETTINGS_KEYS.REMOTE_OCR_CONFIGURED, configured)
|
|
||||||
settingsService.set(SETTINGS_KEYS.REMOTE_OCR_MODE, mode)
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ReprocessConfirmDialogComponent)
|
|
||||||
component = fixture.componentInstance
|
|
||||||
fixture.detectChanges()
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [
|
|
||||||
NgbActiveModal,
|
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
|
||||||
provideHttpClientTesting(),
|
|
||||||
],
|
|
||||||
imports: [ReprocessConfirmDialogComponent],
|
|
||||||
}).compileComponents()
|
|
||||||
|
|
||||||
settingsService = TestBed.inject(SettingsService)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not request remote OCR by default', () => {
|
|
||||||
createComponent(true, RemoteOCRModeConfig.WORKFLOW_ONLY)
|
|
||||||
|
|
||||||
expect(component.remoteOcr).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not offer remote OCR when no engine is configured', () => {
|
|
||||||
createComponent(false, RemoteOCRModeConfig.WORKFLOW_ONLY)
|
|
||||||
|
|
||||||
expect(component.showRemoteOcr).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
fixture.nativeElement.querySelector('#reprocessRemoteOcr')
|
|
||||||
).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not offer remote OCR when it already handles every document', () => {
|
|
||||||
createComponent(true, RemoteOCRModeConfig.ALWAYS)
|
|
||||||
|
|
||||||
expect(component.showRemoteOcr).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
fixture.nativeElement.querySelector('#reprocessRemoteOcr')
|
|
||||||
).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should offer remote OCR when configured and selective', () => {
|
|
||||||
createComponent(true, RemoteOCRModeConfig.WORKFLOW_ONLY)
|
|
||||||
|
|
||||||
expect(component.showRemoteOcr).toBeTruthy()
|
|
||||||
const checkbox = fixture.nativeElement.querySelector('#reprocessRemoteOcr')
|
|
||||||
expect(checkbox).not.toBeNull()
|
|
||||||
|
|
||||||
checkbox.click()
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(component.remoteOcr).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
|
||||||
import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'pngx-reprocess-confirm-dialog',
|
|
||||||
templateUrl: './reprocess-confirm-dialog.component.html',
|
|
||||||
imports: [FormsModule],
|
|
||||||
})
|
|
||||||
export class ReprocessConfirmDialogComponent extends ConfirmDialogComponent {
|
|
||||||
private settings = inject(SettingsService)
|
|
||||||
|
|
||||||
remoteOcr: boolean = false
|
|
||||||
|
|
||||||
public get showRemoteOcr(): boolean {
|
|
||||||
// Hidden when it is not configured, or when it already handles every document anyway.
|
|
||||||
return this.settings.remoteOCRIsSelectable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-4
@@ -52,10 +52,10 @@ describe('CustomFieldsValuesComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should set selectedFields and map values correctly', () => {
|
it('should set selectedFields and map values correctly', () => {
|
||||||
component.value = { 1: 'value1' }
|
component.value = { 1: 'value1', 3: 0, 4: false }
|
||||||
component.selectedFields = [1, 2]
|
component.selectedFields = [1, 2, 3, 4]
|
||||||
expect(component.selectedFields).toEqual([1, 2])
|
expect(component.selectedFields).toEqual([1, 2, 3, 4])
|
||||||
expect(component.value).toEqual({ 1: 'value1', 2: null })
|
expect(component.value).toEqual({ 1: 'value1', 2: null, 3: 0, 4: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return the correct custom field by id', () => {
|
it('should return the correct custom field by id', () => {
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ export class CustomFieldsValuesComponent extends AbstractInputComponent<Object>
|
|||||||
this._selectedFields = newFields
|
this._selectedFields = newFields
|
||||||
// map the selected fields to an object with field_id as key and value as value
|
// map the selected fields to an object with field_id as key and value as value
|
||||||
this.value = newFields.reduce((acc, fieldId) => {
|
this.value = newFields.reduce((acc, fieldId) => {
|
||||||
acc[fieldId] = this.value?.[fieldId] || null
|
acc[fieldId] = this.value?.[fieldId] ?? null
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
this.onChange(this.value)
|
this.onChange(this.value)
|
||||||
|
|||||||
@@ -963,24 +963,12 @@ describe('DocumentDetailComponent', () => {
|
|||||||
component.reprocess()
|
component.reprocess()
|
||||||
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
const modalCloseSpy = jest.spyOn(openModal, 'close')
|
||||||
openModal.componentInstance.confirmClicked.next()
|
openModal.componentInstance.confirmClicked.next()
|
||||||
expect(reprocessSpy).toHaveBeenCalledWith({ documents: [doc.id] }, false)
|
expect(reprocessSpy).toHaveBeenCalledWith({ documents: [doc.id] })
|
||||||
expect(modalSpy).toHaveBeenCalled()
|
expect(modalSpy).toHaveBeenCalled()
|
||||||
expect(toastSpy).toHaveBeenCalled()
|
expect(toastSpy).toHaveBeenCalled()
|
||||||
expect(modalCloseSpy).toHaveBeenCalled()
|
expect(modalCloseSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should pass remote OCR choice when reprocessing', () => {
|
|
||||||
initNormally()
|
|
||||||
const reprocessSpy = jest.spyOn(documentService, 'reprocessDocuments')
|
|
||||||
reprocessSpy.mockReturnValue(of(true))
|
|
||||||
let openModal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
|
|
||||||
component.reprocess()
|
|
||||||
openModal.componentInstance.remoteOcr = true
|
|
||||||
openModal.componentInstance.confirmClicked.next()
|
|
||||||
expect(reprocessSpy).toHaveBeenCalledWith({ documents: [doc.id] }, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show error if redo ocr call fails', () => {
|
it('should show error if redo ocr call fails', () => {
|
||||||
initNormally()
|
initNormally()
|
||||||
const reprocessSpy = jest.spyOn(documentService, 'reprocessDocuments')
|
const reprocessSpy = jest.spyOn(documentService, 'reprocessDocuments')
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
|||||||
import * as UTIF from 'utif'
|
import * as UTIF from 'utif'
|
||||||
import { DocumentDetailFieldID } from '../admin/settings/settings.component'
|
import { DocumentDetailFieldID } from '../admin/settings/settings.component'
|
||||||
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
|
||||||
import { ReprocessConfirmDialogComponent } from '../common/confirm-dialog/reprocess-confirm-dialog/reprocess-confirm-dialog.component'
|
|
||||||
import { PasswordRemovalConfirmDialogComponent } from '../common/confirm-dialog/password-removal-confirm-dialog/password-removal-confirm-dialog.component'
|
import { PasswordRemovalConfirmDialogComponent } from '../common/confirm-dialog/password-removal-confirm-dialog/password-removal-confirm-dialog.component'
|
||||||
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
|
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
|
||||||
import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||||
@@ -1403,7 +1402,7 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
reprocess() {
|
reprocess() {
|
||||||
let modal = this.modalService.open(ReprocessConfirmDialogComponent, {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Reprocess confirm`
|
modal.componentInstance.title = $localize`Reprocess confirm`
|
||||||
@@ -1414,10 +1413,7 @@ export class DocumentDetailComponent
|
|||||||
modal.componentInstance.confirmClicked.subscribe(() => {
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled = false
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.reprocessDocuments(
|
.reprocessDocuments({ documents: [this.document().id] })
|
||||||
{ documents: [this.document().id] },
|
|
||||||
modal.componentInstance.remoteOcr
|
|
||||||
)
|
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo(
|
this.toastService.showInfo(
|
||||||
|
|||||||
@@ -1122,7 +1122,6 @@ describe('BulkEditorComponent', () => {
|
|||||||
req.flush(true)
|
req.flush(true)
|
||||||
expect(req.request.body).toEqual({
|
expect(req.request.body).toEqual({
|
||||||
documents: [3, 4],
|
documents: [3, 4],
|
||||||
remote_ocr: false,
|
|
||||||
})
|
})
|
||||||
httpTestingController.match(
|
httpTestingController.match(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true`
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import { ToastService } from 'src/app/services/toast.service'
|
|||||||
import { flattenTags } from 'src/app/utils/flatten-tags'
|
import { flattenTags } from 'src/app/utils/flatten-tags'
|
||||||
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
|
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
|
||||||
import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
|
import { MergeConfirmDialogComponent } from '../../common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
|
||||||
import { ReprocessConfirmDialogComponent } from '../../common/confirm-dialog/reprocess-confirm-dialog/reprocess-confirm-dialog.component'
|
|
||||||
import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
|
import { RotateConfirmDialogComponent } from '../../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
|
||||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||||
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||||
@@ -910,7 +909,7 @@ export class BulkEditorComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
reprocessSelected() {
|
reprocessSelected() {
|
||||||
let modal = this.modalService.open(ReprocessConfirmDialogComponent, {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Reprocess confirm`
|
modal.componentInstance.title = $localize`Reprocess confirm`
|
||||||
@@ -924,10 +923,7 @@ export class BulkEditorComponent
|
|||||||
modal.componentInstance.buttonsEnabled = false
|
modal.componentInstance.buttonsEnabled = false
|
||||||
this.executeDocumentAction(
|
this.executeDocumentAction(
|
||||||
modal,
|
modal,
|
||||||
this.documentService.reprocessDocuments(
|
this.documentService.reprocessDocuments(this.getSelectionQuery())
|
||||||
this.getSelectionQuery(),
|
|
||||||
modal.componentInstance.remoteOcr
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
<form [formGroup]="savedViewsForm" (ngSubmit)="save()">
|
<form [formGroup]="savedViewsForm" (ngSubmit)="save()">
|
||||||
<ul class="list-group mb-3" formGroupName="savedViews">
|
<ul class="list-group mb-3" formGroupName="savedViews">
|
||||||
@for (view of savedViews(); track view) {
|
@for (view of pagedSavedViews(); track view) {
|
||||||
<li class="list-group-item py-3">
|
<li class="list-group-item py-3">
|
||||||
<div [formGroupName]="view.id">
|
<div [formGroupName]="view.id">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -81,6 +81,11 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
|
<div class="d-flex align-items-center mb-3">
|
||||||
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
||||||
|
@if (savedViews()?.length > pageSize) {
|
||||||
|
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="savedViews().length" [page]="page()" [maxSize]="5" (pageChange)="page.set($event)" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'
|
|||||||
import { signal } from '@angular/core'
|
import { signal } from '@angular/core'
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import { By } from '@angular/platform-browser'
|
||||||
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, of, throwError } from 'rxjs'
|
import { Subject, of, throwError } from 'rxjs'
|
||||||
@@ -222,6 +223,44 @@ describe('SavedViewsComponent', () => {
|
|||||||
).toEqual(view.show_on_dashboard)
|
).toEqual(view.show_on_dashboard)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should page saved views, clamp the page if views are removed', () => {
|
||||||
|
const manyViews = Array.from({ length: 30 }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
name: `view${i + 1}`,
|
||||||
|
})) as SavedView[]
|
||||||
|
const listSpy = jest.spyOn(savedViewService, 'list').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: manyViews.map((v) => v.id),
|
||||||
|
count: manyViews.length,
|
||||||
|
results: manyViews.concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
component.ngOnInit()
|
||||||
|
fixture.detectChanges()
|
||||||
|
expect(listSpy).toHaveBeenCalledWith(1, 100000, null, false, {
|
||||||
|
full_perms: true,
|
||||||
|
})
|
||||||
|
expect(component.pagedSavedViews()).toHaveLength(25)
|
||||||
|
expect(fixture.debugElement.query(By.css('ngb-pagination'))).not.toBeNull()
|
||||||
|
// all views have controls, not just the current page
|
||||||
|
expect(
|
||||||
|
Object.keys(component.savedViewsForm.get('savedViews').value)
|
||||||
|
).toHaveLength(30)
|
||||||
|
|
||||||
|
component.page.set(2)
|
||||||
|
expect(component.pagedSavedViews()).toHaveLength(5)
|
||||||
|
|
||||||
|
listSpy.mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: manyViews.slice(0, 25).map((v) => v.id),
|
||||||
|
count: 25,
|
||||||
|
results: manyViews.slice(0, 25),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
component.ngOnInit()
|
||||||
|
expect(component.page()).toEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('should support editing permissions', () => {
|
it('should support editing permissions', () => {
|
||||||
const confirmClicked = new Subject<any>()
|
const confirmClicked = new Subject<any>()
|
||||||
const modalRef = {
|
const modalRef = {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { AsyncPipe } from '@angular/common'
|
import { AsyncPipe } from '@angular/common'
|
||||||
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
computed,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
import { dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs'
|
import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs'
|
||||||
@@ -42,6 +49,7 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
NgbPaginationModule,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -58,6 +66,14 @@ export class SavedViewsComponent
|
|||||||
DisplayMode = DisplayMode
|
DisplayMode = DisplayMode
|
||||||
|
|
||||||
readonly savedViews = signal<SavedView[]>(undefined)
|
readonly savedViews = signal<SavedView[]>(undefined)
|
||||||
|
readonly page = signal(1)
|
||||||
|
public readonly pageSize = 25
|
||||||
|
// All views are loaded at init, so paging is only for display
|
||||||
|
readonly pagedSavedViews = computed(() => {
|
||||||
|
const start = (this.page() - 1) * this.pageSize
|
||||||
|
return this.savedViews()?.slice(start, start + this.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
private savedViewsGroup = new FormGroup({})
|
private savedViewsGroup = new FormGroup({})
|
||||||
public savedViewsForm: FormGroup = new FormGroup({
|
public savedViewsForm: FormGroup = new FormGroup({
|
||||||
savedViews: this.savedViewsGroup,
|
savedViews: this.savedViewsGroup,
|
||||||
@@ -84,9 +100,11 @@ export class SavedViewsComponent
|
|||||||
private reloadViews(): void {
|
private reloadViews(): void {
|
||||||
this.loading.set(true)
|
this.loading.set(true)
|
||||||
this.savedViewService
|
this.savedViewService
|
||||||
.list(null, null, null, false, { full_perms: true })
|
.list(1, 100000, null, false, { full_perms: true })
|
||||||
.subscribe((r) => {
|
.subscribe((r) => {
|
||||||
this.savedViews.set(r.results)
|
this.savedViews.set(r.results)
|
||||||
|
const pageCount = Math.ceil(r.results.length / this.pageSize)
|
||||||
|
this.page.update((page) => Math.min(page, Math.max(1, pageCount)))
|
||||||
this.initialize()
|
this.initialize()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,10 +54,6 @@ export const ConfigCategory = {
|
|||||||
AI: $localize`AI Settings`,
|
AI: $localize`AI Settings`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigSection = {
|
|
||||||
RemoteOCR: $localize`Remote OCR`,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LLMEmbeddingBackendConfig = {
|
export const LLMEmbeddingBackendConfig = {
|
||||||
OPENAI_LIKE: 'openai-like',
|
OPENAI_LIKE: 'openai-like',
|
||||||
HUGGINGFACE: 'huggingface',
|
HUGGINGFACE: 'huggingface',
|
||||||
@@ -69,15 +65,6 @@ export const LLMBackendConfig = {
|
|||||||
OLLAMA: 'ollama',
|
OLLAMA: 'ollama',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RemoteOCREngineConfig = {
|
|
||||||
AZURE_AI: 'azureai',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RemoteOCRModeConfig = {
|
|
||||||
ALWAYS: 'always',
|
|
||||||
WORKFLOW_ONLY: 'workflow_only',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfigOption {
|
export interface ConfigOption {
|
||||||
key: string
|
key: string
|
||||||
title: string
|
title: string
|
||||||
@@ -85,7 +72,6 @@ export interface ConfigOption {
|
|||||||
choices?: Array<{ id: string; name: string }>
|
choices?: Array<{ id: string; name: string }>
|
||||||
config_key?: string
|
config_key?: string
|
||||||
category: string
|
category: string
|
||||||
section?: string
|
|
||||||
note?: string
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,43 +181,6 @@ export const PaperlessConfigOptions: ConfigOption[] = [
|
|||||||
config_key: 'PAPERLESS_OCR_USER_ARGS',
|
config_key: 'PAPERLESS_OCR_USER_ARGS',
|
||||||
category: ConfigCategory.OCR,
|
category: ConfigCategory.OCR,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'remote_ocr_engine',
|
|
||||||
title: $localize`Remote OCR Engine`,
|
|
||||||
type: ConfigOptionType.Select,
|
|
||||||
choices: mapToItems(RemoteOCREngineConfig),
|
|
||||||
config_key: 'PAPERLESS_REMOTE_OCR_ENGINE',
|
|
||||||
category: ConfigCategory.OCR,
|
|
||||||
section: ConfigSection.RemoteOCR,
|
|
||||||
note: $localize`Enabling remote OCR sends documents to a third-party service for processing. Consider the privacy implications as well as potential costs before enabling.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'remote_ocr_api_key',
|
|
||||||
title: $localize`Remote OCR API Key`,
|
|
||||||
type: ConfigOptionType.Password,
|
|
||||||
config_key: 'PAPERLESS_REMOTE_OCR_API_KEY',
|
|
||||||
category: ConfigCategory.OCR,
|
|
||||||
section: ConfigSection.RemoteOCR,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'remote_ocr_endpoint',
|
|
||||||
title: $localize`Remote OCR Endpoint`,
|
|
||||||
type: ConfigOptionType.String,
|
|
||||||
config_key: 'PAPERLESS_REMOTE_OCR_ENDPOINT',
|
|
||||||
category: ConfigCategory.OCR,
|
|
||||||
section: ConfigSection.RemoteOCR,
|
|
||||||
note: $localize`Required when using the Azure AI engine.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'remote_ocr_mode',
|
|
||||||
title: $localize`Remote OCR Mode`,
|
|
||||||
type: ConfigOptionType.Select,
|
|
||||||
choices: mapToItems(RemoteOCRModeConfig),
|
|
||||||
config_key: 'PAPERLESS_REMOTE_OCR_MODE',
|
|
||||||
category: ConfigCategory.OCR,
|
|
||||||
section: ConfigSection.RemoteOCR,
|
|
||||||
note: $localize`Which documents are sent to the remote engine. Use 'workflow_only' to keep remote OCR off unless a workflow enables it for a document.`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'app_logo',
|
key: 'app_logo',
|
||||||
title: $localize`Application Logo`,
|
title: $localize`Application Logo`,
|
||||||
@@ -449,10 +398,6 @@ export interface PaperlessConfig extends ObjectWithId {
|
|||||||
barcode_enable_tag: boolean
|
barcode_enable_tag: boolean
|
||||||
barcode_tag_mapping: object
|
barcode_tag_mapping: object
|
||||||
barcode_tag_split: boolean
|
barcode_tag_split: boolean
|
||||||
remote_ocr_engine: string
|
|
||||||
remote_ocr_api_key: string
|
|
||||||
remote_ocr_endpoint: string
|
|
||||||
remote_ocr_mode: string
|
|
||||||
ai_enabled: boolean
|
ai_enabled: boolean
|
||||||
llm_embedding_backend: string
|
llm_embedding_backend: string
|
||||||
llm_embedding_model: string
|
llm_embedding_model: string
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { PdfEditorEditMode } from '../components/common/pdf-editor/pdf-editor-edit-mode'
|
import { PdfEditorEditMode } from '../components/common/pdf-editor/pdf-editor-edit-mode'
|
||||||
import { PdfZoomScale } from '../components/common/pdf-viewer/pdf-viewer.types'
|
import { PdfZoomScale } from '../components/common/pdf-viewer/pdf-viewer.types'
|
||||||
import { RemoteOCRModeConfig } from './paperless-config'
|
|
||||||
import { User } from './user'
|
import { User } from './user'
|
||||||
|
|
||||||
export interface UiSettings {
|
export interface UiSettings {
|
||||||
@@ -95,8 +94,6 @@ export const SETTINGS_KEYS = {
|
|||||||
OUTLOOK_OAUTH_URL: 'outlook_oauth_url',
|
OUTLOOK_OAUTH_URL: 'outlook_oauth_url',
|
||||||
EMAIL_ENABLED: 'email_enabled',
|
EMAIL_ENABLED: 'email_enabled',
|
||||||
AI_ENABLED: 'ai_enabled',
|
AI_ENABLED: 'ai_enabled',
|
||||||
REMOTE_OCR_CONFIGURED: 'remote_ocr:configured',
|
|
||||||
REMOTE_OCR_MODE: 'remote_ocr:mode',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SETTINGS: UiSetting[] = [
|
export const SETTINGS: UiSetting[] = [
|
||||||
@@ -350,14 +347,4 @@ export const SETTINGS: UiSetting[] = [
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
default: PdfEditorEditMode.Create,
|
default: PdfEditorEditMode.Create,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.REMOTE_OCR_CONFIGURED,
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.REMOTE_OCR_MODE,
|
|
||||||
type: 'string',
|
|
||||||
default: RemoteOCRModeConfig.ALWAYS,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -284,21 +284,6 @@ describe(`DocumentService`, () => {
|
|||||||
expect(req.request.method).toEqual('POST')
|
expect(req.request.method).toEqual('POST')
|
||||||
expect(req.request.body).toEqual({
|
expect(req.request.body).toEqual({
|
||||||
documents: ids,
|
documents: ids,
|
||||||
remote_ocr: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should request remote OCR when reprocessing with it enabled', () => {
|
|
||||||
const ids = [1, 2, 3]
|
|
||||||
subscription = service
|
|
||||||
.reprocessDocuments({ documents: ids }, true)
|
|
||||||
.subscribe()
|
|
||||||
const req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}${endpoint}/reprocess/`
|
|
||||||
)
|
|
||||||
expect(req.request.body).toEqual({
|
|
||||||
documents: ids,
|
|
||||||
remote_ocr: true,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -349,13 +349,9 @@ export class DocumentService extends AbstractPaperlessService<Document> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
reprocessDocuments(
|
reprocessDocuments(selection: DocumentSelectionQuery) {
|
||||||
selection: DocumentSelectionQuery,
|
|
||||||
remoteOcr: boolean = false
|
|
||||||
) {
|
|
||||||
return this.http.post(this.getResourceUrl(null, 'reprocess'), {
|
return this.http.post(this.getResourceUrl(null, 'reprocess'), {
|
||||||
...selection,
|
...selection,
|
||||||
remote_ocr: remoteOcr,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { environment } from 'src/environments/environment'
|
|||||||
import { CustomFieldDataType } from '../data/custom-field'
|
import { CustomFieldDataType } from '../data/custom-field'
|
||||||
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
|
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
|
||||||
import { SavedView } from '../data/saved-view'
|
import { SavedView } from '../data/saved-view'
|
||||||
import { RemoteOCRModeConfig } from '../data/paperless-config'
|
|
||||||
import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings'
|
import { SETTINGS_KEYS, UiSettings } from '../data/ui-settings'
|
||||||
import { PermissionsService } from './permissions.service'
|
import { PermissionsService } from './permissions.service'
|
||||||
import { CustomFieldsService } from './rest/custom-fields.service'
|
import { CustomFieldsService } from './rest/custom-fields.service'
|
||||||
@@ -435,26 +434,4 @@ describe('SettingsService', () => {
|
|||||||
).name
|
).name
|
||||||
).toEqual(customFields[0].name)
|
).toEqual(customFields[0].name)
|
||||||
})
|
})
|
||||||
it('should offer remote OCR only when configured and selective', () => {
|
|
||||||
settingsService.set(SETTINGS_KEYS.REMOTE_OCR_CONFIGURED, false)
|
|
||||||
settingsService.set(
|
|
||||||
SETTINGS_KEYS.REMOTE_OCR_MODE,
|
|
||||||
RemoteOCRModeConfig.WORKFLOW_ONLY
|
|
||||||
)
|
|
||||||
expect(settingsService.remoteOCRIsSelectable).toBeFalsy()
|
|
||||||
|
|
||||||
// configured, but already handling every document
|
|
||||||
settingsService.set(SETTINGS_KEYS.REMOTE_OCR_CONFIGURED, true)
|
|
||||||
settingsService.set(
|
|
||||||
SETTINGS_KEYS.REMOTE_OCR_MODE,
|
|
||||||
RemoteOCRModeConfig.ALWAYS
|
|
||||||
)
|
|
||||||
expect(settingsService.remoteOCRIsSelectable).toBeFalsy()
|
|
||||||
|
|
||||||
settingsService.set(
|
|
||||||
SETTINGS_KEYS.REMOTE_OCR_MODE,
|
|
||||||
RemoteOCRModeConfig.WORKFLOW_ONLY
|
|
||||||
)
|
|
||||||
expect(settingsService.remoteOCRIsSelectable).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
} from 'src/app/utils/color'
|
} from 'src/app/utils/color'
|
||||||
import { DEFAULT_APP_TITLE, environment } from 'src/environments/environment'
|
import { DEFAULT_APP_TITLE, environment } from 'src/environments/environment'
|
||||||
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
|
import { DEFAULT_DISPLAY_FIELDS, DisplayField } from '../data/document'
|
||||||
import { RemoteOCRModeConfig } from '../data/paperless-config'
|
|
||||||
import { SavedView } from '../data/saved-view'
|
import { SavedView } from '../data/saved-view'
|
||||||
import {
|
import {
|
||||||
PAPERLESS_GREEN_HEX,
|
PAPERLESS_GREEN_HEX,
|
||||||
@@ -688,17 +687,6 @@ export class SettingsService {
|
|||||||
return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)
|
return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Offering remote OCR as a choice only makes sense when an engine
|
|
||||||
* is configured but is not already handling every document.
|
|
||||||
*/
|
|
||||||
get remoteOCRIsSelectable(): boolean {
|
|
||||||
return (
|
|
||||||
this.get(SETTINGS_KEYS.REMOTE_OCR_CONFIGURED) &&
|
|
||||||
this.get(SETTINGS_KEYS.REMOTE_OCR_MODE) !== RemoteOCRModeConfig.ALWAYS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
offerTour(): boolean {
|
offerTour(): boolean {
|
||||||
return this.dashboardIsEmpty() && !this.get(SETTINGS_KEYS.TOUR_COMPLETE)
|
return this.dashboardIsEmpty() && !this.get(SETTINGS_KEYS.TOUR_COMPLETE)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2034
-2028
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -394,16 +394,10 @@ def delete(doc_ids: list[int]) -> Literal["OK"]:
|
|||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
||||||
def reprocess(doc_ids: list[int], *, remote_ocr: bool = False) -> Literal["OK"]:
|
def reprocess(doc_ids: list[int]) -> Literal["OK"]:
|
||||||
"""
|
|
||||||
Re-run parsing for the given documents.
|
|
||||||
|
|
||||||
Consumption workflows do not run here, so ``remote_ocr`` is how the user
|
|
||||||
asks for the remote engine when it is not configured to handle everything.
|
|
||||||
"""
|
|
||||||
for document_id in doc_ids:
|
for document_id in doc_ids:
|
||||||
update_document_content_maybe_archive_file.apply_async(
|
update_document_content_maybe_archive_file.apply_async(
|
||||||
kwargs={"document_id": document_id, "remote_ocr": remote_ocr},
|
kwargs={"document_id": document_id},
|
||||||
headers={"trigger_source": PaperlessTask.TriggerSource.MANUAL},
|
headers={"trigger_source": PaperlessTask.TriggerSource.MANUAL},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ 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
|
||||||
from documents.utils import run_subprocess
|
from documents.utils import run_subprocess
|
||||||
from paperless.config import OcrConfig
|
from paperless.config import OcrConfig
|
||||||
from paperless.config import RemoteOCRConfig
|
|
||||||
from paperless.models import ArchiveFileGenerationChoices
|
from paperless.models import ArchiveFileGenerationChoices
|
||||||
from paperless.parsers import ParserContext
|
from paperless.parsers import ParserContext
|
||||||
from paperless.parsers import ParserProtocol
|
from paperless.parsers import ParserProtocol
|
||||||
@@ -452,19 +451,12 @@ class ConsumerPlugin(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Error attempting to clean PDF: {e}")
|
self.log.error(f"Error attempting to clean PDF: {e}")
|
||||||
|
|
||||||
# Workflows have already run at this point, so the metadata knows
|
|
||||||
# whether this document was singled out for remote OCR
|
|
||||||
allow_remote = (
|
|
||||||
self.metadata.remote_ocr or RemoteOCRConfig().remote_ocr_by_default
|
|
||||||
)
|
|
||||||
|
|
||||||
# Based on the mime type, get the parser for that type
|
# Based on the mime type, get the parser for that type
|
||||||
parser_class: type[ParserProtocol] | None = (
|
parser_class: type[ParserProtocol] | None = (
|
||||||
get_parser_registry().get_parser_for_file(
|
get_parser_registry().get_parser_for_file(
|
||||||
mime_type,
|
mime_type,
|
||||||
self.filename,
|
self.filename,
|
||||||
self.working_copy,
|
self.working_copy,
|
||||||
allow_remote=allow_remote,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not parser_class:
|
if not parser_class:
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class DocumentMetadataOverrides:
|
|||||||
skip_asn_if_exists: bool = False
|
skip_asn_if_exists: bool = False
|
||||||
version_label: str | None = None
|
version_label: str | None = None
|
||||||
actor_id: int | None = None
|
actor_id: int | None = None
|
||||||
remote_ocr: bool = False
|
|
||||||
|
|
||||||
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
|
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
|
||||||
"""
|
"""
|
||||||
@@ -58,8 +57,6 @@ class DocumentMetadataOverrides:
|
|||||||
self.actor_id = other.actor_id
|
self.actor_id = other.actor_id
|
||||||
if other.skip_asn_if_exists:
|
if other.skip_asn_if_exists:
|
||||||
self.skip_asn_if_exists = True
|
self.skip_asn_if_exists = True
|
||||||
if other.remote_ocr:
|
|
||||||
self.remote_ocr = True
|
|
||||||
if other.version_label is not None:
|
if other.version_label is not None:
|
||||||
self.version_label = other.version_label
|
self.version_label = other.version_label
|
||||||
|
|
||||||
|
|||||||
@@ -1744,7 +1744,7 @@ class DeleteDocumentsSerializer(DocumentSelectionSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ReprocessDocumentsSerializer(DocumentSelectionSerializer):
|
class ReprocessDocumentsSerializer(DocumentSelectionSerializer):
|
||||||
remote_ocr = serializers.BooleanField(required=False, default=False)
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BulkEditSerializer(
|
class BulkEditSerializer(
|
||||||
@@ -2086,13 +2086,6 @@ class BulkEditSerializer(
|
|||||||
f"Page {op['page']} is out of bounds for document with {doc.page_count} pages.",
|
f"Page {op['page']} is out of bounds for document with {doc.page_count} pages.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _validate_parameters_reprocess(self, parameters) -> None:
|
|
||||||
if "remote_ocr" in parameters:
|
|
||||||
if not isinstance(parameters["remote_ocr"], bool):
|
|
||||||
raise serializers.ValidationError("remote_ocr must be a boolean")
|
|
||||||
else:
|
|
||||||
parameters["remote_ocr"] = False
|
|
||||||
|
|
||||||
def validate_parameters_remove_password(self, parameters):
|
def validate_parameters_remove_password(self, parameters):
|
||||||
if "password" not in parameters:
|
if "password" not in parameters:
|
||||||
raise serializers.ValidationError("password not specified")
|
raise serializers.ValidationError("password not specified")
|
||||||
@@ -2157,8 +2150,6 @@ class BulkEditSerializer(
|
|||||||
self._validate_parameters_edit_pdf(parameters, attrs["documents"][0])
|
self._validate_parameters_edit_pdf(parameters, attrs["documents"][0])
|
||||||
elif method == bulk_edit.remove_password:
|
elif method == bulk_edit.remove_password:
|
||||||
self.validate_parameters_remove_password(parameters)
|
self.validate_parameters_remove_password(parameters)
|
||||||
elif method == bulk_edit.reprocess:
|
|
||||||
self._validate_parameters_reprocess(parameters)
|
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@@ -3222,6 +3213,13 @@ class WorkflowActionSerializer(serializers.ModelSerializer[WorkflowAction]):
|
|||||||
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
|
{"assign_title": f'Invalid f-string detected: "{e.args[0]}"'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if attrs.get("assign_custom_fields_values"):
|
||||||
|
# Empty strings treated as None to avoid unexpected behavior
|
||||||
|
attrs["assign_custom_fields_values"] = {
|
||||||
|
field_id: (None if value == "" else value)
|
||||||
|
for field_id, value in attrs["assign_custom_fields_values"].items()
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"type" in attrs
|
"type" in attrs
|
||||||
and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL
|
and attrs["type"] == WorkflowAction.WorkflowActionType.EMAIL
|
||||||
|
|||||||
+1
-10
@@ -66,7 +66,6 @@ from documents.utils import compute_checksum
|
|||||||
from documents.utils import identity
|
from documents.utils import identity
|
||||||
from documents.workflows.utils import get_workflows_for_trigger
|
from documents.workflows.utils import get_workflows_for_trigger
|
||||||
from paperless.config import AIConfig
|
from paperless.config import AIConfig
|
||||||
from paperless.config import RemoteOCRConfig
|
|
||||||
from paperless.logging import consume_task_id
|
from paperless.logging import consume_task_id
|
||||||
from paperless.parsers import ParserContext
|
from paperless.parsers import ParserContext
|
||||||
from paperless.parsers.registry import get_parser_registry
|
from paperless.parsers.registry import get_parser_registry
|
||||||
@@ -338,17 +337,10 @@ def bulk_update_documents(document_ids) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def update_document_content_maybe_archive_file(
|
def update_document_content_maybe_archive_file(document_id) -> None:
|
||||||
document_id,
|
|
||||||
*,
|
|
||||||
remote_ocr: bool = False,
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Re-creates OCR content and thumbnail for a document, and archive file if
|
Re-creates OCR content and thumbnail for a document, and archive file if
|
||||||
it exists.
|
it exists.
|
||||||
|
|
||||||
Remote OCR is used only when the engine is configured to handle everything
|
|
||||||
or if explicitly asked for via ``remote_ocr``.
|
|
||||||
"""
|
"""
|
||||||
document = Document.objects.get(id=document_id)
|
document = Document.objects.get(id=document_id)
|
||||||
|
|
||||||
@@ -358,7 +350,6 @@ def update_document_content_maybe_archive_file(
|
|||||||
mime_type,
|
mime_type,
|
||||||
document.original_filename or "",
|
document.original_filename or "",
|
||||||
document.source_path,
|
document.source_path,
|
||||||
allow_remote=remote_ocr or RemoteOCRConfig().remote_ocr_by_default,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not parser_class:
|
if not parser_class:
|
||||||
|
|||||||
@@ -72,10 +72,6 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
|||||||
"barcode_enable_tag": None,
|
"barcode_enable_tag": None,
|
||||||
"barcode_tag_mapping": None,
|
"barcode_tag_mapping": None,
|
||||||
"barcode_tag_split": None,
|
"barcode_tag_split": None,
|
||||||
"remote_ocr_engine": None,
|
|
||||||
"remote_ocr_api_key": None,
|
|
||||||
"remote_ocr_endpoint": None,
|
|
||||||
"remote_ocr_mode": None,
|
|
||||||
"ai_enabled": False,
|
"ai_enabled": False,
|
||||||
"llm_embedding_backend": None,
|
"llm_embedding_backend": None,
|
||||||
"llm_embedding_model": None,
|
"llm_embedding_model": None,
|
||||||
@@ -874,49 +870,6 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
|||||||
config.refresh_from_db()
|
config.refresh_from_db()
|
||||||
self.assertEqual(config.llm_api_key, None)
|
self.assertEqual(config.llm_api_key, None)
|
||||||
|
|
||||||
def test_update_remote_ocr_api_key(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Existing config with remote_ocr_api_key specified
|
|
||||||
WHEN:
|
|
||||||
- API to update remote_ocr_api_key is called with all *s
|
|
||||||
- API to update remote_ocr_api_key is called with empty string
|
|
||||||
THEN:
|
|
||||||
- remote_ocr_api_key is unchanged
|
|
||||||
- remote_ocr_api_key is set to None
|
|
||||||
"""
|
|
||||||
config = ApplicationConfiguration.objects.first()
|
|
||||||
assert config is not None
|
|
||||||
config.remote_ocr_api_key = "1234567890"
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
# Test with all *
|
|
||||||
response = self.client.patch(
|
|
||||||
f"{self.ENDPOINT}1/",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"remote_ocr_api_key": "*" * 32,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
config.refresh_from_db()
|
|
||||||
self.assertEqual(config.remote_ocr_api_key, "1234567890")
|
|
||||||
# Test with empty string
|
|
||||||
response = self.client.patch(
|
|
||||||
f"{self.ENDPOINT}1/",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"remote_ocr_api_key": "",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
config.refresh_from_db()
|
|
||||||
self.assertEqual(config.remote_ocr_api_key, None)
|
|
||||||
|
|
||||||
def test_enable_ai_index_triggers_update(self) -> None:
|
def test_enable_ai_index_triggers_update(self) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
|||||||
@@ -532,29 +532,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
m.assert_called_once()
|
m.assert_called_once()
|
||||||
args, kwargs = m.call_args
|
args, kwargs = m.call_args
|
||||||
self.assertEqual(args[0], [self.doc1.id])
|
self.assertEqual(args[0], [self.doc1.id])
|
||||||
self.assertEqual(kwargs, {"remote_ocr": False})
|
self.assertEqual(len(kwargs), 0)
|
||||||
|
|
||||||
@mock.patch("documents.views.bulk_edit.reprocess")
|
|
||||||
def test_reprocess_documents_endpoint_remote_ocr(self, m) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- API data to reprocess a document with remote OCR requested
|
|
||||||
WHEN:
|
|
||||||
- API is called
|
|
||||||
THEN:
|
|
||||||
- reprocess is called with remote_ocr=True
|
|
||||||
"""
|
|
||||||
self.setup_mock(m, "reprocess")
|
|
||||||
response = self.client.post(
|
|
||||||
"/api/documents/reprocess/",
|
|
||||||
json.dumps({"documents": [self.doc1.id], "remote_ocr": True}),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
m.assert_called_once()
|
|
||||||
args, kwargs = m.call_args
|
|
||||||
self.assertEqual(args[0], [self.doc1.id])
|
|
||||||
self.assertEqual(kwargs, {"remote_ocr": True})
|
|
||||||
|
|
||||||
@mock.patch("documents.serialisers.bulk_edit.set_storage_path")
|
@mock.patch("documents.serialisers.bulk_edit.set_storage_path")
|
||||||
def test_api_set_storage_path(self, m) -> None:
|
def test_api_set_storage_path(self, m) -> None:
|
||||||
@@ -1575,29 +1553,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_legacy_bulk_edit_reprocess_invalid_remote_ocr(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- The deprecated bulk_edit endpoint with a non-boolean remote_ocr
|
|
||||||
WHEN:
|
|
||||||
- API is called
|
|
||||||
THEN:
|
|
||||||
- The request is rejected rather than passed through to the task
|
|
||||||
"""
|
|
||||||
response = self.client.post(
|
|
||||||
"/api/documents/bulk_edit/",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"documents": [self.doc1.id],
|
|
||||||
"method": "reprocess",
|
|
||||||
"parameters": {"remote_ocr": "yes please"},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
@mock.patch("documents.views.bulk_edit.edit_pdf")
|
@mock.patch("documents.views.bulk_edit.edit_pdf")
|
||||||
def test_edit_pdf(self, m) -> None:
|
def test_edit_pdf(self, m) -> None:
|
||||||
self.setup_mock(m, "edit_pdf")
|
self.setup_mock(m, "edit_pdf")
|
||||||
|
|||||||
@@ -60,10 +60,6 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
|
|||||||
},
|
},
|
||||||
"email_enabled": False,
|
"email_enabled": False,
|
||||||
"ai_enabled": False,
|
"ai_enabled": False,
|
||||||
"remote_ocr": {
|
|
||||||
"configured": False,
|
|
||||||
"mode": "always",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -158,50 +154,6 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
|
|||||||
str(response.data["settings"]),
|
str(response.data["settings"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
REMOTE_OCR_ENGINE="azureai",
|
|
||||||
REMOTE_OCR_API_KEY="somekey",
|
|
||||||
REMOTE_OCR_ENDPOINT="https://example.cognitiveservices.azure.com",
|
|
||||||
REMOTE_OCR_MODE="workflow_only",
|
|
||||||
)
|
|
||||||
def test_settings_reports_remote_ocr_when_configured(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A fully configured remote OCR engine in workflow_only mode
|
|
||||||
WHEN:
|
|
||||||
- The ui_settings endpoint is called
|
|
||||||
THEN:
|
|
||||||
- The UI is told remote OCR is available and selective, so it can
|
|
||||||
offer it where it would actually change something
|
|
||||||
"""
|
|
||||||
response = self.client.get(self.ENDPOINT, format="json")
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(
|
|
||||||
response.data["settings"]["remote_ocr"],
|
|
||||||
{"configured": True, "mode": "workflow_only"},
|
|
||||||
)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
REMOTE_OCR_ENGINE="azureai",
|
|
||||||
REMOTE_OCR_API_KEY=None,
|
|
||||||
REMOTE_OCR_ENDPOINT=None,
|
|
||||||
)
|
|
||||||
def test_settings_reports_remote_ocr_incompletely_configured(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- An engine named but missing its endpoint and API key
|
|
||||||
WHEN:
|
|
||||||
- The ui_settings endpoint is called
|
|
||||||
THEN:
|
|
||||||
- It is reported as not configured, matching what the parser
|
|
||||||
registry will actually do
|
|
||||||
"""
|
|
||||||
response = self.client.get(self.ENDPOINT, format="json")
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertFalse(response.data["settings"]["remote_ocr"]["configured"])
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
OAUTH_CALLBACK_BASE_URL="http://localhost:8000",
|
OAUTH_CALLBACK_BASE_URL="http://localhost:8000",
|
||||||
GMAIL_OAUTH_CLIENT_ID="abc123",
|
GMAIL_OAUTH_CLIENT_ID="abc123",
|
||||||
|
|||||||
@@ -422,6 +422,11 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
|||||||
json.dumps(
|
json.dumps(
|
||||||
{
|
{
|
||||||
"assign_title": "",
|
"assign_title": "",
|
||||||
|
"assign_custom_fields": [self.cf1.id, self.cf2.id],
|
||||||
|
"assign_custom_fields_values": {
|
||||||
|
str(self.cf1.id): "",
|
||||||
|
str(self.cf2.id): 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
@@ -429,6 +434,10 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
action = WorkflowAction.objects.get(id=response.data["id"])
|
action = WorkflowAction.objects.get(id=response.data["id"])
|
||||||
self.assertIsNone(action.assign_title)
|
self.assertIsNone(action.assign_title)
|
||||||
|
self.assertEqual(
|
||||||
|
action.assign_custom_fields_values,
|
||||||
|
{str(self.cf1.id): None, str(self.cf2.id): 0},
|
||||||
|
)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.ENDPOINT_TRIGGERS,
|
self.ENDPOINT_TRIGGERS,
|
||||||
|
|||||||
@@ -1782,56 +1782,3 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
self.assertIn("wrong password", str(exc.exception))
|
self.assertIn("wrong password", str(exc.exception))
|
||||||
self.assertIn("Error removing password from document", cm.output[0])
|
self.assertIn("Error removing password from document", cm.output[0])
|
||||||
|
|
||||||
|
|
||||||
class TestBulkEditReprocess(DirectoriesMixin, TestCase):
|
|
||||||
def setUp(self) -> None:
|
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
self.doc = Document.objects.create(
|
|
||||||
title="test",
|
|
||||||
checksum="A",
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file")
|
|
||||||
def test_reprocess_defaults_to_local(self, mock_task: mock.Mock) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A reprocess request that says nothing about remote OCR
|
|
||||||
WHEN:
|
|
||||||
- reprocess is called
|
|
||||||
THEN:
|
|
||||||
- The task is queued without asking for the remote engine
|
|
||||||
"""
|
|
||||||
result = bulk_edit.reprocess([self.doc.id])
|
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
|
||||||
mock_task.apply_async.assert_called_once()
|
|
||||||
_, kwargs = mock_task.apply_async.call_args
|
|
||||||
self.assertEqual(
|
|
||||||
kwargs["kwargs"],
|
|
||||||
{"document_id": self.doc.id, "remote_ocr": False},
|
|
||||||
)
|
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file")
|
|
||||||
def test_reprocess_passes_remote_ocr(self, mock_task: mock.Mock) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A reprocess request that explicitly asks for remote OCR
|
|
||||||
WHEN:
|
|
||||||
- reprocess is called
|
|
||||||
THEN:
|
|
||||||
- The request is forwarded to the task for every document
|
|
||||||
"""
|
|
||||||
other = Document.objects.create(
|
|
||||||
title="test2",
|
|
||||||
checksum="B",
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
bulk_edit.reprocess([self.doc.id, other.id], remote_ocr=True)
|
|
||||||
|
|
||||||
self.assertEqual(mock_task.apply_async.call_count, 2)
|
|
||||||
for call in mock_task.apply_async.call_args_list:
|
|
||||||
self.assertTrue(call.kwargs["kwargs"]["remote_ocr"])
|
|
||||||
|
|||||||
@@ -1559,72 +1559,6 @@ class PostConsumeTestCase(DirectoriesMixin, GetConsumerMixin, TestCase):
|
|||||||
consumer.run_post_consume_script(doc)
|
consumer.run_post_consume_script(doc)
|
||||||
|
|
||||||
|
|
||||||
class TestConsumerRemoteOCR(
|
|
||||||
DirectoriesMixin,
|
|
||||||
FileSystemAssertsMixin,
|
|
||||||
GetConsumerMixin,
|
|
||||||
TestCase,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
The consumer resolves the remote OCR mode and the per-document request from
|
|
||||||
workflows into the allow_remote flag it hands to the parser registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self) -> None:
|
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
patcher = mock.patch("documents.consumer.get_parser_registry")
|
|
||||||
self.mock_registry = patcher.start()
|
|
||||||
self.mock_registry.return_value.get_parser_for_file.return_value = DummyParser
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
|
|
||||||
def _consume(self, *, overrides: DocumentMetadataOverrides | None = None) -> bool:
|
|
||||||
src = (
|
|
||||||
Path(__file__).parent
|
|
||||||
/ "samples"
|
|
||||||
/ "documents"
|
|
||||||
/ "originals"
|
|
||||||
/ "0000001.pdf"
|
|
||||||
)
|
|
||||||
dst = self.dirs.scratch_dir / "sample.pdf"
|
|
||||||
shutil.copy(src, dst)
|
|
||||||
|
|
||||||
with self.get_consumer(dst, overrides=overrides) as consumer:
|
|
||||||
consumer.run()
|
|
||||||
|
|
||||||
_, kwargs = self.mock_registry.return_value.get_parser_for_file.call_args
|
|
||||||
return kwargs["allow_remote"]
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="always")
|
|
||||||
def test_always_mode_allows_remote(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN: Remote OCR mode is 'always'.
|
|
||||||
WHEN: A document is consumed without any workflow asking for it.
|
|
||||||
THEN: The registry is allowed to pick the remote parser.
|
|
||||||
"""
|
|
||||||
self.assertTrue(self._consume())
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="workflow_only")
|
|
||||||
def test_workflow_only_mode_denies_remote_by_default(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN: Remote OCR mode is 'workflow_only'.
|
|
||||||
WHEN: A document is consumed and nothing asked for remote OCR.
|
|
||||||
THEN: The remote parser is excluded.
|
|
||||||
"""
|
|
||||||
self.assertFalse(self._consume())
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="workflow_only")
|
|
||||||
def test_workflow_only_mode_allows_remote_when_requested(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN: Remote OCR mode is 'workflow_only'.
|
|
||||||
WHEN: A workflow set remote_ocr on the metadata overrides.
|
|
||||||
THEN: The registry is allowed to pick the remote parser.
|
|
||||||
"""
|
|
||||||
self.assertTrue(
|
|
||||||
self._consume(overrides=DocumentMetadataOverrides(remote_ocr=True)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestMetadataOverrides(TestCase):
|
class TestMetadataOverrides(TestCase):
|
||||||
def test_update_skip_asn_if_exists(self) -> None:
|
def test_update_skip_asn_if_exists(self) -> None:
|
||||||
base = DocumentMetadataOverrides()
|
base = DocumentMetadataOverrides()
|
||||||
@@ -1632,20 +1566,6 @@ class TestMetadataOverrides(TestCase):
|
|||||||
base.update(incoming)
|
base.update(incoming)
|
||||||
self.assertTrue(base.skip_asn_if_exists)
|
self.assertTrue(base.skip_asn_if_exists)
|
||||||
|
|
||||||
def test_update_remote_ocr(self) -> None:
|
|
||||||
base = DocumentMetadataOverrides()
|
|
||||||
base.update(DocumentMetadataOverrides(remote_ocr=True))
|
|
||||||
self.assertTrue(base.remote_ocr)
|
|
||||||
|
|
||||||
def test_update_remote_ocr_is_not_unset(self) -> None:
|
|
||||||
"""
|
|
||||||
A later workflow that says nothing must not undo an earlier one that
|
|
||||||
asked for remote OCR.
|
|
||||||
"""
|
|
||||||
base = DocumentMetadataOverrides(remote_ocr=True)
|
|
||||||
base.update(DocumentMetadataOverrides())
|
|
||||||
self.assertTrue(base.remote_ocr)
|
|
||||||
|
|
||||||
def test_update_actor_and_version_label(self) -> None:
|
def test_update_actor_and_version_label(self) -> None:
|
||||||
base = DocumentMetadataOverrides(
|
base = DocumentMetadataOverrides(
|
||||||
actor_id=1,
|
actor_id=1,
|
||||||
|
|||||||
@@ -287,45 +287,6 @@ class TestUpdateContent(DirectoriesMixin, TestCase):
|
|||||||
self.assertNotEqual(Document.objects.get(pk=doc.pk).content, "test")
|
self.assertNotEqual(Document.objects.get(pk=doc.pk).content, "test")
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateContentRemoteOCR(DirectoriesMixin, TestCase):
|
|
||||||
"""
|
|
||||||
Consumption workflows do not run on reprocess, so the remote parser is
|
|
||||||
used only in 'always' mode or when the caller explicitly asks for it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self) -> None:
|
|
||||||
super().setUp()
|
|
||||||
|
|
||||||
patcher = mock.patch("documents.tasks.get_parser_registry")
|
|
||||||
self.mock_registry = patcher.start()
|
|
||||||
self.mock_registry.return_value.get_parser_for_file.return_value = None
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
|
|
||||||
self.doc = Document.objects.create(
|
|
||||||
title="test",
|
|
||||||
content="my document",
|
|
||||||
checksum="wow",
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _allow_remote(self, **kwargs) -> bool:
|
|
||||||
tasks.update_document_content_maybe_archive_file(self.doc.pk, **kwargs)
|
|
||||||
_, call_kwargs = self.mock_registry.return_value.get_parser_for_file.call_args
|
|
||||||
return call_kwargs["allow_remote"]
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="always")
|
|
||||||
def test_always_mode_allows_remote(self) -> None:
|
|
||||||
self.assertTrue(self._allow_remote())
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="workflow_only")
|
|
||||||
def test_workflow_only_mode_denies_remote_by_default(self) -> None:
|
|
||||||
self.assertFalse(self._allow_remote())
|
|
||||||
|
|
||||||
@override_settings(REMOTE_OCR_MODE="workflow_only")
|
|
||||||
def test_workflow_only_mode_allows_remote_when_requested(self) -> None:
|
|
||||||
self.assertTrue(self._allow_remote(remote_ocr=True))
|
|
||||||
|
|
||||||
|
|
||||||
class TestAIIndex(DirectoriesMixin, TestCase):
|
class TestAIIndex(DirectoriesMixin, TestCase):
|
||||||
@override_settings(
|
@override_settings(
|
||||||
AI_ENABLED=True,
|
AI_ENABLED=True,
|
||||||
|
|||||||
@@ -2000,6 +2000,55 @@ class TestWorkflows(
|
|||||||
r"Doc added in \w{3,}",
|
r"Doc added in \w{3,}",
|
||||||
) # Match any 3-letter month name
|
) # Match any 3-letter month name
|
||||||
|
|
||||||
|
def test_document_updated_workflow_existing_custom_field_empty_value(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing workflow with UPDATED trigger and action that assigns a custom field
|
||||||
|
with an empty value
|
||||||
|
WHEN:
|
||||||
|
- Document is updated that already contains the field with a value
|
||||||
|
THEN:
|
||||||
|
- The existing value is left untouched, see GH #13627
|
||||||
|
"""
|
||||||
|
trigger = WorkflowTrigger.objects.create(
|
||||||
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
||||||
|
filter_has_document_type=self.dt,
|
||||||
|
)
|
||||||
|
action = WorkflowAction.objects.create()
|
||||||
|
action.assign_custom_fields.add(self.cf1)
|
||||||
|
action.assign_custom_fields_values = {self.cf1.pk: ""}
|
||||||
|
action.save()
|
||||||
|
w = Workflow.objects.create(
|
||||||
|
name="Workflow 1",
|
||||||
|
order=0,
|
||||||
|
)
|
||||||
|
w.triggers.add(trigger)
|
||||||
|
w.actions.add(action)
|
||||||
|
w.save()
|
||||||
|
|
||||||
|
doc = Document.objects.create(
|
||||||
|
title="sample test",
|
||||||
|
correspondent=self.c,
|
||||||
|
original_filename="sample.pdf",
|
||||||
|
)
|
||||||
|
CustomFieldInstance.objects.create(
|
||||||
|
document=doc,
|
||||||
|
field=self.cf1,
|
||||||
|
value_text="existing value",
|
||||||
|
)
|
||||||
|
|
||||||
|
superuser = User.objects.create_superuser("superuser")
|
||||||
|
self.client.force_authenticate(user=superuser)
|
||||||
|
|
||||||
|
self.client.patch(
|
||||||
|
f"/api/documents/{doc.id}/",
|
||||||
|
{"document_type": self.dt.id},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.refresh_from_db()
|
||||||
|
self.assertEqual(doc.custom_fields.get(field=self.cf1).value, "existing value")
|
||||||
|
|
||||||
def test_document_updated_workflow_existing_custom_field(self) -> None:
|
def test_document_updated_workflow_existing_custom_field(self) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
|||||||
@@ -236,10 +236,8 @@ from paperless import version
|
|||||||
from paperless.celery import app as celery_app
|
from paperless.celery import app as celery_app
|
||||||
from paperless.config import AIConfig
|
from paperless.config import AIConfig
|
||||||
from paperless.config import GeneralConfig
|
from paperless.config import GeneralConfig
|
||||||
from paperless.config import RemoteOCRConfig
|
|
||||||
from paperless.models import ApplicationConfiguration
|
from paperless.models import ApplicationConfiguration
|
||||||
from paperless.parsers.registry import get_parser_registry
|
from paperless.parsers.registry import get_parser_registry
|
||||||
from paperless.parsers.remote import RemoteEngineConfig
|
|
||||||
from paperless.serialisers import GroupSerializer
|
from paperless.serialisers import GroupSerializer
|
||||||
from paperless.serialisers import UserSerializer
|
from paperless.serialisers import UserSerializer
|
||||||
from paperless.views import StandardPagination
|
from paperless.views import StandardPagination
|
||||||
@@ -2269,7 +2267,7 @@ class ChatStreamingView(GenericAPIView[Any]):
|
|||||||
if not has_perms_owner_aware(request.user, "view_document", document):
|
if not has_perms_owner_aware(request.user, "view_document", document):
|
||||||
return HttpResponseForbidden("Insufficient permissions")
|
return HttpResponseForbidden("Insufficient permissions")
|
||||||
|
|
||||||
documents = [document]
|
documents = Document.objects.filter(pk=document.pk)
|
||||||
else:
|
else:
|
||||||
documents = Document.objects.filter(
|
documents = Document.objects.filter(
|
||||||
id__in=permitted_document_ids(request.user),
|
id__in=permitted_document_ids(request.user),
|
||||||
@@ -4012,11 +4010,6 @@ class UiSettingsView(GenericAPIView[Any]):
|
|||||||
|
|
||||||
ui_settings["auditlog_enabled"] = settings.AUDIT_LOG_ENABLED
|
ui_settings["auditlog_enabled"] = settings.AUDIT_LOG_ENABLED
|
||||||
|
|
||||||
ui_settings["remote_ocr"] = {
|
|
||||||
"configured": RemoteEngineConfig.from_app_config().engine_is_valid(),
|
|
||||||
"mode": RemoteOCRConfig().remote_ocr_mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
if settings.GMAIL_OAUTH_ENABLED or settings.OUTLOOK_OAUTH_ENABLED:
|
if settings.GMAIL_OAUTH_ENABLED or settings.OUTLOOK_OAUTH_ENABLED:
|
||||||
manager = PaperlessMailOAuth2Manager()
|
manager = PaperlessMailOAuth2Manager()
|
||||||
if settings.GMAIL_OAUTH_ENABLED:
|
if settings.GMAIL_OAUTH_ENABLED:
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ def apply_assignment_to_document(
|
|||||||
field=field,
|
field=field,
|
||||||
document=document,
|
document=document,
|
||||||
).first()
|
).first()
|
||||||
if instance and args[value_field_name] is not None:
|
# empty string is indistinguishable from no value in the UI
|
||||||
|
if instance and args[value_field_name] not in (None, ""):
|
||||||
setattr(instance, value_field_name, args[value_field_name])
|
setattr(instance, value_field_name, args[value_field_name])
|
||||||
instance.save()
|
instance.save()
|
||||||
elif not instance:
|
elif not instance:
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Afrikaans\n"
|
"Language-Team: Afrikaans\n"
|
||||||
"Language: af_ZA\n"
|
"Language: af_ZA\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumente"
|
msgstr "Dokumente"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Waarde moet geldige JSON wees."
|
msgstr "Waarde moet geldige JSON wees."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Ongeldige gepasmaakte veldnavraaguitdrukking"
|
msgstr "Ongeldige gepasmaakte veldnavraaguitdrukking"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Ongeldige uitdrukking lys. Moet nie leeg wees nie."
|
msgstr "Ongeldige uitdrukking lys. Moet nie leeg wees nie."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Ongeldige logiese uitdrukking {op!r}"
|
msgstr "Ongeldige logiese uitdrukking {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr ""
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Ongeldige kleur."
|
msgstr "Ongeldige kleur."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Lêertipe %(type)s word nie ondersteun nie"
|
msgstr "Lêertipe %(type)s word nie ondersteun nie"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Ongeldige veranderlike bespeur."
|
msgstr "Ongeldige veranderlike bespeur."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1634,36 +1634,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Amharic\n"
|
"Language-Team: Amharic\n"
|
||||||
"Language: am_ET\n"
|
"Language: am_ET\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "መዝገባት"
|
msgstr "መዝገባት"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "የሚሰራው እሴት \"JSON\" መሆን አለበት"
|
msgstr "የሚሰራው እሴት \"JSON\" መሆን አለበት"
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "ልክ ያልሆነ የተወሰነ የቦታ መጠይቅ አገላለጽ"
|
msgstr "ልክ ያልሆነ የተወሰነ የቦታ መጠይቅ አገላለጽ"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "ልክ ያልሆነ የመግለጫ ዝርዝር። ባዶ መሆን የለበትም።"
|
msgstr "ልክ ያልሆነ የመግለጫ ዝርዝር። ባዶ መሆን የለበትም።"
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "ልክ ያልሆነ የሎጂክ ኦፕሬተር {op!r}"
|
msgstr "ልክ ያልሆነ የሎጂክ ኦፕሬተር {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "ከፍተኛው የጥያቄ ሁኔታዎች/መጠን ብዛት አልፏል።"
|
msgstr "ከፍተኛው የጥያቄ ሁኔታዎች/መጠን ብዛት አልፏል።"
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} ይሄ ታዐማኒነት ያለው ልማድ አይደለም።"
|
msgstr "{name!r} ይሄ ታዐማኒነት ያለው ልማድ አይደለም።"
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "ጥያቄን አይደግፍም expr {expr!r}."
|
msgstr "ጥያቄን አይደግፍም expr {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "ከፍተኛው የጥገኝነት ጥልቀት አልፏል።"
|
msgstr "ከፍተኛው የጥገኝነት ጥልቀት አልፏል።"
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "ይህ ልማድ አልተገኘም"
|
msgstr "ይህ ልማድ አልተገኘም"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr ""
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1634,36 +1634,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
"Language: ar_SA\n"
|
"Language: ar_SA\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "المستندات"
|
msgstr "المستندات"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "يجب أن تكون القيمة JSON."
|
msgstr "يجب أن تكون القيمة JSON."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "تعبير استعلام غير صالح للحقول المخصصة"
|
msgstr "تعبير استعلام غير صالح للحقول المخصصة"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "قائمة عبارة خاطئة."
|
msgstr "قائمة عبارة خاطئة."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "تجاوز الحد الأقصى لعدد شروط الاستعلام."
|
msgstr "تجاوز الحد الأقصى لعدد شروط الاستعلام."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} حقل مخصص غير صالح."
|
msgstr "{name!r} حقل مخصص غير صالح."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} لا يدعم تعبير الاستعلام {expr!r}."
|
msgstr "{data_type} لا يدعم تعبير الاستعلام {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "لم يتم العثور على حقل مخصص"
|
msgstr "لم يتم العثور على حقل مخصص"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr ""
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "لون خاطئ."
|
msgstr "لون خاطئ."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "نوع الملف %(type)s غير مدعوم"
|
msgstr "نوع الملف %(type)s غير مدعوم"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "اكتشاف متغير خاطئ."
|
msgstr "اكتشاف متغير خاطئ."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1635,36 +1635,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Belarusian\n"
|
"Language-Team: Belarusian\n"
|
||||||
"Language: be_BY\n"
|
"Language: be_BY\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Дакументы"
|
msgstr "Дакументы"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr ""
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Няправільны колер."
|
msgstr "Няправільны колер."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Тып файла %(type)s не падтрымліваецца"
|
msgstr "Тып файла %(type)s не падтрымліваецца"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Выяўлена няправільная зменная."
|
msgstr "Выяўлена няправільная зменная."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1634,36 +1634,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Language: bg_BG\n"
|
"Language: bg_BG\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Документи"
|
msgstr "Документи"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Стойността трябва да е валидна JSON."
|
msgstr "Стойността трябва да е валидна JSON."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Невалидна заявка на персонализираното полето"
|
msgstr "Невалидна заявка на персонализираното полето"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Списък с невалиден израз. Не може да е празно."
|
msgstr "Списък с невалиден израз. Не може да е празно."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Невалиден логически оператор {op!r}"
|
msgstr "Невалиден логически оператор {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Надвишен е максимален брой за заявки."
|
msgstr "Надвишен е максимален брой за заявки."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} не е валидно персонализирано поле."
|
msgstr "{name!r} не е валидно персонализирано поле."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} не поддържа заявка expr {expr!r}."
|
msgstr "{data_type} не поддържа заявка expr {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Надвишена е максималната дълбочина на вмъкване."
|
msgstr "Надвишена е максималната дълбочина на вмъкване."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Персонализирано поле не е намерено"
|
msgstr "Персонализирано поле не е намерено"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "стартиране на работния процес"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "стартиране на работните процеси"
|
msgstr "стартиране на работните процеси"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Невалиден цвят."
|
msgstr "Невалиден цвят."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Файловия тип %(type)s не се поддържа"
|
msgstr "Файловия тип %(type)s не се поддържа"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Засечена е невалидна променлива."
|
msgstr "Засечена е невалидна променлива."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1635,36 +1635,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-12 12:23\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Catalan\n"
|
"Language-Team: Catalan\n"
|
||||||
"Language: ca_ES\n"
|
"Language: ca_ES\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documents "
|
msgstr "Documents "
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Valor ha de ser un JSON valid."
|
msgstr "Valor ha de ser un JSON valid."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Expressió de camp de consulta invàlid"
|
msgstr "Expressió de camp de consulta invàlid"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Expressió de llista invàlida. No ha d'estar buida."
|
msgstr "Expressió de llista invàlida. No ha d'estar buida."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Invàlid operand lògic {op!r}"
|
msgstr "Invàlid operand lògic {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Condicions de consulta excedits."
|
msgstr "Condicions de consulta excedits."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} no és un camp personalitzat vàlid."
|
msgstr "{name!r} no és un camp personalitzat vàlid."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} no suporta expressió de consulta {expr!r}."
|
msgstr "{data_type} no suporta expressió de consulta {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Màxima profunditat anidada excedida."
|
msgstr "Màxima profunditat anidada excedida."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Camp personalitzat no trobat"
|
msgstr "Camp personalitzat no trobat"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "data del flux"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "flux corrents"
|
msgstr "flux corrents"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Permisos insuficients."
|
msgstr "Permisos insuficients."
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Color Invàlid."
|
msgstr "Color Invàlid."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Tipus arxiu %(type)s no suportat"
|
msgstr "Tipus arxiu %(type)s no suportat"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr "ID de camp personalizat ha de ser enter: %(id)s"
|
msgstr "ID de camp personalizat ha de ser enter: %(id)s"
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr "Camp personalitzat amb ID %(id)s no existeix"
|
msgstr "Camp personalitzat amb ID %(id)s no existeix"
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr "Camps personalitzats han de ser una llista d'enters o un objecte que mapegi els identificadors amb els valors."
|
msgstr "Camps personalitzats han de ser una llista d'enters o un objecte que mapegi els identificadors amb els valors."
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr "Alguns camps personalitzats no existeixen o s'han especificat dues vegades."
|
msgstr "Alguns camps personalitzats no existeixen o s'han especificat dues vegades."
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Variable detectada invàlida."
|
msgstr "Variable detectada invàlida."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Duplicat d'identificadors de documents no permès."
|
msgstr "Duplicat d'identificadors de documents no permès."
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documents no trobats: %(ids)s"
|
msgstr "Documents no trobats: %(ids)s"
|
||||||
@@ -1635,36 +1635,36 @@ msgstr "L'esquema d'URI '{parts.scheme}' no està permès. Esquemes permesos: {'
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr "No s'ha pogut analitzar l'URI {value}"
|
msgstr "No s'ha pogut analitzar l'URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Invalid more_like_id"
|
msgstr "Invalid more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configuració AI invàlida."
|
msgstr "Configuració AI invàlida."
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr "Especifica només un dels següents valors: text, title_search, query o more_like_id."
|
msgstr "Especifica només un dels següents valors: text, title_search, query o more_like_id."
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Permisos insuficients per compartir document %(id)s."
|
msgstr "Permisos insuficients per compartir document %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paquet ja s'està processant."
|
msgstr "Paquet ja s'està processant."
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr "El paquet de link encarà s'està preparant. Prova de nou més tard."
|
msgstr "El paquet de link encarà s'està preparant. Prova de nou més tard."
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "El paquet d'enllaç no està disponible."
|
msgstr "El paquet d'enllaç no està disponible."
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Czech\n"
|
"Language-Team: Czech\n"
|
||||||
"Language: cs_CZ\n"
|
"Language: cs_CZ\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumenty"
|
msgstr "Dokumenty"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Hodnota musí být platný JSON."
|
msgstr "Hodnota musí být platný JSON."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Neplatný výraz dotazu na vlastní pole"
|
msgstr "Neplatný výraz dotazu na vlastní pole"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Neplatný seznam výrazů. Nesmí být prázdný."
|
msgstr "Neplatný seznam výrazů. Nesmí být prázdný."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Neplatný logický operátor {op!r}"
|
msgstr "Neplatný logický operátor {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Překročen maximální počet podmínek dotazu."
|
msgstr "Překročen maximální počet podmínek dotazu."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} není platné vlastní pole."
|
msgstr "{name!r} není platné vlastní pole."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} nepodporuje výraz dotazu {expr!r}."
|
msgstr "{data_type} nepodporuje výraz dotazu {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Překročena maximální hloubka větvení."
|
msgstr "Překročena maximální hloubka větvení."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Vlastní pole nebylo nalezeno"
|
msgstr "Vlastní pole nebylo nalezeno"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "spuštění pracovního postupu"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "spuštění pracovních postupů"
|
msgstr "spuštění pracovních postupů"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nedostatečná oprávnění."
|
msgstr "Nedostatečná oprávnění."
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Neplatná barva."
|
msgstr "Neplatná barva."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Typ souboru %(type)s není podporován"
|
msgstr "Typ souboru %(type)s není podporován"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr "Vlastní ID pole musí být celé číslo: %(id)s"
|
msgstr "Vlastní ID pole musí být celé číslo: %(id)s"
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr "Vlastní pole s ID %(id)s neexistuje"
|
msgstr "Vlastní pole s ID %(id)s neexistuje"
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr "Vlastní pole musí být seznam celých čísel nebo ID pro mapování objektů na hodnoty."
|
msgstr "Vlastní pole musí být seznam celých čísel nebo ID pro mapování objektů na hodnoty."
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr "Některá vlastní pole neexistují nebo byla zadána dvakrát."
|
msgstr "Některá vlastní pole neexistují nebo byla zadána dvakrát."
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Zjištěna neplatná proměnná."
|
msgstr "Zjištěna neplatná proměnná."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1636,36 +1636,36 @@ msgstr "URI schéma '{parts.scheme}' není povoleno. Povolená schémata: {',\n"
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr "Nelze zpracovat URI {value}"
|
msgstr "Nelze zpracovat URI {value}"
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
|
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Danish\n"
|
"Language-Team: Danish\n"
|
||||||
"Language: da_DK\n"
|
"Language: da_DK\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumenter"
|
msgstr "Dokumenter"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Værdien skal være gyldig JSON."
|
msgstr "Værdien skal være gyldig JSON."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Ugyldigt tilpasset feltforespørgselsudtryk"
|
msgstr "Ugyldigt tilpasset feltforespørgselsudtryk"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Ugyldig udtryksliste. Må ikke være tom."
|
msgstr "Ugyldig udtryksliste. Må ikke være tom."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Ugyldig logisk operatør {op!r}"
|
msgstr "Ugyldig logisk operatør {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Maksimalt antal forespørgselsbetingelser overskredet."
|
msgstr "Maksimalt antal forespørgselsbetingelser overskredet."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} er ikke et gyldigt tilpasset felt."
|
msgstr "{name!r} er ikke et gyldigt tilpasset felt."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} understøtter ikke forespørgsel expr {expr!r}."
|
msgstr "{data_type} understøtter ikke forespørgsel expr {expr!r}."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Maksimal indlejringsdybde overskredet."
|
msgstr "Maksimal indlejringsdybde overskredet."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Tilpasset felt ikke fundet"
|
msgstr "Tilpasset felt ikke fundet"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "workflow-kørsel"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "workflow-kørsler"
|
msgstr "workflow-kørsler"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Ugyldig farve."
|
msgstr "Ugyldig farve."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Filtype %(type)s understøttes ikke"
|
msgstr "Filtype %(type)s understøttes ikke"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Ugyldig variabel fundet."
|
msgstr "Ugyldig variabel fundet."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1635,36 +1635,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:28\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German, Switzerland\n"
|
"Language-Team: German, Switzerland\n"
|
||||||
"Language: de_CH\n"
|
"Language: de_CH\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumente"
|
msgstr "Dokumente"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Wert muss gültiges JSON sein."
|
msgstr "Wert muss gültiges JSON sein."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Ungültiger benutzerdefinierter Feldabfrageausdruck"
|
msgstr "Ungültiger benutzerdefinierter Feldabfrageausdruck"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Ungültige Ausdrucksliste. Darf nicht leer sein."
|
msgstr "Ungültige Ausdrucksliste. Darf nicht leer sein."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Ungültiger logischer Operator {op!r}"
|
msgstr "Ungültiger logischer Operator {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Maximale Anzahl an Abfragebedingungen überschritten."
|
msgstr "Maximale Anzahl an Abfragebedingungen überschritten."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} ist kein gültiges Zusatzfeld."
|
msgstr "{name!r} ist kein gültiges Zusatzfeld."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} unterstützt den Abfrageausdruck {expr!r} nicht."
|
msgstr "{data_type} unterstützt den Abfrageausdruck {expr!r} nicht."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Maximale Verschachtelungstiefe überschritten."
|
msgstr "Maximale Verschachtelungstiefe überschritten."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Benutzerdefiniertes Feld nicht gefunden"
|
msgstr "Benutzerdefiniertes Feld nicht gefunden"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "Arbeitsablauf-Ausführung"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "Arbeitsablauf wird ausgeführt"
|
msgstr "Arbeitsablauf wird ausgeführt"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Unzureichende Berechtigungen."
|
msgstr "Unzureichende Berechtigungen."
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Ungültige Farbe."
|
msgstr "Ungültige Farbe."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Dateityp %(type)s nicht unterstützt"
|
msgstr "Dateityp %(type)s nicht unterstützt"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr "Feld-ID eines benutzerdefinierten Felds muss eine Ganzzahl sein: %(id)s"
|
msgstr "Feld-ID eines benutzerdefinierten Felds muss eine Ganzzahl sein: %(id)s"
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr "Benutzerdefiniertes Feld mit ID %(id)s existiert nicht"
|
msgstr "Benutzerdefiniertes Feld mit ID %(id)s existiert nicht"
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr "Benutzerdefinierte Felder müssen eine Liste von Ganzzahlen oder ein Objekt mit Zuordnung von IDs zu Werten sein."
|
msgstr "Benutzerdefinierte Felder müssen eine Liste von Ganzzahlen oder ein Objekt mit Zuordnung von IDs zu Werten sein."
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr "Einige benutzerdefinierte Felder existieren nicht oder wurden zweimal angegeben."
|
msgstr "Einige benutzerdefinierte Felder existieren nicht oder wurden zweimal angegeben."
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Ungültige Variable erkannt."
|
msgstr "Ungültige Variable erkannt."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumente nicht gefunden: %(ids)s"
|
msgstr "Dokumente nicht gefunden: %(ids)s"
|
||||||
@@ -1635,36 +1635,36 @@ msgstr "URI-Schema „{parts.scheme}“ ist nicht erlaubt. Erlaubte Schemata: {'
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr "URI {value} kann nicht gelesen werden"
|
msgstr "URI {value} kann nicht gelesen werden"
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Ungültige more_like_id"
|
msgstr "Ungültige more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Ungültige KI-Konfiguration."
|
msgstr "Ungültige KI-Konfiguration."
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket wird bereits verarbeitet."
|
msgstr "Paket wird bereits verarbeitet."
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumente"
|
msgstr "Dokumente"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Wert muss gültiges JSON sein."
|
msgstr "Wert muss gültiges JSON sein."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Ungültiger Zusatzfeld-Abfrageausdruck"
|
msgstr "Ungültiger Zusatzfeld-Abfrageausdruck"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Ungültige Ausdrucksliste. Darf nicht leer sein."
|
msgstr "Ungültige Ausdrucksliste. Darf nicht leer sein."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Ungültiger logischer Operator {op!r}"
|
msgstr "Ungültiger logischer Operator {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Maximale Anzahl an Abfragebedingungen überschritten."
|
msgstr "Maximale Anzahl an Abfragebedingungen überschritten."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "{name!r} ist kein gültiges Zusatzfeld."
|
msgstr "{name!r} ist kein gültiges Zusatzfeld."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "{data_type} unterstützt den Abfrageausdruck {expr!r} nicht."
|
msgstr "{data_type} unterstützt den Abfrageausdruck {expr!r} nicht."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Maximale Verschachtelungstiefe überschritten."
|
msgstr "Maximale Verschachtelungstiefe überschritten."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Zusatzfeld nicht gefunden"
|
msgstr "Zusatzfeld nicht gefunden"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "Arbeitsablauf-Ausführung"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "Arbeitsablauf wird ausgeführt"
|
msgstr "Arbeitsablauf wird ausgeführt"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Unzureichende Berechtigungen."
|
msgstr "Unzureichende Berechtigungen."
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Ungültige Farbe."
|
msgstr "Ungültige Farbe."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Dateityp %(type)s nicht unterstützt"
|
msgstr "Dateityp %(type)s nicht unterstützt"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr "Zusatzfeld-ID muss eine Ganzzahl sein: %(id)s"
|
msgstr "Zusatzfeld-ID muss eine Ganzzahl sein: %(id)s"
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr "Zusatzfeld mit ID %(id)s existiert nicht"
|
msgstr "Zusatzfeld mit ID %(id)s existiert nicht"
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr "Zusatzfelder müssen eine Liste von Ganzzahlen oder ein Objekt mit Zuordnung von IDs zu Werten sein."
|
msgstr "Zusatzfelder müssen eine Liste von Ganzzahlen oder ein Objekt mit Zuordnung von IDs zu Werten sein."
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr "Einige Zusatzfelder existieren nicht oder wurden zweimal angegeben."
|
msgstr "Einige Zusatzfelder existieren nicht oder wurden zweimal angegeben."
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Ungültige Variable erkannt."
|
msgstr "Ungültige Variable erkannt."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
msgstr "Doppelte Dokumentbezeichner sind nicht erlaubt."
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumente nicht gefunden: %(ids)s"
|
msgstr "Dokumente nicht gefunden: %(ids)s"
|
||||||
@@ -1635,36 +1635,36 @@ msgstr "URI-Schema „{parts.scheme}“ ist nicht erlaubt. Erlaubte Schemata: {'
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr "URI {value} kann nicht gelesen werden"
|
msgstr "URI {value} kann nicht gelesen werden"
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Ungültige more_like_id"
|
msgstr "Ungültige more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Ungültige KI-Konfiguration."
|
msgstr "Ungültige KI-Konfiguration."
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Zeitüberschreitung bei der KI-Backendanfrage."
|
msgstr "Zeitüberschreitung bei der KI-Backendanfrage."
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
msgstr "Geben Sie nur einen von text, title_search, query, oder more_like_id an."
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
msgstr "Unzureichende Berechtigungen, um Dokument %(id)s zu teilen."
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket wird bereits verarbeitet."
|
msgstr "Paket wird bereits verarbeitet."
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
msgstr "Das Freigabelink-Paket wird noch vorbereitet. Bitte versuchen Sie es später erneut."
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
msgstr "Das Freigabelink-Paket ist nicht verfügbar."
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-31 16:14+0000\n"
|
"POT-Creation-Date: 2026-08-10 02:25+0000\n"
|
||||||
"PO-Revision-Date: 2026-07-31 16:15\n"
|
"PO-Revision-Date: 2026-08-10 02:27\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Greek\n"
|
"Language-Team: Greek\n"
|
||||||
"Language: el_GR\n"
|
"Language: el_GR\n"
|
||||||
@@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Έγγραφα"
|
msgstr "Έγγραφα"
|
||||||
|
|
||||||
#: documents/filters.py:472
|
#: documents/filters.py:471
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr "Η τιμή πρέπει να είναι σε έγκυρη μορφή JSON."
|
msgstr "Η τιμή πρέπει να είναι σε έγκυρη μορφή JSON."
|
||||||
|
|
||||||
#: documents/filters.py:491
|
#: documents/filters.py:490
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr "Μη έγκυρη έκφραση προσαρμοσμένου ερωτήματος πεδίου"
|
msgstr "Μη έγκυρη έκφραση προσαρμοσμένου ερωτήματος πεδίου"
|
||||||
|
|
||||||
#: documents/filters.py:501
|
#: documents/filters.py:500
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr "Μη έγκυρη λίστα έκφρασης. Πρέπει να είναι μη κενή."
|
msgstr "Μη έγκυρη λίστα έκφρασης. Πρέπει να είναι μη κενή."
|
||||||
|
|
||||||
#: documents/filters.py:522
|
#: documents/filters.py:521
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr "Μη έγκυρος λογικός τελεστής {op!r}"
|
msgstr "Μη έγκυρος λογικός τελεστής {op!r}"
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:535
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr "Υπέρβαση μέγιστου αριθμού συνθηκών ερωτήματος."
|
msgstr "Υπέρβαση μέγιστου αριθμού συνθηκών ερωτήματος."
|
||||||
|
|
||||||
#: documents/filters.py:600
|
#: documents/filters.py:599
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr "Το προσαρμοσμένο πεδίο {name!r} δεν είναι ένα έγκυρο."
|
msgstr "Το προσαρμοσμένο πεδίο {name!r} δεν είναι ένα έγκυρο."
|
||||||
|
|
||||||
#: documents/filters.py:637
|
#: documents/filters.py:636
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr "Το {data_type} δεν υποστηρίζει το ερώτημα expr {expr!r}s."
|
msgstr "Το {data_type} δεν υποστηρίζει το ερώτημα expr {expr!r}s."
|
||||||
|
|
||||||
#: documents/filters.py:752 documents/models.py:136
|
#: documents/filters.py:755 documents/models.py:136
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr "Υπέρβαση μέγιστου βάθους εμφώλευσης."
|
msgstr "Υπέρβαση μέγιστου βάθους εμφώλευσης."
|
||||||
|
|
||||||
#: documents/filters.py:1094
|
#: documents/filters.py:1079
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr "Το προσαρμοσμένο πεδίο δε βρέθηκε"
|
msgstr "Το προσαρμοσμένο πεδίο δε βρέθηκε"
|
||||||
|
|
||||||
@@ -1338,48 +1338,48 @@ msgstr "εκτέλεση ροής εργασίας"
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr "εκτελέσεις ροής εργασίας"
|
msgstr "εκτελέσεις ροής εργασίας"
|
||||||
|
|
||||||
#: documents/serialisers.py:522 documents/serialisers.py:874
|
#: documents/serialisers.py:521 documents/serialisers.py:873
|
||||||
#: documents/serialisers.py:2776 documents/views.py:299 documents/views.py:2558
|
#: documents/serialisers.py:2767 documents/views.py:299 documents/views.py:2555
|
||||||
#: paperless_mail/serialisers.py:155
|
#: paperless_mail/serialisers.py:155
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:710
|
#: documents/serialisers.py:709
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr "Άκυρο χρώμα."
|
msgstr "Άκυρο χρώμα."
|
||||||
|
|
||||||
#: documents/serialisers.py:2248
|
#: documents/serialisers.py:2244
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr "Ο τύπος αρχείου %(type)s δεν υποστηρίζεται"
|
msgstr "Ο τύπος αρχείου %(type)s δεν υποστηρίζεται"
|
||||||
|
|
||||||
#: documents/serialisers.py:2292
|
#: documents/serialisers.py:2288
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2299
|
#: documents/serialisers.py:2295
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2316 documents/serialisers.py:2326
|
#: documents/serialisers.py:2312 documents/serialisers.py:2322
|
||||||
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
msgid "Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2321
|
#: documents/serialisers.py:2317
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2468
|
#: documents/serialisers.py:2464
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr "Εντοπίστηκε μη έγκυρη μεταβλητή."
|
msgstr "Εντοπίστηκε μη έγκυρη μεταβλητή."
|
||||||
|
|
||||||
#: documents/serialisers.py:2832
|
#: documents/serialisers.py:2823
|
||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2862 documents/views.py:4517
|
#: documents/serialisers.py:2853 documents/views.py:4509
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1635,36 +1635,36 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:292 documents/views.py:2555
|
#: documents/views.py:292 documents/views.py:2552
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1567
|
#: documents/views.py:1566
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1576
|
#: documents/views.py:1575
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:2380 documents/views.py:2701
|
#: documents/views.py:2377 documents/views.py:2698
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4529
|
#: documents/views.py:4522
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4575
|
#: documents/views.py:4568
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4636
|
#: documents/views.py:4629
|
||||||
msgid "The share link bundle is still being prepared. Please try again later."
|
msgid "The share link bundle is still being prepared. Please try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4646
|
#: documents/views.py:4639
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user