mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-19 14:28:14 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d98be3b47 | ||
|
|
6322a0bad3 | ||
|
|
70eab32503 | ||
|
|
4f91865826 | ||
|
|
a5c3e74b18 | ||
|
|
c06827acdc |
No files matched your search
@@ -76,12 +76,11 @@ jobs:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reaching here with the honeypot means AI usage was not disclosed in the form, so that
|
||||||
|
// message can name the cause.
|
||||||
// A missing template label only tells us the issue did not come from the form, which
|
// A missing template label only tells us the issue did not come from the form, which
|
||||||
// is reason enough to close it, but not proof of how it was written. It takes priority:
|
// is reason enough to close it, but not proof of how it was written.
|
||||||
// a hand-built body can carry the honeypot *and* a disclosure, and the form message is
|
const body = honeypot
|
||||||
// the only one that is accurate for both. Reaching the honeypot message means the issue
|
|
||||||
// came from the form without disclosing AI usage, so that message can name the cause.
|
|
||||||
const body = honeypot && !bypassedTemplate
|
|
||||||
? 'This issue was automatically closed because it contains a marker that is only visible to ' +
|
? 'This issue was automatically closed because it contains a marker that is only visible to ' +
|
||||||
'automated tools, which indicates it was generated by an AI agent, but the "AI usage" field does not ' +
|
'automated tools, which indicates it was generated by an AI agent, but the "AI usage" field does not ' +
|
||||||
'disclose this.\n\n' +
|
'disclose this.\n\n' +
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ src/documents/bulk_edit.py:0: error: Incompatible types in assignment (expressio
|
|||||||
src/documents/bulk_edit.py:0: error: Invalid index type "str" for "dict[FieldDataType, str]"; expected type "FieldDataType" [index]
|
src/documents/bulk_edit.py:0: error: Invalid index type "str" for "dict[FieldDataType, str]"; expected type "FieldDataType" [index]
|
||||||
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, Any]]; expected List[int] [misc]
|
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, Any]]; expected List[int] [misc]
|
||||||
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, None]]; expected List[int] [misc]
|
src/documents/bulk_edit.py:0: error: List comprehension has incompatible type List[tuple[int, None]]; expected List[int] [misc]
|
||||||
src/documents/bulk_edit.py:0: error: Missing named argument "p" for "remove" of "PageList" [call-arg]
|
|
||||||
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
||||||
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
src/documents/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
|
||||||
src/documents/bulk_edit.py:0: error: Need type annotation for "to_create" (hint: "to_create: list[<type>] = ...") [var-annotated]
|
src/documents/bulk_edit.py:0: error: Need type annotation for "to_create" (hint: "to_create: list[<type>] = ...") [var-annotated]
|
||||||
|
|||||||
@@ -91,13 +91,6 @@
|
|||||||
"concise_description": "Argument `list[int]` is not assignable to parameter `args` with type `tuple[Any, ...] | None` in function `celery.app.task.Task.apply_async`",
|
"concise_description": "Argument `list[int]` is not assignable to parameter `args` with type `tuple[Any, ...] | None` in function `celery.app.task.Task.apply_async`",
|
||||||
"severity": "error"
|
"severity": "error"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"column": 33,
|
|
||||||
"path": "src/documents/bulk_edit.py",
|
|
||||||
"name": "missing-argument",
|
|
||||||
"concise_description": "Missing argument `p` in function `pikepdf._core.PageList.remove`",
|
|
||||||
"severity": "error"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"column": 25,
|
"column": 25,
|
||||||
"path": "src/documents/caching.py",
|
"path": "src/documents/caching.py",
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ dependencies = [
|
|||||||
"drf-spectacular~=0.30",
|
"drf-spectacular~=0.30",
|
||||||
"drf-spectacular-sidecar>=2026.7.1,<2026.10",
|
"drf-spectacular-sidecar>=2026.7.1,<2026.10",
|
||||||
"drf-writable-nested~=0.7.1",
|
"drf-writable-nested~=0.7.1",
|
||||||
"filelock>=3.32,<4.1",
|
"filelock~=3.32.0",
|
||||||
"flower>=2.0.1,<2.3",
|
"flower>=2.0.1,<2.3",
|
||||||
"gotenberg-client[httpx]~=1.0",
|
"gotenberg-client[httpx]~=1.0",
|
||||||
"httpx-oauth~=0.17",
|
"httpx-oauth~=0.17",
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ module.exports = {
|
|||||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
useESM: true,
|
useESM: true,
|
||||||
// Disable the builder's deprecated override; tsconfig.spec.json enables isolation.
|
|
||||||
isolatedModules: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+167
-178
File diff suppressed because it is too large.
Load diff
@@ -102,34 +102,25 @@
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (selectedSection !== TaskSection.All && totalTasks() > pageSize) {
|
<ngb-pagination
|
||||||
<ngb-pagination
|
[pageSize]="pageSize"
|
||||||
[pageSize]="pageSize"
|
[collectionSize]="totalTasks()"
|
||||||
[collectionSize]="totalTasks()"
|
[page]="page"
|
||||||
[page]="page"
|
[maxSize]="5"
|
||||||
[maxSize]="5"
|
[rotate]="true"
|
||||||
[rotate]="true"
|
size="sm"
|
||||||
size="sm"
|
aria-label="Tasks pagination"
|
||||||
aria-label="Tasks pagination"
|
(pageChange)="setPage($event)">
|
||||||
(pageChange)="setPage($event)">
|
</ngb-pagination>
|
||||||
</ngb-pagination>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template let-tasks="tasks" let-section="section" #tasksTemplate>
|
<ng-template let-tasks="tasks" let-section="section" #tasksTemplate>
|
||||||
<div class="section-header mb-2">
|
<div class="section-header d-flex align-items-center justify-content-between mb-2">
|
||||||
<h5 class="mb-0">{{ sectionLabel(section) }}</h5>
|
<div>
|
||||||
@let count = sectionCount(section);
|
<h5 class="mb-0">{{ sectionLabel(section) }}</h5>
|
||||||
@let hasMore = selectedSection === TaskSection.All && count > tasks.length;
|
<div class="small text-muted">
|
||||||
<div class="small text-muted">
|
<ng-container i18n>{tasks.length, plural, =1 {1 task} other {{{tasks.length}} tasks}}</ng-container>
|
||||||
@if (hasMore) {
|
</div>
|
||||||
<ng-container i18n>{{tasks.length}} of {{count}} tasks</ng-container>
|
|
||||||
<button class="btn btn-link show-all p-0 ms-2 align-baseline" type="button" (click)="setSection(section)">
|
|
||||||
<ng-container i18n>Show all</ng-container><i-bs class="ms-1" width="1em" height="1em" name="arrow-right"></i-bs>
|
|
||||||
</button>
|
|
||||||
} @else {
|
|
||||||
<ng-container i18n>{count, plural, =1 {1 task} other {{{count}} tasks}}</ng-container>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -178,9 +169,10 @@
|
|||||||
<div>{{ taskDisplayName(task) }}</div>
|
<div>{{ taskDisplayName(task) }}</div>
|
||||||
<div class="small text-muted">
|
<div class="small text-muted">
|
||||||
@if (taskShowsSeparateTypeLabel(task)) {
|
@if (taskShowsSeparateTypeLabel(task)) {
|
||||||
<span class="me-2">{{ task.task_type_display }}</span>
|
<span>{{ task.task_type_display }}</span>
|
||||||
|
<span class="mx-1">•</span>
|
||||||
}
|
}
|
||||||
<span class="text-nowrap"><i-bs class="me-1" width="1em" height="1em" name="box-arrow-in-right"></i-bs>{{ task.trigger_source_display }}</span>
|
<span>{{ task.trigger_source_display }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-lg-table-cell created-col">{{ task.date_created | customDate:'short' }}</td>
|
<td class="d-none d-lg-table-cell created-col">{{ task.date_created | customDate:'short' }}</td>
|
||||||
|
|||||||
@@ -72,7 +72,3 @@ pre {
|
|||||||
tbody tr:nth-last-child(2) td {
|
tbody tr:nth-last-child(2) td {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header .show-all {
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
@@ -223,6 +223,16 @@ describe('TasksComponent', () => {
|
|||||||
)
|
)
|
||||||
.flush(paginatedTasks)
|
.flush(paginatedTasks)
|
||||||
|
|
||||||
|
httpTestingController
|
||||||
|
.expectOne(
|
||||||
|
(req) =>
|
||||||
|
req.url === `${environment.apiBaseUrl}tasks/` &&
|
||||||
|
req.params.get('acknowledged') === 'false' &&
|
||||||
|
req.params.get('page_size') === '25' &&
|
||||||
|
req.params.get('page') === '1'
|
||||||
|
)
|
||||||
|
.flush(paginatedTasks)
|
||||||
|
|
||||||
httpTestingController
|
httpTestingController
|
||||||
.expectOne(
|
.expectOne(
|
||||||
(req) =>
|
(req) =>
|
||||||
@@ -231,30 +241,8 @@ describe('TasksComponent', () => {
|
|||||||
!req.params.has('status')
|
!req.params.has('status')
|
||||||
)
|
)
|
||||||
.flush(sectionCountResponse)
|
.flush(sectionCountResponse)
|
||||||
|
|
||||||
flushTaskLists(10)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// answers task list requests with the fixture tasks matching their status filter
|
|
||||||
function flushTaskLists(pageSize: number, count?: number) {
|
|
||||||
httpTestingController
|
|
||||||
.match(
|
|
||||||
(req) =>
|
|
||||||
req.url === `${environment.apiBaseUrl}tasks/` &&
|
|
||||||
req.params.get('page_size') === `${pageSize}`
|
|
||||||
)
|
|
||||||
.forEach((req) => {
|
|
||||||
const statuses = req.request.params.getAll('status')
|
|
||||||
const results = tasks.filter((task) => statuses.includes(task.status))
|
|
||||||
req.flush({ count: count ?? results.length, results })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectSection(section: TaskSection, count: number) {
|
|
||||||
component.setSection(section)
|
|
||||||
flushTaskLists(25, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should display task sections with counts', () => {
|
it('should display task sections with counts', () => {
|
||||||
expect(component.selectedSection).toBe(TaskSection.All)
|
expect(component.selectedSection).toBe(TaskSection.All)
|
||||||
expect(component.selectedTaskType).toBeNull()
|
expect(component.selectedTaskType).toBeNull()
|
||||||
@@ -350,7 +338,6 @@ describe('TasksComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should render pagination controls next to the task filter', () => {
|
it('should render pagination controls next to the task filter', () => {
|
||||||
selectSection(TaskSection.Completed, 30)
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
const controls = fixture.debugElement.query(By.css('.task-controls'))
|
const controls = fixture.debugElement.query(By.css('.task-controls'))
|
||||||
@@ -361,51 +348,6 @@ describe('TasksComponent', () => {
|
|||||||
expect(pagination).not.toBeNull()
|
expect(pagination).not.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should only paginate a section with more than one page of tasks', () => {
|
|
||||||
const pagination = () =>
|
|
||||||
fixture.debugElement.query(By.css('ngb-pagination'))
|
|
||||||
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(pagination()).toBeNull()
|
|
||||||
|
|
||||||
selectSection(TaskSection.Completed, 2)
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(pagination()).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should load the all tasks overview one section at a time', () => {
|
|
||||||
expect(component.tasksForSection(TaskSection.NeedsAttention)).toHaveLength(
|
|
||||||
2
|
|
||||||
)
|
|
||||||
expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(3)
|
|
||||||
expect(component.tasksForSection(TaskSection.Completed)).toHaveLength(2)
|
|
||||||
expect(component.totalTasks()).toBe(7)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show section totals and link truncated sections to their full list', () => {
|
|
||||||
component.sectionCounts.update((counts) => ({
|
|
||||||
...counts,
|
|
||||||
[TaskSection.Completed]: 27,
|
|
||||||
}))
|
|
||||||
fixture.detectChanges()
|
|
||||||
|
|
||||||
const header = (label: string) =>
|
|
||||||
fixture.debugElement
|
|
||||||
.queryAll(By.css('.section-header'))
|
|
||||||
.find((h) => h.nativeElement.textContent.includes(label))
|
|
||||||
|
|
||||||
expect(header('Needs attention').nativeElement.textContent).toContain(
|
|
||||||
'2 tasks'
|
|
||||||
)
|
|
||||||
expect(header('Needs attention').query(By.css('button'))).toBeNull()
|
|
||||||
|
|
||||||
expect(header('Recently completed').nativeElement.textContent).toContain(
|
|
||||||
'2 of 27 tasks'
|
|
||||||
)
|
|
||||||
header('Recently completed').query(By.css('button')).nativeElement.click()
|
|
||||||
expect(component.selectedSection).toBe(TaskSection.Completed)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should apply the selected section to the server-side task query', () => {
|
it('should apply the selected section to the server-side task query', () => {
|
||||||
component.setSection(TaskSection.NeedsAttention)
|
component.setSection(TaskSection.NeedsAttention)
|
||||||
|
|
||||||
@@ -426,56 +368,55 @@ describe('TasksComponent', () => {
|
|||||||
it('should apply task type and trigger source filters to the server-side task query', () => {
|
it('should apply task type and trigger source filters to the server-side task query', () => {
|
||||||
component.setTaskType(PaperlessTaskType.SanityCheck)
|
component.setTaskType(PaperlessTaskType.SanityCheck)
|
||||||
|
|
||||||
expect(
|
httpTestingController
|
||||||
httpTestingController.match(
|
.expectOne(
|
||||||
(request) =>
|
(request) =>
|
||||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||||
request.params.get('page_size') === '10' &&
|
request.params.get('page_size') === '25' &&
|
||||||
request.params.get('task_type') === PaperlessTaskType.SanityCheck
|
request.params.get('task_type') === PaperlessTaskType.SanityCheck
|
||||||
)
|
)
|
||||||
).toHaveLength(3)
|
.flush({ count: 1, results: [tasks[6]] })
|
||||||
|
|
||||||
component.setTriggerSource(PaperlessTaskTriggerSource.System)
|
component.setTriggerSource(PaperlessTaskTriggerSource.System)
|
||||||
|
|
||||||
expect(
|
httpTestingController
|
||||||
httpTestingController.match(
|
.expectOne(
|
||||||
(request) =>
|
(request) =>
|
||||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||||
request.params.get('page_size') === '10' &&
|
request.params.get('page_size') === '25' &&
|
||||||
request.params.get('task_type') === PaperlessTaskType.SanityCheck &&
|
request.params.get('task_type') === PaperlessTaskType.SanityCheck &&
|
||||||
request.params.get('trigger_source') ===
|
request.params.get('trigger_source') ===
|
||||||
PaperlessTaskTriggerSource.System
|
PaperlessTaskTriggerSource.System
|
||||||
)
|
)
|
||||||
).toHaveLength(3)
|
.flush({ count: 1, results: [tasks[6]] })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply text filters to the server-side task query', () => {
|
it('should apply text filters to the server-side task query', () => {
|
||||||
component.filterText = 'invoice'
|
component.filterText = 'invoice'
|
||||||
jest.advanceTimersByTime(150)
|
jest.advanceTimersByTime(150)
|
||||||
|
|
||||||
expect(
|
httpTestingController
|
||||||
httpTestingController.match(
|
.expectOne(
|
||||||
(request) =>
|
(request) =>
|
||||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||||
request.params.get('page_size') === '10' &&
|
request.params.get('page_size') === '25' &&
|
||||||
request.params.get('name') === 'invoice'
|
request.params.get('name') === 'invoice'
|
||||||
)
|
)
|
||||||
).toHaveLength(3)
|
.flush({ count: 1, results: [tasks[0]] })
|
||||||
|
|
||||||
component.setFilterTarget(TaskFilterTargetID.Result)
|
component.setFilterTarget(TaskFilterTargetID.Result)
|
||||||
|
|
||||||
expect(
|
httpTestingController
|
||||||
httpTestingController.match(
|
.expectOne(
|
||||||
(request) =>
|
(request) =>
|
||||||
request.url === `${environment.apiBaseUrl}tasks/` &&
|
request.url === `${environment.apiBaseUrl}tasks/` &&
|
||||||
request.params.get('page_size') === '10' &&
|
request.params.get('page_size') === '25' &&
|
||||||
request.params.get('result') === 'invoice'
|
request.params.get('result') === 'invoice'
|
||||||
)
|
)
|
||||||
).toHaveLength(3)
|
.flush({ count: 0, results: [] })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should load a different task page when pagination changes', () => {
|
it('should load a different task page when pagination changes', () => {
|
||||||
selectSection(TaskSection.Completed, 30)
|
|
||||||
component.setPage(2)
|
component.setPage(2)
|
||||||
|
|
||||||
const pageTwoTasks = {
|
const pageTwoTasks = {
|
||||||
@@ -499,7 +440,6 @@ describe('TasksComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not replace section counts with current-page counts', () => {
|
it('should not replace section counts with current-page counts', () => {
|
||||||
selectSection(TaskSection.Completed, 30)
|
|
||||||
component.setPage(2)
|
component.setPage(2)
|
||||||
|
|
||||||
httpTestingController
|
httpTestingController
|
||||||
@@ -515,21 +455,11 @@ describe('TasksComponent', () => {
|
|||||||
results: [tasks[0]],
|
results: [tasks[0]],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(component.sectionCount(TaskSection.All)).toBe(7)
|
|
||||||
expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2)
|
expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2)
|
||||||
expect(component.sectionCount(TaskSection.InProgress)).toBe(3)
|
expect(component.sectionCount(TaskSection.InProgress)).toBe(3)
|
||||||
expect(component.sectionCount(TaskSection.Completed)).toBe(2)
|
expect(component.sectionCount(TaskSection.Completed)).toBe(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should stop loading when the overview fails to load', () => {
|
|
||||||
component.setSection(TaskSection.All)
|
|
||||||
httpTestingController
|
|
||||||
.match((req) => req.params.get('page_size') === '10')[0]
|
|
||||||
.flush('', { status: 500, statusText: 'Error' })
|
|
||||||
|
|
||||||
expect(component.loading()).toBe(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should expose stable task type options and disable empty ones', () => {
|
it('should expose stable task type options and disable empty ones', () => {
|
||||||
expect(component.taskTypeOptions.map((option) => option.value)).toContain(
|
expect(component.taskTypeOptions.map((option) => option.value)).toContain(
|
||||||
PaperlessTaskType.TrainClassifier
|
PaperlessTaskType.TrainClassifier
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ import {
|
|||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
first,
|
first,
|
||||||
forkJoin,
|
|
||||||
map,
|
|
||||||
Observable,
|
|
||||||
Subject,
|
Subject,
|
||||||
takeUntil,
|
takeUntil,
|
||||||
timer,
|
timer,
|
||||||
@@ -28,7 +25,6 @@ import {
|
|||||||
PaperlessTaskTriggerSource,
|
PaperlessTaskTriggerSource,
|
||||||
PaperlessTaskType,
|
PaperlessTaskType,
|
||||||
} from 'src/app/data/paperless-task'
|
} from 'src/app/data/paperless-task'
|
||||||
import { Results } from 'src/app/data/results'
|
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { TasksService } from 'src/app/services/tasks.service'
|
import { TasksService } from 'src/app/services/tasks.service'
|
||||||
@@ -173,7 +169,6 @@ export class TasksComponent
|
|||||||
public expandedTask: number
|
public expandedTask: number
|
||||||
public autoRefreshEnabled: boolean = true
|
public autoRefreshEnabled: boolean = true
|
||||||
public readonly pageSize = 25
|
public readonly pageSize = 25
|
||||||
public readonly overviewSize = 10
|
|
||||||
public page: number = 1
|
public page: number = 1
|
||||||
readonly totalTasks = signal(0)
|
readonly totalTasks = signal(0)
|
||||||
readonly sectionCounts = signal<Record<TaskSection, number>>({
|
readonly sectionCounts = signal<Record<TaskSection, number>>({
|
||||||
@@ -489,6 +484,13 @@ export class TasksComponent
|
|||||||
return this.sectionCounts()[section]
|
return this.sectionCounts()[section]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setSectionCount(section: TaskSection, count: number) {
|
||||||
|
this.sectionCounts.update((counts) => ({
|
||||||
|
...counts,
|
||||||
|
[section]: count,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
sectionShowsResults(section: TaskSection): boolean {
|
sectionShowsResults(section: TaskSection): boolean {
|
||||||
return section !== TaskSection.InProgress
|
return section !== TaskSection.InProgress
|
||||||
}
|
}
|
||||||
@@ -735,12 +737,21 @@ export class TasksComponent
|
|||||||
this.reloadSectionCounts()
|
this.reloadSectionCounts()
|
||||||
|
|
||||||
this.loading.set(true)
|
this.loading.set(true)
|
||||||
this.listTasks()
|
this.tasksService
|
||||||
|
.list(
|
||||||
|
this.page,
|
||||||
|
this.pageSize,
|
||||||
|
this.getParamsForSection(this.selectedSection)
|
||||||
|
)
|
||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.pagedTasks.set(result.results)
|
this.pagedTasks.set(result.results)
|
||||||
this.totalTasks.set(result.count)
|
this.totalTasks.set(result.count)
|
||||||
|
this.setSectionCount(TaskSection.All, result.count)
|
||||||
|
if (this.selectedSection !== TaskSection.All) {
|
||||||
|
this.setSectionCount(this.selectedSection, result.count)
|
||||||
|
}
|
||||||
this.loading.set(false)
|
this.loading.set(false)
|
||||||
if (
|
if (
|
||||||
this.page > 1 &&
|
this.page > 1 &&
|
||||||
@@ -756,30 +767,4 @@ export class TasksComponent
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private listTasks(): Observable<Results<PaperlessTask>> {
|
|
||||||
if (this.selectedSection !== TaskSection.All) {
|
|
||||||
return this.tasksService.list(
|
|
||||||
this.page,
|
|
||||||
this.pageSize,
|
|
||||||
this.getParamsForSection(this.selectedSection)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// All is an overview of each section to avoid splitting sections across pages
|
|
||||||
return forkJoin(
|
|
||||||
this.sections.map((section) =>
|
|
||||||
this.tasksService.list(
|
|
||||||
1,
|
|
||||||
this.overviewSize,
|
|
||||||
this.getParamsForSection(section)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).pipe(
|
|
||||||
map((results) => ({
|
|
||||||
count: results.reduce((sum, result) => sum + result.count, 0),
|
|
||||||
results: results.flatMap((result) => result.results),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -154,31 +154,6 @@ describe('UsersAndGroupsComponent', () => {
|
|||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should logout current user under base href if password changed', () => {
|
|
||||||
jest.useFakeTimers()
|
|
||||||
const base = document.createElement('base')
|
|
||||||
base.href = '/paperless/'
|
|
||||||
document.head.appendChild(base)
|
|
||||||
completeSetup()
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
component.editUser(users[0])
|
|
||||||
const navSpy = jest
|
|
||||||
.spyOn(navUtils, 'setLocationHref')
|
|
||||||
.mockImplementation(() => {})
|
|
||||||
const editDialog = modal.componentInstance as UserEditDialogComponent
|
|
||||||
editDialog.passwordIsSet = true
|
|
||||||
settingsService.currentUser.set(users[0]) // simulate logged in as same user
|
|
||||||
editDialog.succeeded.emit(users[0])
|
|
||||||
fixture.detectChanges()
|
|
||||||
jest.advanceTimersByTime(2600)
|
|
||||||
expect(navSpy).toHaveBeenCalledWith(
|
|
||||||
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
|
|
||||||
)
|
|
||||||
base.remove()
|
|
||||||
jest.useRealTimers()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support edit / create group, show error if needed', () => {
|
it('should support edit / create group, show error if needed', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
let modal: NgbModalRef
|
let modal: NgbModalRef
|
||||||
|
|||||||
@@ -116,9 +116,8 @@ export class UsersAndGroupsComponent
|
|||||||
$localize`Password has been changed, you will be logged out momentarily.`
|
$localize`Password has been changed, you will be logged out momentarily.`
|
||||||
)
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const basePath = new URL(document.baseURI).pathname
|
|
||||||
setLocationHref(
|
setLocationHref(
|
||||||
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}`
|
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||||
)
|
)
|
||||||
}, 2500)
|
}, 2500)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
-32
@@ -218,38 +218,6 @@ describe('ProfileEditDialogComponent', () => {
|
|||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should logout under base href on save if password changed', () => {
|
|
||||||
jest.useFakeTimers()
|
|
||||||
const base = document.createElement('base')
|
|
||||||
base.href = '/paperless/'
|
|
||||||
document.head.appendChild(base)
|
|
||||||
const getSpy = jest.spyOn(profileService, 'get')
|
|
||||||
getSpy.mockReturnValue(of(profile))
|
|
||||||
const getProvidersSpy = jest.spyOn(
|
|
||||||
profileService,
|
|
||||||
'getSocialAccountProviders'
|
|
||||||
)
|
|
||||||
getProvidersSpy.mockReturnValue(of(socialAccountProviders))
|
|
||||||
component.ngOnInit()
|
|
||||||
component['newPassword'] = 'new*pass'
|
|
||||||
component.form.get('password').patchValue('new*pass')
|
|
||||||
component.form.get('password_confirm').patchValue('new*pass')
|
|
||||||
|
|
||||||
const updateSpy = jest.spyOn(profileService, 'update')
|
|
||||||
updateSpy.mockReturnValue(of(null))
|
|
||||||
const navSpy = jest
|
|
||||||
.spyOn(navUtils, 'setLocationHref')
|
|
||||||
.mockImplementation(() => {})
|
|
||||||
component.save()
|
|
||||||
expect(updateSpy).toHaveBeenCalled()
|
|
||||||
jest.advanceTimersByTime(2600)
|
|
||||||
expect(navSpy).toHaveBeenCalledWith(
|
|
||||||
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
|
|
||||||
)
|
|
||||||
base.remove()
|
|
||||||
jest.useRealTimers()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support auth token copy', () => {
|
it('should support auth token copy', () => {
|
||||||
jest.useFakeTimers()
|
jest.useFakeTimers()
|
||||||
const getSpy = jest.spyOn(profileService, 'get')
|
const getSpy = jest.spyOn(profileService, 'get')
|
||||||
|
|||||||
+1
-2
@@ -210,9 +210,8 @@ export class ProfileEditDialogComponent
|
|||||||
$localize`Password has been changed, you will be logged out momentarily.`
|
$localize`Password has been changed, you will be logged out momentarily.`
|
||||||
)
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const basePath = new URL(document.baseURI).pathname
|
|
||||||
setLocationHref(
|
setLocationHref(
|
||||||
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}`
|
`${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
|
||||||
)
|
)
|
||||||
}, 2500)
|
}, 2500)
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-33
@@ -1,6 +1,6 @@
|
|||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading()" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||||
} @else if (noSuggestions) {
|
} @else if (noSuggestions) {
|
||||||
@@ -18,44 +18,46 @@
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
@if (aiEnabled()) {
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
|
||||||
<span class="visually-hidden" i18n>Show suggestions</span>
|
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
|
||||||
</button>
|
<span class="visually-hidden" i18n>Show suggestions</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
|
||||||
<div class="list-group list-group-flush small pb-0">
|
<div class="list-group list-group-flush small pb-0">
|
||||||
@if (novelSuggestions === 0 && fieldSuggestions === 0) {
|
@if (novelSuggestions === 0 && fieldSuggestions === 0) {
|
||||||
<div class="list-group-item text-muted fst-italic">
|
<div class="list-group-item text-muted fst-italic">
|
||||||
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
<small class="text-muted small fst-italic" i18n>No novel suggestions</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
@if (suggestions()?.suggested_tags?.length > 0) {
|
|
||||||
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
|
||||||
@for (tag of suggestions().suggested_tags; track tag) {
|
|
||||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
|
||||||
}
|
}
|
||||||
}
|
@if (suggestions()?.suggested_tags?.length > 0) {
|
||||||
@if (suggestions()?.suggested_document_types?.length > 0) {
|
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
|
||||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
@for (tag of suggestions().suggested_tags; track tag) {
|
||||||
@for (type of suggestions().suggested_document_types; track type) {
|
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
|
||||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
}
|
||||||
}
|
}
|
||||||
}
|
@if (suggestions()?.suggested_document_types?.length > 0) {
|
||||||
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
|
||||||
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
@for (type of suggestions().suggested_document_types; track type) {
|
||||||
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
|
||||||
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
}
|
||||||
}
|
}
|
||||||
}
|
@if (suggestions()?.suggested_correspondents?.length > 0) {
|
||||||
@if (fieldSuggestions > 0) {
|
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
|
||||||
<div class="list-group-item text-muted fst-italic">
|
@for (correspondent of suggestions().suggested_correspondents; track correspondent) {
|
||||||
<small class="text-muted small fst-italic" i18n>{fieldSuggestions, plural, =1 {1 suggestion available below} other {{{fieldSuggestions}} suggestions available below}}</small>
|
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
|
||||||
</div>
|
}
|
||||||
}
|
}
|
||||||
|
@if (fieldSuggestions > 0) {
|
||||||
|
<div class="list-group-item text-muted fst-italic">
|
||||||
|
<small class="text-muted small fst-italic" i18n>{fieldSuggestions, plural, =1 {1 suggestion available below} other {{{fieldSuggestions}} suggestions available below}}</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (aiEnabled()) {
|
@if (aiEnabled()) {
|
||||||
|
|||||||
-31
@@ -251,37 +251,6 @@ describe('SuggestionsDropdownComponent', () => {
|
|||||||
expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
|
expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should open ML date suggestions with AI disabled', () => {
|
|
||||||
const getSuggestions = jest.spyOn(component.getSuggestions, 'emit')
|
|
||||||
fixture.componentRef.setInput('aiEnabled', false)
|
|
||||||
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
|
|
||||||
fixture.componentRef.setInput('suggestions', {
|
|
||||||
dates: ['2026-01-04', '2026-02-01'],
|
|
||||||
})
|
|
||||||
fixture.componentRef.setInput('appliedCreated', '2026-01-04')
|
|
||||||
fixture.detectChanges()
|
|
||||||
|
|
||||||
const button = fixture.nativeElement.querySelector('button')
|
|
||||||
expect(button.disabled).toBeFalsy()
|
|
||||||
expect(button.querySelector('.badge').textContent.trim()).toBe('1')
|
|
||||||
|
|
||||||
button.click()
|
|
||||||
fixture.detectChanges()
|
|
||||||
|
|
||||||
expect(component.dropdown.isOpen()).toBeTruthy()
|
|
||||||
expect(fixture.nativeElement.textContent).toContain(
|
|
||||||
'1 suggestion available below'
|
|
||||||
)
|
|
||||||
expect(getSuggestions).not.toHaveBeenCalled()
|
|
||||||
expect(
|
|
||||||
fixture.nativeElement.querySelector('button[title="Suggestion options"]')
|
|
||||||
).toBeNull()
|
|
||||||
|
|
||||||
button.click()
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(component.dropdown.isOpen()).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should fetch unfetched sources and show existing suggestions', () => {
|
it('should fetch unfetched sources and show existing suggestions', () => {
|
||||||
jest.spyOn(component.getSuggestions, 'emit')
|
jest.spyOn(component.getSuggestions, 'emit')
|
||||||
fixture.componentRef.setInput('aiEnabled', true)
|
fixture.componentRef.setInput('aiEnabled', true)
|
||||||
|
|||||||
+5
-1
@@ -53,7 +53,11 @@ export class SuggestionsDropdownComponent {
|
|||||||
addCorrespondent: EventEmitter<string> = new EventEmitter()
|
addCorrespondent: EventEmitter<string> = new EventEmitter()
|
||||||
|
|
||||||
public clickSuggest(): void {
|
public clickSuggest(): void {
|
||||||
if (this.disabled() || this.loading()) {
|
if (
|
||||||
|
this.disabled() ||
|
||||||
|
this.loading() ||
|
||||||
|
(this.suggestions() && !this.aiEnabled())
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-2
@@ -25,8 +25,6 @@
|
|||||||
@if (displayFields().includes(field)) {
|
@if (displayFields().includes(field)) {
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="text-truncate"
|
|
||||||
[title]="getColumnTitle(field)"
|
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'd-none d-md-table-cell': i > 1,
|
'd-none d-md-table-cell': i > 1,
|
||||||
'w-25': field === DisplayField.CREATED || field === DisplayField.ADDED
|
'w-25': field === DisplayField.CREATED || field === DisplayField.ADDED
|
||||||
|
|||||||
+10
-46
@@ -484,22 +484,6 @@ describe('FilterEditorComponent', () => {
|
|||||||
expect(component.textFilter).toBeNull()
|
expect(component.textFilter).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ingest title filter rules alongside a fulltext query with date created', () => {
|
|
||||||
component.filterRules = [
|
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TITLE,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
|
||||||
value: 'created:[-1 week to now]',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
|
||||||
expect(component.textFilter).toEqual('foo')
|
|
||||||
expect(component.textFilterTarget).toEqual('title') // TEXT_FILTER_TARGET_TITLE
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should ingest text filter rules for fulltext query that include date added', () => {
|
it('should ingest text filter rules for fulltext query that include date added', () => {
|
||||||
expect(component.dateAddedRelativeDate).toBeNull()
|
expect(component.dateAddedRelativeDate).toBeNull()
|
||||||
component.filterRules = [
|
component.filterRules = [
|
||||||
@@ -1861,7 +1845,7 @@ describe('FilterEditorComponent', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep text filtering separate from date created relative date', () => {
|
it('should carry over text filtering on date created with relative date', () => {
|
||||||
component.textFilter = 'foo'
|
component.textFilter = 'foo'
|
||||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
@@ -1871,18 +1855,14 @@ describe('FilterEditorComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TEXT,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'created:[-1 week to now]',
|
value: 'foo,created:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep text filtering separate from created and added relative dates', () => {
|
it('should carry over text filtering once with created and added relative dates', () => {
|
||||||
component.textFilter = 'foo'
|
component.textFilter = 'foo'
|
||||||
const datesDropdown = fixture.debugElement.query(
|
const datesDropdown = fixture.debugElement.query(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
@@ -1893,18 +1873,14 @@ describe('FilterEditorComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TEXT,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'created:[-1 week to now],added:[-1 month to now]',
|
value: 'foo,created:[-1 week to now],added:[-1 month to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep legacy title filters as a title search when adding a created relative date', () => {
|
it('should convert legacy title filters into full text query when adding a created relative date', () => {
|
||||||
component.filterRules = [
|
component.filterRules = [
|
||||||
{
|
{
|
||||||
rule_type: FILTER_TITLE,
|
rule_type: FILTER_TITLE,
|
||||||
@@ -1919,18 +1895,14 @@ describe('FilterEditorComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TITLE,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'created:[-1 week to now]',
|
value: 'foo,created:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep simple title filters as a title search when adding a created relative date', () => {
|
it('should convert simple title filters into full text query when adding a created relative date', () => {
|
||||||
component.filterRules = [
|
component.filterRules = [
|
||||||
{
|
{
|
||||||
rule_type: FILTER_SIMPLE_TITLE,
|
rule_type: FILTER_SIMPLE_TITLE,
|
||||||
@@ -1945,13 +1917,9 @@ describe('FilterEditorComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TITLE,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'created:[-1 week to now]',
|
value: 'foo,created:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@@ -2037,7 +2005,7 @@ describe('FilterEditorComponent', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep text filtering separate from date added relative date', () => {
|
it('should carry over text filtering on date added with relative date', () => {
|
||||||
component.textFilter = 'foo'
|
component.textFilter = 'foo'
|
||||||
const datesDropdown = fixture.debugElement.query(
|
const datesDropdown = fixture.debugElement.query(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
@@ -2047,13 +2015,9 @@ describe('FilterEditorComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
|
||||||
rule_type: FILTER_SIMPLE_TEXT,
|
|
||||||
value: 'foo',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'added:[-1 week to now]',
|
value: 'foo,added:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1083,6 +1083,23 @@ export class FilterEditorComponent
|
|||||||
(fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
|
(fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// if had a title / content search and added a relative date we need to carry it over...
|
||||||
|
if (
|
||||||
|
!existingRule &&
|
||||||
|
this._textFilter?.length > 0 &&
|
||||||
|
(this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT ||
|
||||||
|
this.textFilterTarget == TEXT_FILTER_TARGET_TITLE)
|
||||||
|
) {
|
||||||
|
existingRule = filterRules.find(
|
||||||
|
(fr) =>
|
||||||
|
fr.rule_type == FILTER_TITLE_CONTENT ||
|
||||||
|
fr.rule_type == FILTER_SIMPLE_TEXT ||
|
||||||
|
fr.rule_type == FILTER_TITLE ||
|
||||||
|
fr.rule_type == FILTER_SIMPLE_TITLE
|
||||||
|
)
|
||||||
|
existingRule.rule_type = FILTER_FULLTEXT_QUERY
|
||||||
|
}
|
||||||
|
|
||||||
let queryArgs = existingRule?.value.split(',') ?? []
|
let queryArgs = existingRule?.value.split(',') ?? []
|
||||||
if (this.dateCreatedRelativeDate !== null) {
|
if (this.dateCreatedRelativeDate !== null) {
|
||||||
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||||
|
|||||||
+3
-5
@@ -11,7 +11,7 @@
|
|||||||
<div class="input-group input-group-sm w-auto d-none d-md-flex">
|
<div class="input-group input-group-sm w-auto d-none d-md-flex">
|
||||||
<label class="input-group-text bg-transparent border-0" for="managementPageSize" i18n>Show:</label>
|
<label class="input-group-text bg-transparent border-0" for="managementPageSize" i18n>Show:</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm w-auto">
|
<div class="input-group input-group-sm w-auto me-3">
|
||||||
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||||
<option [ngValue]="25">25</option>
|
<option [ngValue]="25">25</option>
|
||||||
<option [ngValue]="50">50</option>
|
<option [ngValue]="50">50</option>
|
||||||
@@ -19,9 +19,7 @@
|
|||||||
</select>
|
</select>
|
||||||
<span class="input-group-text text-muted d-none d-md-flex" i18n>per page</span>
|
<span class="input-group-text text-muted d-none d-md-flex" i18n>per page</span>
|
||||||
</div>
|
</div>
|
||||||
@if (collectionSize() > pageSize) {
|
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
<ngb-pagination [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" class="ms-3" size="sm" aria-label="Pagination"></ngb-pagination>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (collectionSize() > pageSize) {
|
@if (collectionSize() > 20) {
|
||||||
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
<ngb-pagination class="ms-auto" [pageSize]="pageSize" [collectionSize]="collectionSize()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+9
-11
@@ -93,17 +93,15 @@
|
|||||||
[disabled]="selectedMailIds().size === 0"
|
[disabled]="selectedMailIds().size === 0"
|
||||||
(confirm)="deleteSelected()">
|
(confirm)="deleteSelected()">
|
||||||
</pngx-confirm-button>
|
</pngx-confirm-button>
|
||||||
@if (processedMails().length > 50) {
|
<div class="ms-auto">
|
||||||
<div class="ms-auto">
|
<ngb-pagination
|
||||||
<ngb-pagination
|
[collectionSize]="processedMails().length"
|
||||||
[collectionSize]="processedMails().length"
|
[(page)]="page"
|
||||||
[(page)]="page"
|
[pageSize]="50"
|
||||||
[pageSize]="50"
|
[maxSize]="5"
|
||||||
[maxSize]="5"
|
(pageChange)="loadProcessedMails()">
|
||||||
(pageChange)="loadProcessedMails()">
|
</ngb-pagination>
|
||||||
</ngb-pagination>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<context context-type="linenumber">386,393</context>
|
<context context-type="linenumber">386,393</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
||||||
<target state="translated"> スライド <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> / <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
<target state="needs-translation"> Slide <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> of <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.carousel.previous" datatype="html">
|
<trans-unit id="ngb.carousel.previous" datatype="html">
|
||||||
<source>Previous</source>
|
<source>Previous</source>
|
||||||
@@ -479,7 +479,7 @@
|
|||||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タグ、取引先、ドキュメントの種類、保存先パス、カスタムフィールドなどの属性をここで管理できます。これらはドキュメントの編集画面から作成することもできます。</target>
|
<target state="needs-translation">Attributes like tags, correspondents, document types, storage paths and custom fields can all be managed here. They can also be created from the document edit view.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7851939076947092983" datatype="html">
|
<trans-unit id="7851939076947092983" datatype="html">
|
||||||
<source>Manage e-mail accounts and rules for automatically importing documents.</source>
|
<source>Manage e-mail accounts and rules for automatically importing documents.</source>
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
<context context-type="sourcefile">src/app/app.component.ts</context>
|
<context context-type="sourcefile">src/app/app.component.ts</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">225</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タスクでは、バックグラウンド処理の進行状況、対応が必要な項目、最近完了した処理を確認できます。</target>
|
<target state="needs-translation">Tasks helps you track background work, what needs attention, and what recently completed.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2755033621682206902" datatype="html">
|
<trans-unit id="2755033621682206902" datatype="html">
|
||||||
<source>Check out the settings for various tweaks to the web app.</source>
|
<source>Check out the settings for various tweaks to the web app.</source>
|
||||||
@@ -571,7 +571,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">35</context>
|
<context context-type="linenumber">35</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">この値は、Paperless の外で設定されている <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>を上書きします。</target>
|
<target state="needs-translation">This value overrides <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, which is set outside Paperless.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7221396516204435584" datatype="html">
|
<trans-unit id="7221396516204435584" datatype="html">
|
||||||
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
||||||
@@ -579,7 +579,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">37</context>
|
<context context-type="linenumber">37</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> はPaperlessの外部で設定されています。ここに値を入力すると、その設定を上書きできます。</target>
|
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8318849619178340389" datatype="html">
|
<trans-unit id="8318849619178340389" datatype="html">
|
||||||
<source>Use the externally configured value</source>
|
<source>Use the externally configured value</source>
|
||||||
@@ -587,7 +587,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">外部で設定された値を使用</target>
|
<target state="needs-translation">Use the externally configured value</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6032629623003430385" datatype="html">
|
<trans-unit id="6032629623003430385" datatype="html">
|
||||||
<source>Reset to external</source>
|
<source>Reset to external</source>
|
||||||
@@ -595,7 +595,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">43</context>
|
<context context-type="linenumber">43</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">外部設定に戻す</target>
|
<target state="needs-translation">Reset to external</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7808756054397155068" datatype="html">
|
<trans-unit id="7808756054397155068" datatype="html">
|
||||||
<source>Reset</source>
|
<source>Reset</source>
|
||||||
@@ -875,7 +875,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||||
<context context-type="linenumber">18,19</context>
|
<context context-type="linenumber">18,19</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">行</target>
|
<target state="needs-translation">lines</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8838884664569764142" datatype="html">
|
<trans-unit id="8838884664569764142" datatype="html">
|
||||||
<source>Auto refresh</source>
|
<source>Auto refresh</source>
|
||||||
@@ -1003,7 +1003,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||||
<context context-type="linenumber">63,64</context>
|
<context context-type="linenumber">63,64</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">一番下へ移動</target>
|
<target state="needs-translation">Jump to bottom</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1255048712725285892" datatype="html">
|
<trans-unit id="1255048712725285892" datatype="html">
|
||||||
<source>Options to customize appearance, notifications and more. Settings apply to the <strong>current user only</strong>.</source>
|
<source>Options to customize appearance, notifications and more. Settings apply to the <strong>current user only</strong>.</source>
|
||||||
@@ -1135,7 +1135,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">115,116</context>
|
<context context-type="linenumber">115,116</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">サイドバーに表示するアイテム:</target>
|
<target state="needs-translation">Sidebar items to show:</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1356890996281769972" datatype="html">
|
<trans-unit id="1356890996281769972" datatype="html">
|
||||||
<source>Dark mode</source>
|
<source>Dark mode</source>
|
||||||
@@ -1323,7 +1323,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">サイドバーの保存済みビューにドキュメント数を表示</target>
|
<target state="needs-translation">Show document counts in sidebar saved views</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8939587804990976924" datatype="html">
|
<trans-unit id="8939587804990976924" datatype="html">
|
||||||
<source>Items per page</source>
|
<source>Items per page</source>
|
||||||
@@ -1403,7 +1403,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">259,260</context>
|
<context context-type="linenumber">259,260</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">提案機能のデフォルト設定</target>
|
<target state="needs-translation">Suggestions default to</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6251586038459969294" datatype="html">
|
<trans-unit id="6251586038459969294" datatype="html">
|
||||||
<source>Both</source>
|
<source>Both</source>
|
||||||
@@ -1411,7 +1411,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">264</context>
|
<context context-type="linenumber">264</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">両方</target>
|
<target state="needs-translation">Both</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8967224283122923634" datatype="html">
|
<trans-unit id="8967224283122923634" datatype="html">
|
||||||
<source>ML only</source>
|
<source>ML only</source>
|
||||||
@@ -1427,7 +1427,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">268</context>
|
<context context-type="linenumber">268</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">AIのみ</target>
|
<target state="needs-translation">AI only</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1043449647727456069" datatype="html">
|
<trans-unit id="1043449647727456069" datatype="html">
|
||||||
<source>Automatically request suggestions for inbox documents</source>
|
<source>Automatically request suggestions for inbox documents</source>
|
||||||
@@ -1435,7 +1435,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">276</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">受信トレイ内のドキュメントに対して自動的に提案をリクエストする</target>
|
<target state="needs-translation">Automatically request suggestions for inbox documents</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="460700039378563125" datatype="html">
|
<trans-unit id="460700039378563125" datatype="html">
|
||||||
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
||||||
@@ -1443,7 +1443,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">276</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">チェックを外した場合、提案を取得するには「提案」ボタンを押す必要があります。</target>
|
<target state="needs-translation">If un-checked, suggestions must be requested via the Suggest button.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8793267604636304297" datatype="html">
|
<trans-unit id="8793267604636304297" datatype="html">
|
||||||
<source>Show document thumbnail during loading</source>
|
<source>Show document thumbnail during loading</source>
|
||||||
@@ -1467,7 +1467,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">300,301</context>
|
<context context-type="linenumber">300,301</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ドキュメントの詳細ページで非表示にするフィールドは、チェックを外してください。</target>
|
<target state="needs-translation">Uncheck fields to hide them on the document details page.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8508424367627989968" datatype="html">
|
<trans-unit id="8508424367627989968" datatype="html">
|
||||||
<source>Bulk editing</source>
|
<source>Bulk editing</source>
|
||||||
@@ -1507,7 +1507,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1907</context>
|
<context context-type="linenumber">1907</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">PDF エディター</target>
|
<target state="needs-translation">PDF Editor</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1577733187050997705" datatype="html">
|
<trans-unit id="1577733187050997705" datatype="html">
|
||||||
<source>Default editing mode</source>
|
<source>Default editing mode</source>
|
||||||
@@ -1515,7 +1515,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">317,318</context>
|
<context context-type="linenumber">317,318</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">デフォルトの編集モード</target>
|
<target state="needs-translation">Default editing mode</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7273640930165035289" datatype="html">
|
<trans-unit id="7273640930165035289" datatype="html">
|
||||||
<source>Create new document(s)</source>
|
<source>Create new document(s)</source>
|
||||||
@@ -1527,7 +1527,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
||||||
<context context-type="linenumber">82,83</context>
|
<context context-type="linenumber">82,83</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">新しいドキュメントを作成</target>
|
<target state="needs-translation">Create new document(s)</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7860582931776068318" datatype="html">
|
<trans-unit id="7860582931776068318" datatype="html">
|
||||||
<source>Add document version</source>
|
<source>Add document version</source>
|
||||||
@@ -1539,7 +1539,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/pdf-editor/pdf-editor.component.html</context>
|
||||||
<context context-type="linenumber">87,88</context>
|
<context context-type="linenumber">87,88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ドキュメントのバージョンを追加</target>
|
<target state="needs-translation">Add document version</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8104421162933956065" datatype="html">
|
<trans-unit id="8104421162933956065" datatype="html">
|
||||||
<source>Notes</source>
|
<source>Notes</source>
|
||||||
@@ -1639,7 +1639,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">347</context>
|
<context context-type="linenumber">347</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">これらの設定は、Web UIから作成されたオブジェクト(タグ、メールルールなど)に対して、このユーザーアカウントに適用されます。ドキュメントには適用されません。</target>
|
<target state="needs-translation"> Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI. These settings do not apply to documents. </target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4292903881380648974" datatype="html">
|
<trans-unit id="4292903881380648974" datatype="html">
|
||||||
<source>Default Owner</source>
|
<source>Default Owner</source>
|
||||||
@@ -2203,7 +2203,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||||
<context context-type="linenumber">331</context>
|
<context context-type="linenumber">331</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タスク</target>
|
<target state="needs-translation">Tasks</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8492095365580052820" datatype="html">
|
<trans-unit id="8492095365580052820" datatype="html">
|
||||||
<source>Tasks shows detailed information about document consumption and system tasks.</source>
|
<source>Tasks shows detailed information about document consumption and system tasks.</source>
|
||||||
@@ -2211,7 +2211,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">4,5</context>
|
<context context-type="linenumber">4,5</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タスクでは、ドキュメントの取り込み処理やシステムタスクに関する詳細情報を確認できます。</target>
|
<target state="needs-translation">Tasks shows detailed information about document consumption and system tasks.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="103921551219467537" datatype="html">
|
<trans-unit id="103921551219467537" datatype="html">
|
||||||
<source>Clear selection</source>
|
<source>Clear selection</source>
|
||||||
@@ -2235,7 +2235,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">16</context>
|
<context context-type="linenumber">16</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">すべて閉じる</target>
|
<target state="needs-translation">Dismiss all</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1616102757855967475" datatype="html">
|
<trans-unit id="1616102757855967475" datatype="html">
|
||||||
<source>All</source>
|
<source>All</source>
|
||||||
@@ -2295,7 +2295,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">219</context>
|
<context context-type="linenumber">219</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">すべての種類</target>
|
<target state="needs-translation">All types</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="131016739441837046" datatype="html">
|
<trans-unit id="131016739441837046" datatype="html">
|
||||||
<source>All sources</source>
|
<source>All sources</source>
|
||||||
@@ -2307,7 +2307,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">231</context>
|
<context context-type="linenumber">231</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">すべてのソース</target>
|
<target state="needs-translation">All sources</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3643866539136239218" datatype="html">
|
<trans-unit id="3643866539136239218" datatype="html">
|
||||||
<source>Clear search</source>
|
<source>Clear search</source>
|
||||||
@@ -2327,7 +2327,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">92,93</context>
|
<context context-type="linenumber">92,93</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タスクを検索</target>
|
<target state="needs-translation">Search tasks</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6849725902312323996" datatype="html">
|
<trans-unit id="6849725902312323996" datatype="html">
|
||||||
<source>Reset filters</source>
|
<source>Reset filters</source>
|
||||||
@@ -2351,7 +2351,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">122</context>
|
<context context-type="linenumber">122</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">{VAR_PLURAL, plural, =1 {1件のタスク} other {<x id="INTERPOLATION"/>件のタスク}}</target>
|
<target state="needs-translation">{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8953033926734869941" datatype="html">
|
<trans-unit id="8953033926734869941" datatype="html">
|
||||||
<source>Name</source>
|
<source>Name</source>
|
||||||
@@ -2651,7 +2651,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">207</context>
|
<context context-type="linenumber">207</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">タスクの詳細を表示</target>
|
<target state="needs-translation">View task details</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1536087519743707362" datatype="html">
|
<trans-unit id="1536087519743707362" datatype="html">
|
||||||
<source>Dismiss</source>
|
<source>Dismiss</source>
|
||||||
@@ -2691,7 +2691,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">232,233</context>
|
<context context-type="linenumber">232,233</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">結果メッセージ</target>
|
<target state="needs-translation">Result message</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6621329748219109148" datatype="html">
|
<trans-unit id="6621329748219109148" datatype="html">
|
||||||
<source>Duplicate</source>
|
<source>Duplicate</source>
|
||||||
@@ -2699,7 +2699,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">239,240</context>
|
<context context-type="linenumber">239,240</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">重複</target>
|
<target state="needs-translation">Duplicate</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7593555694782789615" datatype="html">
|
<trans-unit id="7593555694782789615" datatype="html">
|
||||||
<source>Open</source>
|
<source>Open</source>
|
||||||
@@ -2739,7 +2739,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">257,258</context>
|
<context context-type="linenumber">257,258</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">入力データ</target>
|
<target state="needs-translation">Input data</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1585185618099050920" datatype="html">
|
<trans-unit id="1585185618099050920" datatype="html">
|
||||||
<source>Result data</source>
|
<source>Result data</source>
|
||||||
@@ -2747,7 +2747,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">263,264</context>
|
<context context-type="linenumber">263,264</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">結果データ</target>
|
<target state="needs-translation">Result data</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7976920528153858271" datatype="html">
|
<trans-unit id="7976920528153858271" datatype="html">
|
||||||
<source>No tasks match the current filters.</source>
|
<source>No tasks match the current filters.</source>
|
||||||
@@ -2755,7 +2755,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">285,287</context>
|
<context context-type="linenumber">285,287</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">現在のフィルター条件に一致するタスクはありません。</target>
|
<target state="needs-translation">No tasks match the current filters.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2525230676386818985" datatype="html">
|
<trans-unit id="2525230676386818985" datatype="html">
|
||||||
<source>Result</source>
|
<source>Result</source>
|
||||||
@@ -2771,7 +2771,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">55</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">注意が必要</target>
|
<target state="needs-translation">Needs attention</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3307214558906712688" datatype="html">
|
<trans-unit id="3307214558906712688" datatype="html">
|
||||||
<source>In progress</source>
|
<source>In progress</source>
|
||||||
@@ -2779,7 +2779,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">56</context>
|
<context context-type="linenumber">56</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">進行中</target>
|
<target state="needs-translation">In progress</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1371178633976505412" datatype="html">
|
<trans-unit id="1371178633976505412" datatype="html">
|
||||||
<source>Recently completed</source>
|
<source>Recently completed</source>
|
||||||
@@ -2787,7 +2787,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">57</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">最近完了したタスク</target>
|
<target state="needs-translation">Recently completed</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2936452645673957909" datatype="html">
|
<trans-unit id="2936452645673957909" datatype="html">
|
||||||
<source>Consume File</source>
|
<source>Consume File</source>
|
||||||
@@ -2795,7 +2795,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">66</context>
|
<context context-type="linenumber">66</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ファイルの取り込み</target>
|
<target state="needs-translation">Consume File</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5622386857946132209" datatype="html">
|
<trans-unit id="5622386857946132209" datatype="html">
|
||||||
<source>Train Classifier</source>
|
<source>Train Classifier</source>
|
||||||
@@ -2811,7 +2811,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">74</context>
|
<context context-type="linenumber">74</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">整合性チェック</target>
|
<target state="needs-translation">Sanity Check</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7502272564743467653" datatype="html">
|
<trans-unit id="7502272564743467653" datatype="html">
|
||||||
<source>Mail Fetch</source>
|
<source>Mail Fetch</source>
|
||||||
@@ -2839,7 +2839,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">80</context>
|
<context context-type="linenumber">80</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ゴミ箱を空にする</target>
|
<target state="needs-translation">Empty Trash</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4173754487295857311" datatype="html">
|
<trans-unit id="4173754487295857311" datatype="html">
|
||||||
<source>Check Workflows</source>
|
<source>Check Workflows</source>
|
||||||
@@ -2847,7 +2847,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">84</context>
|
<context context-type="linenumber">84</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ワークフローの確認</target>
|
<target state="needs-translation">Check Workflows</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2065831805515896240" datatype="html">
|
<trans-unit id="2065831805515896240" datatype="html">
|
||||||
<source>Bulk Update</source>
|
<source>Bulk Update</source>
|
||||||
@@ -2855,7 +2855,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">88</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">一括更新</target>
|
<target state="needs-translation">Bulk Update</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5062348912423871240" datatype="html">
|
<trans-unit id="5062348912423871240" datatype="html">
|
||||||
<source>Reprocess Document</source>
|
<source>Reprocess Document</source>
|
||||||
@@ -2863,7 +2863,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">92</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">ドキュメントの再処理</target>
|
<target state="needs-translation">Reprocess Document</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6903696804811148799" datatype="html">
|
<trans-unit id="6903696804811148799" datatype="html">
|
||||||
<source>Build Share Link</source>
|
<source>Build Share Link</source>
|
||||||
@@ -2871,7 +2871,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">96</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">共有リンクの生成</target>
|
<target state="needs-translation">Build Share Link</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6581973658756561124" datatype="html">
|
<trans-unit id="6581973658756561124" datatype="html">
|
||||||
<source>Bulk Delete</source>
|
<source>Bulk Delete</source>
|
||||||
@@ -2879,7 +2879,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">100</context>
|
<context context-type="linenumber">100</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">一括削除</target>
|
<target state="needs-translation">Bulk Delete</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5672013191898828327" datatype="html">
|
<trans-unit id="5672013191898828327" datatype="html">
|
||||||
<source>Apply AI Suggestions</source>
|
<source>Apply AI Suggestions</source>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<context context-type="linenumber">386,393</context>
|
<context context-type="linenumber">386,393</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
|
||||||
<target state="translated"> Shlid <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> i <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/></target>
|
<target state="needs-translation"> Slide <x id="INTERPOLATION" equiv-text="xt(source?: Ngb"/> of <x id="INTERPOLATION_1" equiv-text="this._cycleToSe"/> </target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ngb.carousel.previous" datatype="html">
|
<trans-unit id="ngb.carousel.previous" datatype="html">
|
||||||
<source>Previous</source>
|
<source>Previous</source>
|
||||||
@@ -571,7 +571,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">35</context>
|
<context context-type="linenumber">35</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Ky vlerë mbizotëron <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, i cili është vendosur jashtë Paperless.</target>
|
<target state="needs-translation">This value overrides <x id="INTERPOLATION" equiv-text="{{option.config_key}}"/>, which is set outside Paperless.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7221396516204435584" datatype="html">
|
<trans-unit id="7221396516204435584" datatype="html">
|
||||||
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
<source><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</source>
|
||||||
@@ -579,7 +579,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">37</context>
|
<context context-type="linenumber">37</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> është vendosur jashtë Paperless. Shkruani një vlerë këtu për ta mbingarkuar atë.</target>
|
<target state="needs-translation"><x id="INTERPOLATION" equiv-text="{{option.config_key}}"/> is set outside Paperless. Enter a value here to override it.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8318849619178340389" datatype="html">
|
<trans-unit id="8318849619178340389" datatype="html">
|
||||||
<source>Use the externally configured value</source>
|
<source>Use the externally configured value</source>
|
||||||
@@ -587,7 +587,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Përdorni vlerën e konfiguruar nga jashtë</target>
|
<target state="needs-translation">Use the externally configured value</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6032629623003430385" datatype="html">
|
<trans-unit id="6032629623003430385" datatype="html">
|
||||||
<source>Reset to external</source>
|
<source>Reset to external</source>
|
||||||
@@ -595,7 +595,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/config/config.component.html</context>
|
||||||
<context context-type="linenumber">43</context>
|
<context context-type="linenumber">43</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Rinisni në të jashtëm</target>
|
<target state="needs-translation">Reset to external</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7808756054397155068" datatype="html">
|
<trans-unit id="7808756054397155068" datatype="html">
|
||||||
<source>Reset</source>
|
<source>Reset</source>
|
||||||
@@ -1135,7 +1135,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">115,116</context>
|
<context context-type="linenumber">115,116</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Elementët e shiritit anësor për t'u shfaqur:</target>
|
<target state="needs-translation">Sidebar items to show:</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1356890996281769972" datatype="html">
|
<trans-unit id="1356890996281769972" datatype="html">
|
||||||
<source>Dark mode</source>
|
<source>Dark mode</source>
|
||||||
@@ -1403,7 +1403,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">259,260</context>
|
<context context-type="linenumber">259,260</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Sugjerimet parazgjedhore janë</target>
|
<target state="needs-translation">Suggestions default to</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6251586038459969294" datatype="html">
|
<trans-unit id="6251586038459969294" datatype="html">
|
||||||
<source>Both</source>
|
<source>Both</source>
|
||||||
@@ -1411,7 +1411,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">264</context>
|
<context context-type="linenumber">264</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Të dyja</target>
|
<target state="needs-translation">Both</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8967224283122923634" datatype="html">
|
<trans-unit id="8967224283122923634" datatype="html">
|
||||||
<source>ML only</source>
|
<source>ML only</source>
|
||||||
@@ -1419,7 +1419,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">266</context>
|
<context context-type="linenumber">266</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Vetëm ML</target>
|
<target state="needs-translation">ML only</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6418969597665857901" datatype="html">
|
<trans-unit id="6418969597665857901" datatype="html">
|
||||||
<source>AI only</source>
|
<source>AI only</source>
|
||||||
@@ -1427,7 +1427,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">268</context>
|
<context context-type="linenumber">268</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Vetëm AI</target>
|
<target state="needs-translation">AI only</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1043449647727456069" datatype="html">
|
<trans-unit id="1043449647727456069" datatype="html">
|
||||||
<source>Automatically request suggestions for inbox documents</source>
|
<source>Automatically request suggestions for inbox documents</source>
|
||||||
@@ -1435,7 +1435,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">276</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Kërkoni automatikisht sugjerime për dokumentet në kutinë e postës elektronike</target>
|
<target state="needs-translation">Automatically request suggestions for inbox documents</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="460700039378563125" datatype="html">
|
<trans-unit id="460700039378563125" datatype="html">
|
||||||
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
<source>If un-checked, suggestions must be requested via the Suggest button.</source>
|
||||||
@@ -1443,7 +1443,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">276</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Nëse nuk është shënuar, sugjerimet duhet të kërkohen përmes butonit Sugjero.</target>
|
<target state="needs-translation">If un-checked, suggestions must be requested via the Suggest button.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8793267604636304297" datatype="html">
|
<trans-unit id="8793267604636304297" datatype="html">
|
||||||
<source>Show document thumbnail during loading</source>
|
<source>Show document thumbnail during loading</source>
|
||||||
@@ -2319,7 +2319,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||||
<context context-type="linenumber">21</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Pastro kërkimin</target>
|
<target state="needs-translation">Clear search</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3570013039254960445" datatype="html">
|
<trans-unit id="3570013039254960445" datatype="html">
|
||||||
<source>Search tasks</source>
|
<source>Search tasks</source>
|
||||||
@@ -2651,7 +2651,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||||
<context context-type="linenumber">207</context>
|
<context context-type="linenumber">207</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Shiko detajet e detyrës</target>
|
<target state="needs-translation">View task details</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1536087519743707362" datatype="html">
|
<trans-unit id="1536087519743707362" datatype="html">
|
||||||
<source>Dismiss</source>
|
<source>Dismiss</source>
|
||||||
@@ -2887,7 +2887,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||||
<context context-type="linenumber">104</context>
|
<context context-type="linenumber">104</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Apliko sugjerimet e AI</target>
|
<target state="needs-translation">Apply AI Suggestions</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9172233176401579786" datatype="html">
|
<trans-unit id="9172233176401579786" datatype="html">
|
||||||
<source>Scheduled</source>
|
<source>Scheduled</source>
|
||||||
@@ -3783,7 +3783,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Menuja e përdoruesit</target>
|
<target state="needs-translation">User menu</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2448391510242468907" datatype="html">
|
<trans-unit id="2448391510242468907" datatype="html">
|
||||||
<source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source>
|
<source>Logged in as <x id="INTERPOLATION" equiv-text="{{this.settingsService.displayName}}"/></source>
|
||||||
@@ -3939,7 +3939,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/manage/share-links/share-links.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/share-links/share-links.component.html</context>
|
||||||
<context context-type="linenumber">2,3</context>
|
<context context-type="linenumber">2,3</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Ndani lidhjet</target>
|
<target state="needs-translation">Share links</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7844706011418789951" datatype="html">
|
<trans-unit id="7844706011418789951" datatype="html">
|
||||||
<source>Administration</source>
|
<source>Administration</source>
|
||||||
@@ -4015,7 +4015,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||||
<context context-type="linenumber">403</context>
|
<context context-type="linenumber">403</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Konfiguro kontrollin e përditësimeve</target>
|
<target state="needs-translation">Configure update checking</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1542489069631984294" datatype="html">
|
<trans-unit id="1542489069631984294" datatype="html">
|
||||||
<source>Sidebar views updated</source>
|
<source>Sidebar views updated</source>
|
||||||
@@ -4259,7 +4259,7 @@
|
|||||||
<context context-type="sourcefile">src/app/data/saved-view-icons.ts</context>
|
<context context-type="sourcefile">src/app/data/saved-view-icons.ts</context>
|
||||||
<context context-type="linenumber">21</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Bisedë</target>
|
<target state="needs-translation">Chat</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8066608938393600549" datatype="html">
|
<trans-unit id="8066608938393600549" datatype="html">
|
||||||
<source>Message</source>
|
<source>Message</source>
|
||||||
@@ -4379,7 +4379,7 @@
|
|||||||
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-as-versions-confirm-dialog/merge-as-versions-confirm-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-as-versions-confirm-dialog/merge-as-versions-confirm-dialog.component.html</context>
|
||||||
<context context-type="linenumber">8,9</context>
|
<context context-type="linenumber">8,9</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<target state="translated">Dokumenti rrënjësor:</target>
|
<target state="needs-translation">Root document:</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2121026240713807342" datatype="html">
|
<trans-unit id="2121026240713807342" datatype="html">
|
||||||
<source>Versions (oldest first):</source>
|
<source>Versions (oldest first):</source>
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import {
|
|||||||
bell,
|
bell,
|
||||||
bodyText,
|
bodyText,
|
||||||
bookmark,
|
bookmark,
|
||||||
boxArrowInRight,
|
|
||||||
boxArrowUp,
|
boxArrowUp,
|
||||||
boxArrowUpRight,
|
boxArrowUpRight,
|
||||||
boxes,
|
boxes,
|
||||||
@@ -294,7 +293,6 @@ const icons = {
|
|||||||
braces,
|
braces,
|
||||||
bodyText,
|
bodyText,
|
||||||
bookmark,
|
bookmark,
|
||||||
boxArrowInRight,
|
|
||||||
boxArrowUp,
|
boxArrowUp,
|
||||||
boxArrowUpRight,
|
boxArrowUpRight,
|
||||||
boxes,
|
boxes,
|
||||||
|
|||||||
+184
-219
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
@@ -23,6 +24,7 @@ from django.db.models import When
|
|||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from documents import pdf_ops
|
||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
from documents.data_models import DocumentMetadataOverrides
|
from documents.data_models import DocumentMetadataOverrides
|
||||||
from documents.data_models import DocumentSource
|
from documents.data_models import DocumentSource
|
||||||
@@ -123,6 +125,11 @@ def _resolve_root_and_source_doc(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _scratch_path(name: str) -> Path:
|
||||||
|
"""A path inside a fresh directory under SCRATCH_DIR."""
|
||||||
|
return Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR)) / name
|
||||||
|
|
||||||
|
|
||||||
def set_correspondent(
|
def set_correspondent(
|
||||||
doc_ids: list[int],
|
doc_ids: list[int],
|
||||||
correspondent: Correspondent,
|
correspondent: Correspondent,
|
||||||
@@ -499,8 +506,6 @@ def rotate(
|
|||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
docs_by_root_id.setdefault(pair.root_doc.id, pair)
|
docs_by_root_id.setdefault(pair.root_doc.id, pair)
|
||||||
|
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
for pair in docs_by_root_id.values():
|
for pair in docs_by_root_id.values():
|
||||||
if pair.source_doc.mime_type != "application/pdf":
|
if pair.source_doc.mime_type != "application/pdf":
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -513,11 +518,7 @@ def rotate(
|
|||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||||
/ f"{pair.root_doc.id}_rotated.pdf"
|
/ f"{pair.root_doc.id}_rotated.pdf"
|
||||||
)
|
)
|
||||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
pdf_ops.rotate_pdf(pair.source_doc.source_path, filepath, degrees)
|
||||||
for page in pdf.pages:
|
|
||||||
page.rotate(degrees, relative=True)
|
|
||||||
pdf.remove_unreferenced_resources()
|
|
||||||
pdf.save(filepath)
|
|
||||||
|
|
||||||
# Preserve metadata/permissions via overrides; mark as new version
|
# Preserve metadata/permissions via overrides; mark as new version
|
||||||
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
@@ -560,48 +561,45 @@ def merge(
|
|||||||
qs = Document.objects.select_related("root_document").filter(id__in=doc_ids)
|
qs = Document.objects.select_related("root_document").filter(id__in=doc_ids)
|
||||||
docs_by_id = {doc.id: doc for doc in qs}
|
docs_by_id = {doc.id: doc for doc in qs}
|
||||||
affected_docs: list[int] = []
|
affected_docs: list[int] = []
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
merged_pdf = pikepdf.new()
|
|
||||||
version: str = merged_pdf.pdf_version
|
|
||||||
handoff_asn: int | None = None
|
handoff_asn: int | None = None
|
||||||
# use doc_ids to preserve order
|
with pdf_ops.PdfMerger() as merger:
|
||||||
for doc_id in doc_ids:
|
# use doc_ids to preserve order
|
||||||
doc = docs_by_id.get(doc_id)
|
for doc_id in doc_ids:
|
||||||
if doc is None:
|
doc = docs_by_id.get(doc_id)
|
||||||
continue
|
if doc is None:
|
||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
continue
|
||||||
try:
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
doc_path = (
|
try:
|
||||||
pair.source_doc.archive_path
|
# archive_path is None when there is no archive version
|
||||||
if archive_fallback
|
archive_path = (
|
||||||
and pair.source_doc.mime_type != "application/pdf"
|
pair.source_doc.archive_path
|
||||||
and pair.source_doc.has_archive_version
|
if archive_fallback
|
||||||
else pair.source_doc.source_path
|
and pair.source_doc.mime_type != "application/pdf"
|
||||||
)
|
else None
|
||||||
with pikepdf.open(str(doc_path)) as pdf:
|
)
|
||||||
version = max(version, pdf.pdf_version)
|
merger.add(
|
||||||
merged_pdf.pages.extend(pdf.pages)
|
archive_path
|
||||||
affected_docs.append(doc.id)
|
if archive_path is not None
|
||||||
if handoff_asn is None and doc.archive_serial_number is not None:
|
else pair.source_doc.source_path,
|
||||||
handoff_asn = doc.archive_serial_number
|
)
|
||||||
except Exception as e:
|
affected_docs.append(doc.id)
|
||||||
logger.exception(
|
if handoff_asn is None and doc.archive_serial_number is not None:
|
||||||
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
handoff_asn = doc.archive_serial_number
|
||||||
)
|
except Exception as e:
|
||||||
if len(affected_docs) == 0:
|
logger.exception(
|
||||||
logger.warning("No documents were merged")
|
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
||||||
return "OK"
|
)
|
||||||
|
if len(affected_docs) == 0:
|
||||||
|
logger.warning("No documents were merged")
|
||||||
|
return "OK"
|
||||||
|
|
||||||
filepath = (
|
filepath = (
|
||||||
Path(
|
Path(
|
||||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
||||||
|
)
|
||||||
|
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf"
|
||||||
)
|
)
|
||||||
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf"
|
merger.save(filepath)
|
||||||
)
|
|
||||||
merged_pdf.remove_unreferenced_resources()
|
|
||||||
merged_pdf.save(filepath, min_version=version)
|
|
||||||
merged_pdf.close()
|
|
||||||
|
|
||||||
if metadata_document_id:
|
if metadata_document_id:
|
||||||
metadata_document = qs.get(id=metadata_document_id)
|
metadata_document = qs.get(id=metadata_document_id)
|
||||||
@@ -777,64 +775,60 @@ def split(
|
|||||||
)
|
)
|
||||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
consume_tasks = []
|
consume_tasks = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
outputs = [
|
||||||
for idx, split_doc in enumerate(pages):
|
(
|
||||||
dst: pikepdf.Pdf = pikepdf.new()
|
[pdf_ops.PageSpec(page) for page in split_doc],
|
||||||
for page in split_doc:
|
partial(_scratch_path, f"{doc.id}_{split_doc[0]}-{split_doc[-1]}.pdf"),
|
||||||
dst.pages.append(pdf.pages[page - 1])
|
)
|
||||||
filepath: Path = (
|
for split_doc in pages
|
||||||
Path(
|
]
|
||||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
filepaths = pdf_ops.build_pdfs(pair.source_doc.source_path, outputs)
|
||||||
)
|
|
||||||
/ f"{doc.id}_{split_doc[0]}-{split_doc[-1]}.pdf"
|
|
||||||
)
|
|
||||||
dst.remove_unreferenced_resources()
|
|
||||||
dst.save(filepath)
|
|
||||||
dst.close()
|
|
||||||
|
|
||||||
overrides: DocumentMetadataOverrides = (
|
for idx, (split_doc, filepath) in enumerate(
|
||||||
DocumentMetadataOverrides().from_document(doc)
|
zip(pages, filepaths, strict=True),
|
||||||
)
|
):
|
||||||
overrides.title = f"{doc.title} (split {idx + 1})"
|
overrides: DocumentMetadataOverrides = (
|
||||||
if user is not None:
|
DocumentMetadataOverrides().from_document(doc)
|
||||||
overrides.owner_id = user.id
|
)
|
||||||
if not delete_originals:
|
overrides.title = f"{doc.title} (split {idx + 1})"
|
||||||
overrides.skip_asn_if_exists = True
|
if user is not None:
|
||||||
logger.info(
|
overrides.owner_id = user.id
|
||||||
f"Adding split document with pages {split_doc} to the task queue.",
|
if not delete_originals:
|
||||||
)
|
overrides.skip_asn_if_exists = True
|
||||||
consume_tasks.append(
|
logger.info(
|
||||||
consume_file.s(
|
f"Adding split document with pages {split_doc} to the task queue.",
|
||||||
input_doc=ConsumableDocument(
|
)
|
||||||
source=DocumentSource.ConsumeFolder,
|
consume_tasks.append(
|
||||||
original_file=filepath,
|
consume_file.s(
|
||||||
),
|
input_doc=ConsumableDocument(
|
||||||
overrides=overrides,
|
source=DocumentSource.ConsumeFolder,
|
||||||
).set(headers={"trigger_source": trigger_source}),
|
original_file=filepath,
|
||||||
)
|
),
|
||||||
|
overrides=overrides,
|
||||||
|
).set(headers={"trigger_source": trigger_source}),
|
||||||
|
)
|
||||||
|
|
||||||
if delete_originals:
|
if delete_originals:
|
||||||
backup = release_archive_serial_numbers([doc.id])
|
backup = release_archive_serial_numbers([doc.id])
|
||||||
logger.info(
|
logger.info(
|
||||||
"Queueing removal of original document after consumption of the split documents",
|
"Queueing removal of original document after consumption of the split documents",
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
chord(
|
chord(
|
||||||
header=consume_tasks,
|
header=consume_tasks,
|
||||||
body=delete.si([doc.id]),
|
body=delete.si([doc.id]),
|
||||||
).on_error(
|
).on_error(
|
||||||
restore_archive_serial_numbers_task.s(backup),
|
restore_archive_serial_numbers_task.s(backup),
|
||||||
).apply_async()
|
).apply_async()
|
||||||
except Exception:
|
except Exception:
|
||||||
restore_archive_serial_numbers(backup)
|
restore_archive_serial_numbers(backup)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
group(consume_tasks).delay()
|
group(consume_tasks).delay()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error splitting document {doc.id}: {e}")
|
logger.exception(f"Error splitting document {doc.id}: {e}")
|
||||||
@@ -855,8 +849,6 @@ def delete_pages(
|
|||||||
)
|
)
|
||||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
pages = sorted(pages) # sort pages to avoid index issues
|
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Produce edited PDF to a temp file and create a new version
|
# Produce edited PDF to a temp file and create a new version
|
||||||
@@ -864,13 +856,7 @@ def delete_pages(
|
|||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||||
/ f"{pair.root_doc.id}_pages_deleted.pdf"
|
/ f"{pair.root_doc.id}_pages_deleted.pdf"
|
||||||
)
|
)
|
||||||
with pikepdf.open(pair.source_doc.source_path) as pdf:
|
pdf_ops.remove_pages(pair.source_doc.source_path, filepath, pages)
|
||||||
offset = 1 # pages are 1-indexed
|
|
||||||
for page_num in pages:
|
|
||||||
pdf.pages.remove(pdf.pages[page_num - offset])
|
|
||||||
offset += 1 # remove() changes the index of the pages
|
|
||||||
pdf.remove_unreferenced_resources()
|
|
||||||
pdf.save(filepath)
|
|
||||||
|
|
||||||
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
overrides = DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
@@ -919,47 +905,28 @@ def edit_pdf(
|
|||||||
)
|
)
|
||||||
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
|
||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
pdf_docs: list[pikepdf.Pdf] = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not operations:
|
output_count = pdf_ops.validate_page_operations(
|
||||||
raise ValueError("Output document index is out of bounds")
|
operations,
|
||||||
|
single_output=update_document,
|
||||||
max_idx = max(op.get("doc", 0) for op in operations)
|
)
|
||||||
if update_document and max_idx > 0:
|
page_specs: list[list[pdf_ops.PageSpec]] = [[] for _ in range(output_count)]
|
||||||
logger.error(
|
for op in operations:
|
||||||
"Update requested but multiple output documents specified",
|
page_specs[op.get("doc", 0)].append(
|
||||||
|
pdf_ops.PageSpec(op["page"], op.get("rotate", 0)),
|
||||||
)
|
)
|
||||||
raise ValueError("Multiple output documents specified")
|
|
||||||
|
|
||||||
if any(
|
|
||||||
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(operations)
|
|
||||||
for op in operations
|
|
||||||
):
|
|
||||||
raise ValueError("Output document index is out of bounds")
|
|
||||||
|
|
||||||
with pikepdf.open(pair.source_doc.source_path) as src:
|
|
||||||
# prepare output documents
|
|
||||||
pdf_docs = [pikepdf.new() for _ in range(max_idx + 1)]
|
|
||||||
|
|
||||||
for op in operations:
|
|
||||||
dst = pdf_docs[op.get("doc", 0)]
|
|
||||||
page = src.pages[op["page"] - 1]
|
|
||||||
dst.pages.append(page)
|
|
||||||
if op.get("rotate"):
|
|
||||||
dst.pages[-1].rotate(op["rotate"], relative=True)
|
|
||||||
|
|
||||||
if update_document:
|
if update_document:
|
||||||
# Create a new version from the edited PDF rather than replacing in-place
|
# Create a new version from the edited PDF rather than replacing in-place
|
||||||
pdf = pdf_docs[0]
|
(filepath,) = pdf_ops.build_pdfs(
|
||||||
pdf.remove_unreferenced_resources()
|
pair.source_doc.source_path,
|
||||||
filepath: Path = (
|
[
|
||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
(
|
||||||
/ f"{pair.root_doc.id}_edited.pdf"
|
page_specs[0],
|
||||||
|
partial(_scratch_path, f"{pair.root_doc.id}_edited.pdf"),
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
pdf.save(filepath)
|
|
||||||
overrides = (
|
overrides = (
|
||||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
if include_metadata
|
if include_metadata
|
||||||
@@ -980,6 +947,19 @@ def edit_pdf(
|
|||||||
headers={"trigger_source": trigger_source},
|
headers={"trigger_source": trigger_source},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
version_filepaths = pdf_ops.build_pdfs(
|
||||||
|
pair.source_doc.source_path,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
specs,
|
||||||
|
partial(
|
||||||
|
_scratch_path,
|
||||||
|
f"{pair.root_doc.id}_edit_{idx}.pdf",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for idx, specs in enumerate(page_specs, start=1)
|
||||||
|
],
|
||||||
|
)
|
||||||
consume_tasks = []
|
consume_tasks = []
|
||||||
overrides = (
|
overrides = (
|
||||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
@@ -991,15 +971,9 @@ def edit_pdf(
|
|||||||
overrides.actor_id = user.id
|
overrides.actor_id = user.id
|
||||||
if not delete_original:
|
if not delete_original:
|
||||||
overrides.skip_asn_if_exists = True
|
overrides.skip_asn_if_exists = True
|
||||||
if delete_original and len(pdf_docs) == 1:
|
if delete_original and output_count == 1:
|
||||||
overrides.asn = pair.root_doc.archive_serial_number
|
overrides.asn = pair.root_doc.archive_serial_number
|
||||||
for idx, pdf in enumerate(pdf_docs, start=1):
|
for version_filepath in version_filepaths:
|
||||||
version_filepath: Path = (
|
|
||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
|
||||||
/ f"{pair.root_doc.id}_edit_{idx}.pdf"
|
|
||||||
)
|
|
||||||
pdf.remove_unreferenced_resources()
|
|
||||||
pdf.save(version_filepath)
|
|
||||||
consume_tasks.append(
|
consume_tasks.append(
|
||||||
consume_file.s(
|
consume_file.s(
|
||||||
input_doc=ConsumableDocument(
|
input_doc=ConsumableDocument(
|
||||||
@@ -1049,8 +1023,6 @@ def remove_password(
|
|||||||
"""
|
"""
|
||||||
Remove password protection from PDF documents.
|
Remove password protection from PDF documents.
|
||||||
"""
|
"""
|
||||||
import pikepdf
|
|
||||||
|
|
||||||
for doc_id in doc_ids:
|
for doc_id in doc_ids:
|
||||||
doc = Document.objects.select_related("root_document").get(id=doc_id)
|
doc = Document.objects.select_related("root_document").get(id=doc_id)
|
||||||
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
pair = _resolve_root_and_source_doc(doc, source_mode=source_mode)
|
||||||
@@ -1064,76 +1036,69 @@ def remove_password(
|
|||||||
doc.id,
|
doc.id,
|
||||||
pair.source_doc.source_path,
|
pair.source_doc.source_path,
|
||||||
)
|
)
|
||||||
try:
|
if not pdf_ops.needs_decrypt(source_path):
|
||||||
with pikepdf.open(source_path) as pdf:
|
logger.info(
|
||||||
if not pdf.is_encrypted:
|
"Skipping password removal for document %s because the "
|
||||||
logger.info(
|
"source PDF is not encrypted",
|
||||||
"Skipping password removal for document %s because the "
|
pair.root_doc.id,
|
||||||
"source PDF is not encrypted",
|
|
||||||
pair.root_doc.id,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
except pikepdf.PasswordError:
|
|
||||||
# Password-protected PDFs need the supplied password below.
|
|
||||||
pass
|
|
||||||
|
|
||||||
with pikepdf.open(source_path, password=password) as pdf:
|
|
||||||
filepath: Path = (
|
|
||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
|
||||||
/ f"{pair.root_doc.id}_unprotected.pdf"
|
|
||||||
)
|
)
|
||||||
pdf.remove_unreferenced_resources()
|
continue
|
||||||
pdf.save(filepath)
|
|
||||||
|
|
||||||
if update_document:
|
filepath = pdf_ops.decrypt_pdf(
|
||||||
# Create a new version rather than modifying the root/original in place.
|
source_path,
|
||||||
overrides = (
|
partial(_scratch_path, f"{pair.root_doc.id}_unprotected.pdf"),
|
||||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
password,
|
||||||
if include_metadata
|
)
|
||||||
else DocumentMetadataOverrides()
|
|
||||||
)
|
if update_document:
|
||||||
if user is not None:
|
# Create a new version rather than modifying the root/original in place.
|
||||||
overrides.owner_id = user.id
|
overrides = (
|
||||||
overrides.actor_id = user.id
|
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
consume_file.apply_async(
|
if include_metadata
|
||||||
kwargs={
|
else DocumentMetadataOverrides()
|
||||||
"input_doc": ConsumableDocument(
|
)
|
||||||
source=DocumentSource.ConsumeFolder,
|
if user is not None:
|
||||||
original_file=filepath,
|
overrides.owner_id = user.id
|
||||||
root_document_id=pair.root_doc.id,
|
overrides.actor_id = user.id
|
||||||
),
|
consume_file.apply_async(
|
||||||
"overrides": overrides,
|
kwargs={
|
||||||
},
|
"input_doc": ConsumableDocument(
|
||||||
headers={"trigger_source": trigger_source},
|
source=DocumentSource.ConsumeFolder,
|
||||||
)
|
original_file=filepath,
|
||||||
|
root_document_id=pair.root_doc.id,
|
||||||
|
),
|
||||||
|
"overrides": overrides,
|
||||||
|
},
|
||||||
|
headers={"trigger_source": trigger_source},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
consume_tasks = []
|
||||||
|
overrides = (
|
||||||
|
DocumentMetadataOverrides().from_document(pair.root_doc)
|
||||||
|
if include_metadata
|
||||||
|
else DocumentMetadataOverrides()
|
||||||
|
)
|
||||||
|
if user is not None:
|
||||||
|
overrides.owner_id = user.id
|
||||||
|
overrides.actor_id = user.id
|
||||||
|
|
||||||
|
consume_tasks.append(
|
||||||
|
consume_file.s(
|
||||||
|
input_doc=ConsumableDocument(
|
||||||
|
source=DocumentSource.ConsumeFolder,
|
||||||
|
original_file=filepath,
|
||||||
|
),
|
||||||
|
overrides=overrides,
|
||||||
|
).set(headers={"trigger_source": trigger_source}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if delete_original:
|
||||||
|
chord(
|
||||||
|
header=consume_tasks,
|
||||||
|
body=delete.si([doc.id]),
|
||||||
|
).delay()
|
||||||
else:
|
else:
|
||||||
consume_tasks = []
|
group(consume_tasks).delay()
|
||||||
overrides = (
|
|
||||||
DocumentMetadataOverrides().from_document(pair.root_doc)
|
|
||||||
if include_metadata
|
|
||||||
else DocumentMetadataOverrides()
|
|
||||||
)
|
|
||||||
if user is not None:
|
|
||||||
overrides.owner_id = user.id
|
|
||||||
overrides.actor_id = user.id
|
|
||||||
|
|
||||||
consume_tasks.append(
|
|
||||||
consume_file.s(
|
|
||||||
input_doc=ConsumableDocument(
|
|
||||||
source=DocumentSource.ConsumeFolder,
|
|
||||||
original_file=filepath,
|
|
||||||
),
|
|
||||||
overrides=overrides,
|
|
||||||
).set(headers={"trigger_source": trigger_source}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if delete_original:
|
|
||||||
chord(
|
|
||||||
header=consume_tasks,
|
|
||||||
body=delete.si([doc.id]),
|
|
||||||
).delay()
|
|
||||||
else:
|
|
||||||
group(consume_tasks).delay()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
|
|||||||
@@ -54,10 +54,6 @@ def _predict_with_threshold(classifier, X, threshold: float) -> int | None:
|
|||||||
cutoff so that uncertain predictions are discarded rather than assigned.
|
cutoff so that uncertain predictions are discarded rather than assigned.
|
||||||
"""
|
"""
|
||||||
probas = classifier.predict_proba(X)[0]
|
probas = classifier.predict_proba(X)[0]
|
||||||
if len(classifier.classes_) == 1:
|
|
||||||
# scikit-learn treats a single-class fit as binary, so predict_proba
|
|
||||||
# returns [1 - p, p] for one class. Column 0 is the real class.
|
|
||||||
probas = probas[:1]
|
|
||||||
best_idx = int(probas.argmax())
|
best_idx = int(probas.argmax())
|
||||||
best_class = int(classifier.classes_[best_idx])
|
best_class = int(classifier.classes_[best_idx])
|
||||||
|
|
||||||
|
|||||||
@@ -130,14 +130,12 @@ class DocumentMetadataOverrides:
|
|||||||
get_users_with_perms(
|
get_users_with_perms(
|
||||||
doc,
|
doc,
|
||||||
only_with_perms_in=["view_document"],
|
only_with_perms_in=["view_document"],
|
||||||
with_group_users=False,
|
|
||||||
).values_list("id", flat=True),
|
).values_list("id", flat=True),
|
||||||
)
|
)
|
||||||
overrides.change_users = list(
|
overrides.change_users = list(
|
||||||
get_users_with_perms(
|
get_users_with_perms(
|
||||||
doc,
|
doc,
|
||||||
only_with_perms_in=["change_document"],
|
only_with_perms_in=["change_document"],
|
||||||
with_group_users=False,
|
|
||||||
).values_list("id", flat=True),
|
).values_list("id", flat=True),
|
||||||
)
|
)
|
||||||
overrides.custom_fields = {
|
overrides.custom_fields = {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from itertools import islice
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Final
|
|
||||||
|
|
||||||
from allauth.mfa.models import Authenticator
|
from allauth.mfa.models import Authenticator
|
||||||
from allauth.socialaccount.models import SocialAccount
|
from allauth.socialaccount.models import SocialAccount
|
||||||
@@ -71,15 +70,6 @@ from paperless.models import ApplicationConfiguration
|
|||||||
from paperless_mail.models import MailAccount
|
from paperless_mail.models import MailAccount
|
||||||
from paperless_mail.models import MailRule
|
from paperless_mail.models import MailRule
|
||||||
|
|
||||||
# Derived names replace the original's extension with these suffixes
|
|
||||||
THUMBNAIL_SUFFIX: Final[str] = "-thumbnail.webp"
|
|
||||||
ARCHIVE_SUFFIX: Final[str] = "-archive.pdf"
|
|
||||||
# Leave room for the longest derived suffix, which replaces the original's extension
|
|
||||||
MAX_BASE_NAME_BYTES: Final[int] = Document.MAX_PUBLIC_FILENAME_BYTES - max(
|
|
||||||
len(THUMBNAIL_SUFFIX),
|
|
||||||
len(ARCHIVE_SUFFIX),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_queryset_batched(
|
def serialize_queryset_batched(
|
||||||
queryset: "QuerySet[Any]",
|
queryset: "QuerySet[Any]",
|
||||||
@@ -522,10 +512,7 @@ class Command(CryptMixin, PaperlessCommand):
|
|||||||
counter=filename_counter,
|
counter=filename_counter,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
base_name = document.get_public_filename(
|
base_name = document.get_public_filename(counter=filename_counter)
|
||||||
counter=filename_counter,
|
|
||||||
max_bytes=MAX_BASE_NAME_BYTES,
|
|
||||||
)
|
|
||||||
|
|
||||||
# The thumbnail and archive names are derived from the stem, so
|
# The thumbnail and archive names are derived from the stem, so
|
||||||
# uniqueness must hold without the original's extension
|
# uniqueness must hold without the original's extension
|
||||||
@@ -554,7 +541,7 @@ class Command(CryptMixin, PaperlessCommand):
|
|||||||
document_dict[EXPORTER_FILE_NAME] = original_arc
|
document_dict[EXPORTER_FILE_NAME] = original_arc
|
||||||
|
|
||||||
if not self.no_thumbnail:
|
if not self.no_thumbnail:
|
||||||
thumbnail_name = base_name.parent / (base_name.stem + THUMBNAIL_SUFFIX)
|
thumbnail_name = base_name.parent / (base_name.stem + "-thumbnail.webp")
|
||||||
if self.use_folder_prefix:
|
if self.use_folder_prefix:
|
||||||
thumbnail_name = Path("thumbnails") / thumbnail_name
|
thumbnail_name = Path("thumbnails") / thumbnail_name
|
||||||
thumbnail_arc = thumbnail_name.as_posix()
|
thumbnail_arc = thumbnail_name.as_posix()
|
||||||
@@ -563,7 +550,7 @@ class Command(CryptMixin, PaperlessCommand):
|
|||||||
thumbnail_arc = None
|
thumbnail_arc = None
|
||||||
|
|
||||||
if not self.no_archive and document.has_archive_version:
|
if not self.no_archive and document.has_archive_version:
|
||||||
archive_name = base_name.parent / (base_name.stem + ARCHIVE_SUFFIX)
|
archive_name = base_name.parent / (base_name.stem + "-archive.pdf")
|
||||||
if self.use_folder_prefix:
|
if self.use_folder_prefix:
|
||||||
archive_name = Path("archive") / archive_name
|
archive_name = Path("archive") / archive_name
|
||||||
archive_arc = archive_name.as_posix()
|
archive_arc = archive_name.as_posix()
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def iter_manifest_records(path: Path) -> Generator[dict, None, None]:
|
|||||||
"""Yield records one at a time from a manifest JSON array via ijson."""
|
"""Yield records one at a time from a manifest JSON array via ijson."""
|
||||||
try:
|
try:
|
||||||
with path.open("rb") as f:
|
with path.open("rb") as f:
|
||||||
yield from ijson.items(f, "item", use_float=True)
|
yield from ijson.items(f, "item")
|
||||||
except ijson.JSONError as e:
|
except ijson.JSONError as e:
|
||||||
raise CommandError(f"Failed to parse manifest file {path}: {e}") from e
|
raise CommandError(f"Failed to parse manifest file {path}: {e}") from e
|
||||||
|
|
||||||
|
|||||||
+12
-23
@@ -157,8 +157,6 @@ class StoragePath(MatchingModel):
|
|||||||
|
|
||||||
class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-missing]
|
class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-missing]
|
||||||
MAX_STORED_FILENAME_LENGTH: Final[int] = 1024
|
MAX_STORED_FILENAME_LENGTH: Final[int] = 1024
|
||||||
# Per-component limit of common filesystems (ext4, XFS, btrfs, ZFS, APFS)
|
|
||||||
MAX_PUBLIC_FILENAME_BYTES: Final[int] = 255
|
|
||||||
|
|
||||||
correspondent = models.ForeignKey(
|
correspondent = models.ForeignKey(
|
||||||
Correspondent,
|
Correspondent,
|
||||||
@@ -486,37 +484,28 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
|||||||
def archive_file(self):
|
def archive_file(self):
|
||||||
return Path(self.archive_path).open("rb")
|
return Path(self.archive_path).open("rb")
|
||||||
|
|
||||||
def get_public_filename(
|
def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
archive: bool = False,
|
|
||||||
counter: int = 0,
|
|
||||||
max_bytes: int = MAX_PUBLIC_FILENAME_BYTES,
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Returns a sanitized filename for the document, not including any paths.
|
Returns a sanitized filename for the document, not including any paths.
|
||||||
|
|
||||||
The name fits in max_bytes (UTF-8). A long title is shortened before
|
|
||||||
the counter and extension are appended, because sanitize_filename
|
|
||||||
truncates from the end. The tail is cut only when max_bytes is
|
|
||||||
smaller than the tail itself.
|
|
||||||
"""
|
"""
|
||||||
# Root owns metadata for all versions
|
# Root owns metadata for all versions
|
||||||
context_document = (
|
context_document = (
|
||||||
self.root_document if self.root_document_id is not None else self
|
self.root_document if self.root_document_id is not None else self
|
||||||
)
|
)
|
||||||
|
result = str(context_document)
|
||||||
|
|
||||||
extension = ".pdf" if archive else self.file_type
|
if counter:
|
||||||
tail = f"_{counter:02}{extension}" if counter else extension
|
result += f"_{counter:02}"
|
||||||
|
|
||||||
head_bytes = max(0, max_bytes - len(tail.encode()))
|
if suffix:
|
||||||
head = str(context_document).encode()[:head_bytes].decode(errors="ignore")
|
result += suffix
|
||||||
|
|
||||||
return pathvalidate.sanitize_filename(
|
if archive:
|
||||||
head + tail,
|
result += ".pdf"
|
||||||
replacement_text="-",
|
else:
|
||||||
max_len=max_bytes,
|
result += self.file_type
|
||||||
)
|
|
||||||
|
return pathvalidate.sanitize_filename(result, replacement_text="-")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_type(self):
|
def file_type(self):
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
"""
|
||||||
|
Pure PDF page operations used by documents.bulk_edit.
|
||||||
|
|
||||||
|
This module deliberately knows nothing about Django, Celery or the documents
|
||||||
|
app: callers resolve documents, choose output paths and queue work. Every
|
||||||
|
function that writes a PDF removes unreferenced resources before saving.
|
||||||
|
|
||||||
|
pikepdf is always called as ``pikepdf.open(...)`` / ``pikepdf.new()`` (never
|
||||||
|
``from pikepdf import open``) so tests can patch those module attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
import pikepdf
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from pathlib import Path
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
|
|
||||||
|
class PageSpec(NamedTuple):
|
||||||
|
"""One page of an output PDF: a 1-indexed source page, optionally rotated."""
|
||||||
|
|
||||||
|
page: int
|
||||||
|
rotate: int = 0 # relative degrees, 0 leaves the page alone
|
||||||
|
|
||||||
|
|
||||||
|
def _require_positive(pages: Iterable[int]) -> None:
|
||||||
|
for page in pages:
|
||||||
|
if page < 1:
|
||||||
|
raise ValueError(f"Page numbers start at 1, got {page}")
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_pdf(src: Path, dst: Path, degrees: int) -> None:
|
||||||
|
"""
|
||||||
|
Rotate every page relatively on the opened document, not a rebuild, so Info,
|
||||||
|
XMP and outlines are kept. ``src`` is not modified.
|
||||||
|
"""
|
||||||
|
with pikepdf.open(src) as pdf:
|
||||||
|
for page in pdf.pages:
|
||||||
|
page.rotate(degrees, relative=True)
|
||||||
|
pdf.remove_unreferenced_resources()
|
||||||
|
pdf.save(dst)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_pages(src: Path, dst: Path, pages: Iterable[int]) -> None:
|
||||||
|
"""
|
||||||
|
Remove 1-indexed pages from the opened document, not a rebuild, so Info, XMP
|
||||||
|
and outlines are kept. ``src`` is not modified.
|
||||||
|
|
||||||
|
Duplicates are ignored. Pages are removed highest first so earlier removals
|
||||||
|
never shift the index of later ones.
|
||||||
|
"""
|
||||||
|
unique = sorted(set(pages))
|
||||||
|
_require_positive(unique)
|
||||||
|
with pikepdf.open(src) as pdf:
|
||||||
|
for page_num in reversed(unique):
|
||||||
|
del pdf.pages[page_num - 1]
|
||||||
|
pdf.remove_unreferenced_resources()
|
||||||
|
pdf.save(dst)
|
||||||
|
|
||||||
|
|
||||||
|
def build_pdfs(
|
||||||
|
src: Path,
|
||||||
|
outputs: Sequence[tuple[Sequence[PageSpec], Callable[[], Path]]],
|
||||||
|
) -> list[Path]:
|
||||||
|
"""
|
||||||
|
Build one new PDF per output from pages of ``src``, opening ``src`` once.
|
||||||
|
|
||||||
|
Each output is ``(page_specs, make_dst)``. Every page number is checked against
|
||||||
|
``src`` before any output is built, and ``make_dst`` is called after that
|
||||||
|
output's pages are copied and immediately before it is saved, so a bad page
|
||||||
|
number in any output never leaves a destination behind. Document-level data
|
||||||
|
(Info, XMP, outlines) is not carried over. Returns the written paths in output
|
||||||
|
order.
|
||||||
|
"""
|
||||||
|
for specs, _ in outputs:
|
||||||
|
_require_positive(spec.page for spec in specs)
|
||||||
|
|
||||||
|
written: list[Path] = []
|
||||||
|
with pikepdf.open(src) as source:
|
||||||
|
page_count = len(source.pages)
|
||||||
|
for specs, _ in outputs:
|
||||||
|
for spec in specs:
|
||||||
|
if spec.page > page_count:
|
||||||
|
raise IndexError(
|
||||||
|
f"Page {spec.page} is out of range, the PDF has "
|
||||||
|
f"{page_count} pages",
|
||||||
|
)
|
||||||
|
for specs, make_dst in outputs:
|
||||||
|
dst = pikepdf.new()
|
||||||
|
for spec in specs:
|
||||||
|
dst.pages.append(source.pages[spec.page - 1])
|
||||||
|
if spec.rotate:
|
||||||
|
dst.pages[-1].rotate(spec.rotate, relative=True)
|
||||||
|
dst.remove_unreferenced_resources()
|
||||||
|
path = make_dst()
|
||||||
|
dst.save(path)
|
||||||
|
dst.close()
|
||||||
|
written.append(path)
|
||||||
|
return written
|
||||||
|
|
||||||
|
|
||||||
|
def validate_page_operations(
|
||||||
|
operations: Sequence[Mapping[str, int]],
|
||||||
|
*,
|
||||||
|
single_output: bool,
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Validate ``edit_pdf`` style operations and return the output document count.
|
||||||
|
|
||||||
|
Each operation has ``page`` and optionally ``rotate`` and ``doc`` (the output
|
||||||
|
document index, default 0). The bounds rule is kept as it was: a ``doc`` index
|
||||||
|
must be below the number of operations.
|
||||||
|
"""
|
||||||
|
if not operations:
|
||||||
|
raise ValueError("Output document index is out of bounds")
|
||||||
|
|
||||||
|
max_idx = max(op.get("doc", 0) for op in operations)
|
||||||
|
if single_output and max_idx > 0:
|
||||||
|
raise ValueError("Multiple output documents specified")
|
||||||
|
|
||||||
|
if any(
|
||||||
|
op.get("doc", 0) < 0 or op.get("doc", 0) >= len(operations) for op in operations
|
||||||
|
):
|
||||||
|
raise ValueError("Output document index is out of bounds")
|
||||||
|
|
||||||
|
return max_idx + 1
|
||||||
|
|
||||||
|
|
||||||
|
def needs_decrypt(src: Path) -> bool:
|
||||||
|
"""
|
||||||
|
True if ``src`` is encrypted. A PDF that needs a password to open at all
|
||||||
|
counts as encrypted.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with pikepdf.open(src) as pdf:
|
||||||
|
return bool(pdf.is_encrypted)
|
||||||
|
except pikepdf.PasswordError:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_pdf(src: Path, make_dst: Callable[[], Path], password: str) -> Path:
|
||||||
|
"""
|
||||||
|
Write an unencrypted copy of ``src`` and return its path.
|
||||||
|
|
||||||
|
``make_dst`` is only called once the password has been accepted, so a wrong
|
||||||
|
password never leaves a destination behind.
|
||||||
|
"""
|
||||||
|
with pikepdf.open(src, password=password) as pdf:
|
||||||
|
pdf.remove_unreferenced_resources()
|
||||||
|
dst = make_dst()
|
||||||
|
pdf.save(dst)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
|
||||||
|
class PdfMerger:
|
||||||
|
"""
|
||||||
|
Accumulates the pages of several PDFs into one new PDF.
|
||||||
|
|
||||||
|
``add`` raises if a source cannot be read; deciding whether to skip it is the
|
||||||
|
caller's policy. Use as a context manager so the merged PDF is closed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._pdf = pikepdf.new()
|
||||||
|
self._version: str = self._pdf.pdf_version
|
||||||
|
|
||||||
|
def __enter__(self) -> PdfMerger:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: type[BaseException] | None,
|
||||||
|
exc: BaseException | None,
|
||||||
|
tb: TracebackType | None,
|
||||||
|
) -> None:
|
||||||
|
self._pdf.close()
|
||||||
|
|
||||||
|
def add(self, path: Path) -> None:
|
||||||
|
with pikepdf.open(str(path)) as pdf:
|
||||||
|
self._version = max(self._version, pdf.pdf_version)
|
||||||
|
self._pdf.pages.extend(pdf.pages)
|
||||||
|
|
||||||
|
def save(self, dst: Path) -> None:
|
||||||
|
self._pdf.remove_unreferenced_resources()
|
||||||
|
self._pdf.save(dst, min_version=self._version)
|
||||||
@@ -511,28 +511,6 @@ def restrict_queryset_to_visible(
|
|||||||
return queryset.filter(pk__in=permitted_object_ids(user, queryset.model, perm))
|
return queryset.filter(pk__in=permitted_object_ids(user, queryset.model, perm))
|
||||||
|
|
||||||
|
|
||||||
def user_is_active_superuser(user: User | None) -> bool:
|
|
||||||
"""
|
|
||||||
True only for an active, authenticated superuser.
|
|
||||||
"""
|
|
||||||
return user is not None and user_is_unrestricted(user)
|
|
||||||
|
|
||||||
|
|
||||||
def restrict_queryset_to_owner_visible(
|
|
||||||
queryset: QuerySet[ModelT],
|
|
||||||
user: User | None,
|
|
||||||
perm: str,
|
|
||||||
) -> QuerySet[ModelT]:
|
|
||||||
"""
|
|
||||||
Like ``restrict_queryset_to_visible``, but ``user=None`` sees only unowned
|
|
||||||
rows (``permitted_object_ids(None, ...)`` semantics) instead of every row.
|
|
||||||
See ``user_is_active_superuser``.
|
|
||||||
"""
|
|
||||||
if user_is_active_superuser(user):
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(pk__in=permitted_object_ids(user, queryset.model, perm))
|
|
||||||
|
|
||||||
|
|
||||||
def permitted_document_ids(
|
def permitted_document_ids(
|
||||||
user: User | None,
|
user: User | None,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -926,7 +926,6 @@ class TantivyBackend:
|
|||||||
sort_field: str | None = None,
|
sort_field: str | None = None,
|
||||||
sort_reverse: bool = False,
|
sort_reverse: bool = False,
|
||||||
search_mode: SearchMode = SearchMode.QUERY,
|
search_mode: SearchMode = SearchMode.QUERY,
|
||||||
filter_query: str | None = None,
|
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
) -> list[int]:
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
@@ -942,7 +941,6 @@ class TantivyBackend:
|
|||||||
sort_field: Field to sort by (None for relevance ranking)
|
sort_field: Field to sort by (None for relevance ranking)
|
||||||
sort_reverse: Whether to reverse the sort order
|
sort_reverse: Whether to reverse the sort order
|
||||||
search_mode: Query parsing mode (QUERY, TEXT, or TITLE)
|
search_mode: Query parsing mode (QUERY, TEXT, or TITLE)
|
||||||
filter_query: Optional advanced query results must also match
|
|
||||||
limit: Maximum number of IDs to return (None = all matching docs)
|
limit: Maximum number of IDs to return (None = all matching docs)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -950,13 +948,6 @@ class TantivyBackend:
|
|||||||
"""
|
"""
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
user_query = self._parse_query(query, search_mode)
|
user_query = self._parse_query(query, search_mode)
|
||||||
if filter_query is not None:
|
|
||||||
user_query = user_query.and_must_match(
|
|
||||||
tantivy.Query.const_score_query(
|
|
||||||
self._parse_query(filter_query, SearchMode.QUERY),
|
|
||||||
0.0, # zero so doesn't affect relevance
|
|
||||||
),
|
|
||||||
)
|
|
||||||
final_query = self._apply_permission_filter(user_query, user)
|
final_query = self._apply_permission_filter(user_query, user)
|
||||||
|
|
||||||
searcher = self._index.searcher()
|
searcher = self._index.searcher()
|
||||||
|
|||||||
@@ -2144,6 +2144,8 @@ class BulkEditSerializer(
|
|||||||
raise serializers.ValidationError("pages must be a list")
|
raise serializers.ValidationError("pages must be a list")
|
||||||
if not all(isinstance(i, int) for i in parameters["pages"]):
|
if not all(isinstance(i, int) for i in parameters["pages"]):
|
||||||
raise serializers.ValidationError("pages must be a list of integers")
|
raise serializers.ValidationError("pages must be a list of integers")
|
||||||
|
if any(i < 1 for i in parameters["pages"]):
|
||||||
|
raise serializers.ValidationError("pages must be positive integers")
|
||||||
|
|
||||||
def _validate_parameters_merge(self, parameters) -> None:
|
def _validate_parameters_merge(self, parameters) -> None:
|
||||||
if "delete_originals" in parameters:
|
if "delete_originals" in parameters:
|
||||||
|
|||||||
@@ -835,7 +835,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
for filters in (
|
for filters in (
|
||||||
{"text": "new doc 2017-03-16"},
|
{"text": "new doc 2017-03-16"},
|
||||||
{"title_search": "apple"},
|
{"title_search": "apple"},
|
||||||
{"title_search": "apple", "query": "created:[-1 year to now]"},
|
|
||||||
):
|
):
|
||||||
with self.subTest(filters=filters):
|
with self.subTest(filters=filters):
|
||||||
get_backend.return_value.search_ids.return_value = [self.doc2.id]
|
get_backend.return_value.search_ids.return_value = [self.doc2.id]
|
||||||
@@ -887,7 +886,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
"all": True,
|
"all": True,
|
||||||
"filters": {
|
"filters": {
|
||||||
"text": "B",
|
"text": "B",
|
||||||
"title_search": "c1",
|
"query": "c1",
|
||||||
},
|
},
|
||||||
"method": "set_storage_path",
|
"method": "set_storage_path",
|
||||||
"parameters": {"storage_path": self.sp1.id},
|
"parameters": {"storage_path": self.sp1.id},
|
||||||
@@ -1844,6 +1843,36 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
|||||||
m.assert_called_once()
|
m.assert_called_once()
|
||||||
self.assertEqual(m.call_args.kwargs["pages"], [[1], [2, 3, 4], [5]])
|
self.assertEqual(m.call_args.kwargs["pages"], [[1], [2, 3, 4], [5]])
|
||||||
|
|
||||||
|
@mock.patch("documents.serialisers.bulk_edit.delete_pages")
|
||||||
|
def test_bulk_edit_delete_pages_rejects_pages_below_one(self, m) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A legacy delete_pages bulk edit
|
||||||
|
WHEN:
|
||||||
|
- API to bulk edit is called with a page number below 1
|
||||||
|
THEN:
|
||||||
|
- API returns HTTP 400
|
||||||
|
- delete_pages is not called
|
||||||
|
"""
|
||||||
|
self.setup_mock(m, "delete_pages")
|
||||||
|
|
||||||
|
for pages in ([0], [-1], [1, 0]):
|
||||||
|
with self.subTest(pages=pages):
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/documents/bulk_edit/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"documents": [self.doc2.id],
|
||||||
|
"method": "delete_pages",
|
||||||
|
"parameters": {"pages": pages},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertIn(b"pages must be positive integers", response.content)
|
||||||
|
m.assert_not_called()
|
||||||
|
|
||||||
@mock.patch("documents.views.bulk_edit.rotate")
|
@mock.patch("documents.views.bulk_edit.rotate")
|
||||||
def test_rotate_insufficient_permissions(self, m) -> None:
|
def test_rotate_insufficient_permissions(self, m) -> None:
|
||||||
self.doc1.owner = User.objects.get(username="temp_admin")
|
self.doc1.owner = User.objects.get(username="temp_admin")
|
||||||
|
|||||||
@@ -245,68 +245,8 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(response.data["count"], 1)
|
self.assertEqual(response.data["count"], 1)
|
||||||
self.assertEqual(response.data["results"][0]["id"], title_match.id)
|
self.assertEqual(response.data["results"][0]["id"], title_match.id)
|
||||||
|
|
||||||
def test_simple_search_combined_with_query_filter(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A document with the term in its title, created this year
|
|
||||||
- A document with the term only in its content, created this year
|
|
||||||
- A document with the term in its title, created years ago
|
|
||||||
WHEN:
|
|
||||||
- A title or text search is combined with a relative created
|
|
||||||
date query, as the filter editor sends it
|
|
||||||
THEN:
|
|
||||||
- The simple search keeps its own field scope and the query only
|
|
||||||
narrows the results (issue #14436)
|
|
||||||
"""
|
|
||||||
this_year = timezone.localdate().replace(month=1, day=1)
|
|
||||||
title_match = Document.objects.create(
|
|
||||||
title="foo report",
|
|
||||||
content="nothing relevant",
|
|
||||||
checksum="F1",
|
|
||||||
created=this_year,
|
|
||||||
pk=21,
|
|
||||||
)
|
|
||||||
content_match = Document.objects.create(
|
|
||||||
title="something else",
|
|
||||||
content="foo appears here",
|
|
||||||
checksum="F2",
|
|
||||||
created=this_year,
|
|
||||||
pk=22,
|
|
||||||
)
|
|
||||||
old_title_match = Document.objects.create(
|
|
||||||
title="foo archive",
|
|
||||||
content="nothing relevant",
|
|
||||||
checksum="F3",
|
|
||||||
created=datetime.date(2001, 1, 1),
|
|
||||||
pk=23,
|
|
||||||
)
|
|
||||||
backend = get_backend()
|
|
||||||
for doc in (title_match, content_match, old_title_match):
|
|
||||||
backend.add_or_update(doc)
|
|
||||||
|
|
||||||
query = "created:[-1 year to now]"
|
|
||||||
response = self.client.get(
|
|
||||||
"/api/documents/",
|
|
||||||
{"title_search": "foo", "query": query},
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertEqual(
|
|
||||||
[r["id"] for r in response.data["results"]],
|
|
||||||
[title_match.id],
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
"/api/documents/",
|
|
||||||
{"text": "foo", "query": query},
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
self.assertCountEqual(
|
|
||||||
[r["id"] for r in response.data["results"]],
|
|
||||||
[title_match.id, content_match.id],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_search_rejects_multiple_search_modes(self) -> None:
|
def test_search_rejects_multiple_search_modes(self) -> None:
|
||||||
response = self.client.get("/api/documents/?text=bank&title_search=bank")
|
response = self.client.get("/api/documents/?text=bank&query=bank")
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.data["detail"],
|
response.data["detail"],
|
||||||
@@ -2137,6 +2077,29 @@ class TestDocumentSearchApi(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(len(response.data["documents"]), 1)
|
self.assertEqual(len(response.data["documents"]), 1)
|
||||||
self.assertEqual(response.data["documents"][0]["id"], title_match.id)
|
self.assertEqual(response.data["documents"][0]["id"], title_match.id)
|
||||||
|
|
||||||
|
def test_global_search_returns_latest_version_content(self) -> None:
|
||||||
|
root = Document.objects.create(
|
||||||
|
title="bank statement",
|
||||||
|
content="superseded content",
|
||||||
|
checksum="GSV1",
|
||||||
|
pk=23,
|
||||||
|
)
|
||||||
|
Document.objects.create(
|
||||||
|
title="bank statement v2",
|
||||||
|
content="latest content",
|
||||||
|
checksum="GSV2",
|
||||||
|
pk=24,
|
||||||
|
root_document=root,
|
||||||
|
version_index=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.force_authenticate(self.user)
|
||||||
|
|
||||||
|
response = self.client.get("/api/search/?query=bank&db_only=true")
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
returned = {doc["id"]: doc["content"] for doc in response.data["documents"]}
|
||||||
|
self.assertEqual(returned.get(root.id), "latest content")
|
||||||
|
|
||||||
def test_global_search_filters_owned_mail_objects(self) -> None:
|
def test_global_search_filters_owned_mail_objects(self) -> None:
|
||||||
user1 = UserFactory(username="mail-search-user")
|
user1 = UserFactory(username="mail-search-user")
|
||||||
user2 = UserFactory(username="other-mail-search-user")
|
user2 = UserFactory(username="other-mail-search-user")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import shutil
|
import shutil
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pikepdf
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
@@ -23,6 +23,7 @@ from documents.models import Document
|
|||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
|
from documents.pdf_ops import PageSpec
|
||||||
from documents.permissions import set_permissions_for_objects
|
from documents.permissions import set_permissions_for_objects
|
||||||
from paperless_testing.dirs import DirectoriesMixin
|
from paperless_testing.dirs import DirectoriesMixin
|
||||||
from paperless_testing.factories import DocumentFactory
|
from paperless_testing.factories import DocumentFactory
|
||||||
@@ -796,16 +797,14 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.img_doc.save()
|
self.img_doc.save()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mock_password_required_pdf(
|
def fake_decrypt(
|
||||||
mock_open: mock.Mock,
|
src: Path,
|
||||||
fake_pdf: mock.Mock,
|
make_dst: Callable[[], Path],
|
||||||
) -> None:
|
password: str,
|
||||||
password_context = mock.MagicMock()
|
) -> Path:
|
||||||
password_context.__enter__.return_value = fake_pdf
|
dst = make_dst()
|
||||||
mock_open.side_effect = [
|
dst.write_bytes(b"password removed")
|
||||||
pikepdf.PasswordError("password required"),
|
return dst
|
||||||
password_context,
|
|
||||||
]
|
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_merge(self, mock_consume_file) -> None:
|
def test_merge(self, mock_consume_file) -> None:
|
||||||
@@ -850,12 +849,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.PdfMerger")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_merge_uses_latest_version_source_for_root_selection(
|
def test_merge_uses_latest_version_source_for_root_selection(
|
||||||
self,
|
self,
|
||||||
mock_consume_file,
|
mock_consume_file,
|
||||||
mock_open_pdf,
|
mock_merger,
|
||||||
) -> None:
|
) -> None:
|
||||||
version_file = self.dirs.scratch_dir / "sample2_version_merge.pdf"
|
version_file = self.dirs.scratch_dir / "sample2_version_merge.pdf"
|
||||||
shutil.copy(self.doc2.source_path, version_file)
|
shutil.copy(self.doc2.source_path, version_file)
|
||||||
@@ -866,16 +865,14 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
filename=version_file,
|
filename=version_file,
|
||||||
mime_type="application/pdf",
|
mime_type="application/pdf",
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
merger = mock_merger.return_value.__enter__.return_value
|
||||||
fake_pdf.pdf_version = "1.7"
|
merger.save.side_effect = lambda dst: shutil.copy(version.source_path, dst)
|
||||||
fake_pdf.pages = [mock.Mock()]
|
|
||||||
mock_open_pdf.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.merge([self.doc2.id])
|
result = bulk_edit.merge([self.doc2.id])
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open_pdf.assert_called_once_with(str(version.source_path))
|
merger.add.assert_called_once_with(version.source_path)
|
||||||
mock_consume_file.assert_not_called()
|
mock_consume_file.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.delete.si")
|
@mock.patch("documents.bulk_edit.delete.si")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
@@ -1037,18 +1034,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.delay")
|
@mock.patch("documents.tasks.consume_file.delay")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.PdfMerger.add")
|
||||||
def test_merge_with_errors(self, mock_open_pdf, mock_consume_file) -> None:
|
def test_merge_with_errors(self, mock_add, mock_consume_file) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Existing documents
|
- Existing documents
|
||||||
WHEN:
|
WHEN:
|
||||||
- Merge action is called with 2 documents
|
- Merge action is called with 2 documents
|
||||||
- Error occurs when opening both files
|
- Error occurs when adding both files
|
||||||
THEN:
|
THEN:
|
||||||
- Consume file should not be called
|
- Consume file should not be called
|
||||||
"""
|
"""
|
||||||
mock_open_pdf.side_effect = Exception("Error opening PDF")
|
mock_add.side_effect = Exception("Error opening PDF")
|
||||||
doc_ids = [self.doc2.id, self.doc3.id]
|
doc_ids = [self.doc2.id, self.doc3.id]
|
||||||
|
|
||||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||||
@@ -1085,12 +1082,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_split_uses_latest_version_source_for_root_selection(
|
def test_split_uses_latest_version_source_for_root_selection(
|
||||||
self,
|
self,
|
||||||
mock_consume_file,
|
mock_consume_file,
|
||||||
mock_open_pdf,
|
mock_build_pdfs,
|
||||||
mock_group,
|
mock_group,
|
||||||
) -> None:
|
) -> None:
|
||||||
version_file = self.dirs.scratch_dir / "sample2_version_split.pdf"
|
version_file = self.dirs.scratch_dir / "sample2_version_split.pdf"
|
||||||
@@ -1102,17 +1099,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
filename=version_file,
|
filename=version_file,
|
||||||
mime_type="application/pdf",
|
mime_type="application/pdf",
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
mock_build_pdfs.return_value = [version.source_path, version.source_path]
|
||||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
|
||||||
mock_open_pdf.return_value.__enter__.return_value = fake_pdf
|
|
||||||
mock_group.return_value.delay.return_value = None
|
mock_group.return_value.delay.return_value = None
|
||||||
|
|
||||||
result = bulk_edit.split([self.doc2.id], [[1], [2]])
|
result = bulk_edit.split([self.doc2.id], [[1], [2]])
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open_pdf.assert_called_once_with(version.source_path)
|
self.assertEqual(mock_build_pdfs.call_args.args[0], version.source_path)
|
||||||
mock_consume_file.assert_not_called()
|
self.assertEqual(mock_consume_file.call_count, 2)
|
||||||
mock_group.return_value.delay.assert_not_called()
|
mock_group.return_value.delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.delete.si")
|
@mock.patch("documents.bulk_edit.delete.si")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
@@ -1200,18 +1195,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(self.doc2.archive_serial_number, 222)
|
self.assertEqual(self.doc2.archive_serial_number, 222)
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.Pdf.save")
|
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||||
def test_split_with_errors(self, mock_save_pdf, mock_consume_file) -> None:
|
def test_split_with_errors(self, mock_build_pdfs, mock_consume_file) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Existing documents
|
- Existing documents
|
||||||
WHEN:
|
WHEN:
|
||||||
- Split action is called with 1 document and 2 page groups
|
- Split action is called with 1 document and 2 page groups
|
||||||
- Error occurs when saving the files
|
- Error occurs when building the files
|
||||||
THEN:
|
THEN:
|
||||||
- Consume file should not be called
|
- Consume file should not be called
|
||||||
"""
|
"""
|
||||||
mock_save_pdf.side_effect = Exception("Error saving PDF")
|
mock_build_pdfs.side_effect = Exception("Error building PDFs")
|
||||||
doc_ids = [self.doc2.id]
|
doc_ids = [self.doc2.id]
|
||||||
pages = [[1, 2], [3]]
|
pages = [[1, 2], [3]]
|
||||||
|
|
||||||
@@ -1246,10 +1241,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.Pdf.save")
|
@mock.patch("documents.pdf_ops.rotate_pdf")
|
||||||
def test_rotate_with_error(
|
def test_rotate_with_error(
|
||||||
self,
|
self,
|
||||||
mock_pdf_save,
|
mock_rotate_pdf,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -1257,11 +1252,11 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
- Existing documents
|
- Existing documents
|
||||||
WHEN:
|
WHEN:
|
||||||
- Rotate action is called with 2 documents
|
- Rotate action is called with 2 documents
|
||||||
- PikePDF raises an error
|
- Rotating the PDF raises an error
|
||||||
THEN:
|
THEN:
|
||||||
- Rotate action should be called 0 times
|
- Rotate action should be called 0 times
|
||||||
"""
|
"""
|
||||||
mock_pdf_save.side_effect = Exception("Error saving PDF")
|
mock_rotate_pdf.side_effect = Exception("Error rotating PDF")
|
||||||
doc_ids = [self.doc2.id, self.doc3.id]
|
doc_ids = [self.doc2.id, self.doc3.id]
|
||||||
|
|
||||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||||
@@ -1296,10 +1291,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.rotate_pdf")
|
||||||
def test_rotate_explicit_selection_uses_root_source_when_root_selected(
|
def test_rotate_explicit_selection_uses_root_source_when_root_selected(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_rotate_pdf,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_magic,
|
mock_magic,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1308,9 +1303,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
title="B version 1",
|
title="B version 1",
|
||||||
root_document=self.doc2,
|
root_document=self.doc2,
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.pages = [mock.Mock()]
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.rotate(
|
result = bulk_edit.rotate(
|
||||||
[self.doc2.id],
|
[self.doc2.id],
|
||||||
@@ -1319,26 +1311,35 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
self.assertEqual(mock_rotate_pdf.call_args.args[0], self.doc2.source_path)
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.Pdf.save")
|
@mock.patch("documents.pdf_ops.remove_pages")
|
||||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||||
def test_delete_pages(self, mock_magic, mock_pdf_save, mock_consume_delay) -> None:
|
def test_delete_pages(
|
||||||
|
self,
|
||||||
|
mock_magic,
|
||||||
|
mock_remove_pages,
|
||||||
|
mock_consume_delay,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Existing documents
|
- Existing documents
|
||||||
WHEN:
|
WHEN:
|
||||||
- Delete pages action is called with 1 document and 2 pages
|
- Delete pages action is called with 1 document and 2 pages
|
||||||
THEN:
|
THEN:
|
||||||
- Save should be called once
|
- The pages are removed from the document's source PDF
|
||||||
- A new version should be enqueued via consume_file
|
- A new version should be enqueued via consume_file
|
||||||
"""
|
"""
|
||||||
doc_ids = [self.doc2.id]
|
doc_ids = [self.doc2.id]
|
||||||
pages = [1, 3]
|
pages = [1, 3]
|
||||||
result = bulk_edit.delete_pages(doc_ids, pages)
|
result = bulk_edit.delete_pages(doc_ids, pages)
|
||||||
mock_pdf_save.assert_called_once()
|
mock_remove_pages.assert_called_once_with(
|
||||||
|
self.doc2.source_path,
|
||||||
|
mock.ANY,
|
||||||
|
[1, 3],
|
||||||
|
)
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
||||||
self.assertEqual(task_kwargs["input_doc"].root_document_id, self.doc2.id)
|
self.assertEqual(task_kwargs["input_doc"].root_document_id, self.doc2.id)
|
||||||
@@ -1350,10 +1351,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.remove_pages")
|
||||||
def test_delete_pages_explicit_selection_uses_root_source_when_root_selected(
|
def test_delete_pages_explicit_selection_uses_root_source_when_root_selected(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_remove_pages,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_magic,
|
mock_magic,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1362,9 +1363,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
title="B version 1",
|
title="B version 1",
|
||||||
root_document=self.doc2,
|
root_document=self.doc2,
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.delete_pages(
|
result = bulk_edit.delete_pages(
|
||||||
[self.doc2.id],
|
[self.doc2.id],
|
||||||
@@ -1373,23 +1371,26 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
self.assertEqual(mock_remove_pages.call_args.args[0], self.doc2.source_path)
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.Pdf.save")
|
@mock.patch("documents.pdf_ops.remove_pages")
|
||||||
def test_delete_pages_with_error(self, mock_pdf_save, mock_consume_delay) -> None:
|
def test_delete_pages_with_error(
|
||||||
|
self,
|
||||||
|
mock_remove_pages,
|
||||||
|
mock_consume_delay,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Existing documents
|
- Existing documents
|
||||||
WHEN:
|
WHEN:
|
||||||
- Delete pages action is called with 1 document and 2 pages
|
- Delete pages action is called with 1 document and 2 pages
|
||||||
- PikePDF raises an error
|
- Removing the pages raises an error
|
||||||
THEN:
|
THEN:
|
||||||
- Save should be called once
|
|
||||||
- No new version should be enqueued
|
- No new version should be enqueued
|
||||||
"""
|
"""
|
||||||
mock_pdf_save.side_effect = Exception("Error saving PDF")
|
mock_remove_pages.side_effect = Exception("Error removing pages")
|
||||||
doc_ids = [self.doc2.id]
|
doc_ids = [self.doc2.id]
|
||||||
pages = [1, 3]
|
pages = [1, 3]
|
||||||
|
|
||||||
@@ -1419,6 +1420,41 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_group.return_value.delay.assert_called_once()
|
mock_group.return_value.delay.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch("documents.bulk_edit.group")
|
||||||
|
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||||
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
|
def test_edit_pdf_maps_operations_to_outputs(
|
||||||
|
self,
|
||||||
|
mock_consume_file: mock.Mock,
|
||||||
|
mock_build_pdfs: mock.Mock,
|
||||||
|
mock_group: mock.Mock,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing document
|
||||||
|
WHEN:
|
||||||
|
- edit_pdf is called with operations interleaved across two outputs,
|
||||||
|
some of them rotated
|
||||||
|
THEN:
|
||||||
|
- Each output is built from its own operations, in operation order,
|
||||||
|
with the requested rotation
|
||||||
|
"""
|
||||||
|
mock_build_pdfs.return_value = [self.doc2.source_path, self.doc2.source_path]
|
||||||
|
mock_group.return_value.delay.return_value = None
|
||||||
|
operations = [
|
||||||
|
{"page": 3, "doc": 1},
|
||||||
|
{"page": 1, "doc": 0, "rotate": 90},
|
||||||
|
{"page": 2, "doc": 1, "rotate": 180},
|
||||||
|
]
|
||||||
|
|
||||||
|
bulk_edit.edit_pdf([self.doc2.id], operations)
|
||||||
|
|
||||||
|
outputs = mock_build_pdfs.call_args.args[1]
|
||||||
|
self.assertEqual(
|
||||||
|
[specs for specs, _ in outputs],
|
||||||
|
[[PageSpec(1, 90)], [PageSpec(3), PageSpec(2, 180)]],
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_edit_pdf_with_user_override(self, mock_consume_file, mock_group) -> None:
|
def test_edit_pdf_with_user_override(self, mock_consume_file, mock_group) -> None:
|
||||||
@@ -1534,12 +1570,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.new")
|
@mock.patch("documents.pdf_ops.build_pdfs")
|
||||||
@mock.patch("pikepdf.open")
|
|
||||||
def test_edit_pdf_explicit_selection_uses_root_source_when_root_selected(
|
def test_edit_pdf_explicit_selection_uses_root_source_when_root_selected(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_build_pdfs,
|
||||||
mock_new,
|
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_magic,
|
mock_magic,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1548,12 +1582,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
title="B version 1",
|
title="B version 1",
|
||||||
root_document=self.doc2,
|
root_document=self.doc2,
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
mock_build_pdfs.return_value = [Path("edited.pdf")]
|
||||||
fake_pdf.pages = [mock.Mock()]
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
output_pdf = mock.MagicMock()
|
|
||||||
output_pdf.pages = []
|
|
||||||
mock_new.return_value = output_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.edit_pdf(
|
result = bulk_edit.edit_pdf(
|
||||||
[self.doc2.id],
|
[self.doc2.id],
|
||||||
@@ -1563,7 +1592,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open.assert_called_once_with(self.doc2.source_path)
|
self.assertEqual(mock_build_pdfs.call_args.args[0], self.doc2.source_path)
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@@ -1589,31 +1618,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_group.return_value.delay.assert_called_once()
|
mock_group.return_value.delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.group")
|
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
|
||||||
def test_edit_pdf_open_failure(
|
|
||||||
self,
|
|
||||||
mock_consume_file: mock.Mock,
|
|
||||||
mock_group: mock.Mock,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Existing document
|
|
||||||
WHEN:
|
|
||||||
- edit_pdf fails to open PDF
|
|
||||||
THEN:
|
|
||||||
- Task group is not called
|
|
||||||
"""
|
|
||||||
doc_ids = [self.doc2.id]
|
|
||||||
operations = [
|
|
||||||
{"page": 9999}, # invalid page, forces error during PDF load
|
|
||||||
]
|
|
||||||
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
bulk_edit.edit_pdf(doc_ids, operations)
|
|
||||||
mock_group.assert_not_called()
|
|
||||||
mock_consume_file.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_edit_pdf_multiple_outputs_with_update_flag_errors(
|
def test_edit_pdf_multiple_outputs_with_update_flag_errors(
|
||||||
@@ -1640,23 +1644,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
mock_group.assert_not_called()
|
mock_group.assert_not_called()
|
||||||
mock_consume_file.assert_not_called()
|
mock_consume_file.assert_not_called()
|
||||||
|
|
||||||
@mock.patch("pikepdf.open")
|
|
||||||
def test_edit_pdf_rejects_invalid_operations(self, mock_open) -> None:
|
|
||||||
for operations in ([], [{"page": 1, "doc": 2**32}]):
|
|
||||||
with self.subTest(operations=operations):
|
|
||||||
with self.assertLogs("paperless.bulk_edit", level="ERROR"):
|
|
||||||
with self.assertRaisesRegex(ValueError, "index is out of bounds"):
|
|
||||||
bulk_edit.edit_pdf([self.doc2.id], operations)
|
|
||||||
|
|
||||||
mock_open.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
def test_remove_password_update_document(
|
def test_remove_password_update_document(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_needs_decrypt,
|
||||||
|
mock_decrypt,
|
||||||
mock_mkdtemp,
|
mock_mkdtemp,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_update_document,
|
mock_update_document,
|
||||||
@@ -1665,16 +1661,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
temp_dir = self.dirs.scratch_dir / "remove-password-update"
|
temp_dir = self.dirs.scratch_dir / "remove-password-update"
|
||||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
mock_mkdtemp.return_value = str(temp_dir)
|
mock_mkdtemp.return_value = str(temp_dir)
|
||||||
|
mock_decrypt.side_effect = self.fake_decrypt
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.pages = [mock.Mock(), mock.Mock(), mock.Mock()]
|
|
||||||
fake_pdf.is_encrypted = True
|
|
||||||
|
|
||||||
def save_side_effect(target_path):
|
|
||||||
Path(target_path).write_bytes(b"new pdf content")
|
|
||||||
|
|
||||||
fake_pdf.save.side_effect = save_side_effect
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
result = bulk_edit.remove_password(
|
||||||
[doc.id],
|
[doc.id],
|
||||||
@@ -1683,14 +1670,8 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
self.assertEqual(
|
mock_needs_decrypt.assert_called_once_with(doc.source_path)
|
||||||
mock_open.call_args_list,
|
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||||
[
|
|
||||||
mock.call(doc.source_path),
|
|
||||||
mock.call(doc.source_path, password="secret"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
fake_pdf.remove_unreferenced_resources.assert_called_once()
|
|
||||||
mock_update_document.assert_not_called()
|
mock_update_document.assert_not_called()
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
task_kwargs = mock_consume_delay.call_args.kwargs["kwargs"]
|
||||||
@@ -1703,40 +1684,15 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(task_kwargs["input_doc"].root_document_id, doc.id)
|
self.assertEqual(task_kwargs["input_doc"].root_document_id, doc.id)
|
||||||
self.assertIsNotNone(task_kwargs["overrides"])
|
self.assertIsNotNone(task_kwargs["overrides"])
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
|
||||||
@mock.patch("pikepdf.open")
|
|
||||||
def test_remove_password_update_document_skips_unencrypted_pdf(
|
|
||||||
self,
|
|
||||||
mock_open,
|
|
||||||
mock_mkdtemp,
|
|
||||||
mock_consume_delay,
|
|
||||||
) -> None:
|
|
||||||
doc = self.doc1
|
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.is_encrypted = False
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
|
||||||
[doc.id],
|
|
||||||
password="secret",
|
|
||||||
update_document=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
|
||||||
mock_open.assert_called_once_with(doc.source_path)
|
|
||||||
fake_pdf.remove_unreferenced_resources.assert_not_called()
|
|
||||||
fake_pdf.save.assert_not_called()
|
|
||||||
mock_mkdtemp.assert_not_called()
|
|
||||||
mock_consume_delay.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
@mock.patch("documents.bulk_edit.update_document_content_maybe_archive_file.delay")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
def test_remove_password_update_document_uses_source_paths(
|
def test_remove_password_update_document_uses_source_paths(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_needs_decrypt,
|
||||||
|
mock_decrypt,
|
||||||
mock_mkdtemp,
|
mock_mkdtemp,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_update_document,
|
mock_update_document,
|
||||||
@@ -1747,14 +1703,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
temp_dir = self.dirs.scratch_dir / "remove-password-source-file"
|
temp_dir = self.dirs.scratch_dir / "remove-password-source-file"
|
||||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
mock_mkdtemp.return_value = str(temp_dir)
|
mock_mkdtemp.return_value = str(temp_dir)
|
||||||
|
mock_decrypt.side_effect = self.fake_decrypt
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
|
||||||
|
|
||||||
def save_side_effect(target_path):
|
|
||||||
Path(target_path).write_bytes(b"new pdf content")
|
|
||||||
|
|
||||||
fake_pdf.save.side_effect = save_side_effect
|
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
result = bulk_edit.remove_password(
|
||||||
[doc.id],
|
[doc.id],
|
||||||
@@ -1764,22 +1713,19 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
self.assertEqual(
|
mock_needs_decrypt.assert_called_once_with(source_file)
|
||||||
mock_open.call_args_list,
|
mock_decrypt.assert_called_once_with(source_file, mock.ANY, "secret")
|
||||||
[
|
|
||||||
mock.call(source_file),
|
|
||||||
mock.call(source_file, password="secret"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
mock_update_document.assert_not_called()
|
mock_update_document.assert_not_called()
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
|
||||||
@mock.patch("documents.tasks.consume_file.apply_async")
|
@mock.patch("documents.tasks.consume_file.apply_async")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
def test_remove_password_explicit_selection_uses_root_source_when_root_selected(
|
def test_remove_password_explicit_selection_uses_root_source_when_root_selected(
|
||||||
self,
|
self,
|
||||||
mock_open,
|
mock_needs_decrypt,
|
||||||
|
mock_decrypt,
|
||||||
mock_consume_delay,
|
mock_consume_delay,
|
||||||
mock_magic,
|
mock_magic,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1788,8 +1734,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
title="A version 1",
|
title="A version 1",
|
||||||
root_document=self.doc1,
|
root_document=self.doc1,
|
||||||
)
|
)
|
||||||
fake_pdf = mock.MagicMock()
|
mock_decrypt.return_value = Path("unprotected.pdf")
|
||||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
result = bulk_edit.remove_password(
|
||||||
[self.doc1.id],
|
[self.doc1.id],
|
||||||
@@ -1799,12 +1744,11 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
self.assertEqual(
|
mock_needs_decrypt.assert_called_once_with(self.doc1.source_path)
|
||||||
mock_open.call_args_list,
|
mock_decrypt.assert_called_once_with(
|
||||||
[
|
self.doc1.source_path,
|
||||||
mock.call(self.doc1.source_path),
|
mock.ANY,
|
||||||
mock.call(self.doc1.source_path, password="secret"),
|
"secret",
|
||||||
],
|
|
||||||
)
|
)
|
||||||
mock_consume_delay.assert_called_once()
|
mock_consume_delay.assert_called_once()
|
||||||
|
|
||||||
@@ -1812,10 +1756,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
def test_remove_password_creates_consumable_document(
|
def test_remove_password_creates_consumable_document(
|
||||||
self,
|
self,
|
||||||
mock_open: mock.Mock,
|
mock_needs_decrypt: mock.Mock,
|
||||||
|
mock_decrypt: mock.Mock,
|
||||||
mock_mkdtemp: mock.Mock,
|
mock_mkdtemp: mock.Mock,
|
||||||
mock_consume_file: mock.Mock,
|
mock_consume_file: mock.Mock,
|
||||||
mock_group: mock.Mock,
|
mock_group: mock.Mock,
|
||||||
@@ -1825,15 +1771,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
temp_dir = self.dirs.scratch_dir / "remove-password"
|
temp_dir = self.dirs.scratch_dir / "remove-password"
|
||||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
mock_mkdtemp.return_value = str(temp_dir)
|
mock_mkdtemp.return_value = str(temp_dir)
|
||||||
|
mock_decrypt.side_effect = self.fake_decrypt
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
|
||||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
|
||||||
|
|
||||||
def save_side_effect(target_path: Path) -> None:
|
|
||||||
target_path.write_bytes(b"password removed")
|
|
||||||
|
|
||||||
fake_pdf.save.side_effect = save_side_effect
|
|
||||||
mock_group.return_value.delay.return_value = None
|
mock_group.return_value.delay.return_value = None
|
||||||
|
|
||||||
user = User.objects.create(username="owner")
|
user = User.objects.create(username="owner")
|
||||||
@@ -1848,13 +1786,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
self.assertEqual(
|
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||||
mock_open.call_args_list,
|
|
||||||
[
|
|
||||||
mock.call(doc.source_path),
|
|
||||||
mock.call(doc.source_path, password="secret"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
mock_consume_file.assert_called_once()
|
mock_consume_file.assert_called_once()
|
||||||
call_kwargs = mock_consume_file.call_args.kwargs
|
call_kwargs = mock_consume_file.call_args.kwargs
|
||||||
consumable_document = call_kwargs["input_doc"]
|
consumable_document = call_kwargs["input_doc"]
|
||||||
@@ -1876,21 +1808,18 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
@mock.patch("documents.bulk_edit.chord")
|
@mock.patch("documents.bulk_edit.chord")
|
||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=False)
|
||||||
def test_remove_password_skips_unencrypted_pdf_without_queueing(
|
def test_remove_password_skips_unencrypted_pdf_without_queueing(
|
||||||
self,
|
self,
|
||||||
mock_open: mock.Mock,
|
mock_needs_decrypt: mock.Mock,
|
||||||
mock_mkdtemp: mock.Mock,
|
mock_decrypt: mock.Mock,
|
||||||
mock_consume_file: mock.Mock,
|
mock_consume_file: mock.Mock,
|
||||||
mock_group: mock.Mock,
|
mock_group: mock.Mock,
|
||||||
mock_chord: mock.Mock,
|
mock_chord: mock.Mock,
|
||||||
mock_delete: mock.Mock,
|
mock_delete: mock.Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
doc = self.doc2
|
doc = self.doc2
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.is_encrypted = False
|
|
||||||
mock_open.return_value.__enter__.return_value = fake_pdf
|
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
result = bulk_edit.remove_password(
|
||||||
[doc.id],
|
[doc.id],
|
||||||
@@ -1900,10 +1829,8 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_open.assert_called_once_with(doc.source_path)
|
mock_needs_decrypt.assert_called_once_with(doc.source_path)
|
||||||
fake_pdf.remove_unreferenced_resources.assert_not_called()
|
mock_decrypt.assert_not_called()
|
||||||
fake_pdf.save.assert_not_called()
|
|
||||||
mock_mkdtemp.assert_not_called()
|
|
||||||
mock_consume_file.assert_not_called()
|
mock_consume_file.assert_not_called()
|
||||||
mock_group.assert_not_called()
|
mock_group.assert_not_called()
|
||||||
mock_chord.assert_not_called()
|
mock_chord.assert_not_called()
|
||||||
@@ -1914,10 +1841,12 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
@mock.patch("documents.bulk_edit.group")
|
@mock.patch("documents.bulk_edit.group")
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
@mock.patch("documents.bulk_edit.tempfile.mkdtemp")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("documents.pdf_ops.decrypt_pdf")
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
def test_remove_password_deletes_original(
|
def test_remove_password_deletes_original(
|
||||||
self,
|
self,
|
||||||
mock_open: mock.Mock,
|
mock_needs_decrypt: mock.Mock,
|
||||||
|
mock_decrypt: mock.Mock,
|
||||||
mock_mkdtemp: mock.Mock,
|
mock_mkdtemp: mock.Mock,
|
||||||
mock_consume_file: mock.Mock,
|
mock_consume_file: mock.Mock,
|
||||||
mock_group: mock.Mock,
|
mock_group: mock.Mock,
|
||||||
@@ -1928,15 +1857,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
temp_dir = self.dirs.scratch_dir / "remove-password-delete"
|
temp_dir = self.dirs.scratch_dir / "remove-password-delete"
|
||||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||||
mock_mkdtemp.return_value = str(temp_dir)
|
mock_mkdtemp.return_value = str(temp_dir)
|
||||||
|
mock_decrypt.side_effect = self.fake_decrypt
|
||||||
fake_pdf = mock.MagicMock()
|
|
||||||
fake_pdf.pages = [mock.Mock(), mock.Mock()]
|
|
||||||
self.mock_password_required_pdf(mock_open, fake_pdf)
|
|
||||||
|
|
||||||
def save_side_effect(target_path: Path) -> None:
|
|
||||||
target_path.write_bytes(b"password removed")
|
|
||||||
|
|
||||||
fake_pdf.save.side_effect = save_side_effect
|
|
||||||
mock_chord.return_value.delay.return_value = None
|
mock_chord.return_value.delay.return_value = None
|
||||||
|
|
||||||
result = bulk_edit.remove_password(
|
result = bulk_edit.remove_password(
|
||||||
@@ -1948,23 +1869,23 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
self.assertEqual(
|
mock_decrypt.assert_called_once_with(doc.source_path, mock.ANY, "secret")
|
||||||
mock_open.call_args_list,
|
|
||||||
[
|
|
||||||
mock.call(doc.source_path),
|
|
||||||
mock.call(doc.source_path, password="secret"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
mock_consume_file.assert_called_once()
|
mock_consume_file.assert_called_once()
|
||||||
mock_group.assert_not_called()
|
mock_group.assert_not_called()
|
||||||
mock_chord.assert_called_once()
|
mock_chord.assert_called_once()
|
||||||
mock_chord.return_value.delay.assert_called_once()
|
mock_chord.return_value.delay.assert_called_once()
|
||||||
mock_delete.si.assert_called_once_with([doc.id])
|
mock_delete.si.assert_called_once_with([doc.id])
|
||||||
|
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch(
|
||||||
def test_remove_password_open_failure(self, mock_open: mock.Mock) -> None:
|
"documents.pdf_ops.decrypt_pdf",
|
||||||
mock_open.side_effect = RuntimeError("wrong password")
|
side_effect=RuntimeError("wrong password"),
|
||||||
|
)
|
||||||
|
@mock.patch("documents.pdf_ops.needs_decrypt", return_value=True)
|
||||||
|
def test_remove_password_failure_raises_value_error(
|
||||||
|
self,
|
||||||
|
mock_needs_decrypt: mock.Mock,
|
||||||
|
mock_decrypt: mock.Mock,
|
||||||
|
) -> None:
|
||||||
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
with self.assertLogs("paperless.bulk_edit", level="ERROR") as cm:
|
||||||
with self.assertRaises(ValueError) as exc:
|
with self.assertRaises(ValueError) as exc:
|
||||||
bulk_edit.remove_password([self.doc1.id], password="secret")
|
bulk_edit.remove_password([self.doc1.id], password="secret")
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from django.test import override_settings
|
|||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
from pytest_django.fixtures import Settings
|
from pytest_django.fixtures import Settings
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from sklearn.neural_network import MLPClassifier
|
|
||||||
|
|
||||||
from documents.classifier import ClassifierModelCorruptError
|
from documents.classifier import ClassifierModelCorruptError
|
||||||
from documents.classifier import DocumentClassifier
|
from documents.classifier import DocumentClassifier
|
||||||
@@ -1037,49 +1036,6 @@ def test_classifier_match_threshold_default() -> None:
|
|||||||
assert settings.CLASSIFIER_MATCH_THRESHOLD == 0.3
|
assert settings.CLASSIFIER_MATCH_THRESHOLD == 0.3
|
||||||
|
|
||||||
|
|
||||||
class TestPredictWithThresholdSingleClass:
|
|
||||||
@pytest.fixture
|
|
||||||
def single_class_model(self) -> tuple[MLPClassifier, np.ndarray]:
|
|
||||||
X = np.random.default_rng(0).random((2, 5))
|
|
||||||
return MLPClassifier(tol=0.01, random_state=0).fit(X, [3, 3]), X[:1]
|
|
||||||
|
|
||||||
def test_returns_only_class(
|
|
||||||
self,
|
|
||||||
single_class_model: tuple[MLPClassifier, np.ndarray],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A classifier fitted on a single class (every document shares one label)
|
|
||||||
WHEN:
|
|
||||||
- A prediction is requested with the threshold disabled
|
|
||||||
THEN:
|
|
||||||
- No error is raised and the only class is returned
|
|
||||||
"""
|
|
||||||
classifier, X = single_class_model
|
|
||||||
|
|
||||||
assert _predict_with_threshold(classifier, X, threshold=0.0) == 3
|
|
||||||
|
|
||||||
def test_applies_threshold(
|
|
||||||
self,
|
|
||||||
single_class_model: tuple[MLPClassifier, np.ndarray],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A classifier fitted on a single class
|
|
||||||
WHEN:
|
|
||||||
- A prediction is requested
|
|
||||||
THEN:
|
|
||||||
- The class's confidence is still compared against the threshold
|
|
||||||
"""
|
|
||||||
classifier, X = single_class_model
|
|
||||||
confidence = classifier.predict_proba(X)[0][0]
|
|
||||||
|
|
||||||
assert _predict_with_threshold(classifier, X, threshold=confidence - 0.01) == 3
|
|
||||||
assert (
|
|
||||||
_predict_with_threshold(classifier, X, threshold=confidence + 0.01) is None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestPreprocessContent:
|
class TestPreprocessContent:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def samples(self) -> Path:
|
def samples(self) -> Path:
|
||||||
|
|||||||
@@ -34,10 +34,8 @@ from documents.tests.utils import GetConsumerMixin
|
|||||||
from paperless_mail.models import MailRule
|
from paperless_mail.models import MailRule
|
||||||
from paperless_testing.assertions import FileSystemAssertsMixin
|
from paperless_testing.assertions import FileSystemAssertsMixin
|
||||||
from paperless_testing.dirs import DirectoriesMixin
|
from paperless_testing.dirs import DirectoriesMixin
|
||||||
from paperless_testing.factories import DocumentFactory
|
|
||||||
from paperless_testing.factories import UserFactory
|
from paperless_testing.factories import UserFactory
|
||||||
from paperless_testing.fakes.progress import FakeProgressManager
|
from paperless_testing.fakes.progress import FakeProgressManager
|
||||||
from paperless_testing.permissions import grant_object
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseNewStyleParser:
|
class _BaseNewStyleParser:
|
||||||
@@ -1685,32 +1683,6 @@ class TestMetadataOverrides(TestCase):
|
|||||||
self.assertEqual(base.actor_id, 2)
|
self.assertEqual(base.actor_id, 2)
|
||||||
self.assertEqual(base.version_label, "v2")
|
self.assertEqual(base.version_label, "v2")
|
||||||
|
|
||||||
def test_from_document_copies_user_and_group_grants_separately(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A document with view and change granted directly to one user and
|
|
||||||
to a group with another user as a member
|
|
||||||
WHEN:
|
|
||||||
- Overrides are built from the document
|
|
||||||
THEN:
|
|
||||||
- The user lists hold only the directly granted user
|
|
||||||
- The group lists hold the group
|
|
||||||
"""
|
|
||||||
direct_user = UserFactory()
|
|
||||||
group_member = UserFactory()
|
|
||||||
group = Group.objects.create(name="group")
|
|
||||||
group_member.groups.add(group)
|
|
||||||
document = DocumentFactory()
|
|
||||||
grant_object(direct_user, document, "view_document", "change_document")
|
|
||||||
grant_object(group, document, "view_document", "change_document")
|
|
||||||
|
|
||||||
overrides = DocumentMetadataOverrides.from_document(document)
|
|
||||||
|
|
||||||
self.assertEqual(overrides.view_users, [direct_user.pk])
|
|
||||||
self.assertEqual(overrides.change_users, [direct_user.pk])
|
|
||||||
self.assertEqual(overrides.view_groups, [group.pk])
|
|
||||||
self.assertEqual(overrides.change_groups, [group.pk])
|
|
||||||
|
|
||||||
|
|
||||||
class TestBarcodeApplyDetectedASN(TestCase):
|
class TestBarcodeApplyDetectedASN(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -423,3 +423,35 @@ class TestTrashAndGlobalSearchEffectiveContentIsNeverPerInstance:
|
|||||||
[result] = [r for r in response.data["results"] if r["id"] == root.id]
|
[result] = [r for r in response.data["results"] if r["id"] == root.id]
|
||||||
assert result["content"] == "own-content"
|
assert result["content"] == "own-content"
|
||||||
assert _get_effective_content_fallback_queries(ctx) == []
|
assert _get_effective_content_fallback_queries(ctx) == []
|
||||||
|
|
||||||
|
def test_global_search_db_only_shows_latest_version_content_with_no_extra_query(
|
||||||
|
self,
|
||||||
|
admin_client: APIClient,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A root document, findable by title, whose own content differs
|
||||||
|
from its latest version's
|
||||||
|
WHEN:
|
||||||
|
- Using the global search endpoint's db_only mode
|
||||||
|
THEN:
|
||||||
|
- The response shows the latest version's content, resolved by
|
||||||
|
GlobalSearchView's own effective_content annotation
|
||||||
|
- There is no per-instance fallback query
|
||||||
|
"""
|
||||||
|
root = DocumentFactory(title="findme", content="own-content")
|
||||||
|
DocumentFactory(
|
||||||
|
root_document=root,
|
||||||
|
version_index=1,
|
||||||
|
content="version-content",
|
||||||
|
)
|
||||||
|
|
||||||
|
with CaptureQueriesContext(connection) as ctx:
|
||||||
|
response = admin_client.get(
|
||||||
|
"/api/search/?query=findme&db_only=true",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
[result] = [d for d in response.data["documents"] if d["id"] == root.id]
|
||||||
|
assert result["content"] == "version-content"
|
||||||
|
assert _get_effective_content_fallback_queries(ctx) == []
|
||||||
@@ -4,16 +4,13 @@ from datetime import date
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
from pytest_mock import MockerFixture
|
|
||||||
|
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.tasks import empty_trash
|
from documents.tasks import empty_trash
|
||||||
from paperless_testing.factories import DocumentFactory
|
|
||||||
|
|
||||||
|
|
||||||
class TestDocument(TestCase):
|
class TestDocument(TestCase):
|
||||||
@@ -260,72 +257,3 @@ def test_suggestion_content() -> None:
|
|||||||
)
|
)
|
||||||
doc.content = content_over_limit
|
doc.content = content_over_limit
|
||||||
assert doc.suggestion_content == expected_cropped_content
|
assert doc.suggestion_content == expected_cropped_content
|
||||||
|
|
||||||
|
|
||||||
class TestPublicFilenameLength:
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _no_parser_registry(self, mocker: MockerFixture) -> None:
|
|
||||||
# The parser registry reads ApplicationConfiguration from the database
|
|
||||||
extensions = {"application/pdf": ".pdf", "image/jpeg": ".jpg"}
|
|
||||||
mocker.patch(
|
|
||||||
"documents.models.get_default_file_extension",
|
|
||||||
side_effect=extensions.__getitem__,
|
|
||||||
)
|
|
||||||
|
|
||||||
LIMIT = Document.MAX_PUBLIC_FILENAME_BYTES
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("title", "archive", "counter", "max_bytes", "tail"),
|
|
||||||
[
|
|
||||||
pytest.param("a" * 300, False, 0, LIMIT, ".jpg", id="ascii"),
|
|
||||||
pytest.param("a" * 300, True, 0, LIMIT, ".pdf", id="archive"),
|
|
||||||
pytest.param("ä" * 200, False, 0, LIMIT, ".jpg", id="umlaut"),
|
|
||||||
pytest.param("ä" * 200, False, 1, LIMIT, "_01.jpg", id="count"),
|
|
||||||
pytest.param("a" * 300, False, 1, 100, "_01.jpg", id="max"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_long_title_keeps_tail_within_limit(
|
|
||||||
self,
|
|
||||||
title: str,
|
|
||||||
*,
|
|
||||||
archive: bool,
|
|
||||||
counter: int,
|
|
||||||
max_bytes: int,
|
|
||||||
tail: str,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A document whose title alone is longer than the byte limit
|
|
||||||
WHEN:
|
|
||||||
- The public file name is generated
|
|
||||||
THEN:
|
|
||||||
- The name ends with the counter and extension
|
|
||||||
- The name fits in the limit
|
|
||||||
"""
|
|
||||||
name = DocumentFactory.build(
|
|
||||||
title=title,
|
|
||||||
mime_type="image/jpeg",
|
|
||||||
).get_public_filename(
|
|
||||||
archive=archive,
|
|
||||||
counter=counter,
|
|
||||||
max_bytes=max_bytes,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert name.endswith(tail)
|
|
||||||
assert len(name.encode()) <= max_bytes
|
|
||||||
|
|
||||||
def test_long_title_counters_give_distinct_names(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- A document whose title alone is longer than the byte limit
|
|
||||||
WHEN:
|
|
||||||
- Public file names are generated with increasing counters
|
|
||||||
THEN:
|
|
||||||
- Each counter produces a distinct name
|
|
||||||
"""
|
|
||||||
doc = DocumentFactory.build(title="ä" * 200, mime_type="application/pdf")
|
|
||||||
|
|
||||||
names = [doc.get_public_filename(counter=counter) for counter in range(3)]
|
|
||||||
|
|
||||||
assert len(set(names)) == 3
|
|
||||||
assert all(len(n.encode()) <= self.LIMIT for n in names)
|
|
||||||
@@ -50,7 +50,6 @@ from documents.settings import EXPORTER_FILE_NAME
|
|||||||
from documents.settings import EXPORTER_SHARE_LINK_BUNDLE_NAME
|
from documents.settings import EXPORTER_SHARE_LINK_BUNDLE_NAME
|
||||||
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||||
from documents.tests.utils import SampleDirMixin
|
from documents.tests.utils import SampleDirMixin
|
||||||
from paperless.models import ApplicationConfiguration
|
|
||||||
from paperless_mail.models import MailAccount
|
from paperless_mail.models import MailAccount
|
||||||
from paperless_testing.assertions import FileSystemAssertsMixin
|
from paperless_testing.assertions import FileSystemAssertsMixin
|
||||||
from paperless_testing.dirs import DirectoriesMixin
|
from paperless_testing.dirs import DirectoriesMixin
|
||||||
@@ -1403,45 +1402,6 @@ class TestCryptExportImport(
|
|||||||
social_token = SocialToken.objects.first()
|
social_token = SocialToken.objects.first()
|
||||||
self.assertIsNotNone(social_token)
|
self.assertIsNotNone(social_token)
|
||||||
|
|
||||||
def test_import_passphrase_with_float_field(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- An application configuration with a float field set
|
|
||||||
WHEN:
|
|
||||||
- Export and import are run with a passphrase
|
|
||||||
THEN:
|
|
||||||
- The import succeeds and the float value is preserved
|
|
||||||
"""
|
|
||||||
ApplicationConfiguration.objects.update_or_create(
|
|
||||||
pk=1,
|
|
||||||
defaults={"rotate_pages_threshold": 12.5},
|
|
||||||
)
|
|
||||||
|
|
||||||
call_command(
|
|
||||||
"document_exporter",
|
|
||||||
"--no-progress-bar",
|
|
||||||
"--passphrase",
|
|
||||||
"securepassword",
|
|
||||||
self.target,
|
|
||||||
skip_checks=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
ApplicationConfiguration.objects.all().delete()
|
|
||||||
|
|
||||||
call_command(
|
|
||||||
"document_importer",
|
|
||||||
"--no-progress-bar",
|
|
||||||
"--passphrase",
|
|
||||||
"securepassword",
|
|
||||||
self.target,
|
|
||||||
skip_checks=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
ApplicationConfiguration.objects.get().rotate_pages_threshold,
|
|
||||||
12.5,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_import_crypt_no_passphrase(self) -> None:
|
def test_import_crypt_no_passphrase(self) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@@ -1577,70 +1537,3 @@ class TestExportDerivedFileNameCollisions:
|
|||||||
assert (
|
assert (
|
||||||
target / record[EXPORTER_ARCHIVE_NAME]
|
target / record[EXPORTER_ARCHIVE_NAME]
|
||||||
).read_text() == f"archive {pk}"
|
).read_text() == f"archive {pk}"
|
||||||
|
|
||||||
def test_long_titles_export_within_file_name_limit(
|
|
||||||
self,
|
|
||||||
paperless_dirs: PaperlessDirs,
|
|
||||||
tmp_path: Path,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Two documents with the same created date and the same title
|
|
||||||
- The title alone is longer than 255 bytes and contains umlauts
|
|
||||||
WHEN:
|
|
||||||
- The exporter runs
|
|
||||||
THEN:
|
|
||||||
- The export finishes with distinct original, thumbnail and archive names
|
|
||||||
- Every exported file name fits in the byte limit
|
|
||||||
- Each exported file holds its own document's content
|
|
||||||
"""
|
|
||||||
for pk in (1, 2):
|
|
||||||
(paperless_dirs.originals_dir / f"{pk:07}.pdf").write_bytes(
|
|
||||||
f"original {pk}".encode(),
|
|
||||||
)
|
|
||||||
(paperless_dirs.archive_dir / f"{pk:07}.pdf").write_bytes(
|
|
||||||
f"archive {pk}".encode(),
|
|
||||||
)
|
|
||||||
(paperless_dirs.thumbnail_dir / f"{pk:07}.webp").write_bytes(
|
|
||||||
f"thumbnail {pk}".encode(),
|
|
||||||
)
|
|
||||||
DocumentFactory(
|
|
||||||
pk=pk,
|
|
||||||
title="Bestellbestätigung " * 15,
|
|
||||||
created=date(2026, 10, 15),
|
|
||||||
filename=f"{pk:07}.pdf",
|
|
||||||
archive_filename=f"{pk:07}.pdf",
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
target = tmp_path / "export"
|
|
||||||
target.mkdir()
|
|
||||||
call_command(
|
|
||||||
"document_exporter",
|
|
||||||
str(target),
|
|
||||||
"--no-progress-bar",
|
|
||||||
skip_checks=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
manifest = json.loads((target / "manifest.json").read_text())
|
|
||||||
records = {r["pk"]: r for r in manifest if r["model"] == "documents.document"}
|
|
||||||
|
|
||||||
for key in (
|
|
||||||
EXPORTER_FILE_NAME,
|
|
||||||
EXPORTER_THUMBNAIL_NAME,
|
|
||||||
EXPORTER_ARCHIVE_NAME,
|
|
||||||
):
|
|
||||||
assert records[1][key] != records[2][key]
|
|
||||||
|
|
||||||
for pk in (1, 2):
|
|
||||||
record = records[pk]
|
|
||||||
for key, content in (
|
|
||||||
(EXPORTER_FILE_NAME, f"original {pk}"),
|
|
||||||
(EXPORTER_THUMBNAIL_NAME, f"thumbnail {pk}"),
|
|
||||||
(EXPORTER_ARCHIVE_NAME, f"archive {pk}"),
|
|
||||||
):
|
|
||||||
assert (
|
|
||||||
len(Path(record[key]).name.encode())
|
|
||||||
<= Document.MAX_PUBLIC_FILENAME_BYTES
|
|
||||||
)
|
|
||||||
assert (target / record[key]).read_text() == content
|
|
||||||
@@ -0,0 +1,520 @@
|
|||||||
|
"""
|
||||||
|
Tests for documents.pdf_ops.
|
||||||
|
|
||||||
|
These use real PDFs from the sample directories. No database, Celery or mocks.
|
||||||
|
Pages are compared by a hash of their content stream, so page identity and order
|
||||||
|
are easy to assert.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pikepdf
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from documents import pdf_ops
|
||||||
|
from documents.pdf_ops import PageSpec
|
||||||
|
|
||||||
|
SRC_ROOT = Path(__file__).parents[2]
|
||||||
|
SAMPLES = Path(__file__).parent / "samples"
|
||||||
|
THREE_PAGES = SAMPLES / "documents" / "originals" / "0000002.pdf"
|
||||||
|
TWELVE_PAGES = SAMPLES / "barcodes" / "split-by-asn-2.pdf"
|
||||||
|
ENCRYPTED = SAMPLES / "password-is-test.pdf"
|
||||||
|
SIGNED = SRC_ROOT / "paperless" / "tests" / "samples" / "tesseract" / "signed.pdf"
|
||||||
|
|
||||||
|
|
||||||
|
def _page_fingerprint(page: pikepdf.Page) -> str:
|
||||||
|
contents = page.obj.get("/Contents")
|
||||||
|
assert contents is not None, "sample page has no /Contents"
|
||||||
|
streams = list(contents) if isinstance(contents, pikepdf.Array) else [contents]
|
||||||
|
return hashlib.sha256(b"".join(s.read_bytes() for s in streams)).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def fingerprints(path: Path) -> list[str]:
|
||||||
|
with pikepdf.open(path) as pdf:
|
||||||
|
return [_page_fingerprint(page) for page in pdf.pages]
|
||||||
|
|
||||||
|
|
||||||
|
def rotations(path: Path) -> list[int]:
|
||||||
|
with pikepdf.open(path) as pdf:
|
||||||
|
return [int(page.obj.get("/Rotate", 0)) for page in pdf.pages]
|
||||||
|
|
||||||
|
|
||||||
|
def docinfo_keys(path: Path) -> set[str]:
|
||||||
|
with pikepdf.open(path) as pdf:
|
||||||
|
return set(pdf.docinfo.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def constant(path: Path) -> Callable[[], Path]:
|
||||||
|
return lambda: path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def source_fingerprints() -> list[str]:
|
||||||
|
fps = fingerprints(THREE_PAGES)
|
||||||
|
assert len(set(fps)) == 3, "sample must have three distinct pages"
|
||||||
|
return fps
|
||||||
|
|
||||||
|
|
||||||
|
class TestRotatePdf:
|
||||||
|
def test_rotation_is_relative_and_applies_to_every_page(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- It is rotated by 90 degrees, then the result is rotated by 90 again
|
||||||
|
THEN:
|
||||||
|
- Every page is rotated relative to its current rotation
|
||||||
|
- The page content itself is unchanged
|
||||||
|
"""
|
||||||
|
once = tmp_path / "once.pdf"
|
||||||
|
twice = tmp_path / "twice.pdf"
|
||||||
|
|
||||||
|
pdf_ops.rotate_pdf(THREE_PAGES, once, 90)
|
||||||
|
pdf_ops.rotate_pdf(once, twice, 90)
|
||||||
|
|
||||||
|
assert rotations(once) == [90, 90, 90]
|
||||||
|
assert rotations(twice) == [180, 180, 180]
|
||||||
|
assert fingerprints(twice) == fingerprints(THREE_PAGES)
|
||||||
|
|
||||||
|
def test_keeps_document_info(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF with document info
|
||||||
|
WHEN:
|
||||||
|
- It is rotated
|
||||||
|
THEN:
|
||||||
|
- The document info is still present in the output
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
pdf_ops.rotate_pdf(THREE_PAGES, dst, 90)
|
||||||
|
|
||||||
|
assert "/Creator" in docinfo_keys(dst)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRemovePages:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("pages", "kept"),
|
||||||
|
[
|
||||||
|
pytest.param([2], [0, 2], id="single"),
|
||||||
|
pytest.param([3, 1], [1], id="unordered"),
|
||||||
|
pytest.param([2, 2], [0, 2], id="duplicates-remove-once"),
|
||||||
|
pytest.param([], [0, 1, 2], id="empty-keeps-everything"),
|
||||||
|
pytest.param([1, 2, 3], [], id="every-page"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_removes_only_the_requested_pages(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
source_fingerprints: list[str],
|
||||||
|
pages: list[int],
|
||||||
|
kept: list[int],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- Pages are removed, in any order and possibly repeated
|
||||||
|
THEN:
|
||||||
|
- Exactly the other pages remain, in their original order
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
pdf_ops.remove_pages(THREE_PAGES, dst, pages)
|
||||||
|
|
||||||
|
assert fingerprints(dst) == [source_fingerprints[i] for i in kept]
|
||||||
|
|
||||||
|
def test_keeps_document_info(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF with document info
|
||||||
|
WHEN:
|
||||||
|
- A page is removed
|
||||||
|
THEN:
|
||||||
|
- The document info is still present in the output
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
pdf_ops.remove_pages(THREE_PAGES, dst, [1])
|
||||||
|
|
||||||
|
assert "/Creator" in docinfo_keys(dst)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"bad_page",
|
||||||
|
[
|
||||||
|
pytest.param(0, id="zero"),
|
||||||
|
pytest.param(-1, id="negative"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_rejects_pages_below_one(self, tmp_path: Path, bad_page: int) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- Pages are removed and one of them is below 1
|
||||||
|
THEN:
|
||||||
|
- ValueError is raised
|
||||||
|
- No output file is written
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="start at 1"):
|
||||||
|
pdf_ops.remove_pages(THREE_PAGES, dst, [1, bad_page])
|
||||||
|
|
||||||
|
assert not dst.exists()
|
||||||
|
|
||||||
|
|
||||||
|
class TestBuildPdfs:
|
||||||
|
def test_selects_and_orders_pages(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
source_fingerprints: list[str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- One output is built from pages 3 then 1
|
||||||
|
THEN:
|
||||||
|
- The output has those pages in that order
|
||||||
|
- Its path is returned
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
written = pdf_ops.build_pdfs(
|
||||||
|
THREE_PAGES,
|
||||||
|
[([PageSpec(3), PageSpec(1)], constant(dst))],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert written == [dst]
|
||||||
|
assert fingerprints(dst) == [source_fingerprints[2], source_fingerprints[0]]
|
||||||
|
|
||||||
|
def test_rotates_only_the_requested_pages(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- One output is built with a different rotation on each page
|
||||||
|
THEN:
|
||||||
|
- Each output page has exactly the rotation requested for it
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
pdf_ops.build_pdfs(
|
||||||
|
THREE_PAGES,
|
||||||
|
[([PageSpec(1), PageSpec(2, 90), PageSpec(3, 180)], constant(dst))],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert rotations(dst) == [0, 90, 180]
|
||||||
|
|
||||||
|
def test_writes_one_file_per_output_in_order(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A twelve page PDF
|
||||||
|
WHEN:
|
||||||
|
- Two outputs are built from different page ranges
|
||||||
|
THEN:
|
||||||
|
- Two files are written and returned in output order
|
||||||
|
- Each holds exactly its own pages
|
||||||
|
"""
|
||||||
|
first = tmp_path / "first.pdf"
|
||||||
|
second = tmp_path / "second.pdf"
|
||||||
|
source = fingerprints(TWELVE_PAGES)
|
||||||
|
|
||||||
|
written = pdf_ops.build_pdfs(
|
||||||
|
TWELVE_PAGES,
|
||||||
|
[
|
||||||
|
([PageSpec(p) for p in (1, 2, 3)], constant(first)),
|
||||||
|
([PageSpec(p) for p in range(4, 13)], constant(second)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert written == [first, second]
|
||||||
|
assert fingerprints(first) == source[:3]
|
||||||
|
assert fingerprints(second) == source[3:]
|
||||||
|
|
||||||
|
def test_empty_page_list_writes_a_zero_page_file(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- An output with no pages is built
|
||||||
|
THEN:
|
||||||
|
- A PDF with zero pages is written
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
pdf_ops.build_pdfs(THREE_PAGES, [([], constant(dst))])
|
||||||
|
|
||||||
|
assert fingerprints(dst) == []
|
||||||
|
|
||||||
|
def test_destination_is_not_requested_when_a_page_is_out_of_range(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- Several outputs are built
|
||||||
|
- A later output refers to a page past the end
|
||||||
|
THEN:
|
||||||
|
- IndexError is raised
|
||||||
|
- No destination was requested for any output, including earlier valid ones
|
||||||
|
"""
|
||||||
|
requested: list[Path] = []
|
||||||
|
|
||||||
|
def make_dst() -> Path:
|
||||||
|
requested.append(tmp_path / "out.pdf")
|
||||||
|
return requested[-1]
|
||||||
|
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
pdf_ops.build_pdfs(
|
||||||
|
THREE_PAGES,
|
||||||
|
[([PageSpec(1)], make_dst), ([PageSpec(99)], make_dst)],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert requested == []
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"bad_page",
|
||||||
|
[
|
||||||
|
pytest.param(0, id="zero"),
|
||||||
|
pytest.param(-1, id="negative"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_rejects_pages_below_one_before_opening_anything(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
bad_page: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A three page PDF
|
||||||
|
WHEN:
|
||||||
|
- Several outputs are built and a later one has a page below 1
|
||||||
|
THEN:
|
||||||
|
- ValueError is raised
|
||||||
|
- No destination was requested for any output
|
||||||
|
"""
|
||||||
|
requested: list[Path] = []
|
||||||
|
|
||||||
|
def make_dst() -> Path:
|
||||||
|
requested.append(tmp_path / "out.pdf")
|
||||||
|
return requested[-1]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="start at 1"):
|
||||||
|
pdf_ops.build_pdfs(
|
||||||
|
THREE_PAGES,
|
||||||
|
[([PageSpec(1)], make_dst), ([PageSpec(bad_page)], make_dst)],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert requested == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidatePageOperations:
|
||||||
|
def test_returns_the_output_count(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Operations that all target the default output
|
||||||
|
WHEN:
|
||||||
|
- They are validated
|
||||||
|
THEN:
|
||||||
|
- One output document is reported
|
||||||
|
"""
|
||||||
|
operations = [{"page": 1}, {"page": 2}, {"page": 3}]
|
||||||
|
|
||||||
|
assert pdf_ops.validate_page_operations(operations, single_output=True) == 1
|
||||||
|
|
||||||
|
def test_gap_in_output_indices_counts_up_to_the_highest(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Operations that target outputs 0 and 2 but never 1
|
||||||
|
WHEN:
|
||||||
|
- They are validated
|
||||||
|
THEN:
|
||||||
|
- Three output documents are reported
|
||||||
|
"""
|
||||||
|
operations = [
|
||||||
|
{"page": 1, "doc": 0},
|
||||||
|
{"page": 2, "doc": 2},
|
||||||
|
{"page": 3, "doc": 0},
|
||||||
|
]
|
||||||
|
|
||||||
|
count = pdf_ops.validate_page_operations(operations, single_output=False)
|
||||||
|
|
||||||
|
assert count == 3
|
||||||
|
|
||||||
|
def test_empty_operations_are_rejected(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- No operations
|
||||||
|
WHEN:
|
||||||
|
- They are validated
|
||||||
|
THEN:
|
||||||
|
- ValueError is raised
|
||||||
|
"""
|
||||||
|
with pytest.raises(ValueError, match="index is out of bounds"):
|
||||||
|
pdf_ops.validate_page_operations([], single_output=False)
|
||||||
|
|
||||||
|
def test_multiple_outputs_rejected_when_single_output_required(self) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Operations that target two outputs
|
||||||
|
WHEN:
|
||||||
|
- They are validated with a single output required
|
||||||
|
THEN:
|
||||||
|
- ValueError is raised
|
||||||
|
"""
|
||||||
|
operations = [{"page": 1, "doc": 0}, {"page": 2, "doc": 1}]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Multiple output documents"):
|
||||||
|
pdf_ops.validate_page_operations(operations, single_output=True)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"doc",
|
||||||
|
[
|
||||||
|
pytest.param(-1, id="negative"),
|
||||||
|
pytest.param(2, id="equal-to-operation-count"),
|
||||||
|
pytest.param(2**32, id="huge"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_output_index_out_of_bounds(self, doc: int) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Two operations, one with an output index that is out of bounds
|
||||||
|
WHEN:
|
||||||
|
- They are validated
|
||||||
|
THEN:
|
||||||
|
- ValueError is raised
|
||||||
|
"""
|
||||||
|
operations = [{"page": 1, "doc": 0}, {"page": 2, "doc": doc}]
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="index is out of bounds"):
|
||||||
|
pdf_ops.validate_page_operations(operations, single_output=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDecrypt:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("path", "expected"),
|
||||||
|
[
|
||||||
|
pytest.param(ENCRYPTED, True, id="password-required"),
|
||||||
|
pytest.param(SIGNED, True, id="opens-without-password-but-encrypted"),
|
||||||
|
pytest.param(THREE_PAGES, False, id="not-encrypted"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_needs_decrypt(self, path: Path, *, expected: bool) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A PDF that is encrypted, or encrypted but openable, or plain
|
||||||
|
WHEN:
|
||||||
|
- needs_decrypt is asked about it
|
||||||
|
THEN:
|
||||||
|
- Only the unencrypted PDF reports False
|
||||||
|
"""
|
||||||
|
assert pdf_ops.needs_decrypt(path) is expected
|
||||||
|
|
||||||
|
def test_decrypt_writes_an_unencrypted_copy(self, tmp_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A password protected PDF
|
||||||
|
WHEN:
|
||||||
|
- It is decrypted with the correct password
|
||||||
|
THEN:
|
||||||
|
- The path from make_dst is returned
|
||||||
|
- The written copy no longer needs decrypting
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "out.pdf"
|
||||||
|
|
||||||
|
result = pdf_ops.decrypt_pdf(ENCRYPTED, constant(dst), "test")
|
||||||
|
|
||||||
|
assert result == dst
|
||||||
|
assert pdf_ops.needs_decrypt(dst) is False
|
||||||
|
|
||||||
|
def test_wrong_password_raises_and_never_requests_a_destination(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A password protected PDF
|
||||||
|
WHEN:
|
||||||
|
- It is decrypted with the wrong password
|
||||||
|
THEN:
|
||||||
|
- PasswordError is raised
|
||||||
|
- No destination was requested
|
||||||
|
"""
|
||||||
|
requested: list[Path] = []
|
||||||
|
|
||||||
|
def make_dst() -> Path:
|
||||||
|
requested.append(tmp_path / "out.pdf")
|
||||||
|
return requested[-1]
|
||||||
|
|
||||||
|
with pytest.raises(pikepdf.PasswordError):
|
||||||
|
pdf_ops.decrypt_pdf(ENCRYPTED, make_dst, "wrong")
|
||||||
|
|
||||||
|
assert requested == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestPdfMerger:
|
||||||
|
def test_pages_are_appended_in_the_order_added(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
source_fingerprints: list[str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A reordered PDF and the original three page PDF
|
||||||
|
WHEN:
|
||||||
|
- Both are added to a merger in that order and saved
|
||||||
|
THEN:
|
||||||
|
- The output holds all pages in the order they were added
|
||||||
|
"""
|
||||||
|
reordered = tmp_path / "reordered.pdf"
|
||||||
|
merged = tmp_path / "merged.pdf"
|
||||||
|
pdf_ops.build_pdfs(
|
||||||
|
THREE_PAGES,
|
||||||
|
[([PageSpec(3), PageSpec(1)], constant(reordered))],
|
||||||
|
)
|
||||||
|
|
||||||
|
with pdf_ops.PdfMerger() as merger:
|
||||||
|
merger.add(reordered)
|
||||||
|
merger.add(THREE_PAGES)
|
||||||
|
merger.save(merged)
|
||||||
|
|
||||||
|
assert fingerprints(merged) == [
|
||||||
|
source_fingerprints[2],
|
||||||
|
source_fingerprints[0],
|
||||||
|
*source_fingerprints,
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_output_version_is_at_least_the_highest_source_version(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Two PDFs with different PDF versions
|
||||||
|
WHEN:
|
||||||
|
- Both are added to a merger and saved
|
||||||
|
THEN:
|
||||||
|
- The output version is at least the highest source version
|
||||||
|
"""
|
||||||
|
merged = tmp_path / "merged.pdf"
|
||||||
|
with pikepdf.open(TWELVE_PAGES) as pdf:
|
||||||
|
source_versions = [pdf.pdf_version]
|
||||||
|
with pikepdf.open(THREE_PAGES) as pdf:
|
||||||
|
source_versions.append(pdf.pdf_version)
|
||||||
|
|
||||||
|
with pdf_ops.PdfMerger() as merger:
|
||||||
|
merger.add(TWELVE_PAGES)
|
||||||
|
merger.add(THREE_PAGES)
|
||||||
|
merger.save(merged)
|
||||||
|
|
||||||
|
with pikepdf.open(merged) as pdf:
|
||||||
|
assert pdf.pdf_version >= max(source_versions)
|
||||||
@@ -7,7 +7,6 @@ from unittest import mock
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.test import override_settings
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -226,25 +225,6 @@ class ShareLinkBundleAPITests(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
||||||
self.assertIn("sharelink_notfound=1", response["Location"])
|
self.assertIn("sharelink_notfound=1", response["Location"])
|
||||||
|
|
||||||
@override_settings(LOGIN_URL="/paperless/accounts/login/")
|
|
||||||
def test_unknown_share_link_redirects_under_subpath(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Paperless is served under a subpath
|
|
||||||
WHEN:
|
|
||||||
- An unknown public share link is requested
|
|
||||||
THEN:
|
|
||||||
- The redirect to the login page keeps the subpath
|
|
||||||
"""
|
|
||||||
self.client.logout()
|
|
||||||
response = self.client.get("/share/unknownsharelink/")
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
|
|
||||||
self.assertEqual(
|
|
||||||
response["Location"],
|
|
||||||
"/paperless/accounts/login/?sharelink_notfound=1",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_share_link_missing_file_redirects(self) -> None:
|
def test_share_link_missing_file_redirects(self) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
|||||||
@@ -6176,50 +6176,6 @@ class TestApplyAISuggestionsWorkflowAction(
|
|||||||
|
|
||||||
self.assertEqual(self.doc.tags.count(), 0)
|
self.assertEqual(self.doc.tags.count(), 0)
|
||||||
|
|
||||||
def test_unowned_document_does_not_get_private_objects_by_id(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- An unowned document
|
|
||||||
- AI suggestions selecting another user's private tag and
|
|
||||||
correspondent by ID, plus an unowned tag
|
|
||||||
WHEN:
|
|
||||||
- The suggestions are applied
|
|
||||||
THEN:
|
|
||||||
- Only the unowned tag is assigned; an unowned document must not
|
|
||||||
be treated as unrestricted
|
|
||||||
"""
|
|
||||||
self.doc.owner = None
|
|
||||||
self.doc.save(update_fields=["owner"])
|
|
||||||
other = User.objects.create(username="someone-else")
|
|
||||||
private_tag = Tag.objects.create(name="Private Tag", owner=other)
|
|
||||||
public_tag = Tag.objects.create(name="Public Tag", owner=None)
|
|
||||||
private_correspondent = Correspondent.objects.create(
|
|
||||||
name="Private Correspondent",
|
|
||||||
owner=other,
|
|
||||||
)
|
|
||||||
action = self.make_action(
|
|
||||||
ai_suggestion_fields=[
|
|
||||||
WorkflowAction.AISuggestionField.TAGS,
|
|
||||||
WorkflowAction.AISuggestionField.CORRESPONDENT,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
suggestions: ClassificationSuggestions = {
|
|
||||||
**SUGGESTIONS,
|
|
||||||
"tags": {
|
|
||||||
"existing_ids": [private_tag.pk, public_tag.pk],
|
|
||||||
"new_names": [],
|
|
||||||
},
|
|
||||||
"correspondents": {
|
|
||||||
"existing_ids": [private_correspondent.pk],
|
|
||||||
"new_names": [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
self.apply(action, suggestions)
|
|
||||||
|
|
||||||
self.assertEqual(list(self.doc.tags.all()), [public_tag])
|
|
||||||
self.assertIsNone(self.doc.correspondent)
|
|
||||||
|
|
||||||
def test_unparsable_dates_are_skipped(self) -> None:
|
def test_unparsable_dates_are_skipped(self) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
|||||||
+27
-46
@@ -289,7 +289,6 @@ logger = logging.getLogger("paperless.api")
|
|||||||
# clauses efficiently, so this threshold mainly protects SQLite users.
|
# clauses efficiently, so this threshold mainly protects SQLite users.
|
||||||
_TANTIVY_INTERSECT_THRESHOLD = 5_000
|
_TANTIVY_INTERSECT_THRESHOLD = 5_000
|
||||||
_TANTIVY_SEARCH_PARAM_NAMES = ("text", "title_search", "query", "more_like_id")
|
_TANTIVY_SEARCH_PARAM_NAMES = ("text", "title_search", "query", "more_like_id")
|
||||||
_TANTIVY_COMBINABLE_SEARCH_PARAMS = ({"text", "query"}, {"title_search", "query"})
|
|
||||||
|
|
||||||
# whoosh-compat's fieldname tagger (used only for SearchMode.QUERY, via the
|
# whoosh-compat's fieldname tagger (used only for SearchMode.QUERY, via the
|
||||||
# whoosh grammar in parse_user_query) is O(n^2) in plain word characters:
|
# whoosh grammar in parse_user_query) is O(n^2) in plain word characters:
|
||||||
@@ -309,18 +308,6 @@ _TANTIVY_COMBINABLE_SEARCH_PARAMS = ({"text", "query"}, {"title_search", "query"
|
|||||||
_MAX_QUERY_LENGTH: Final[int] = 4096
|
_MAX_QUERY_LENGTH: Final[int] = 4096
|
||||||
|
|
||||||
|
|
||||||
def _validate_tantivy_search_params(active: list[str]) -> None:
|
|
||||||
# may also have a filter query alongside a simple search e.g. relative dates
|
|
||||||
if len(active) > 1 and set(active) not in _TANTIVY_COMBINABLE_SEARCH_PARAMS:
|
|
||||||
raise ValidationError(
|
|
||||||
{
|
|
||||||
"detail": _(
|
|
||||||
"Specify only one of text, title_search, query, or more_like_id.",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_tantivy_query_and_mode(params):
|
def _get_tantivy_query_and_mode(params):
|
||||||
from documents.search import QueryTooLongError
|
from documents.search import QueryTooLongError
|
||||||
from documents.search import SearchMode
|
from documents.search import SearchMode
|
||||||
@@ -334,15 +321,9 @@ def _get_tantivy_query_and_mode(params):
|
|||||||
else:
|
else:
|
||||||
return None # pragma: no cover
|
return None # pragma: no cover
|
||||||
|
|
||||||
filter_query = (
|
if len(raw) > _MAX_QUERY_LENGTH:
|
||||||
str(params["query"])
|
raise QueryTooLongError(len(raw), _MAX_QUERY_LENGTH)
|
||||||
if mode is not SearchMode.QUERY and "query" in params
|
return raw, mode
|
||||||
else None
|
|
||||||
)
|
|
||||||
for value in (raw, filter_query):
|
|
||||||
if value is not None and len(value) > _MAX_QUERY_LENGTH:
|
|
||||||
raise QueryTooLongError(len(value), _MAX_QUERY_LENGTH)
|
|
||||||
return raw, mode, filter_query
|
|
||||||
|
|
||||||
|
|
||||||
def _get_more_like_id(query_params: dict[str, Any], user: User | None) -> int:
|
def _get_more_like_id(query_params: dict[str, Any], user: User | None) -> int:
|
||||||
@@ -2585,9 +2566,15 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
|||||||
|
|
||||||
def parse_search_params() -> SearchParams:
|
def parse_search_params() -> SearchParams:
|
||||||
"""Extract query string, search mode, and ordering from request."""
|
"""Extract query string, search mode, and ordering from request."""
|
||||||
_validate_tantivy_search_params(
|
active = self._get_active_search_params(request)
|
||||||
self._get_active_search_params(request),
|
if len(active) > 1:
|
||||||
)
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": _(
|
||||||
|
"Specify only one of text, title_search, query, or more_like_id.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
ordering_param = request.query_params.get("ordering", "")
|
ordering_param = request.query_params.get("ordering", "")
|
||||||
sort_reverse = ordering_param.startswith("-")
|
sort_reverse = ordering_param.startswith("-")
|
||||||
@@ -2649,9 +2636,7 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
|||||||
filtered_qs: QuerySet[Document],
|
filtered_qs: QuerySet[Document],
|
||||||
) -> SearchResultPage:
|
) -> SearchResultPage:
|
||||||
"""Handle text/title/query search: IDs, ORM intersection, page highlights."""
|
"""Handle text/title/query search: IDs, ORM intersection, page highlights."""
|
||||||
query_str, search_mode, filter_query = _get_tantivy_query_and_mode(
|
query_str, search_mode = _get_tantivy_query_and_mode(request.query_params)
|
||||||
request.query_params,
|
|
||||||
)
|
|
||||||
|
|
||||||
# "score" is not a real Tantivy sort field — it means relevance order,
|
# "score" is not a real Tantivy sort field — it means relevance order,
|
||||||
# which is Tantivy's default when no sort field is specified.
|
# which is Tantivy's default when no sort field is specified.
|
||||||
@@ -2664,7 +2649,6 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
|||||||
),
|
),
|
||||||
sort_reverse=sort_reverse,
|
sort_reverse=sort_reverse,
|
||||||
search_mode=search_mode,
|
search_mode=search_mode,
|
||||||
filter_query=filter_query,
|
|
||||||
)
|
)
|
||||||
ordered_ids = intersect_and_order(
|
ordered_ids = intersect_and_order(
|
||||||
all_ids,
|
all_ids,
|
||||||
@@ -2899,7 +2883,14 @@ class DocumentSelectionMixin:
|
|||||||
]
|
]
|
||||||
if not search_filters:
|
if not search_filters:
|
||||||
return None
|
return None
|
||||||
_validate_tantivy_search_params(search_filters)
|
if len(search_filters) > 1:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": _(
|
||||||
|
"Specify only one of text, title_search, query, or more_like_id.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
from documents.search import SearchQueryError
|
from documents.search import SearchQueryError
|
||||||
from documents.search import get_backend
|
from documents.search import get_backend
|
||||||
@@ -2918,14 +2909,11 @@ class DocumentSelectionMixin:
|
|||||||
user=search_user,
|
user=search_user,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query_str, search_mode, filter_query = _get_tantivy_query_and_mode(
|
query_str, search_mode = _get_tantivy_query_and_mode(filters)
|
||||||
filters,
|
|
||||||
)
|
|
||||||
search_ids = backend.search_ids(
|
search_ids = backend.search_ids(
|
||||||
query_str,
|
query_str,
|
||||||
user=search_user,
|
user=search_user,
|
||||||
search_mode=search_mode,
|
search_mode=search_mode,
|
||||||
filter_query=filter_query,
|
|
||||||
)
|
)
|
||||||
except SearchQueryError as e:
|
except SearchQueryError as e:
|
||||||
# Same user-fixable-query mapping as the search list endpoint:
|
# Same user-fixable-query mapping as the search list endpoint:
|
||||||
@@ -3932,12 +3920,7 @@ class GlobalSearchView(PassUserMixin):
|
|||||||
"request": request,
|
"request": request,
|
||||||
}
|
}
|
||||||
|
|
||||||
docs_serializer = DocumentSerializer(
|
docs_serializer = DocumentSerializer(docs, many=True, context=context)
|
||||||
docs,
|
|
||||||
many=True,
|
|
||||||
context=context,
|
|
||||||
fields=("id", "title", "created"),
|
|
||||||
)
|
|
||||||
saved_views_serializer = SavedViewSerializer(
|
saved_views_serializer = SavedViewSerializer(
|
||||||
saved_views,
|
saved_views,
|
||||||
many=True,
|
many=True,
|
||||||
@@ -4916,7 +4899,7 @@ class SharedLinkView(View):
|
|||||||
share_link.expiration is not None
|
share_link.expiration is not None
|
||||||
and share_link.expiration < timezone.now()
|
and share_link.expiration < timezone.now()
|
||||||
):
|
):
|
||||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_expired=1")
|
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||||
try:
|
try:
|
||||||
return serve_file(
|
return serve_file(
|
||||||
doc=share_link.document,
|
doc=share_link.document,
|
||||||
@@ -4925,16 +4908,14 @@ class SharedLinkView(View):
|
|||||||
disposition="inline",
|
disposition="inline",
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect("/accounts/login/?sharelink_notfound=1")
|
||||||
f"{settings.LOGIN_URL}?sharelink_notfound=1",
|
|
||||||
)
|
|
||||||
|
|
||||||
bundle = ShareLinkBundle.objects.filter(slug=slug).first()
|
bundle = ShareLinkBundle.objects.filter(slug=slug).first()
|
||||||
if bundle is None:
|
if bundle is None:
|
||||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_notfound=1")
|
return HttpResponseRedirect("/accounts/login/?sharelink_notfound=1")
|
||||||
|
|
||||||
if bundle.expiration is not None and bundle.expiration < timezone.now():
|
if bundle.expiration is not None and bundle.expiration < timezone.now():
|
||||||
return HttpResponseRedirect(f"{settings.LOGIN_URL}?sharelink_expired=1")
|
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||||
|
|
||||||
if bundle.status in {
|
if bundle.status in {
|
||||||
ShareLinkBundle.Status.PENDING,
|
ShareLinkBundle.Status.PENDING,
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Afrikaans\n"
|
"Language-Team: Afrikaans\n"
|
||||||
"Language: af_ZA\n"
|
"Language: af_ZA\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ongeldige veranderlike bespeur."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Amharic\n"
|
"Language-Team: Amharic\n"
|
||||||
"Language: am_ET\n"
|
"Language: am_ET\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
"Language: ar_SA\n"
|
"Language: ar_SA\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "اكتشاف متغير خاطئ."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "كخطوة أخيرة، يرجى ملء النموذج التالي:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Belarusian\n"
|
"Language-Team: Belarusian\n"
|
||||||
"Language: be_BY\n"
|
"Language: be_BY\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Выяўлена няправільная зменная."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Language: bg_BG\n"
|
"Language: bg_BG\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "стартиране на работните процеси"
|
msgstr "стартиране на работните процеси"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Засечена е невалидна променлива."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Като последна стъпка, моля, попълнете с
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Catalan\n"
|
"Language-Team: Catalan\n"
|
||||||
"Language: ca_ES\n"
|
"Language: ca_ES\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "flux corrents"
|
msgstr "flux corrents"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Permisos insuficients."
|
msgstr "Permisos insuficients."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variable detectada invàlida."
|
|||||||
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:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, 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"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Com a pas final, completa el següent formulari:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
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."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Invalid more_like_id"
|
msgstr "Invalid more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configuració AI invàlida."
|
msgstr "Configuració AI invàlida."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
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."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
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:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Czech\n"
|
"Language-Team: Czech\n"
|
||||||
"Language: cs_CZ\n"
|
"Language: cs_CZ\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "spuštění pracovních postupů"
|
msgstr "spuštění pracovních postupů"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nedostatečná oprávnění."
|
msgstr "Nedostatečná oprávnění."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Zjištěna neplatná proměnná."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Jako poslední krok vyplňte následující formulář:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Danish\n"
|
"Language-Team: Danish\n"
|
||||||
"Language: da_DK\n"
|
"Language: da_DK\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "workflow-kørsler"
|
msgstr "workflow-kørsler"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ugyldig variabel fundet."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Som sidste trin, udfyld venligst flg. formular:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\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"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "Arbeitsablauf wird ausgeführt"
|
msgstr "Arbeitsablauf wird ausgeführt"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Unzureichende Berechtigungen."
|
msgstr "Unzureichende Berechtigungen."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ungültige Variable erkannt."
|
|||||||
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:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, 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"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
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."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
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:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Ungültige KI-Konfiguration."
|
msgstr "Ungültige KI-Konfiguration."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
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."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
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:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "Arbeitsablauf wird ausgeführt"
|
msgstr "Arbeitsablauf wird ausgeführt"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Unzureichende Berechtigungen."
|
msgstr "Unzureichende Berechtigungen."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ungültige Variable erkannt."
|
|||||||
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:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, 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"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Bitte füllen Sie zum Abschluss folgendes Formular aus:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
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."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
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:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Ungültige KI-Konfiguration."
|
msgstr "Ungültige KI-Konfiguration."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
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:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Das KI-Backend hat die Anfrage abgelehnt. Details in den Protokollen."
|
msgstr "Das KI-Backend hat die Anfrage abgelehnt. Details in den Protokollen."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
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."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
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:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Greek\n"
|
"Language-Team: Greek\n"
|
||||||
"Language: el_GR\n"
|
"Language: el_GR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "εκτελέσεις ροής εργασίας"
|
msgstr "εκτελέσεις ροής εργασίας"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Εντοπίστηκε μη έγκυρη μεταβλητή."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Ως τελικό βήμα, παρακαλούμε να συμπληρώ
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "ejecuciones de flujo de trabajo"
|
msgstr "ejecuciones de flujo de trabajo"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Permisos insuficientes."
|
msgstr "Permisos insuficientes."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variable inválida."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "No se permiten identificadores de documento duplicados."
|
msgstr "No se permiten identificadores de documento duplicados."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documentos no encontrados: %(ids)s"
|
msgstr "Documentos no encontrados: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Como último paso, por favor complete el siguiente formulario:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Especifique solo uno entre text, title_search, query, o more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configuración de IA inválida."
|
msgstr "Configuración de IA inválida."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Especifique solo uno entre text, title_search, query, o more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Permisos insuficientes para compartir el documento %(id)s."
|
msgstr "Permisos insuficientes para compartir el documento %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "El paquete ya está siendo procesado."
|
msgstr "El paquete ya está siendo procesado."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 paquete de enlace compartido aún está siendo preparado. Por favor, inténtalo de nuevo más tarde."
|
msgstr "El paquete de enlace compartido aún está siendo preparado. Por favor, inténtalo de nuevo más tarde."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "El paquete de enlace compartido no está disponible."
|
msgstr "El paquete de enlace compartido 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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Estonian\n"
|
"Language-Team: Estonian\n"
|
||||||
"Language: et_EE\n"
|
"Language: et_EE\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Persian\n"
|
"Language-Team: Persian\n"
|
||||||
"Language: fa_IR\n"
|
"Language: fa_IR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "گردش کار اجرا می شود"
|
msgstr "گردش کار اجرا می شود"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "متغیر نامعتبر شناسایی شده است."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr "به عنوان مرحله آخر ، لطفاً فرم زیر را تک
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Finnish\n"
|
"Language-Team: Finnish\n"
|
||||||
"Language: fi_FI\n"
|
"Language: fi_FI\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Virheellinen muuttuja havaittu."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "le flux de travail s'exécute"
|
msgstr "le flux de travail s'exécute"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Droits insuffisants."
|
msgstr "Droits insuffisants."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variable invalide détectée."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Les identificateurs de document en double ne sont pas autorisés."
|
msgstr "Les identificateurs de document en double ne sont pas autorisés."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documents introuvables : %(ids)s"
|
msgstr "Documents introuvables : %(ids)s"
|
||||||
@@ -1916,40 +1916,40 @@ msgstr "Pour finir, veuillez remplir le formulaire suivant :"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Spécifiez seulement un texte, titre, recherche ou more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "More_like_id invalide"
|
msgstr "More_like_id invalide"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configuration IA invalide."
|
msgstr "Configuration IA invalide."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "La requête d'arrière-plan IA a expiré."
|
msgstr "La requête d'arrière-plan IA a expiré."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Spécifiez seulement un texte, titre, recherche ou more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Droits d'accès insuffisant pour partager %(id)s document."
|
msgstr "Droits d'accès insuffisant pour partager %(id)s document."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Le paquet est déjà en cours de traitement."
|
msgstr "Le paquet est déjà en cours de traitement."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Le lot de liens de partage est en cours de préparation. Veuillez réessayer plus tard."
|
msgstr "Le lot de liens de partage est en cours de préparation. Veuillez réessayer plus tard."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Le lot de liens de partage n'est pas disponible."
|
msgstr "Le lot de liens de partage n'est pas 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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"Language: he_IL\n"
|
"Language: he_IL\n"
|
||||||
@@ -1637,7 +1637,7 @@ msgid "workflow runs"
|
|||||||
msgstr "הרצות זרימת עבודה"
|
msgstr "הרצות זרימת עבודה"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "הרשאות אינן מספיקות."
|
msgstr "הרשאות אינן מספיקות."
|
||||||
@@ -1677,7 +1677,7 @@ msgstr "משתנה לא חוקי זוהה."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "מזהי מסמכים כפולים אינם מורשים."
|
msgstr "מזהי מסמכים כפולים אינם מורשים."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "מסמכים לא נמצאו: %(ids)s"
|
msgstr "מסמכים לא נמצאו: %(ids)s"
|
||||||
@@ -1918,40 +1918,40 @@ msgstr ": כצעד אחרון, אנא מלא את הטופס הבא:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "יש לציין רק אחד מהבאים: text, title_search, query או more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "מזהה more_like_id אינו תקין"
|
msgstr "מזהה more_like_id אינו תקין"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "הגדרות בינה מלאכותית שגויות."
|
msgstr "הגדרות בינה מלאכותית שגויות."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "יש לציין רק אחד מהבאים: text, title_search, query או more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "הרשאות לא מספיקות לשיתוף מסמך %(id)s."
|
msgstr "הרשאות לא מספיקות לשיתוף מסמך %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "החבילה (Bundle) כבר נמצאת בתהליך עיבוד."
|
msgstr "החבילה (Bundle) כבר נמצאת בתהליך עיבוד."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hindi\n"
|
"Language-Team: Hindi\n"
|
||||||
"Language: hi_IN\n"
|
"Language: hi_IN\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Croatian\n"
|
"Language-Team: Croatian\n"
|
||||||
"Language: hr_HR\n"
|
"Language: hr_HR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "tijek rada pokrenut"
|
msgstr "tijek rada pokrenut"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nedovoljne ovlasti."
|
msgstr "Nedovoljne ovlasti."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Otkrivena je nevaljana vrsta datoteke."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Duplicirani identifikatori dokumenata nisu dopušteni."
|
msgstr "Duplicirani identifikatori dokumenata nisu dopušteni."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Kao posljednji korak ispunite sljedeći obrazac:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Navedite samo jedno od: text, title_search, query ili more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Nevažeći more_like_id"
|
msgstr "Nevažeći more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Nevažeća AI konfiguracija."
|
msgstr "Nevažeća AI konfiguracija."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Zahtjev prema AI pozadinskom sustavu je istekao."
|
msgstr "Zahtjev prema AI pozadinskom sustavu je istekao."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "AI pozadinski sustav je odbio zahtjev. Provjerite zapise (logs) za više detalja."
|
msgstr "AI pozadinski sustav je odbio zahtjev. Provjerite zapise (logs) za više detalja."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Navedite samo jedno od: text, title_search, query ili more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nedovoljne ovlasti za dijeljenje dokumenta %(id)s."
|
msgstr "Nedovoljne ovlasti za dijeljenje dokumenta %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket se već obrađuje."
|
msgstr "Paket se već obrađuje."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Paket linka za dijeljenje se još priprema. Pokušajte ponovo kasnije."
|
msgstr "Paket linka za dijeljenje se još priprema. Pokušajte ponovo kasnije."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Paket linka za dijeljenje nije dostupan."
|
msgstr "Paket linka za dijeljenje nije dostupan."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hungarian\n"
|
"Language-Team: Hungarian\n"
|
||||||
"Language: hu_HU\n"
|
"Language: hu_HU\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "munkafolyamat futtatások"
|
msgstr "munkafolyamat futtatások"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nincs jogosúltsága."
|
msgstr "Nincs jogosúltsága."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Érvénytelen változó észlelve."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "A dokumentumazonosítók duplikálása nem megengedett."
|
msgstr "A dokumentumazonosítók duplikálása nem megengedett."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumentumok nem találhatók: %(ids)s"
|
msgstr "Dokumentumok nem találhatók: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Utolsó lépésként kérjük válaszoljon az alábbi kérdésekre:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "A text, title_search, query, vagy more_like_id közül csak egyet adjon meg."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Érvénytelen more_like_id"
|
msgstr "Érvénytelen more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Érvénytelen MI konfiguráció."
|
msgstr "Érvénytelen MI konfiguráció."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "A text, title_search, query, vagy more_like_id közül csak egyet adjon meg."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nincs megfelelő jogosultság a %(id)s dokumentum megosztásához."
|
msgstr "Nincs megfelelő jogosultság a %(id)s dokumentum megosztásához."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "A csomag feldolgozása már folyamatban van."
|
msgstr "A csomag feldolgozása már folyamatban van."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "A megosztási linkcsomag készítése folyamatban. Kérjük, próbálja meg később."
|
msgstr "A megosztási linkcsomag készítése folyamatban. Kérjük, próbálja meg később."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "A megosztási linkcsomag nem elérhető."
|
msgstr "A megosztási linkcsomag nem elérhető."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Indonesian\n"
|
"Language-Team: Indonesian\n"
|
||||||
"Language: id_ID\n"
|
"Language: id_ID\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "daftar jalankan alur kerja"
|
msgstr "daftar jalankan alur kerja"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Izin tidak mencukupi"
|
msgstr "Izin tidak mencukupi"
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variabel ilegal terdeteksi."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Penggunaan pengenal dokumen ganda tidak diperbolehkan."
|
msgstr "Penggunaan pengenal dokumen ganda tidak diperbolehkan."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumen tidak ditemukan: %(ids)s"
|
msgstr "Dokumen tidak ditemukan: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Sebagai langkah terakhir, mohon formulir berikut:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Izin tidak mencukupi untuk berbagi dokumen %(id)s"
|
msgstr "Izin tidak mencukupi untuk berbagi dokumen %(id)s"
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket sedang diproses."
|
msgstr "Paket sedang diproses."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Bundel tautan berbagi masih dalam proses persiapan. Silakan coba lagi nanti."
|
msgstr "Bundel tautan berbagi masih dalam proses persiapan. Silakan coba lagi nanti."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Bundel tautan berbagi tidak tersedia."
|
msgstr "Bundel tautan berbagi tidak tersedia."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Italian\n"
|
"Language-Team: Italian\n"
|
||||||
"Language: it_IT\n"
|
"Language: it_IT\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "esecuzioni del flusso di lavoro"
|
msgstr "esecuzioni del flusso di lavoro"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Autorizzazioni insufficienti."
|
msgstr "Autorizzazioni insufficienti."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variabile non valida rilevata."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Non sono consentiti identificatori di documenti duplicati."
|
msgstr "Non sono consentiti identificatori di documenti duplicati."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documenti non trovati: %(ids)s"
|
msgstr "Documenti non trovati: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Come ultimo passo, compila il seguente modulo:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ", "
|
msgstr ", "
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Specificare solo uno tra text, title_search, query o more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "more_like_id non valido"
|
msgstr "more_like_id non valido"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configurazione AI non valida."
|
msgstr "Configurazione AI non valida."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Richiesta di backend AI scaduta."
|
msgstr "Richiesta di backend AI scaduta."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Il backend IA ha rifiutato la richiesta. Controlla i log per i dettagli."
|
msgstr "Il backend IA ha rifiutato la richiesta. Controlla i log per i dettagli."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Specificare solo uno tra text, title_search, query o more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Autorizzazioni insufficienti per condividere il documento %(id)s."
|
msgstr "Autorizzazioni insufficienti per condividere il documento %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Il pacchetto è già in fase di elaborazione."
|
msgstr "Il pacchetto è già in fase di elaborazione."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Il pacchetto di link di condivisione è ancora in fase di preparazione. Riprova più tardi."
|
msgstr "Il pacchetto di link di condivisione è ancora in fase di preparazione. Riprova più tardi."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Il pacchetto di link di condivisione non è disponibile."
|
msgstr "Il pacchetto di link di condivisione non è disponibile."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-15 12:29\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Japanese\n"
|
"Language-Team: Japanese\n"
|
||||||
"Language: ja_JP\n"
|
"Language: ja_JP\n"
|
||||||
@@ -136,7 +136,7 @@ msgstr "タグ"
|
|||||||
|
|
||||||
#: documents/models.py:125
|
#: documents/models.py:125
|
||||||
msgid "Cannot set itself as parent."
|
msgid "Cannot set itself as parent."
|
||||||
msgstr "親として設定することはできません。"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:127
|
#: documents/models.py:127
|
||||||
msgid "Cannot set parent to a descendant."
|
msgid "Cannot set parent to a descendant."
|
||||||
@@ -249,23 +249,23 @@ msgstr "物理的なドキュメントアーカイブ内での、このドキュ
|
|||||||
|
|
||||||
#: documents/models.py:321
|
#: documents/models.py:321
|
||||||
msgid "root document for this version"
|
msgid "root document for this version"
|
||||||
msgstr "このバージョンのルートドキュメント"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:325
|
#: documents/models.py:325
|
||||||
msgid "version index"
|
msgid "version index"
|
||||||
msgstr "バージョンのインデックス"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:329
|
#: documents/models.py:329
|
||||||
msgid "Index of this version within the root document."
|
msgid "Index of this version within the root document."
|
||||||
msgstr "ルートドキュメント内における、このバージョンのインデックス"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:333
|
#: documents/models.py:333
|
||||||
msgid "version label"
|
msgid "version label"
|
||||||
msgstr "バージョンラベル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:337
|
#: documents/models.py:337
|
||||||
msgid "Optional short label for a document version."
|
msgid "Optional short label for a document version."
|
||||||
msgstr "ドキュメントバージョンの任意の短いラベル。"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:342 documents/models.py:961 documents/models.py:992
|
#: documents/models.py:342 documents/models.py:961 documents/models.py:992
|
||||||
#: documents/models.py:1096 documents/models.py:2126
|
#: documents/models.py:1096 documents/models.py:2126
|
||||||
@@ -282,119 +282,119 @@ msgstr "アーカイブ"
|
|||||||
|
|
||||||
#: documents/models.py:563
|
#: documents/models.py:563
|
||||||
msgid "Bank"
|
msgid "Bank"
|
||||||
msgstr "銀行"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:564
|
#: documents/models.py:564
|
||||||
msgid "Basket"
|
msgid "Basket"
|
||||||
msgstr "買い物かご"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:565
|
#: documents/models.py:565
|
||||||
msgid "Bell"
|
msgid "Bell"
|
||||||
msgstr "ベル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:566
|
#: documents/models.py:566
|
||||||
msgid "Bookmark"
|
msgid "Bookmark"
|
||||||
msgstr "ブックマーク"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:567
|
#: documents/models.py:567
|
||||||
msgid "Boxes"
|
msgid "Boxes"
|
||||||
msgstr "箱"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:568
|
#: documents/models.py:568
|
||||||
msgid "Briefcase"
|
msgid "Briefcase"
|
||||||
msgstr "ブリーフケース"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:569
|
#: documents/models.py:569
|
||||||
msgid "Building"
|
msgid "Building"
|
||||||
msgstr "建物"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:570
|
#: documents/models.py:570
|
||||||
msgid "Calculator"
|
msgid "Calculator"
|
||||||
msgstr "電卓"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:571
|
#: documents/models.py:571
|
||||||
msgid "Calendar"
|
msgid "Calendar"
|
||||||
msgstr "カレンダー"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:572
|
#: documents/models.py:572
|
||||||
msgid "Camera"
|
msgid "Camera"
|
||||||
msgstr "カメラ"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:573
|
#: documents/models.py:573
|
||||||
msgid "Checklist"
|
msgid "Checklist"
|
||||||
msgstr "チェックリスト"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:574
|
#: documents/models.py:574
|
||||||
msgid "Cash"
|
msgid "Cash"
|
||||||
msgstr "現金"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:575
|
#: documents/models.py:575
|
||||||
msgid "Chat"
|
msgid "Chat"
|
||||||
msgstr "チャット"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:576
|
#: documents/models.py:576
|
||||||
msgid "Check"
|
msgid "Check"
|
||||||
msgstr "確認"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:577
|
#: documents/models.py:577
|
||||||
msgid "Clipboard"
|
msgid "Clipboard"
|
||||||
msgstr "クリップボード"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:578
|
#: documents/models.py:578
|
||||||
msgid "Clock"
|
msgid "Clock"
|
||||||
msgstr "時計"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:579
|
#: documents/models.py:579
|
||||||
msgid "Credit card"
|
msgid "Credit card"
|
||||||
msgstr "クレジットカード"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:580
|
#: documents/models.py:580
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr "ダウンロード"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:581
|
#: documents/models.py:581
|
||||||
msgid "Envelope"
|
msgid "Envelope"
|
||||||
msgstr "封筒"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:582
|
#: documents/models.py:582
|
||||||
msgid "Warning"
|
msgid "Warning"
|
||||||
msgstr "警告"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:583
|
#: documents/models.py:583
|
||||||
msgid "File"
|
msgid "File"
|
||||||
msgstr "ファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:584
|
#: documents/models.py:584
|
||||||
msgid "Checked file"
|
msgid "Checked file"
|
||||||
msgstr "チェック済みファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:585
|
#: documents/models.py:585
|
||||||
msgid "Locked file"
|
msgid "Locked file"
|
||||||
msgstr "ロックされたファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:586
|
#: documents/models.py:586
|
||||||
msgid "Medical file"
|
msgid "Medical file"
|
||||||
msgstr "医療ファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:587
|
#: documents/models.py:587
|
||||||
msgid "Person file"
|
msgid "Person file"
|
||||||
msgstr "個人ファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:590
|
#: documents/models.py:590
|
||||||
msgid "Spreadsheet"
|
msgid "Spreadsheet"
|
||||||
msgstr "スプレッドシート"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:592
|
#: documents/models.py:592
|
||||||
msgid "Text file"
|
msgid "Text file"
|
||||||
msgstr "テキストファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:593
|
#: documents/models.py:593
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "ファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:594
|
#: documents/models.py:594
|
||||||
msgid "Folder"
|
msgid "Folder"
|
||||||
msgstr "フォルダー"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:595 paperless_mail/admin.py:58
|
#: documents/models.py:595 paperless_mail/admin.py:58
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
@@ -406,11 +406,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:597
|
#: documents/models.py:597
|
||||||
msgid "Globe"
|
msgid "Globe"
|
||||||
msgstr "地球儀"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:598
|
#: documents/models.py:598
|
||||||
msgid "Hash"
|
msgid "Hash"
|
||||||
msgstr "ハッシュ"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:599
|
#: documents/models.py:599
|
||||||
msgid "Heart"
|
msgid "Heart"
|
||||||
@@ -418,27 +418,27 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:600
|
#: documents/models.py:600
|
||||||
msgid "House"
|
msgid "House"
|
||||||
msgstr "家"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:601
|
#: documents/models.py:601
|
||||||
msgid "Inbox"
|
msgid "Inbox"
|
||||||
msgstr "受信トレイ"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:602
|
#: documents/models.py:602
|
||||||
msgid "Journals"
|
msgid "Journals"
|
||||||
msgstr "日記"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:603
|
#: documents/models.py:603
|
||||||
msgid "Task list"
|
msgid "Task list"
|
||||||
msgstr "タスクリスト"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:604
|
#: documents/models.py:604
|
||||||
msgid "Newspaper"
|
msgid "Newspaper"
|
||||||
msgstr "新聞"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:605
|
#: documents/models.py:605
|
||||||
msgid "Attachment"
|
msgid "Attachment"
|
||||||
msgstr "添付ファイル"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:606
|
#: documents/models.py:606
|
||||||
msgid "People"
|
msgid "People"
|
||||||
@@ -446,15 +446,15 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:607
|
#: documents/models.py:607
|
||||||
msgid "Person"
|
msgid "Person"
|
||||||
msgstr "人物"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:608
|
#: documents/models.py:608
|
||||||
msgid "Printer"
|
msgid "Printer"
|
||||||
msgstr "プリンター"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:609
|
#: documents/models.py:609
|
||||||
msgid "Receipt"
|
msgid "Receipt"
|
||||||
msgstr "領収書"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:610
|
#: documents/models.py:610
|
||||||
msgid "Safe"
|
msgid "Safe"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "ワークフローの実行"
|
msgstr "ワークフローの実行"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "無効な変数を検出しました"
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "最後のステップとして、以下のフォームに記入してく
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Language: ko_KR\n"
|
"Language: ko_KR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "워크플로우 실행"
|
msgstr "워크플로우 실행"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "잘못된 변수가 감지되었습니다."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "마지막 단계로 다음 양식을 작성해 주세요:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Luxembourgish\n"
|
"Language-Team: Luxembourgish\n"
|
||||||
"Language: lb_LU\n"
|
"Language: lb_LU\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ongëlteg Zeechen detektéiert."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Lithuanian\n"
|
"Language-Team: Lithuanian\n"
|
||||||
"Language: lt_LT\n"
|
"Language: lt_LT\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr "Galiausiai užpildykite šią formą:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nepakanka leidimų bendrinti dokumentą %(id)s."
|
msgstr "Nepakanka leidimų bendrinti dokumentą %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Rinkinys jau apdorojamas."
|
msgstr "Rinkinys jau apdorojamas."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Bendrinimo nuorodų rinkinys vis dar ruošiamas. Bandykite dar kartą vėliau."
|
msgstr "Bendrinimo nuorodų rinkinys vis dar ruošiamas. Bandykite dar kartą vėliau."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Dalinimos nuorodų rinkinys yra nepasiekiamas."
|
msgstr "Dalinimos nuorodų rinkinys yra nepasiekiamas."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Latvian\n"
|
"Language-Team: Latvian\n"
|
||||||
"Language: lv_LV\n"
|
"Language: lv_LV\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Macedonian\n"
|
"Language-Team: Macedonian\n"
|
||||||
"Language: mk_MK\n"
|
"Language: mk_MK\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Malay\n"
|
"Language-Team: Malay\n"
|
||||||
"Language: ms_MY\n"
|
"Language: ms_MY\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr ""
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
"Language: nl_NL\n"
|
"Language: nl_NL\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ongeldige variabele ontdekt."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Vul als laatste stap het volgende formulier in:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Language: no_NO\n"
|
"Language: no_NO\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "workflow runs (NO)"
|
msgstr "workflow runs (NO)"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Utilstrekkelige tillatelser."
|
msgstr "Utilstrekkelige tillatelser."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ugyldig variabel oppdaget."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Duplicate document identifiers are not allowed. (NO)"
|
msgstr "Duplicate document identifiers are not allowed. (NO)"
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documents not found: %(ids)s (NO)"
|
msgstr "Documents not found: %(ids)s (NO)"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "As a final step, please complete the following form: (NO)"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Specify only one of text, title_search, query, or more_like_id. (NO)"
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Invalid more_like_id (NO)"
|
msgstr "Invalid more_like_id (NO)"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Invalid AI configuration. (NO)"
|
msgstr "Invalid AI configuration. (NO)"
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Forespørselen til KI-tjenesten fikk tidsavbrudd."
|
msgstr "Forespørselen til KI-tjenesten fikk tidsavbrudd."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Specify only one of text, title_search, query, or more_like_id. (NO)"
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Insufficient permissions to share document %(id)s. (NO)"
|
msgstr "Insufficient permissions to share document %(id)s. (NO)"
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Bundle is already being processed. (NO)"
|
msgstr "Bundle is already being processed. (NO)"
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "The share link bundle is still being prepared. Please try again later. (NO)"
|
msgstr "The share link bundle is still being prepared. Please try again later. (NO)"
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "The share link bundle is unavailable. (NO)"
|
msgstr "The share link bundle is unavailable. (NO)"
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
"Language: pl_PL\n"
|
"Language: pl_PL\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "uruchomienia przepływu pracy"
|
msgstr "uruchomienia przepływu pracy"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Niewystarczające uprawnienia."
|
msgstr "Niewystarczające uprawnienia."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Wykryto nieprawidłową zmienną."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Nie dopuszcza się powielania identyfikatorów dokumentów."
|
msgstr "Nie dopuszcza się powielania identyfikatorów dokumentów."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Nie znaleziono dokumentów: %(ids)s"
|
msgstr "Nie znaleziono dokumentów: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Na koniec należy wypełnić poniższy formularz:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Nieprawidłowy more_like_id"
|
msgstr "Nieprawidłowy more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Brak uprawnień do udostępnienia dokumentu %(id)s."
|
msgstr "Brak uprawnień do udostępnienia dokumentu %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Pakiet jest już przetwarzany."
|
msgstr "Pakiet jest już przetwarzany."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Pakiet do udostępnienia jest nadal przygotowywany. Spróbuj ponownie później."
|
msgstr "Pakiet do udostępnienia jest nadal przygotowywany. Spróbuj ponownie później."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Pakiet do udostępnienia jest niedostępny."
|
msgstr "Pakiet do udostępnienia jest niedostępny."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese, Brazilian\n"
|
"Language-Team: Portuguese, Brazilian\n"
|
||||||
"Language: pt_BR\n"
|
"Language: pt_BR\n"
|
||||||
@@ -1637,7 +1637,7 @@ msgid "workflow runs"
|
|||||||
msgstr "execução de fluxo de trabalho"
|
msgstr "execução de fluxo de trabalho"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Permissões insuficientes."
|
msgstr "Permissões insuficientes."
|
||||||
@@ -1677,7 +1677,7 @@ msgstr "Variável inválida detectada."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Não são permitidos identificadores de documento duplicados."
|
msgstr "Não são permitidos identificadores de documento duplicados."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Documentos não encontrados: %(ids)s"
|
msgstr "Documentos não encontrados: %(ids)s"
|
||||||
@@ -1918,40 +1918,40 @@ msgstr "Como etapa final, por favor preencha o seguinte formulário:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Especifique apenas um text, title_search, consulta ou mais_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "more_like_id inválido"
|
msgstr "more_like_id inválido"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Configuração de IA inválida."
|
msgstr "Configuração de IA inválida."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "A requisição backend da IA expirou."
|
msgstr "A requisição backend da IA expirou."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Especifique apenas um text, title_search, consulta ou mais_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Permissões insuficientes para compartilhar o documento %(id)s."
|
msgstr "Permissões insuficientes para compartilhar o documento %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Pacote já está sendo processado."
|
msgstr "Pacote já está sendo processado."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "O pacote de link de compartilhamento ainda está sendo preparado. Tente novamente mais tarde."
|
msgstr "O pacote de link de compartilhamento ainda está sendo preparado. Tente novamente mais tarde."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "O pacote de link de compartilhamento não está disponível."
|
msgstr "O pacote de link de compartilhamento não está disponível."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese\n"
|
"Language-Team: Portuguese\n"
|
||||||
"Language: pt_PT\n"
|
"Language: pt_PT\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variável inválida detetada."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Language: ro_RO\n"
|
"Language: ro_RO\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "rulări flux de lucru"
|
msgstr "rulări flux de lucru"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Variabilă nevalidă detectată."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "În etapa finală, vă rugăm să completați următorul formular:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "запуски рабочего процесса"
|
msgstr "запуски рабочего процесса"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Недостаточно прав."
|
msgstr "Недостаточно прав."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Обнаружена неверная переменная."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Повторяющиеся идентификаторы документа не допускаются."
|
msgstr "Повторяющиеся идентификаторы документа не допускаются."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Документы не найдены: %(ids)s"
|
msgstr "Документы не найдены: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "На завершение, пожалуйста заполните сл
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ", "
|
msgstr ", "
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Укажите только одно: текст, title_search, query или more _like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Недопустимый more_like_id"
|
msgstr "Недопустимый more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Неверная конфигурация ИИ."
|
msgstr "Неверная конфигурация ИИ."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Таймаут запроса ИИ."
|
msgstr "Таймаут запроса ИИ."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Движок ИИ отклонил запрос. Проверьте логи."
|
msgstr "Движок ИИ отклонил запрос. Проверьте логи."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Укажите только одно: текст, title_search, query или more _like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Недостаточно прав для совместного доступа к документу %(id)s."
|
msgstr "Недостаточно прав для совместного доступа к документу %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Пакет уже обрабатывается."
|
msgstr "Пакет уже обрабатывается."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:40\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovak\n"
|
"Language-Team: Slovak\n"
|
||||||
"Language: sk_SK\n"
|
"Language: sk_SK\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "spustenia pracovných postupov"
|
msgstr "spustenia pracovných postupov"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nedostatočné oprávnenia."
|
msgstr "Nedostatočné oprávnenia."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Zistená neplatná premenná."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Duplicitné identifikátory dokumentov nie sú povolené."
|
msgstr "Duplicitné identifikátory dokumentov nie sú povolené."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumenty sa nenašli: %(ids)s"
|
msgstr "Dokumenty sa nenašli: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Ako posledný krok vyplňte nasledujúci formulár:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Zadajte iba jednu z možností: text, title_search, query alebo more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Neplatné more_like_id"
|
msgstr "Neplatné more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Neplatná konfigurácia umelej inteligencie."
|
msgstr "Neplatná konfigurácia umelej inteligencie."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Vypršal časový limit požiadavky na backend umelej inteligencie."
|
msgstr "Vypršal časový limit požiadavky na backend umelej inteligencie."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Backend umelej inteligencie zamietol požiadavku. Podrobnosti nájdete v záznamoch."
|
msgstr "Backend umelej inteligencie zamietol požiadavku. Podrobnosti nájdete v záznamoch."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Zadajte iba jednu z možností: text, title_search, query alebo more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nedostatočné oprávnenia na zdieľanie dokumentu %(id)s."
|
msgstr "Nedostatočné oprávnenia na zdieľanie dokumentu %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Balík sa už spracúva."
|
msgstr "Balík sa už spracúva."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Balík odkazov na zdieľanie sa stále pripravuje. Skúste to znova neskôr."
|
msgstr "Balík odkazov na zdieľanie sa stále pripravuje. Skúste to znova neskôr."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Balík odkazov na zdieľanie nie je k dispozícii."
|
msgstr "Balík odkazov na zdieľanie nie je k dispozícii."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Language: sl_SI\n"
|
"Language: sl_SI\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "poteka dela"
|
msgstr "poteka dela"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nezadostna dovoljenja."
|
msgstr "Nezadostna dovoljenja."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Zaznani neveljavni znaki."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Podvojeni identifikatorji dokumentov niso dovoljeni."
|
msgstr "Podvojeni identifikatorji dokumentov niso dovoljeni."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumentov ni bilo mogoče najti: %(ids)s"
|
msgstr "Dokumentov ni bilo mogoče najti: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Kot zadnji korak vas prosimo, da izpolnite naslednji obrazec:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Navedite samo eno od naslednjih vrednosti: text, title_search, query ali more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Neveljaven more_like_id"
|
msgstr "Neveljaven more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Neveljavna konfiguracija umetne inteligence."
|
msgstr "Neveljavna konfiguracija umetne inteligence."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Časovna omejitev zahteve za umetno inteligenco je potekla."
|
msgstr "Časovna omejitev zahteve za umetno inteligenco je potekla."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Zaledni sistem umetne inteligence je zavrnil zahtevo. Za podrobnosti preverite dnevnike."
|
msgstr "Zaledni sistem umetne inteligence je zavrnil zahtevo. Za podrobnosti preverite dnevnike."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Navedite samo eno od naslednjih vrednosti: text, title_search, query ali more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nezadostna dovoljenja za skupno rabo dokumenta %(id)s."
|
msgstr "Nezadostna dovoljenja za skupno rabo dokumenta %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket se že obdeluje."
|
msgstr "Paket se že obdeluje."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Paket povezav za deljenje je še vedno v pripravi. Poskusite znova pozneje."
|
msgstr "Paket povezav za deljenje je še vedno v pripravi. Poskusite znova pozneje."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Paket povezav za deljenje ni na voljo."
|
msgstr "Paket povezav za deljenje ni na voljo."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Albanian\n"
|
"Language-Team: Albanian\n"
|
||||||
"Language: sq_AL\n"
|
"Language: sq_AL\n"
|
||||||
@@ -482,7 +482,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:616
|
#: documents/models.py:616
|
||||||
msgid "Tag"
|
msgid "Tag"
|
||||||
msgstr "Etiketa"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:617 documents/models.py:632 documents/models.py:1804
|
#: documents/models.py:617 documents/models.py:632 documents/models.py:1804
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
@@ -494,15 +494,15 @@ msgstr ""
|
|||||||
|
|
||||||
#: documents/models.py:619
|
#: documents/models.py:619
|
||||||
msgid "Truck"
|
msgid "Truck"
|
||||||
msgstr "Kamion"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:620
|
#: documents/models.py:620
|
||||||
msgid "Barcode"
|
msgid "Barcode"
|
||||||
msgstr "Barkodi"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:621
|
#: documents/models.py:621
|
||||||
msgid "Wallet"
|
msgid "Wallet"
|
||||||
msgstr "Portofol"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:624
|
#: documents/models.py:624
|
||||||
msgid "Table"
|
msgid "Table"
|
||||||
@@ -562,7 +562,7 @@ msgstr "Faqet"
|
|||||||
|
|
||||||
#: documents/models.py:646
|
#: documents/models.py:646
|
||||||
msgid "icon"
|
msgid "icon"
|
||||||
msgstr "ikonë"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:653
|
#: documents/models.py:653
|
||||||
msgid "sort field"
|
msgid "sort field"
|
||||||
@@ -794,7 +794,7 @@ msgstr "kërkim i thjeshtë teksti"
|
|||||||
|
|
||||||
#: documents/models.py:742
|
#: documents/models.py:742
|
||||||
msgid "has duplicates"
|
msgid "has duplicates"
|
||||||
msgstr "ka dublikatë"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:752
|
#: documents/models.py:752
|
||||||
msgid "rule type"
|
msgid "rule type"
|
||||||
@@ -882,7 +882,7 @@ msgstr "Fshirje në masë"
|
|||||||
|
|
||||||
#: documents/models.py:807
|
#: documents/models.py:807
|
||||||
msgid "Apply AI Suggestions"
|
msgid "Apply AI Suggestions"
|
||||||
msgstr "Apliko sugjerimet e AI"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:816 documents/models.py:1468
|
#: documents/models.py:816 documents/models.py:1468
|
||||||
msgid "Scheduled"
|
msgid "Scheduled"
|
||||||
@@ -1006,23 +1006,23 @@ msgstr "shënime"
|
|||||||
|
|
||||||
#: documents/models.py:996
|
#: documents/models.py:996
|
||||||
msgid "page"
|
msgid "page"
|
||||||
msgstr "Faqe"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:997
|
#: documents/models.py:997
|
||||||
msgid "Page of the original file, starting at 1"
|
msgid "Page of the original file, starting at 1"
|
||||||
msgstr "Faqe e skedarit origjinal, duke filluar nga 1"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1052
|
#: documents/models.py:1052
|
||||||
msgid "format"
|
msgid "format"
|
||||||
msgstr "Format"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1056
|
#: documents/models.py:1056
|
||||||
msgid "document barcode"
|
msgid "document barcode"
|
||||||
msgstr "Dokumenti me barkod"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1057
|
#: documents/models.py:1057
|
||||||
msgid "document barcodes"
|
msgid "document barcodes"
|
||||||
msgstr "Kodi i barkodit në dokument"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1066
|
#: documents/models.py:1066
|
||||||
msgid "Original"
|
msgid "Original"
|
||||||
@@ -1404,23 +1404,23 @@ msgstr "Zhvendos në kosh"
|
|||||||
|
|
||||||
#: documents/models.py:1795
|
#: documents/models.py:1795
|
||||||
msgid "Remote OCR"
|
msgid "Remote OCR"
|
||||||
msgstr "OCR në distancë"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1799
|
#: documents/models.py:1799
|
||||||
msgid "Apply AI suggestions"
|
msgid "Apply AI suggestions"
|
||||||
msgstr "Apliko sugjerimet e IA-së"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1806
|
#: documents/models.py:1806
|
||||||
msgid "Document type"
|
msgid "Document type"
|
||||||
msgstr "Lloji i dokumentit"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1807
|
#: documents/models.py:1807
|
||||||
msgid "Storage path"
|
msgid "Storage path"
|
||||||
msgstr "Rruga e ruajtjes"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1808
|
#: documents/models.py:1808
|
||||||
msgid "Created date"
|
msgid "Created date"
|
||||||
msgstr "Data e krijimit"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1811
|
#: documents/models.py:1811
|
||||||
msgid "Workflow Action Type"
|
msgid "Workflow Action Type"
|
||||||
@@ -1573,27 +1573,27 @@ msgstr "Fjalëkalimet që provohen gjatë heqjes së mbrojtjes PDF. Ndaji me pre
|
|||||||
|
|
||||||
#: documents/models.py:2048
|
#: documents/models.py:2048
|
||||||
msgid "AI suggestion fields"
|
msgid "AI suggestion fields"
|
||||||
msgstr "Fushat e sugjerimeve të AI"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2052
|
#: documents/models.py:2052
|
||||||
msgid "Which of the AI-suggested fields to apply to the document."
|
msgid "Which of the AI-suggested fields to apply to the document."
|
||||||
msgstr "Cilat nga fushat e sugjeruara nga AI t'i aplikoni në dokument."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2057
|
#: documents/models.py:2057
|
||||||
msgid "create missing objects"
|
msgid "create missing objects"
|
||||||
msgstr "krijo objektet e munguara"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2060
|
#: documents/models.py:2060
|
||||||
msgid "Create suggested tags, correspondents, document types and storage paths that do not already exist instead of skipping them."
|
msgid "Create suggested tags, correspondents, document types and storage paths that do not already exist instead of skipping them."
|
||||||
msgstr "Krijoni etiketa të sugjeruara, korrespondentë, lloje dokumentesh dhe shtigje ruajtjeje që nuk ekzistojnë ende, në vend që t'i anashkaloni."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2066
|
#: documents/models.py:2066
|
||||||
msgid "overwrite existing values"
|
msgid "overwrite existing values"
|
||||||
msgstr "mbishkruaj vlera ekzistuese"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2069
|
#: documents/models.py:2069
|
||||||
msgid "Apply suggestions even if the document already has a value for that field. Tags are always added to, never replaced."
|
msgid "Apply suggestions even if the document already has a value for that field. Tags are always added to, never replaced."
|
||||||
msgstr "Zbato sugjerimet edhe nëse dokumenti tashmë ka një vlerë për atë fushë. Etiketat gjithmonë shtohen, kurrë nuk zëvendësohen."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2075
|
#: documents/models.py:2075
|
||||||
msgid "workflow action"
|
msgid "workflow action"
|
||||||
@@ -1605,7 +1605,7 @@ msgstr "veprime rrjedhe pune"
|
|||||||
|
|
||||||
#: documents/models.py:2091
|
#: documents/models.py:2091
|
||||||
msgid "triggers"
|
msgid "triggers"
|
||||||
msgstr "shkaktarë"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:2098
|
#: documents/models.py:2098
|
||||||
msgid "actions"
|
msgid "actions"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "ekzekutime rrjedhash pune"
|
msgstr "ekzekutime rrjedhash pune"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Leje të pamjaftueshme."
|
msgstr "Leje të pamjaftueshme."
|
||||||
@@ -1662,7 +1662,7 @@ msgstr "Fusha e personalizuar me ID %(id)s nuk ekziston"
|
|||||||
|
|
||||||
#: documents/serialisers.py:2437 documents/serialisers.py:2447
|
#: documents/serialisers.py:2437 documents/serialisers.py:2447
|
||||||
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 "Fushat e personalizuara duhet të jenë listë numrash të plotë ose objekt që lidh ID-të me vlerat"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2442
|
#: documents/serialisers.py:2442
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
@@ -1676,14 +1676,14 @@ msgstr "U zbulua variabël e pavlefshme."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Identifikuesit dublikatë të dokumenteve nuk lejohen."
|
msgstr "Identifikuesit dublikatë të dokumenteve nuk lejohen."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumentet nuk u gjetën: %(ids)s"
|
msgstr "Dokumentet nuk u gjetën: %(ids)s"
|
||||||
|
|
||||||
#: documents/templates/account/account_inactive.html:5
|
#: documents/templates/account/account_inactive.html:5
|
||||||
msgid "Paperless-ngx account inactive"
|
msgid "Paperless-ngx account inactive"
|
||||||
msgstr "Llogaria Paperless-ngx është joaktive."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/account/account_inactive.html:9
|
#: documents/templates/account/account_inactive.html:9
|
||||||
msgid "Account inactive."
|
msgid "Account inactive."
|
||||||
@@ -1807,7 +1807,7 @@ msgstr "Ndrysho fjalëkalimin tim"
|
|||||||
|
|
||||||
#: documents/templates/account/password_reset_from_key_done.html:5
|
#: documents/templates/account/password_reset_from_key_done.html:5
|
||||||
msgid "Paperless-ngx reset password complete"
|
msgid "Paperless-ngx reset password complete"
|
||||||
msgstr "Rivendosja e fjalëkalimit për Paperless-ngx përfundoi me sukses."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/account/password_reset_from_key_done.html:9
|
#: documents/templates/account/password_reset_from_key_done.html:9
|
||||||
msgid "Password reset complete."
|
msgid "Password reset complete."
|
||||||
@@ -1881,20 +1881,20 @@ msgstr "Lidhja e ndarjes ka skaduar."
|
|||||||
#: documents/templates/socialaccount/authentication_error.html:5
|
#: documents/templates/socialaccount/authentication_error.html:5
|
||||||
#: documents/templates/socialaccount/login.html:5
|
#: documents/templates/socialaccount/login.html:5
|
||||||
msgid "Paperless-ngx social account sign in"
|
msgid "Paperless-ngx social account sign in"
|
||||||
msgstr "Hyrje në llogarinë sociale të Paperless-ngx"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/socialaccount/authentication_error.html:10
|
#: documents/templates/socialaccount/authentication_error.html:10
|
||||||
msgid "An error occurred while attempting to login via your social network account. Back to the"
|
msgid "An error occurred while attempting to login via your social network account. Back to the"
|
||||||
msgstr "Ndodhi një gabim gjatë përpjekjes për t'u identifikuar përmes llogarisë tuaj të rrjetit social. Kthehu tek"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/socialaccount/authentication_error.html:10
|
#: documents/templates/socialaccount/authentication_error.html:10
|
||||||
msgid "login page"
|
msgid "login page"
|
||||||
msgstr "faqja e hyrjes"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/socialaccount/login.html:11
|
#: documents/templates/socialaccount/login.html:11
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You are about to connect a new third-party account from %(provider)s."
|
msgid "You are about to connect a new third-party account from %(provider)s."
|
||||||
msgstr "Jeni duke u lidhur me një llogari të re të palës së tretë nga %(provider)s."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/templates/socialaccount/login.html:15
|
#: documents/templates/socialaccount/login.html:15
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
@@ -1915,64 +1915,64 @@ msgstr "Si hap i fundit, ju lutemi plotësoni formularin e mëposhtëm:"
|
|||||||
|
|
||||||
#: documents/validators.py:36
|
#: documents/validators.py:36
|
||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr " ,"
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Specifikoni vetëm njërën prej: text, title_search, query, ose more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "ID e pavlefshme e \"më shumë ngjashmëri\""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Konfigurim AI i pavlefshëm."
|
msgstr "Konfigurim AI i pavlefshëm."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Kërkesa në backend-in e AI-së ka skaduar kohën."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "Backend-i i AI-së refuzoi kërkesën. Kontrolloni regjistrimet për detaje."
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Leje të pamjaftueshme për të ndarë dokumentin %(id)s."
|
msgstr "Leje të pamjaftueshme për të ndarë dokumentin %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paketa po përpunohet tashmë."
|
msgstr "Paketa po përpunohet tashmë."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Paketa e lidhjes së ndarjes ende po përgatitet. Ju lutemi provoni përsëri më vonë."
|
msgstr "Paketa e lidhjes së ndarjes ende po përgatitet. Ju lutemi provoni përsëri më vonë."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Paketa e lidhjes së ndarjes nuk është e disponueshme."
|
msgstr "Paketa e lidhjes së ndarjes nuk është e disponueshme."
|
||||||
|
|
||||||
#: paperless/apps.py:11
|
#: paperless/apps.py:11
|
||||||
msgid "Paperless"
|
msgid "Paperless"
|
||||||
msgstr "Paperless"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:26
|
#: paperless/models.py:26
|
||||||
msgid "pdf"
|
msgid "pdf"
|
||||||
msgstr "pdf"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:27
|
#: paperless/models.py:27
|
||||||
msgid "pdfa"
|
msgid "pdfa"
|
||||||
msgstr "pdfa"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:28
|
#: paperless/models.py:28
|
||||||
msgid "pdfa-1"
|
msgid "pdfa-1"
|
||||||
msgstr "pdfa-1"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:29
|
#: paperless/models.py:29
|
||||||
msgid "pdfa-2"
|
msgid "pdfa-2"
|
||||||
msgstr "pdfa-2"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:30
|
#: paperless/models.py:30
|
||||||
msgid "pdfa-3"
|
msgid "pdfa-3"
|
||||||
@@ -2004,11 +2004,11 @@ msgstr "kurrë"
|
|||||||
|
|
||||||
#: paperless/models.py:60
|
#: paperless/models.py:60
|
||||||
msgid "clean"
|
msgid "clean"
|
||||||
msgstr "pastro"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:61
|
#: paperless/models.py:61
|
||||||
msgid "clean-final"
|
msgid "clean-final"
|
||||||
msgstr "versioni përfundimtar i pastruar"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:62
|
#: paperless/models.py:62
|
||||||
msgid "none"
|
msgid "none"
|
||||||
@@ -2016,7 +2016,7 @@ msgstr "asnjë"
|
|||||||
|
|
||||||
#: paperless/models.py:70
|
#: paperless/models.py:70
|
||||||
msgid "LeaveColorUnchanged"
|
msgid "LeaveColorUnchanged"
|
||||||
msgstr "LëreNgjyrënTëPandryshuar"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:71
|
#: paperless/models.py:71
|
||||||
msgid "RGB"
|
msgid "RGB"
|
||||||
@@ -2024,7 +2024,7 @@ msgstr "RGB"
|
|||||||
|
|
||||||
#: paperless/models.py:72
|
#: paperless/models.py:72
|
||||||
msgid "UseDeviceIndependentColor"
|
msgid "UseDeviceIndependentColor"
|
||||||
msgstr "PërdorNgjyrën e Pavarur nga Pajisjet"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:73
|
#: paperless/models.py:73
|
||||||
msgid "Gray"
|
msgid "Gray"
|
||||||
@@ -2036,31 +2036,31 @@ msgstr "CMYK"
|
|||||||
|
|
||||||
#: paperless/models.py:82
|
#: paperless/models.py:82
|
||||||
msgid "Azure AI Document Intelligence"
|
msgid "Azure AI Document Intelligence"
|
||||||
msgstr "Inteligjenca e Dokumenteve Azure AI"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:90
|
#: paperless/models.py:90
|
||||||
msgid "All supported documents"
|
msgid "All supported documents"
|
||||||
msgstr "Të gjitha dokumentet e mbështetura"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:91
|
#: paperless/models.py:91
|
||||||
msgid "Only when a workflow enables it"
|
msgid "Only when a workflow enables it"
|
||||||
msgstr "Vetëm kur një rrjedhë pune e lejon"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:95 paperless/models.py:105
|
#: paperless/models.py:95 paperless/models.py:105
|
||||||
msgid "OpenAI-compatible"
|
msgid "OpenAI-compatible"
|
||||||
msgstr "E pajtueshme me OpenAI"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:96
|
#: paperless/models.py:96
|
||||||
msgid "Huggingface"
|
msgid "Huggingface"
|
||||||
msgstr "Huggingface"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:97 paperless/models.py:106
|
#: paperless/models.py:97 paperless/models.py:106
|
||||||
msgid "Ollama"
|
msgid "Ollama"
|
||||||
msgstr "Ollama"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:115
|
#: paperless/models.py:115
|
||||||
msgid "Sets the output PDF type"
|
msgid "Sets the output PDF type"
|
||||||
msgstr "Cakton llojin e PDF-së së daljes"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:127
|
#: paperless/models.py:127
|
||||||
msgid "Do OCR from page 1 to this value"
|
msgid "Do OCR from page 1 to this value"
|
||||||
@@ -2096,7 +2096,7 @@ msgstr "Aktivizon rrotullimin e faqeve"
|
|||||||
|
|
||||||
#: paperless/models.py:178
|
#: paperless/models.py:178
|
||||||
msgid "Sets the threshold for rotation of pages"
|
msgid "Sets the threshold for rotation of pages"
|
||||||
msgstr "Vendos pragun për rrotullimin e faqeve"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:184
|
#: paperless/models.py:184
|
||||||
msgid "Sets the maximum image size for decompression"
|
msgid "Sets the maximum image size for decompression"
|
||||||
@@ -2104,11 +2104,11 @@ msgstr "Cakton madhësinë maksimale të figurës për dekompresim"
|
|||||||
|
|
||||||
#: paperless/models.py:190
|
#: paperless/models.py:190
|
||||||
msgid "Sets the Ghostscript color conversion strategy"
|
msgid "Sets the Ghostscript color conversion strategy"
|
||||||
msgstr "Përcakton strategjinë e konvertimit të ngjyrave për Ghostscript"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:198
|
#: paperless/models.py:198
|
||||||
msgid "Adds additional user arguments for OCRMyPDF"
|
msgid "Adds additional user arguments for OCRMyPDF"
|
||||||
msgstr "Shton argumente shtesë të përdoruesit për OCRMyPDF"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:207
|
#: paperless/models.py:207
|
||||||
msgid "Application title"
|
msgid "Application title"
|
||||||
@@ -2120,7 +2120,7 @@ msgstr "Logoja e aplikacionit"
|
|||||||
|
|
||||||
#: paperless/models.py:229
|
#: paperless/models.py:229
|
||||||
msgid "Enables barcode scanning"
|
msgid "Enables barcode scanning"
|
||||||
msgstr "Mundëson skanimin e kodeve të vijëzuara"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:235
|
#: paperless/models.py:235
|
||||||
msgid "Enables barcode TIFF support"
|
msgid "Enables barcode TIFF support"
|
||||||
@@ -2132,19 +2132,19 @@ msgstr "Cakton vargun e barkodit"
|
|||||||
|
|
||||||
#: paperless/models.py:249
|
#: paperless/models.py:249
|
||||||
msgid "Retains split pages"
|
msgid "Retains split pages"
|
||||||
msgstr "Ruan faqet e ndara"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:255
|
#: paperless/models.py:255
|
||||||
msgid "Enables ASN barcode"
|
msgid "Enables ASN barcode"
|
||||||
msgstr "Aktivizon barkodin ASN"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:261
|
#: paperless/models.py:261
|
||||||
msgid "Sets the ASN barcode prefix"
|
msgid "Sets the ASN barcode prefix"
|
||||||
msgstr "Vendos prefiksin e barkodit ASN"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:269
|
#: paperless/models.py:269
|
||||||
msgid "Sets the barcode upscale factor"
|
msgid "Sets the barcode upscale factor"
|
||||||
msgstr "Vendos faktorin e zmadhimit të barkodit"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:276
|
#: paperless/models.py:276
|
||||||
msgid "Sets the barcode DPI"
|
msgid "Sets the barcode DPI"
|
||||||
@@ -2152,11 +2152,11 @@ msgstr "Cakton DPI-në e barkodit"
|
|||||||
|
|
||||||
#: paperless/models.py:283
|
#: paperless/models.py:283
|
||||||
msgid "Sets the maximum pages for barcode"
|
msgid "Sets the maximum pages for barcode"
|
||||||
msgstr "Vendos numrin maksimal të faqeve për barkodin"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:290
|
#: paperless/models.py:290
|
||||||
msgid "Enables tag barcode"
|
msgid "Enables tag barcode"
|
||||||
msgstr "Aktivizon barkodin e etiketës"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:296
|
#: paperless/models.py:296
|
||||||
msgid "Sets the tag barcode mapping"
|
msgid "Sets the tag barcode mapping"
|
||||||
@@ -2168,23 +2168,23 @@ msgstr "Mundëson ndarjen bazuar në barkodet e etiketave"
|
|||||||
|
|
||||||
#: paperless/models.py:308
|
#: paperless/models.py:308
|
||||||
msgid "Stores the values of detected barcodes"
|
msgid "Stores the values of detected barcodes"
|
||||||
msgstr "Ruan vlerat e barkodeve të zbuluar"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:318
|
#: paperless/models.py:318
|
||||||
msgid "Sets the remote OCR engine"
|
msgid "Sets the remote OCR engine"
|
||||||
msgstr "Cakton motorin OCR të largët"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:327
|
#: paperless/models.py:327
|
||||||
msgid "Sets the remote OCR API key"
|
msgid "Sets the remote OCR API key"
|
||||||
msgstr "Cakton çelësin e API-së së OCR-it në distancë"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:335
|
#: paperless/models.py:335
|
||||||
msgid "Sets the remote OCR endpoint"
|
msgid "Sets the remote OCR endpoint"
|
||||||
msgstr "Cakton pikën e destinacionit të largët të OCR-së"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:343
|
#: paperless/models.py:343
|
||||||
msgid "Sets which documents are sent to the remote OCR engine"
|
msgid "Sets which documents are sent to the remote OCR engine"
|
||||||
msgstr "Cakton se cilat dokumente dërgohen te motori OCR i largët"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:355
|
#: paperless/models.py:355
|
||||||
msgid "Enables AI features"
|
msgid "Enables AI features"
|
||||||
@@ -2192,23 +2192,23 @@ msgstr "Aktivizon veçoritë AI"
|
|||||||
|
|
||||||
#: paperless/models.py:360
|
#: paperless/models.py:360
|
||||||
msgid "Sets the LLM embedding backend"
|
msgid "Sets the LLM embedding backend"
|
||||||
msgstr "Vendos mekanizmin e pasme (backend) për embedding-un e LLM-së"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:368
|
#: paperless/models.py:368
|
||||||
msgid "Sets the LLM embedding model"
|
msgid "Sets the LLM embedding model"
|
||||||
msgstr "Vendos modelin e ngulitjes (embedding) të LLM-së"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:375
|
#: paperless/models.py:375
|
||||||
msgid "Sets the LLM embedding API key"
|
msgid "Sets the LLM embedding API key"
|
||||||
msgstr "Cakton çelësin e API-së së integrimit të LLM-së"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:382
|
#: paperless/models.py:382
|
||||||
msgid "Sets the LLM embedding endpoint, optional"
|
msgid "Sets the LLM embedding endpoint, optional"
|
||||||
msgstr "Vendos pikën përfundimtare të \"embedding\"-ut për LLM-në (opsionale)"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:389
|
#: paperless/models.py:389
|
||||||
msgid "Sets the LLM embedding chunk size"
|
msgid "Sets the LLM embedding chunk size"
|
||||||
msgstr "Vendos madhësinë e segmentit të enkodimit (embedding) për LLM-në."
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:395
|
#: paperless/models.py:395
|
||||||
msgid "Sets the LLM context size"
|
msgid "Sets the LLM context size"
|
||||||
@@ -2216,7 +2216,7 @@ msgstr "Cakton madhësinë e kontekstit LLM"
|
|||||||
|
|
||||||
#: paperless/models.py:401
|
#: paperless/models.py:401
|
||||||
msgid "Sets the LLM backend"
|
msgid "Sets the LLM backend"
|
||||||
msgstr "Vendos backend-in e LLM-së"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:409
|
#: paperless/models.py:409
|
||||||
msgid "Sets the LLM model"
|
msgid "Sets the LLM model"
|
||||||
@@ -2228,7 +2228,7 @@ msgstr "Cakton çelësin API të LLM"
|
|||||||
|
|
||||||
#: paperless/models.py:423
|
#: paperless/models.py:423
|
||||||
msgid "Sets the LLM endpoint, optional"
|
msgid "Sets the LLM endpoint, optional"
|
||||||
msgstr "Cakton endpoint-in e embedding LLM, opsionale"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/models.py:430
|
#: paperless/models.py:430
|
||||||
msgid "Sets the LLM output language"
|
msgid "Sets the LLM output language"
|
||||||
@@ -2240,7 +2240,7 @@ msgstr "Cakton kohën limit të LLM në sekonda"
|
|||||||
|
|
||||||
#: paperless/models.py:443
|
#: paperless/models.py:443
|
||||||
msgid "paperless application settings"
|
msgid "paperless application settings"
|
||||||
msgstr "cilësimet e aplikimit pa përdorim letre"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings/__init__.py:562
|
#: paperless/settings/__init__.py:562
|
||||||
msgid "English (US)"
|
msgid "English (US)"
|
||||||
@@ -2252,7 +2252,7 @@ msgstr "Arabisht"
|
|||||||
|
|
||||||
#: paperless/settings/__init__.py:564
|
#: paperless/settings/__init__.py:564
|
||||||
msgid "Afrikaans"
|
msgid "Afrikaans"
|
||||||
msgstr "Afrikanisht"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings/__init__.py:565
|
#: paperless/settings/__init__.py:565
|
||||||
msgid "Belarusian"
|
msgid "Belarusian"
|
||||||
@@ -2264,7 +2264,7 @@ msgstr "Bullgarisht"
|
|||||||
|
|
||||||
#: paperless/settings/__init__.py:567
|
#: paperless/settings/__init__.py:567
|
||||||
msgid "Catalan"
|
msgid "Catalan"
|
||||||
msgstr "Katalanisht"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings/__init__.py:568
|
#: paperless/settings/__init__.py:568
|
||||||
msgid "Czech"
|
msgid "Czech"
|
||||||
@@ -2308,7 +2308,7 @@ msgstr "Hungarisht"
|
|||||||
|
|
||||||
#: paperless/settings/__init__.py:578
|
#: paperless/settings/__init__.py:578
|
||||||
msgid "Indonesian"
|
msgid "Indonesian"
|
||||||
msgstr "Indonezisht"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings/__init__.py:579
|
#: paperless/settings/__init__.py:579
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
@@ -2376,7 +2376,7 @@ msgstr "Turqisht"
|
|||||||
|
|
||||||
#: paperless/settings/__init__.py:595
|
#: paperless/settings/__init__.py:595
|
||||||
msgid "Ukrainian"
|
msgid "Ukrainian"
|
||||||
msgstr "Ukrainisht"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings/__init__.py:596
|
#: paperless/settings/__init__.py:596
|
||||||
msgid "Vietnamese"
|
msgid "Vietnamese"
|
||||||
@@ -2528,7 +2528,7 @@ msgstr "Process full Email (with embedded attachments in skedar) as .eml"
|
|||||||
|
|
||||||
#: paperless_mail/models.py:109
|
#: paperless_mail/models.py:109
|
||||||
msgid "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents"
|
msgid "Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents"
|
||||||
msgstr "Përpuno email-in e plotë (me bashkëngjitje të përfshira në skedar) si .eml + përpuno bashkëngjitjet si dokumente të veçanta."
|
msgstr ""
|
||||||
|
|
||||||
#: paperless_mail/models.py:116
|
#: paperless_mail/models.py:116
|
||||||
msgid "Process all files, including 'inline' attachments."
|
msgid "Process all files, including 'inline' attachments."
|
||||||
@@ -2564,7 +2564,7 @@ msgstr "Zhvendos në dosjen e specifikuar"
|
|||||||
|
|
||||||
#: paperless_mail/models.py:128
|
#: paperless_mail/models.py:128
|
||||||
msgid "Mark as read, don't process read mails"
|
msgid "Mark as read, don't process read mails"
|
||||||
msgstr "Shënoji si të lexuara, mos i përpuno emailet e lexuara"
|
msgstr ""
|
||||||
|
|
||||||
#: paperless_mail/models.py:129
|
#: paperless_mail/models.py:129
|
||||||
msgid "Flag the mail, don't process flagged mails"
|
msgid "Flag the mail, don't process flagged mails"
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Serbian (Latin)\n"
|
"Language-Team: Serbian (Latin)\n"
|
||||||
"Language: sr_CS\n"
|
"Language: sr_CS\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "pokretanje tokova rada"
|
msgstr "pokretanje tokova rada"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Nedovoljne dozvole."
|
msgstr "Nedovoljne dozvole."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Otkrivena je nevažeća promenljiva."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Duplirani identifikatori dokumenata nisu dozvoljeni."
|
msgstr "Duplirani identifikatori dokumenata nisu dozvoljeni."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
msgstr "Dokumenti nisu pronađeni: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Kao poslednji korak, molimo Vas da popunite sledeći obrazac:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Navedite samo jedno od sledećeg: text, title_search, query ili more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Nevažeći more_like_id"
|
msgstr "Nevažeći more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Nevažeća konfiguracija veštačke inteligencije."
|
msgstr "Nevažeća konfiguracija veštačke inteligencije."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Navedite samo jedno od sledećeg: text, title_search, query ili more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Nedovoljne dozvole za deljenje dokumenta %(id)s."
|
msgstr "Nedovoljne dozvole za deljenje dokumenta %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket se već obrađuje."
|
msgstr "Paket se već obrađuje."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Paket linkova za deljenje se još uvek priprema. Molimo pokušajte ponovo kasnije."
|
msgstr "Paket linkova za deljenje se još uvek priprema. Molimo pokušajte ponovo kasnije."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Paket linkova za deljenje nije dostupan."
|
msgstr "Paket linkova za deljenje nije dostupan."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Swedish\n"
|
"Language-Team: Swedish\n"
|
||||||
"Language: sv_SE\n"
|
"Language: sv_SE\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "arbetsflöde körs"
|
msgstr "arbetsflöde körs"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Otillräckliga behörigheter."
|
msgstr "Otillräckliga behörigheter."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Ogiltig variabel upptäckt."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Dubbletter av dokumentidentifierare är inte tillåtna."
|
msgstr "Dubbletter av dokumentidentifierare är inte tillåtna."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dokumenten hittades inte: %(ids)s"
|
msgstr "Dokumenten hittades inte: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Slutför följande formulär som ett sista steg:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Ange endast ett av text, title_search, query och more_like_id."
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Ogiltig more_like_id"
|
msgstr "Ogiltig more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Ogiltig AI-konfiguration."
|
msgstr "Ogiltig AI-konfiguration."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Begäran till AI-serverdelen tog för lång tid."
|
msgstr "Begäran till AI-serverdelen tog för lång tid."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "AI-serverdelen avvisade begäran. Kontrollera loggarna för detaljer."
|
msgstr "AI-serverdelen avvisade begäran. Kontrollera loggarna för detaljer."
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Ange endast ett av text, title_search, query och more_like_id."
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Otillräckliga behörigheter för att dela dokument %(id)s."
|
msgstr "Otillräckliga behörigheter för att dela dokument %(id)s."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paketet behandlas redan."
|
msgstr "Paketet behandlas redan."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Delningslänkpaketet förbereds fortfarande. Försök igen senare."
|
msgstr "Delningslänkpaketet förbereds fortfarande. Försök igen senare."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Delningslänkpaketet är inte tillgängligt."
|
msgstr "Delningslänkpaketet är inte tillgängligt."
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Thai\n"
|
"Language-Team: Thai\n"
|
||||||
"Language: th_TH\n"
|
"Language: th_TH\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "ตรวจพบตัวแปรไม่ถูกต้อง"
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1916,40 +1916,40 @@ msgstr ""
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Turkish\n"
|
"Language-Team: Turkish\n"
|
||||||
"Language: tr_TR\n"
|
"Language: tr_TR\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "iş akışı çalıştırma"
|
msgstr "iş akışı çalıştırma"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Yetersiz izin."
|
msgstr "Yetersiz izin."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Geçersiz değişken algılandı."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Doküman kimliği çakışmasına izin verilmiyor."
|
msgstr "Doküman kimliği çakışmasına izin verilmiyor."
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Dosyalar bulunamadı: %(ids)s"
|
msgstr "Dosyalar bulunamadı: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Son adım olarak, lütfen aşağıdaki formu doldurun:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "Yalnızca bir metin, title_search, sorgu veya more_like_id"
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "Geçersiz more_like_id"
|
msgstr "Geçersiz more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "Geçersiz yapay zeka konfigürasyonu."
|
msgstr "Geçersiz yapay zeka konfigürasyonu."
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "Yapay zeka isteği zaman aşımına uğradı."
|
msgstr "Yapay zeka isteği zaman aşımına uğradı."
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "Yalnızca bir metin, title_search, sorgu veya more_like_id"
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "%(id)s dosyasını paylaşmak için yetkiniz yok."
|
msgstr "%(id)s dosyasını paylaşmak için yetkiniz yok."
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Paket zaten işlemede."
|
msgstr "Paket zaten işlemede."
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
"Language: uk_UA\n"
|
"Language: uk_UA\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Виявлено неправильну змінну."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Як останній крок, будь ласка, заповніть
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, 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:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:20\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\n"
|
||||||
"Language: vi_VN\n"
|
"Language: vi_VN\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "các lần chạy quy trình"
|
msgstr "các lần chạy quy trình"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "Không có đủ quyền."
|
msgstr "Không có đủ quyền."
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "Đã phát hiện biến số không hợp lệ."
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "Không cho phép các định danh tài liệu trùng lặp"
|
msgstr "Không cho phép các định danh tài liệu trùng lặp"
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "Không tìm thấy tài liệu: %(ids)s"
|
msgstr "Không tìm thấy tài liệu: %(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "Là bước cuối cùng, vui lòng hoàn thành mẫu đơn sau:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "Không đủ quyền để chia sẻ tài liệu %(id)s"
|
msgstr "Không đủ quyền để chia sẻ tài liệu %(id)s"
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "Gói đang được xử lý"
|
msgstr "Gói đang được xử lý"
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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 "Liên kết chia sẻ gói vẫn đang được chuẩn bị. Vui lòng thử lại sau."
|
msgstr "Liên kết chia sẻ gói vẫn đang được chuẩn bị. Vui lòng thử lại sau."
|
||||||
|
|
||||||
#: documents/views.py:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "Không tồn tại liên kết chia sẻ gói"
|
msgstr "Không tồn tại liên kết chia sẻ gói"
|
||||||
|
|
||||||
|
|||||||
@@ -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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Simplified\n"
|
"Language-Team: Chinese Simplified\n"
|
||||||
"Language: zh_CN\n"
|
"Language: zh_CN\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "工作流运行"
|
msgstr "工作流运行"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "权限不足。"
|
msgstr "权限不足。"
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "检测到无效变量。"
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "不允许重复文档标识符。"
|
msgstr "不允许重复文档标识符。"
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "未找到文档:%(ids)s"
|
msgstr "未找到文档:%(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "作为最后一步,请填写以下表格:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ","
|
msgstr ","
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "只能指定 text、title_search、query 或 more_like_id 中的一项。"
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "无效的more_like_id"
|
msgstr "无效的more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "无效的 AI 配置。"
|
msgstr "无效的 AI 配置。"
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "AI 后端请求超时。"
|
msgstr "AI 后端请求超时。"
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "AI 后端拒绝了请求。请检查日志的详细信息。"
|
msgstr "AI 后端拒绝了请求。请检查日志的详细信息。"
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "只能指定 text、title_search、query 或 more_like_id 中的一项。"
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "权限不足以分享文件:%(id)s。"
|
msgstr "权限不足以分享文件:%(id)s。"
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "捆绑包已在处理中。"
|
msgstr "捆绑包已在处理中。"
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
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-10-14 15:18+0000\n"
|
"POT-Creation-Date: 2026-10-12 19:12+0000\n"
|
||||||
"PO-Revision-Date: 2026-10-14 15:19\n"
|
"PO-Revision-Date: 2026-10-12 19:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_TW\n"
|
||||||
@@ -1636,7 +1636,7 @@ msgid "workflow runs"
|
|||||||
msgstr "執行工作流程"
|
msgstr "執行工作流程"
|
||||||
|
|
||||||
#: documents/serialisers.py:515 documents/serialisers.py:872
|
#: documents/serialisers.py:515 documents/serialisers.py:872
|
||||||
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
|
#: documents/serialisers.py:2902 documents/views.py:344 documents/views.py:2752
|
||||||
#: paperless_mail/serialisers.py:156
|
#: paperless_mail/serialisers.py:156
|
||||||
msgid "Insufficient permissions."
|
msgid "Insufficient permissions."
|
||||||
msgstr "權限不足。"
|
msgstr "權限不足。"
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "偵測到無效的變數。"
|
|||||||
msgid "Duplicate document identifiers are not allowed."
|
msgid "Duplicate document identifiers are not allowed."
|
||||||
msgstr "文件識別碼不得重複。"
|
msgstr "文件識別碼不得重複。"
|
||||||
|
|
||||||
#: documents/serialisers.py:2988 documents/views.py:4811
|
#: documents/serialisers.py:2988 documents/views.py:4799
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Documents not found: %(ids)s"
|
msgid "Documents not found: %(ids)s"
|
||||||
msgstr "找不到文件:%(ids)s"
|
msgstr "找不到文件:%(ids)s"
|
||||||
@@ -1917,40 +1917,40 @@ msgstr "最後一步,請填寫以下表單:"
|
|||||||
msgid ", "
|
msgid ", "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/views.py:318
|
#: documents/views.py:337 documents/views.py:2749
|
||||||
msgid "Specify only one of text, title_search, query, or more_like_id."
|
|
||||||
msgstr "只能指定 text、title_search、query 或 more_like_id 的其中一項。"
|
|
||||||
|
|
||||||
#: documents/views.py:356 documents/views.py:2765
|
|
||||||
msgid "Invalid more_like_id"
|
msgid "Invalid more_like_id"
|
||||||
msgstr "無效的 more_like_id"
|
msgstr "無效的 more_like_id"
|
||||||
|
|
||||||
#: documents/views.py:1703
|
#: documents/views.py:1684
|
||||||
msgid "Invalid AI configuration."
|
msgid "Invalid AI configuration."
|
||||||
msgstr "無效的 AI 配置。"
|
msgstr "無效的 AI 配置。"
|
||||||
|
|
||||||
#: documents/views.py:1714
|
#: documents/views.py:1695
|
||||||
msgid "AI backend request timed out."
|
msgid "AI backend request timed out."
|
||||||
msgstr "AI 後端請求逾時。"
|
msgstr "AI 後端請求逾時。"
|
||||||
|
|
||||||
#: documents/views.py:1726
|
#: documents/views.py:1707
|
||||||
msgid "AI backend rejected the request. Check logs for details."
|
msgid "AI backend rejected the request. Check logs for details."
|
||||||
msgstr "AI 後端拒絕了請求。請檢查日誌以取得詳細資訊。"
|
msgstr "AI 後端拒絕了請求。請檢查日誌以取得詳細資訊。"
|
||||||
|
|
||||||
#: documents/views.py:4827
|
#: documents/views.py:2574 documents/views.py:2890
|
||||||
|
msgid "Specify only one of text, title_search, query, or more_like_id."
|
||||||
|
msgstr "只能指定 text、title_search、query 或 more_like_id 的其中一項。"
|
||||||
|
|
||||||
|
#: documents/views.py:4815
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Insufficient permissions to share document %(id)s."
|
msgid "Insufficient permissions to share document %(id)s."
|
||||||
msgstr "權限不足,無法共用文件:%(id)s"
|
msgstr "權限不足,無法共用文件:%(id)s"
|
||||||
|
|
||||||
#: documents/views.py:4874
|
#: documents/views.py:4862
|
||||||
msgid "Bundle is already being processed."
|
msgid "Bundle is already being processed."
|
||||||
msgstr "組合已在處理中。"
|
msgstr "組合已在處理中。"
|
||||||
|
|
||||||
#: documents/views.py:4938
|
#: documents/views.py:4926
|
||||||
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:4952
|
#: documents/views.py:4940
|
||||||
msgid "The share link bundle is unavailable."
|
msgid "The share link bundle is unavailable."
|
||||||
msgstr "共用連結組合無法使用。"
|
msgstr "共用連結組合無法使用。"
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ logger = logging.getLogger("paperless.auth")
|
|||||||
class AutoLoginMiddleware(MiddlewareMixin):
|
class AutoLoginMiddleware(MiddlewareMixin):
|
||||||
def process_request(self, request: HttpRequest) -> None:
|
def process_request(self, request: HttpRequest) -> None:
|
||||||
# Dont use auto-login with token request
|
# Dont use auto-login with token request
|
||||||
if request.path_info.startswith("/api/token/") and request.method == "POST":
|
if request.path.startswith("/api/token/") and request.method == "POST":
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
request.user = User.objects.get(
|
request.user = User.objects.get(
|
||||||
|
|||||||
@@ -51,21 +51,3 @@ class TestAutoLoginMiddleware(TestCase):
|
|||||||
self._process(request)
|
self._process(request)
|
||||||
|
|
||||||
self.assertFalse(request.user.is_authenticated)
|
self.assertFalse(request.user.is_authenticated)
|
||||||
|
|
||||||
@override_settings(FORCE_SCRIPT_NAME="/paperless")
|
|
||||||
def test_token_request_is_not_logged_in_under_subpath(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Paperless is served under a subpath
|
|
||||||
WHEN:
|
|
||||||
- A token is requested via POST
|
|
||||||
THEN:
|
|
||||||
- The request is left anonymous so the posted credentials are used
|
|
||||||
"""
|
|
||||||
UserFactory(username="autologin")
|
|
||||||
|
|
||||||
request = self.factory.post("/api/token/")
|
|
||||||
request.user = AnonymousUser()
|
|
||||||
self._process(request)
|
|
||||||
|
|
||||||
self.assertFalse(request.user.is_authenticated)
|
|
||||||
@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.permissions import permitted_document_ids
|
from documents.permissions import permitted_document_ids
|
||||||
from documents.permissions import user_is_active_superuser
|
from documents.permissions import user_is_unrestricted
|
||||||
from documents.versioning import annotate_effective_content
|
from documents.versioning import annotate_effective_content
|
||||||
from paperless.config import AIConfig
|
from paperless.config import AIConfig
|
||||||
from paperless_ai.base_model import ClassificationSuggestions
|
from paperless_ai.base_model import ClassificationSuggestions
|
||||||
@@ -50,17 +50,16 @@ def _fulltext_similar_documents(
|
|||||||
similarity - cruder, but far better than no candidates at all.
|
similarity - cruder, but far better than no candidates at all.
|
||||||
more_like_this_ids returns only a ranked ID list, no scores, so weight is
|
more_like_this_ids returns only a ranked ID list, no scores, so weight is
|
||||||
synthesized from rank (descending from top_k) rather than claiming a
|
synthesized from rank (descending from top_k) rather than claiming a
|
||||||
similarity magnitude that doesn't exist. An active superuser is
|
similarity magnitude that doesn't exist. An unrestricted user (none, or an
|
||||||
normalized to ``None`` (no filtering) before calling, since the backend's
|
active superuser - see user_is_unrestricted) is normalized to ``None``
|
||||||
permission filter has no superuser short-circuit of its own. ``user=None``
|
before calling, since the backend's permission filter has no superuser
|
||||||
means the document is unowned, so results are limited to unowned documents
|
short-circuit of its own. Results are re-checked with
|
||||||
rather than the whole library. Results are re-checked with
|
|
||||||
permitted_document_ids() since Tantivy's indexed permission fields lag the
|
permitted_document_ids() since Tantivy's indexed permission fields lag the
|
||||||
DB via async reindexing and judge a version by its own owner.
|
DB via async reindexing and judge a version by its own owner.
|
||||||
"""
|
"""
|
||||||
from documents.search import get_backend
|
from documents.search import get_backend
|
||||||
|
|
||||||
unrestricted = user_is_active_superuser(user)
|
unrestricted = user_is_unrestricted(user)
|
||||||
search_user = None if unrestricted else user
|
search_user = None if unrestricted else user
|
||||||
backend = get_backend()
|
backend = get_backend()
|
||||||
similar_ids = backend.more_like_this_ids(
|
similar_ids = backend.more_like_this_ids(
|
||||||
@@ -188,16 +187,13 @@ def get_taxonomy_context(
|
|||||||
candidates/context rather than propagating the exception - neither a
|
candidates/context rather than propagating the exception - neither a
|
||||||
vector-store outage nor a search-index issue should block classification,
|
vector-store outage nor a search-index issue should block classification,
|
||||||
only its context-assisted enrichment.
|
only its context-assisted enrichment.
|
||||||
|
|
||||||
``user=None`` means "acting for an unowned document" (workflows act as the
|
|
||||||
document owner), NOT "no restriction": only unowned documents and taxonomy
|
|
||||||
are considered, so another user's private documents never reach the prompt.
|
|
||||||
"""
|
"""
|
||||||
ai_config = AIConfig()
|
ai_config = AIConfig()
|
||||||
try:
|
try:
|
||||||
if ai_config.llm_embedding_backend:
|
if ai_config.llm_embedding_backend:
|
||||||
# None means "no restriction" to retrieve_similar_nodes. An active
|
# None means "no restriction" to retrieve_similar_nodes. An
|
||||||
# superuser can see every document, so skip
|
# unrestricted user (no user at all, or an active superuser -- see
|
||||||
|
# user_is_unrestricted) can see every document, so skip
|
||||||
# materializing every visible pk into a Python list and passing it
|
# materializing every visible pk into a Python list and passing it
|
||||||
# through as an IN filter: for a large library that is a wasted
|
# through as an IN filter: for a large library that is a wasted
|
||||||
# quadratic scan in the vector store at best, and past ~32,763
|
# quadratic scan in the vector store at best, and past ~32,763
|
||||||
@@ -208,7 +204,7 @@ def get_taxonomy_context(
|
|||||||
# which documents are considered -- only how we get there.
|
# which documents are considered -- only how we get there.
|
||||||
visible_document_ids = (
|
visible_document_ids = (
|
||||||
None
|
None
|
||||||
if user_is_active_superuser(user)
|
if user_is_unrestricted(user)
|
||||||
else list(permitted_document_ids(user))
|
else list(permitted_document_ids(user))
|
||||||
)
|
)
|
||||||
nodes = retrieve_similar_nodes(
|
nodes = retrieve_similar_nodes(
|
||||||
@@ -230,14 +226,8 @@ def get_taxonomy_context(
|
|||||||
|
|
||||||
# similar_documents is already ordered by descending weight; don't lose it.
|
# similar_documents is already ordered by descending weight; don't lose it.
|
||||||
similar_document_ids = [s["document_id"] for s in similar_documents]
|
similar_document_ids = [s["document_id"] for s in similar_documents]
|
||||||
similar_queryset = Document.objects.all()
|
|
||||||
if not user_is_active_superuser(user):
|
|
||||||
# Already scoped by retrieval but re-checked here before being used in the prompt
|
|
||||||
similar_queryset = similar_queryset.filter(
|
|
||||||
id__in=permitted_document_ids(user),
|
|
||||||
)
|
|
||||||
similar_documents_by_id = annotate_effective_content(
|
similar_documents_by_id = annotate_effective_content(
|
||||||
similar_queryset,
|
Document.objects.all(),
|
||||||
).in_bulk(similar_document_ids)
|
).in_bulk(similar_document_ids)
|
||||||
similar_docs = [
|
similar_docs = [
|
||||||
similar_documents_by_id[document_id]
|
similar_documents_by_id[document_id]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from documents.models import DocumentType
|
|||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
from documents.permissions import permitted_object_ids
|
from documents.permissions import permitted_object_ids
|
||||||
from documents.permissions import restrict_queryset_to_owner_visible
|
from documents.permissions import restrict_queryset_to_visible
|
||||||
|
|
||||||
MATCH_THRESHOLD = 0.8
|
MATCH_THRESHOLD = 0.8
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ def _resolve_visible_ids(
|
|||||||
"""
|
"""
|
||||||
if not ids:
|
if not ids:
|
||||||
return []
|
return []
|
||||||
queryset = restrict_queryset_to_owner_visible(
|
queryset = restrict_queryset_to_visible(
|
||||||
model.objects.filter(pk__in=ids),
|
model.objects.filter(pk__in=ids),
|
||||||
user,
|
user,
|
||||||
perm,
|
perm,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from documents.models import Document
|
|||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
from documents.permissions import restrict_queryset_to_owner_visible
|
from documents.permissions import restrict_queryset_to_visible
|
||||||
from paperless_ai.prompts.context import TaxonomyBlockPromptContext
|
from paperless_ai.prompts.context import TaxonomyBlockPromptContext
|
||||||
from paperless_ai.prompts.render import render_prompt
|
from paperless_ai.prompts.render import render_prompt
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ def _visible_ranked_candidates(
|
|||||||
"""
|
"""
|
||||||
if not weighted_ids:
|
if not weighted_ids:
|
||||||
return []
|
return []
|
||||||
visible_queryset = restrict_queryset_to_owner_visible(
|
visible_queryset = restrict_queryset_to_visible(
|
||||||
model.objects.filter(pk__in=weighted_ids),
|
model.objects.filter(pk__in=weighted_ids),
|
||||||
user,
|
user,
|
||||||
perm,
|
perm,
|
||||||
|
|||||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user