mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-31 13:58:04 -06:00
Compare commits
1 Commits
feature-be
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65aed2405c |
4
.github/workflows/translate-strings.yml
vendored
4
.github/workflows/translate-strings.yml
vendored
@@ -12,11 +12,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
env:
|
|
||||||
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
|
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||||
ref: ${{ env.GH_REF }}
|
ref: ${{ github.head_ref }}
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
@@ -170,11 +170,18 @@ Available options are `postgresql` and `mariadb`.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
A small pool is typically sufficient — for example, a size of 4.
|
A pool of 8-10 connections per worker is typically sufficient.
|
||||||
Make sure your PostgreSQL server's max_connections setting is large enough to handle:
|
If you encounter error messages such as `couldn't get a connection`
|
||||||
```(Paperless workers + Celery workers) × pool size + safety margin```
|
or database connection timeouts, you probably need to increase the pool size.
|
||||||
For example, with 4 Paperless workers and 2 Celery workers, and a pool size of 4:
|
|
||||||
(4 + 2) × 4 + 10 = 34 connections required.
|
!!! warning
|
||||||
|
Make sure your PostgreSQL `max_connections` setting is large enough to handle the connection pools:
|
||||||
|
`(NB_PAPERLESS_WORKERS + NB_CELERY_WORKERS) × POOL_SIZE + SAFETY_MARGIN`. For example, with
|
||||||
|
4 Paperless workers and 2 Celery workers, and a pool size of 8:``(4 + 2) × 8 + 10 = 58`,
|
||||||
|
so `max_connections = 60` (or even more) is appropriate.
|
||||||
|
|
||||||
|
This assumes only Paperless-ngx connects to your PostgreSQL instance. If you have other applications,
|
||||||
|
you should increase `max_connections` accordingly.
|
||||||
|
|
||||||
#### [`PAPERLESS_DB_READ_CACHE_ENABLED=<bool>`](#PAPERLESS_DB_READ_CACHE_ENABLED) {#PAPERLESS_DB_READ_CACHE_ENABLED}
|
#### [`PAPERLESS_DB_READ_CACHE_ENABLED=<bool>`](#PAPERLESS_DB_READ_CACHE_ENABLED) {#PAPERLESS_DB_READ_CACHE_ENABLED}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
"fi-FI": "src/locale/messages.fi_FI.xlf",
|
"fi-FI": "src/locale/messages.fi_FI.xlf",
|
||||||
"fr-FR": "src/locale/messages.fr_FR.xlf",
|
"fr-FR": "src/locale/messages.fr_FR.xlf",
|
||||||
"hu-HU": "src/locale/messages.hu_HU.xlf",
|
"hu-HU": "src/locale/messages.hu_HU.xlf",
|
||||||
"id-ID": "src/locale/messages.id_ID.xlf",
|
|
||||||
"it-IT": "src/locale/messages.it_IT.xlf",
|
"it-IT": "src/locale/messages.it_IT.xlf",
|
||||||
"ja-JP": "src/locale/messages.ja_JP.xlf",
|
"ja-JP": "src/locale/messages.ja_JP.xlf",
|
||||||
"lb-LU": "src/locale/messages.lb_LU.xlf",
|
"lb-LU": "src/locale/messages.lb_LU.xlf",
|
||||||
|
|||||||
@@ -10028,186 +10028,179 @@
|
|||||||
<context context-type="linenumber">135</context>
|
<context context-type="linenumber">135</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8312065814232621608" datatype="html">
|
|
||||||
<source>Indonesian</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
|
||||||
<context context-type="linenumber">141</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2935232983274991580" datatype="html">
|
<trans-unit id="2935232983274991580" datatype="html">
|
||||||
<source>Italian</source>
|
<source>Italian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">147</context>
|
<context context-type="linenumber">141</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6924606686202701860" datatype="html">
|
<trans-unit id="6924606686202701860" datatype="html">
|
||||||
<source>Japanese</source>
|
<source>Japanese</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">153</context>
|
<context context-type="linenumber">147</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6145439649200570157" datatype="html">
|
<trans-unit id="6145439649200570157" datatype="html">
|
||||||
<source>Korean</source>
|
<source>Korean</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">159</context>
|
<context context-type="linenumber">153</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1334425850005897370" datatype="html">
|
<trans-unit id="1334425850005897370" datatype="html">
|
||||||
<source>Luxembourgish</source>
|
<source>Luxembourgish</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">165</context>
|
<context context-type="linenumber">159</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3071065188816255493" datatype="html">
|
<trans-unit id="3071065188816255493" datatype="html">
|
||||||
<source>Dutch</source>
|
<source>Dutch</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">171</context>
|
<context context-type="linenumber">165</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8069284467804715623" datatype="html">
|
<trans-unit id="8069284467804715623" datatype="html">
|
||||||
<source>Norwegian</source>
|
<source>Norwegian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">177</context>
|
<context context-type="linenumber">171</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4977087909184008115" datatype="html">
|
<trans-unit id="4977087909184008115" datatype="html">
|
||||||
<source>Persian</source>
|
<source>Persian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">183</context>
|
<context context-type="linenumber">177</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="792060551707690640" datatype="html">
|
<trans-unit id="792060551707690640" datatype="html">
|
||||||
<source>Polish</source>
|
<source>Polish</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">189</context>
|
<context context-type="linenumber">183</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9184513005098760425" datatype="html">
|
<trans-unit id="9184513005098760425" datatype="html">
|
||||||
<source>Portuguese (Brazil)</source>
|
<source>Portuguese (Brazil)</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">195</context>
|
<context context-type="linenumber">189</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="153799456510623899" datatype="html">
|
<trans-unit id="153799456510623899" datatype="html">
|
||||||
<source>Portuguese</source>
|
<source>Portuguese</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">195</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8118856427047826368" datatype="html">
|
<trans-unit id="8118856427047826368" datatype="html">
|
||||||
<source>Romanian</source>
|
<source>Romanian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">207</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7137419789978325708" datatype="html">
|
<trans-unit id="7137419789978325708" datatype="html">
|
||||||
<source>Russian</source>
|
<source>Russian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">213</context>
|
<context context-type="linenumber">207</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9102963095355753902" datatype="html">
|
<trans-unit id="9102963095355753902" datatype="html">
|
||||||
<source>Slovak</source>
|
<source>Slovak</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">219</context>
|
<context context-type="linenumber">213</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4287008301409320881" datatype="html">
|
<trans-unit id="4287008301409320881" datatype="html">
|
||||||
<source>Slovenian</source>
|
<source>Slovenian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">219</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8608389829607915090" datatype="html">
|
<trans-unit id="8608389829607915090" datatype="html">
|
||||||
<source>Serbian</source>
|
<source>Serbian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">231</context>
|
<context context-type="linenumber">225</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="499386805970351976" datatype="html">
|
<trans-unit id="499386805970351976" datatype="html">
|
||||||
<source>Swedish</source>
|
<source>Swedish</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">237</context>
|
<context context-type="linenumber">231</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5682359291233237791" datatype="html">
|
<trans-unit id="5682359291233237791" datatype="html">
|
||||||
<source>Turkish</source>
|
<source>Turkish</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">243</context>
|
<context context-type="linenumber">237</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3578644052206125685" datatype="html">
|
<trans-unit id="3578644052206125685" datatype="html">
|
||||||
<source>Ukrainian</source>
|
<source>Ukrainian</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">249</context>
|
<context context-type="linenumber">243</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3611216939636790848" datatype="html">
|
<trans-unit id="3611216939636790848" datatype="html">
|
||||||
<source>Vietnamese</source>
|
<source>Vietnamese</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">255</context>
|
<context context-type="linenumber">249</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4689443708886954687" datatype="html">
|
<trans-unit id="4689443708886954687" datatype="html">
|
||||||
<source>Chinese Simplified</source>
|
<source>Chinese Simplified</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">261</context>
|
<context context-type="linenumber">255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8082606363137705994" datatype="html">
|
<trans-unit id="8082606363137705994" datatype="html">
|
||||||
<source>Chinese Traditional</source>
|
<source>Chinese Traditional</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">267</context>
|
<context context-type="linenumber">261</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4912706592792948707" datatype="html">
|
<trans-unit id="4912706592792948707" datatype="html">
|
||||||
<source>ISO 8601</source>
|
<source>ISO 8601</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">275</context>
|
<context context-type="linenumber">269</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="313643372755303297" datatype="html">
|
<trans-unit id="313643372755303297" datatype="html">
|
||||||
<source>Successfully completed one-time migratration of settings to the database!</source>
|
<source>Successfully completed one-time migratration of settings to the database!</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">609</context>
|
<context context-type="linenumber">603</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5558341108007064934" datatype="html">
|
<trans-unit id="5558341108007064934" datatype="html">
|
||||||
<source>Unable to migrate settings to the database, please try saving manually.</source>
|
<source>Unable to migrate settings to the database, please try saving manually.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">610</context>
|
<context context-type="linenumber">604</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1168781785897678748" datatype="html">
|
<trans-unit id="1168781785897678748" datatype="html">
|
||||||
<source>You can restart the tour from the settings page.</source>
|
<source>You can restart the tour from the settings page.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
|
||||||
<context context-type="linenumber">683</context>
|
<context context-type="linenumber">677</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3852289441366561594" datatype="html">
|
<trans-unit id="3852289441366561594" datatype="html">
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import localeFa from '@angular/common/locales/fa'
|
|||||||
import localeFi from '@angular/common/locales/fi'
|
import localeFi from '@angular/common/locales/fi'
|
||||||
import localeFr from '@angular/common/locales/fr'
|
import localeFr from '@angular/common/locales/fr'
|
||||||
import localeHu from '@angular/common/locales/hu'
|
import localeHu from '@angular/common/locales/hu'
|
||||||
import localeId from '@angular/common/locales/id'
|
|
||||||
import localeIt from '@angular/common/locales/it'
|
import localeIt from '@angular/common/locales/it'
|
||||||
import localeJa from '@angular/common/locales/ja'
|
import localeJa from '@angular/common/locales/ja'
|
||||||
import localeKo from '@angular/common/locales/ko'
|
import localeKo from '@angular/common/locales/ko'
|
||||||
@@ -64,7 +63,6 @@ registerLocaleData(localeFa)
|
|||||||
registerLocaleData(localeFi)
|
registerLocaleData(localeFi)
|
||||||
registerLocaleData(localeFr)
|
registerLocaleData(localeFr)
|
||||||
registerLocaleData(localeHu)
|
registerLocaleData(localeHu)
|
||||||
registerLocaleData(localeId)
|
|
||||||
registerLocaleData(localeIt)
|
registerLocaleData(localeIt)
|
||||||
registerLocaleData(localeJa)
|
registerLocaleData(localeJa)
|
||||||
registerLocaleData(localeKo)
|
registerLocaleData(localeKo)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
@if (previewText) {
|
@if (previewText) {
|
||||||
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
|
||||||
} @else {
|
} @else {
|
||||||
<object [data]="previewUrl | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@if (requiresPassword) {
|
@if (requiresPassword) {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
@if (!requiresPassword) {
|
@if (!requiresPassword) {
|
||||||
<pdf-viewer
|
<pdf-viewer
|
||||||
[src]="previewUrl"
|
[src]="previewURL"
|
||||||
[original-size]="false"
|
[original-size]="false"
|
||||||
[show-borders]="false"
|
[show-borders]="false"
|
||||||
[show-all]="true"
|
[show-all]="true"
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
|
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
|
||||||
}
|
}
|
||||||
|
|
||||||
get previewUrl() {
|
get previewURL() {
|
||||||
return this.documentService.getPreviewUrl(this.document.id)
|
return this.documentService.getPreviewUrl(this.document.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
init() {
|
init() {
|
||||||
if (this.document.mime_type?.includes('text')) {
|
if (this.document.mime_type?.includes('text')) {
|
||||||
this.http
|
this.http
|
||||||
.get(this.previewUrl, { responseType: 'text' })
|
.get(this.previewURL, { responseType: 'text' })
|
||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
@@ -126,6 +126,10 @@ export class PreviewPopupComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get previewUrl() {
|
||||||
|
return this.documentService.getPreviewUrl(this.document.id)
|
||||||
|
}
|
||||||
|
|
||||||
mouseEnterPreview() {
|
mouseEnterPreview() {
|
||||||
this.mouseOnPreview = true
|
this.mouseOnPreview = true
|
||||||
if (!this.popover.isOpen()) {
|
if (!this.popover.isOpen()) {
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
<ng-template #previewContent>
|
<ng-template #previewContent>
|
||||||
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
|
||||||
@if (showThumbnailOverlay) {
|
@if (showThumbnailOverlay) {
|
||||||
<img [src]="thumbUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
<img [src]="thumbUrl | safeUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
|
||||||
}
|
}
|
||||||
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
}
|
}
|
||||||
@case (ContentRenderType.Image) {
|
@case (ContentRenderType.Image) {
|
||||||
<div class="preview-sticky">
|
<div class="preview-sticky">
|
||||||
<img [src]="previewUrl" width="100%" height="100%" alt="{{title}}" />
|
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case (ContentRenderType.TIFF) {
|
@case (ContentRenderType.TIFF) {
|
||||||
|
|||||||
@@ -136,12 +136,6 @@ const LANGUAGE_OPTIONS = [
|
|||||||
englishName: 'Hungarian',
|
englishName: 'Hungarian',
|
||||||
dateInputFormat: 'yyyy.mm.dd',
|
dateInputFormat: 'yyyy.mm.dd',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
code: 'id-id',
|
|
||||||
name: $localize`Indonesian`,
|
|
||||||
englishName: 'Indonesian',
|
|
||||||
dateInputFormat: 'dd-mm-yyyy',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
code: 'it-it',
|
code: 'it-it',
|
||||||
name: $localize`Italian`,
|
name: $localize`Italian`,
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ import localeFa from '@angular/common/locales/fa'
|
|||||||
import localeFi from '@angular/common/locales/fi'
|
import localeFi from '@angular/common/locales/fi'
|
||||||
import localeFr from '@angular/common/locales/fr'
|
import localeFr from '@angular/common/locales/fr'
|
||||||
import localeHu from '@angular/common/locales/hu'
|
import localeHu from '@angular/common/locales/hu'
|
||||||
import localeId from '@angular/common/locales/id'
|
|
||||||
import localeIt from '@angular/common/locales/it'
|
import localeIt from '@angular/common/locales/it'
|
||||||
import localeJa from '@angular/common/locales/ja'
|
import localeJa from '@angular/common/locales/ja'
|
||||||
import localeKo from '@angular/common/locales/ko'
|
import localeKo from '@angular/common/locales/ko'
|
||||||
@@ -210,7 +209,6 @@ registerLocaleData(localeFa)
|
|||||||
registerLocaleData(localeFi)
|
registerLocaleData(localeFi)
|
||||||
registerLocaleData(localeFr)
|
registerLocaleData(localeFr)
|
||||||
registerLocaleData(localeHu)
|
registerLocaleData(localeHu)
|
||||||
registerLocaleData(localeId)
|
|
||||||
registerLocaleData(localeIt)
|
registerLocaleData(localeIt)
|
||||||
registerLocaleData(localeJa)
|
registerLocaleData(localeJa)
|
||||||
registerLocaleData(localeKo)
|
registerLocaleData(localeKo)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from pathlib import Path
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
from celery import chain
|
||||||
from celery import chord
|
from celery import chord
|
||||||
from celery import group
|
from celery import group
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
@@ -37,42 +38,6 @@ if TYPE_CHECKING:
|
|||||||
logger: logging.Logger = logging.getLogger("paperless.bulk_edit")
|
logger: logging.Logger = logging.getLogger("paperless.bulk_edit")
|
||||||
|
|
||||||
|
|
||||||
@shared_task(bind=True)
|
|
||||||
def restore_archive_serial_numbers_task(
|
|
||||||
self,
|
|
||||||
backup: dict[int, int],
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
) -> None:
|
|
||||||
restore_archive_serial_numbers(backup)
|
|
||||||
|
|
||||||
|
|
||||||
def release_archive_serial_numbers(doc_ids: list[int]) -> dict[int, int]:
|
|
||||||
"""
|
|
||||||
Clears ASNs on documents that are about to be replaced so new documents
|
|
||||||
can be assigned ASNs without uniqueness collisions. Returns a backup map
|
|
||||||
of doc_id -> previous ASN for potential restoration.
|
|
||||||
"""
|
|
||||||
qs = Document.objects.filter(
|
|
||||||
id__in=doc_ids,
|
|
||||||
archive_serial_number__isnull=False,
|
|
||||||
).only("pk", "archive_serial_number")
|
|
||||||
backup = dict(qs.values_list("pk", "archive_serial_number"))
|
|
||||||
qs.update(archive_serial_number=None)
|
|
||||||
logger.info(f"Released archive serial numbers for documents {list(backup.keys())}")
|
|
||||||
return backup
|
|
||||||
|
|
||||||
|
|
||||||
def restore_archive_serial_numbers(backup: dict[int, int]) -> None:
|
|
||||||
"""
|
|
||||||
Restores ASNs using the provided backup map, intended for
|
|
||||||
rollback when replacement consumption fails.
|
|
||||||
"""
|
|
||||||
for doc_id, asn in backup.items():
|
|
||||||
Document.objects.filter(pk=doc_id).update(archive_serial_number=asn)
|
|
||||||
logger.info(f"Restored archive serial numbers for documents {list(backup.keys())}")
|
|
||||||
|
|
||||||
|
|
||||||
def set_correspondent(
|
def set_correspondent(
|
||||||
doc_ids: list[int],
|
doc_ids: list[int],
|
||||||
correspondent: Correspondent,
|
correspondent: Correspondent,
|
||||||
@@ -421,7 +386,6 @@ def merge(
|
|||||||
|
|
||||||
merged_pdf = pikepdf.new()
|
merged_pdf = pikepdf.new()
|
||||||
version: str = merged_pdf.pdf_version
|
version: str = merged_pdf.pdf_version
|
||||||
handoff_asn: int | None = None
|
|
||||||
# use doc_ids to preserve order
|
# use doc_ids to preserve order
|
||||||
for doc_id in doc_ids:
|
for doc_id in doc_ids:
|
||||||
doc = qs.get(id=doc_id)
|
doc = qs.get(id=doc_id)
|
||||||
@@ -437,8 +401,6 @@ def merge(
|
|||||||
version = max(version, pdf.pdf_version)
|
version = max(version, pdf.pdf_version)
|
||||||
merged_pdf.pages.extend(pdf.pages)
|
merged_pdf.pages.extend(pdf.pages)
|
||||||
affected_docs.append(doc.id)
|
affected_docs.append(doc.id)
|
||||||
if handoff_asn is None and doc.archive_serial_number is not None:
|
|
||||||
handoff_asn = doc.archive_serial_number
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
f"Error merging document {doc.id}, it will not be included in the merge: {e}",
|
||||||
@@ -464,8 +426,6 @@ def merge(
|
|||||||
DocumentMetadataOverrides.from_document(metadata_document)
|
DocumentMetadataOverrides.from_document(metadata_document)
|
||||||
)
|
)
|
||||||
overrides.title = metadata_document.title + " (merged)"
|
overrides.title = metadata_document.title + " (merged)"
|
||||||
if metadata_document.archive_serial_number is not None:
|
|
||||||
handoff_asn = metadata_document.archive_serial_number
|
|
||||||
else:
|
else:
|
||||||
overrides = DocumentMetadataOverrides()
|
overrides = DocumentMetadataOverrides()
|
||||||
else:
|
else:
|
||||||
@@ -474,9 +434,6 @@ def merge(
|
|||||||
if user is not None:
|
if user is not None:
|
||||||
overrides.owner_id = user.id
|
overrides.owner_id = user.id
|
||||||
|
|
||||||
if delete_originals and handoff_asn is not None:
|
|
||||||
overrides.asn = handoff_asn
|
|
||||||
|
|
||||||
logger.info("Adding merged document to the task queue.")
|
logger.info("Adding merged document to the task queue.")
|
||||||
|
|
||||||
consume_task = consume_file.s(
|
consume_task = consume_file.s(
|
||||||
@@ -488,20 +445,12 @@ def merge(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if delete_originals:
|
if delete_originals:
|
||||||
backup = release_archive_serial_numbers(affected_docs)
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Queueing removal of original documents after consumption of merged document",
|
"Queueing removal of original documents after consumption of merged document",
|
||||||
)
|
)
|
||||||
try:
|
chain(consume_task, delete.si(affected_docs)).delay()
|
||||||
consume_task.apply_async(
|
else:
|
||||||
link=[delete.si(affected_docs)],
|
consume_task.delay()
|
||||||
link_error=[restore_archive_serial_numbers_task.s(backup)],
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
restore_archive_serial_numbers(backup)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
consume_task.delay()
|
|
||||||
|
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
@@ -557,20 +506,10 @@ def split(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if delete_originals:
|
if delete_originals:
|
||||||
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:
|
chord(header=consume_tasks, body=delete.si([doc.id])).delay()
|
||||||
chord(
|
|
||||||
header=consume_tasks,
|
|
||||||
body=delete.si([doc.id]),
|
|
||||||
).apply_async(
|
|
||||||
link_error=[restore_archive_serial_numbers_task.s(backup)],
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
restore_archive_serial_numbers(backup)
|
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
group(consume_tasks).delay()
|
group(consume_tasks).delay()
|
||||||
|
|
||||||
@@ -673,8 +612,7 @@ def edit_pdf(
|
|||||||
)
|
)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
overrides.owner_id = user.id
|
overrides.owner_id = user.id
|
||||||
if delete_original and len(pdf_docs) == 1:
|
|
||||||
overrides.asn = doc.archive_serial_number
|
|
||||||
for idx, pdf in enumerate(pdf_docs, start=1):
|
for idx, pdf in enumerate(pdf_docs, start=1):
|
||||||
filepath: Path = (
|
filepath: Path = (
|
||||||
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR))
|
||||||
@@ -693,17 +631,7 @@ def edit_pdf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if delete_original:
|
if delete_original:
|
||||||
backup = release_archive_serial_numbers([doc.id])
|
chord(header=consume_tasks, body=delete.si([doc.id])).delay()
|
||||||
try:
|
|
||||||
chord(
|
|
||||||
header=consume_tasks,
|
|
||||||
body=delete.si([doc.id]),
|
|
||||||
).apply_async(
|
|
||||||
link_error=[restore_archive_serial_numbers_task.s(backup)],
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
restore_archive_serial_numbers(backup)
|
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
group(consume_tasks).delay()
|
group(consume_tasks).delay()
|
||||||
|
|
||||||
|
|||||||
@@ -813,7 +813,7 @@ class ConsumerPreflightPlugin(
|
|||||||
Check that if override_asn is given, it is unique and within a valid range
|
Check that if override_asn is given, it is unique and within a valid range
|
||||||
"""
|
"""
|
||||||
if self.metadata.asn is None:
|
if self.metadata.asn is None:
|
||||||
# if ASN is None
|
# check not necessary in case no ASN gets set
|
||||||
return
|
return
|
||||||
# Validate the range is above zero and less than uint32_t max
|
# Validate the range is above zero and less than uint32_t max
|
||||||
# otherwise, Whoosh can't handle it in the index
|
# otherwise, Whoosh can't handle it in the index
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class DocumentMetadataOverrides:
|
|||||||
document_type_id: int | None = None
|
document_type_id: int | None = None
|
||||||
tag_ids: list[int] | None = None
|
tag_ids: list[int] | None = None
|
||||||
storage_path_id: int | None = None
|
storage_path_id: int | None = None
|
||||||
created: datetime.date | None = None
|
created: datetime.datetime | None = None
|
||||||
asn: int | None = None
|
asn: int | None = None
|
||||||
owner_id: int | None = None
|
owner_id: int | None = None
|
||||||
view_users: list[int] | None = None
|
view_users: list[int] | None = None
|
||||||
@@ -100,7 +100,6 @@ class DocumentMetadataOverrides:
|
|||||||
overrides.storage_path_id = doc.storage_path.id if doc.storage_path else None
|
overrides.storage_path_id = doc.storage_path.id if doc.storage_path else None
|
||||||
overrides.owner_id = doc.owner.id if doc.owner else None
|
overrides.owner_id = doc.owner.id if doc.owner else None
|
||||||
overrides.tag_ids = list(doc.tags.values_list("id", flat=True))
|
overrides.tag_ids = list(doc.tags.values_list("id", flat=True))
|
||||||
overrides.created = doc.created
|
|
||||||
|
|
||||||
overrides.view_users = list(
|
overrides.view_users = list(
|
||||||
get_users_with_perms(
|
get_users_with_perms(
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.core.validators import DecimalValidator
|
from django.core.validators import DecimalValidator
|
||||||
from django.core.validators import EmailValidator
|
from django.core.validators import EmailValidator
|
||||||
from django.core.validators import MaxLengthValidator
|
from django.core.validators import MaxLengthValidator
|
||||||
from django.core.validators import MaxValueValidator
|
|
||||||
from django.core.validators import MinValueValidator
|
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.core.validators import integer_validator
|
from django.core.validators import integer_validator
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
@@ -877,13 +875,6 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
|
|||||||
uri_validator(data["value"])
|
uri_validator(data["value"])
|
||||||
elif field.data_type == CustomField.FieldDataType.INT:
|
elif field.data_type == CustomField.FieldDataType.INT:
|
||||||
integer_validator(data["value"])
|
integer_validator(data["value"])
|
||||||
try:
|
|
||||||
value_int = int(data["value"])
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise serializers.ValidationError("Enter a valid integer.")
|
|
||||||
# Keep values within the PostgreSQL integer range
|
|
||||||
MinValueValidator(-2147483648)(value_int)
|
|
||||||
MaxValueValidator(2147483647)(value_int)
|
|
||||||
elif (
|
elif (
|
||||||
field.data_type == CustomField.FieldDataType.MONETARY
|
field.data_type == CustomField.FieldDataType.MONETARY
|
||||||
and data["value"] != ""
|
and data["value"] != ""
|
||||||
|
|||||||
@@ -1664,44 +1664,6 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
|
|
||||||
self.consume_file_mock.assert_not_called()
|
self.consume_file_mock.assert_not_called()
|
||||||
|
|
||||||
def test_patch_document_integer_custom_field_out_of_range(self):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- An integer custom field
|
|
||||||
- A document
|
|
||||||
WHEN:
|
|
||||||
- Patching the document with an integer value exceeding PostgreSQL's range
|
|
||||||
THEN:
|
|
||||||
- HTTP 400 is returned (validation catches the overflow)
|
|
||||||
- No custom field instance is created
|
|
||||||
"""
|
|
||||||
cf_int = CustomField.objects.create(
|
|
||||||
name="intfield",
|
|
||||||
data_type=CustomField.FieldDataType.INT,
|
|
||||||
)
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="Doc",
|
|
||||||
checksum="123",
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"/api/documents/{doc.pk}/",
|
|
||||||
{
|
|
||||||
"custom_fields": [
|
|
||||||
{
|
|
||||||
"field": cf_int.pk,
|
|
||||||
"value": 2**31, # overflow for PostgreSQL integer fields
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertIn("custom_fields", response.data)
|
|
||||||
self.assertEqual(CustomFieldInstance.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_upload_with_webui_source(self):
|
def test_upload_with_webui_source(self):
|
||||||
"""
|
"""
|
||||||
GIVEN: A document with a source file
|
GIVEN: A document with a source file
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
- Consume file should be called
|
- Consume file should be called
|
||||||
"""
|
"""
|
||||||
doc_ids = [self.doc1.id, self.doc2.id, self.doc3.id]
|
doc_ids = [self.doc1.id, self.doc2.id, self.doc3.id]
|
||||||
metadata_document_id = self.doc2.id
|
metadata_document_id = self.doc1.id
|
||||||
user = User.objects.create(username="test_user")
|
user = User.objects.create(username="test_user")
|
||||||
|
|
||||||
result = bulk_edit.merge(
|
result = bulk_edit.merge(
|
||||||
@@ -602,21 +602,20 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
expected_filename,
|
expected_filename,
|
||||||
)
|
)
|
||||||
self.assertEqual(consume_file_args[1].title, None)
|
self.assertEqual(consume_file_args[1].title, None)
|
||||||
# No metadata_document_id, delete_originals False, so ASN should be None
|
|
||||||
self.assertIsNone(consume_file_args[1].asn)
|
|
||||||
|
|
||||||
# With metadata_document_id overrides
|
# With metadata_document_id overrides
|
||||||
result = bulk_edit.merge(doc_ids, metadata_document_id=metadata_document_id)
|
result = bulk_edit.merge(doc_ids, metadata_document_id=metadata_document_id)
|
||||||
consume_file_args, _ = mock_consume_file.call_args
|
consume_file_args, _ = mock_consume_file.call_args
|
||||||
self.assertEqual(consume_file_args[1].title, "B (merged)")
|
self.assertEqual(consume_file_args[1].title, "A (merged)")
|
||||||
self.assertEqual(consume_file_args[1].created, self.doc2.created)
|
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
@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")
|
||||||
|
@mock.patch("documents.bulk_edit.chain")
|
||||||
def test_merge_and_delete_originals(
|
def test_merge_and_delete_originals(
|
||||||
self,
|
self,
|
||||||
|
mock_chain,
|
||||||
mock_consume_file,
|
mock_consume_file,
|
||||||
mock_delete_documents,
|
mock_delete_documents,
|
||||||
):
|
):
|
||||||
@@ -630,12 +629,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
- Document deletion task should be called
|
- Document deletion task should be called
|
||||||
"""
|
"""
|
||||||
doc_ids = [self.doc1.id, self.doc2.id, self.doc3.id]
|
doc_ids = [self.doc1.id, self.doc2.id, self.doc3.id]
|
||||||
self.doc1.archive_serial_number = 101
|
|
||||||
self.doc2.archive_serial_number = 102
|
|
||||||
self.doc3.archive_serial_number = 103
|
|
||||||
self.doc1.save()
|
|
||||||
self.doc2.save()
|
|
||||||
self.doc3.save()
|
|
||||||
|
|
||||||
result = bulk_edit.merge(doc_ids, delete_originals=True)
|
result = bulk_edit.merge(doc_ids, delete_originals=True)
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
@@ -646,8 +639,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
mock_consume_file.assert_called()
|
mock_consume_file.assert_called()
|
||||||
mock_delete_documents.assert_called()
|
mock_delete_documents.assert_called()
|
||||||
consume_sig = mock_consume_file.return_value
|
mock_chain.assert_called_once()
|
||||||
consume_sig.apply_async.assert_called_once()
|
|
||||||
|
|
||||||
consume_file_args, _ = mock_consume_file.call_args
|
consume_file_args, _ = mock_consume_file.call_args
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -655,7 +647,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
expected_filename,
|
expected_filename,
|
||||||
)
|
)
|
||||||
self.assertEqual(consume_file_args[1].title, None)
|
self.assertEqual(consume_file_args[1].title, None)
|
||||||
self.assertEqual(consume_file_args[1].asn, 101)
|
|
||||||
|
|
||||||
delete_documents_args, _ = mock_delete_documents.call_args
|
delete_documents_args, _ = mock_delete_documents.call_args
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -663,13 +654,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
doc_ids,
|
doc_ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.doc1.refresh_from_db()
|
|
||||||
self.doc2.refresh_from_db()
|
|
||||||
self.doc3.refresh_from_db()
|
|
||||||
self.assertIsNone(self.doc1.archive_serial_number)
|
|
||||||
self.assertIsNone(self.doc2.archive_serial_number)
|
|
||||||
self.assertIsNone(self.doc3.archive_serial_number)
|
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_merge_with_archive_fallback(self, mock_consume_file):
|
def test_merge_with_archive_fallback(self, mock_consume_file):
|
||||||
"""
|
"""
|
||||||
@@ -738,7 +722,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
self.assertEqual(mock_consume_file.call_count, 2)
|
self.assertEqual(mock_consume_file.call_count, 2)
|
||||||
consume_file_args, _ = mock_consume_file.call_args
|
consume_file_args, _ = mock_consume_file.call_args
|
||||||
self.assertEqual(consume_file_args[1].title, "B (split 2)")
|
self.assertEqual(consume_file_args[1].title, "B (split 2)")
|
||||||
self.assertIsNone(consume_file_args[1].asn)
|
|
||||||
|
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
@@ -763,8 +746,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
"""
|
"""
|
||||||
doc_ids = [self.doc2.id]
|
doc_ids = [self.doc2.id]
|
||||||
pages = [[1, 2], [3]]
|
pages = [[1, 2], [3]]
|
||||||
self.doc2.archive_serial_number = 200
|
|
||||||
self.doc2.save()
|
|
||||||
|
|
||||||
result = bulk_edit.split(doc_ids, pages, delete_originals=True)
|
result = bulk_edit.split(doc_ids, pages, delete_originals=True)
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
@@ -782,9 +763,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
doc_ids,
|
doc_ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.doc2.refresh_from_db()
|
|
||||||
self.assertIsNone(self.doc2.archive_serial_number)
|
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.delay")
|
@mock.patch("documents.tasks.consume_file.delay")
|
||||||
@mock.patch("pikepdf.Pdf.save")
|
@mock.patch("pikepdf.Pdf.save")
|
||||||
def test_split_with_errors(self, mock_save_pdf, mock_consume_file):
|
def test_split_with_errors(self, mock_save_pdf, mock_consume_file):
|
||||||
@@ -985,16 +963,10 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
mock_chord.return_value.delay.return_value = None
|
mock_chord.return_value.delay.return_value = None
|
||||||
doc_ids = [self.doc2.id]
|
doc_ids = [self.doc2.id]
|
||||||
operations = [{"page": 1}, {"page": 2}]
|
operations = [{"page": 1}, {"page": 2}]
|
||||||
self.doc2.archive_serial_number = 250
|
|
||||||
self.doc2.save()
|
|
||||||
|
|
||||||
result = bulk_edit.edit_pdf(doc_ids, operations, delete_original=True)
|
result = bulk_edit.edit_pdf(doc_ids, operations, delete_original=True)
|
||||||
self.assertEqual(result, "OK")
|
self.assertEqual(result, "OK")
|
||||||
mock_chord.assert_called_once()
|
mock_chord.assert_called_once()
|
||||||
consume_file_args, _ = mock_consume_file.call_args
|
|
||||||
self.assertEqual(consume_file_args[1].asn, 250)
|
|
||||||
self.doc2.refresh_from_db()
|
|
||||||
self.assertIsNone(self.doc2.archive_serial_number)
|
|
||||||
|
|
||||||
@mock.patch("documents.tasks.update_document_content_maybe_archive_file.delay")
|
@mock.patch("documents.tasks.update_document_content_maybe_archive_file.delay")
|
||||||
def test_edit_pdf_with_update_document(self, mock_update_document):
|
def test_edit_pdf_with_update_document(self, mock_update_document):
|
||||||
|
|||||||
@@ -708,7 +708,6 @@ class DocumentViewSet(
|
|||||||
"title",
|
"title",
|
||||||
"correspondent__name",
|
"correspondent__name",
|
||||||
"document_type__name",
|
"document_type__name",
|
||||||
"storage_path__name",
|
|
||||||
"created",
|
"created",
|
||||||
"modified",
|
"modified",
|
||||||
"added",
|
"added",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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: 2025-12-29 14:49+0000\n"
|
"POT-Creation-Date: 2025-12-12 17:41+0000\n"
|
||||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@@ -1219,35 +1219,35 @@ msgstr ""
|
|||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:642
|
#: documents/serialisers.py:640
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1835
|
#: documents/serialisers.py:1826
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1879
|
#: documents/serialisers.py:1870
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field id must be an integer: %(id)s"
|
msgid "Custom field id must be an integer: %(id)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1886
|
#: documents/serialisers.py:1877
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Custom field with id %(id)s does not exist"
|
msgid "Custom field with id %(id)s does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1903 documents/serialisers.py:1913
|
#: documents/serialisers.py:1894 documents/serialisers.py:1904
|
||||||
msgid ""
|
msgid ""
|
||||||
"Custom fields must be a list of integers or an object mapping ids to values."
|
"Custom fields must be a list of integers or an object mapping ids to values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1908
|
#: documents/serialisers.py:1899
|
||||||
msgid "Some custom fields don't exist or were specified twice."
|
msgid "Some custom fields don't exist or were specified twice."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:2023
|
#: documents/serialisers.py:2014
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1767,86 +1767,82 @@ msgid "Hungarian"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:789
|
#: paperless/settings.py:789
|
||||||
msgid "Indonesian"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: paperless/settings.py:790
|
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:791
|
#: paperless/settings.py:790
|
||||||
msgid "Japanese"
|
msgid "Japanese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:792
|
#: paperless/settings.py:791
|
||||||
msgid "Korean"
|
msgid "Korean"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:793
|
#: paperless/settings.py:792
|
||||||
msgid "Luxembourgish"
|
msgid "Luxembourgish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:794
|
#: paperless/settings.py:793
|
||||||
msgid "Norwegian"
|
msgid "Norwegian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:795
|
#: paperless/settings.py:794
|
||||||
msgid "Dutch"
|
msgid "Dutch"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:796
|
#: paperless/settings.py:795
|
||||||
msgid "Polish"
|
msgid "Polish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:797
|
#: paperless/settings.py:796
|
||||||
msgid "Portuguese (Brazil)"
|
msgid "Portuguese (Brazil)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:798
|
#: paperless/settings.py:797
|
||||||
msgid "Portuguese"
|
msgid "Portuguese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:799
|
#: paperless/settings.py:798
|
||||||
msgid "Romanian"
|
msgid "Romanian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:800
|
#: paperless/settings.py:799
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:801
|
#: paperless/settings.py:800
|
||||||
msgid "Slovak"
|
msgid "Slovak"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:802
|
#: paperless/settings.py:801
|
||||||
msgid "Slovenian"
|
msgid "Slovenian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:803
|
#: paperless/settings.py:802
|
||||||
msgid "Serbian"
|
msgid "Serbian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:804
|
#: paperless/settings.py:803
|
||||||
msgid "Swedish"
|
msgid "Swedish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:805
|
#: paperless/settings.py:804
|
||||||
msgid "Turkish"
|
msgid "Turkish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:806
|
#: paperless/settings.py:805
|
||||||
msgid "Ukrainian"
|
msgid "Ukrainian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:807
|
#: paperless/settings.py:806
|
||||||
msgid "Vietnamese"
|
msgid "Vietnamese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:808
|
#: paperless/settings.py:807
|
||||||
msgid "Chinese Simplified"
|
msgid "Chinese Simplified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:809
|
#: paperless/settings.py:808
|
||||||
msgid "Chinese Traditional"
|
msgid "Chinese Traditional"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -786,7 +786,6 @@ LANGUAGES = [
|
|||||||
("fi-fi", _("Finnish")),
|
("fi-fi", _("Finnish")),
|
||||||
("fr-fr", _("French")),
|
("fr-fr", _("French")),
|
||||||
("hu-hu", _("Hungarian")),
|
("hu-hu", _("Hungarian")),
|
||||||
("id-id", _("Indonesian")),
|
|
||||||
("it-it", _("Italian")),
|
("it-it", _("Italian")),
|
||||||
("ja-jp", _("Japanese")),
|
("ja-jp", _("Japanese")),
|
||||||
("ko-kr", _("Korean")),
|
("ko-kr", _("Korean")),
|
||||||
|
|||||||
@@ -1108,7 +1108,6 @@ class TestMail(
|
|||||||
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 2)
|
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 2)
|
||||||
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages_spam), 1)
|
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages_spam), 1)
|
||||||
|
|
||||||
@pytest.mark.flaky(reruns=4)
|
|
||||||
def test_error_skip_rule(self):
|
def test_error_skip_rule(self):
|
||||||
account = MailAccount.objects.create(
|
account = MailAccount.objects.create(
|
||||||
name="test2",
|
name="test2",
|
||||||
|
|||||||
Reference in New Issue
Block a user