Compare commits

...
Author SHA1 Message Date
shamoonandCodex 8de2e2c5da Performance: replace django-treenode with adjacency-list tags
Co-Authored-By: Codex <267193182+codex@users.noreply.github.com>
2026-10-29 16:01:09 -07:00
github-actions[bot]andshamoon e240c89f27 Documentation: Add v3.4.0 changelog (#14582)
* Changelog v3.4.0 - GHA

* Remove 3.3.4 PR

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-10-29 15:45:43 -07:00
shamoon ced3cf9303 Bump version to 3.4.0 2026-10-29 15:11:14 -07:00
Trenton HandClaude Sonnet 5.5 08a7d1d4b8 Chore: Consolidate test sample files (#14430)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 12:43:51 -07:00
github-actions[bot]andCrowdin Bot 47052d6506 New Crowdin translations by GitHub Action (#14499)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-10-29 18:35:37 +00:00
shamoon 9bd28d3e57 Chore: bump some frontend deps for security fixes (#14581) 2026-10-29 11:09:55 -07:00
156 changed files with 46188 additions and 33495 deletions

No files matched your search

@@ -39,7 +39,6 @@ declare -a scope=(
"${site_packages}/allauth"
"${site_packages}/drf_spectacular"
"${site_packages}/drf_spectacular_sidecar"
"${site_packages}/treenode"
"${site_packages}/compression_middleware"
)
+86
View File
@@ -1,5 +1,91 @@
# Changelog
## paperless-ngx 3.4.0
### Features / Enhancements
- Enhancement: support custom fields in workflow title templates [@shamoon](https://github.com/shamoon) ([#14564](https://github.com/paperless-ngx/paperless-ngx/pull/14564))
- Enhancement: relative date custom field queries [@shamoon](https://github.com/shamoon) ([#14549](https://github.com/paperless-ngx/paperless-ngx/pull/14549))
- Performance: reduce tasks badge load times [@JanKleine](https://github.com/JanKleine) ([#14552](https://github.com/paperless-ngx/paperless-ngx/pull/14552))
- Enhancement: skip local 2FA for trusted OIDC AMR [@shamoon](https://github.com/shamoon) ([#14403](https://github.com/paperless-ngx/paperless-ngx/pull/14403))
- Feature: Generate PDF thumbnails via poppler utils [@stumpylog](https://github.com/stumpylog) ([#14420](https://github.com/paperless-ngx/paperless-ngx/pull/14420))
- Enhancement: add upload count to button, link to tray, reduce notifications [@shamoon](https://github.com/shamoon) ([#14501](https://github.com/paperless-ngx/paperless-ngx/pull/14501))
- Enhancement: notifications improvements [@shamoon](https://github.com/shamoon) ([#14500](https://github.com/paperless-ngx/paperless-ngx/pull/14500))
- Enhancement: doc details ux improvements [@shamoon](https://github.com/shamoon) ([#14485](https://github.com/paperless-ngx/paperless-ngx/pull/14485))
- Enhancement (QoL): allow independently toggling child + parent tags in WF tag fields [@shamoon](https://github.com/shamoon) ([#14400](https://github.com/paperless-ngx/paperless-ngx/pull/14400))
- Tweak: drop the user dropdown caret [@shamoon](https://github.com/shamoon) ([#14444](https://github.com/paperless-ngx/paperless-ngx/pull/14444))
- Enhancement: navbar upload button [@shamoon](https://github.com/shamoon) ([#14442](https://github.com/paperless-ngx/paperless-ngx/pull/14442))
- Enhancement: AI chat UX tweaks [@shamoon](https://github.com/shamoon) ([#14415](https://github.com/paperless-ngx/paperless-ngx/pull/14415))
- Enhancement: support markdown in AI chat [@shamoon](https://github.com/shamoon) ([#14409](https://github.com/paperless-ngx/paperless-ngx/pull/14409))
### Bug Fixes
- Fix: truncate long version labels in dropdown [@shamoon](https://github.com/shamoon) ([#14578](https://github.com/paperless-ngx/paperless-ngx/pull/14578))
- Fixhancement: show better result for revoked / expired tasks [@shamoon](https://github.com/shamoon) ([#14566](https://github.com/paperless-ngx/paperless-ngx/pull/14566))
- Fix: move re-index after workflow runs on doc edit [@shamoon](https://github.com/shamoon) ([#14558](https://github.com/paperless-ngx/paperless-ngx/pull/14558))
- Fix: folder export with --delete fails on a stale manifest.json.tmp [@benpetty](https://github.com/benpetty) ([#14526](https://github.com/paperless-ngx/paperless-ngx/pull/14526))
- Fix: fix list type for api schema document object filters [@shamoon](https://github.com/shamoon) ([#14524](https://github.com/paperless-ngx/paperless-ngx/pull/14524))
- Fix: standardize some dropdown sizing [@shamoon](https://github.com/shamoon) ([#14510](https://github.com/paperless-ngx/paperless-ngx/pull/14510))
- Fix: prevent doc permissions queries on schema view [@shamoon](https://github.com/shamoon) ([#14503](https://github.com/paperless-ngx/paperless-ngx/pull/14503))
- Fix: dont defer tag assignment when barcode will not result in a split [@shamoon](https://github.com/shamoon) ([#14497](https://github.com/paperless-ngx/paperless-ngx/pull/14497))
### Dependencies
<details>
<summary>6 changes</summary>
- Chore: bump some frontend deps for security fixes [@shamoon](https://github.com/shamoon) ([#14581](https://github.com/paperless-ngx/paperless-ngx/pull/14581))
- Chore: bump brotli, djangorestframework, filelock, msgpack, setuptools for security fixes [@stumpylog](https://github.com/stumpylog) ([#14575](https://github.com/paperless-ngx/paperless-ngx/pull/14575))
- Chore: bump django-allauth to 65.20 and oauthlib to 4.0 [@stumpylog](https://github.com/stumpylog) ([#14570](https://github.com/paperless-ngx/paperless-ngx/pull/14570))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14517](https://github.com/paperless-ngx/paperless-ngx/pull/14517))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14493](https://github.com/paperless-ngx/paperless-ngx/pull/14493))
- Chore: Drop ImageMagik as a dependency [@stumpylog](https://github.com/stumpylog) ([#14440](https://github.com/paperless-ngx/paperless-ngx/pull/14440))
</details>
### All App Changes
<details>
<summary>35 changes</summary>
- Chore: Consolidate test sample files [@stumpylog](https://github.com/stumpylog) ([#14430](https://github.com/paperless-ngx/paperless-ngx/pull/14430))
- Chore: bump some frontend deps for security fixes [@shamoon](https://github.com/shamoon) ([#14581](https://github.com/paperless-ngx/paperless-ngx/pull/14581))
- Enhancement: support custom fields in workflow title templates [@shamoon](https://github.com/shamoon) ([#14564](https://github.com/paperless-ngx/paperless-ngx/pull/14564))
- Enhancement: relative date custom field queries [@shamoon](https://github.com/shamoon) ([#14549](https://github.com/paperless-ngx/paperless-ngx/pull/14549))
- Fix: truncate long version labels in dropdown [@shamoon](https://github.com/shamoon) ([#14578](https://github.com/paperless-ngx/paperless-ngx/pull/14578))
- Chore: bump brotli, djangorestframework, filelock, msgpack, setuptools for security fixes [@stumpylog](https://github.com/stumpylog) ([#14575](https://github.com/paperless-ngx/paperless-ngx/pull/14575))
- Chore: bump django-allauth to 65.20 and oauthlib to 4.0 [@stumpylog](https://github.com/stumpylog) ([#14570](https://github.com/paperless-ngx/paperless-ngx/pull/14570))
- Fixhancement: show better result for revoked / expired tasks [@shamoon](https://github.com/shamoon) ([#14566](https://github.com/paperless-ngx/paperless-ngx/pull/14566))
- Fix: move re-index after workflow runs on doc edit [@shamoon](https://github.com/shamoon) ([#14558](https://github.com/paperless-ngx/paperless-ngx/pull/14558))
- Performance: reduce tasks badge load times [@JanKleine](https://github.com/JanKleine) ([#14552](https://github.com/paperless-ngx/paperless-ngx/pull/14552))
- Enhancement: skip local 2FA for trusted OIDC AMR [@shamoon](https://github.com/shamoon) ([#14403](https://github.com/paperless-ngx/paperless-ngx/pull/14403))
- Chore: consolidate direct permission grant lookups into one helper [@stumpylog](https://github.com/stumpylog) ([#14456](https://github.com/paperless-ngx/paperless-ngx/pull/14456))
- Chore: drop get\_groups\_with\_only\_permission [@stumpylog](https://github.com/stumpylog) ([#14454](https://github.com/paperless-ngx/paperless-ngx/pull/14454))
- Chore: Consolidate to a single way to set permission on an object [@stumpylog](https://github.com/stumpylog) ([#14453](https://github.com/paperless-ngx/paperless-ngx/pull/14453))
- Chore: Refactor bulk edit to remove PDF knowledge [@stumpylog](https://github.com/stumpylog) ([#14445](https://github.com/paperless-ngx/paperless-ngx/pull/14445))
- Fix: folder export with --delete fails on a stale manifest.json.tmp [@benpetty](https://github.com/benpetty) ([#14526](https://github.com/paperless-ngx/paperless-ngx/pull/14526))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14517](https://github.com/paperless-ngx/paperless-ngx/pull/14517))
- Fix: fix list type for api schema document object filters [@shamoon](https://github.com/shamoon) ([#14524](https://github.com/paperless-ngx/paperless-ngx/pull/14524))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14493](https://github.com/paperless-ngx/paperless-ngx/pull/14493))
- Chore: drop get\_objects\_for\_user\_owner\_aware [@stumpylog](https://github.com/stumpylog) ([#14457](https://github.com/paperless-ngx/paperless-ngx/pull/14457))
- Chore: update Angular components to 22.2.2 [@shamoon](https://github.com/shamoon) ([#14515](https://github.com/paperless-ngx/paperless-ngx/pull/14515))
- Chore: share one base class for the method-mapped permission classes [@stumpylog](https://github.com/stumpylog) ([#14458](https://github.com/paperless-ngx/paperless-ngx/pull/14458))
- Fix: standardize some dropdown sizing [@shamoon](https://github.com/shamoon) ([#14510](https://github.com/paperless-ngx/paperless-ngx/pull/14510))
- Chore: Drop ImageMagik as a dependency [@stumpylog](https://github.com/stumpylog) ([#14440](https://github.com/paperless-ngx/paperless-ngx/pull/14440))
- Feature: Generate PDF thumbnails via poppler utils [@stumpylog](https://github.com/stumpylog) ([#14420](https://github.com/paperless-ngx/paperless-ngx/pull/14420))
- Fix: prevent doc permissions queries on schema view [@shamoon](https://github.com/shamoon) ([#14503](https://github.com/paperless-ngx/paperless-ngx/pull/14503))
- Enhancement: add upload count to button, link to tray, reduce notifications [@shamoon](https://github.com/shamoon) ([#14501](https://github.com/paperless-ngx/paperless-ngx/pull/14501))
- Enhancement: notifications improvements [@shamoon](https://github.com/shamoon) ([#14500](https://github.com/paperless-ngx/paperless-ngx/pull/14500))
- Enhancement: doc details ux improvements [@shamoon](https://github.com/shamoon) ([#14485](https://github.com/paperless-ngx/paperless-ngx/pull/14485))
- Enhancement (QoL): allow independently toggling child + parent tags in WF tag fields [@shamoon](https://github.com/shamoon) ([#14400](https://github.com/paperless-ngx/paperless-ngx/pull/14400))
- Tweak: drop the user dropdown caret [@shamoon](https://github.com/shamoon) ([#14444](https://github.com/paperless-ngx/paperless-ngx/pull/14444))
- Enhancement: navbar upload button [@shamoon](https://github.com/shamoon) ([#14442](https://github.com/paperless-ngx/paperless-ngx/pull/14442))
- Enhancement: AI chat UX tweaks [@shamoon](https://github.com/shamoon) ([#14415](https://github.com/paperless-ngx/paperless-ngx/pull/14415))
- Enhancement: support markdown in AI chat [@shamoon](https://github.com/shamoon) ([#14409](https://github.com/paperless-ngx/paperless-ngx/pull/14409))
- Fix: dont defer tag assignment when barcode will not result in a split [@shamoon](https://github.com/shamoon) ([#14497](https://github.com/paperless-ngx/paperless-ngx/pull/14497))
</details>
## paperless-ngx 3.3.4
### All App Changes
+3 -4
View File
@@ -1,6 +1,6 @@
[project]
name = "paperless-ngx"
version = "3.3.4"
version = "3.4.0"
description = """\
A community-supported supercharged document management system: scan, index and archive all your physical documents\
"""
@@ -38,7 +38,6 @@ dependencies = [
"django-multiselectfield~=1.0.1",
"django-rich~=2.2.0",
"django-soft-delete~=1.0.18",
"django-treenode>=0.24",
"djangorestframework~=3.16",
"drf-spectacular~=0.30",
"drf-spectacular-sidecar>=2026.7.1,<2026.10",
@@ -256,8 +255,8 @@ isort.force-single-line = true
[tool.codespell]
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober,commitish,NIN,nin,reprot"
skip = """\
src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/paperless/tests/samples\
/mail/*,src/documents/tests/samples/*,*.po,*.json\
src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless/tests/samples/mail/*,src/documents/tests/samples\
/*,src/paperless_testing/sample_files/*,*.po,*.json\
"""
write-changes = true
+3 -1
View File
@@ -134,7 +134,9 @@ def seed_database() -> None:
originals.mkdir(parents=True)
thumbnails = Path(os.environ["PAPERLESS_MEDIA_ROOT"]) / "documents" / "thumbnails"
thumbnails.mkdir(parents=True)
sample_pdf = SOURCE_ROOT / "documents" / "tests" / "samples" / "simple.pdf"
sample_pdf = (
SOURCE_ROOT / "paperless_testing" / "sample_files" / "simple-digital.pdf"
)
for document in documents:
shutil.copyfile(sample_pdf, originals / document.filename)
shutil.copyfile(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "paperless-ngx-ui",
"version": "3.3.4",
"version": "3.4.0",
"scripts": {
"preinstall": "npx only-allow pnpm",
"ng": "ng",
+228 -35
View File
@@ -4,6 +4,10 @@ settings:
autoInstallPeers: false
excludeLinksFromLockfile: false
overrides:
vite@>=8.3.0 <8.3.3: ^8.3.3
zod@<4.6.6: ^4.6.6
importers:
.:
@@ -1650,6 +1654,9 @@ packages:
'@oxc-project/types@0.150.0':
resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==}
'@oxc-project/types@0.155.0':
resolution: {integrity: sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==}
'@parcel/watcher-android-arm64@2.6.0':
resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==}
engines: {node: '>= 10.0.0'}
@@ -1748,42 +1755,85 @@ packages:
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
'@rolldown/binding-android-arm-eabi@1.2.15':
resolution: {integrity: sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@rolldown/binding-android-arm-eabi@1.2.8':
resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
'@rolldown/binding-android-arm64@1.2.15':
resolution: {integrity: sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-android-arm64@1.2.8':
resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.2.15':
resolution: {integrity: sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-arm64@1.2.8':
resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.2.15':
resolution: {integrity: sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.2.8':
resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.2.15':
resolution: {integrity: sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-freebsd-x64@1.2.8':
resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.2.15':
resolution: {integrity: sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm-gnueabihf@1.2.8':
resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.2.15':
resolution: {integrity: sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-gnu@1.2.8':
resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1791,6 +1841,13 @@ packages:
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.2.15':
resolution: {integrity: sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-arm64-musl@1.2.8':
resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1798,6 +1855,13 @@ packages:
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.2.15':
resolution: {integrity: sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-ppc64-gnu@1.2.8':
resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1805,6 +1869,13 @@ packages:
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.2.15':
resolution: {integrity: sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.2.8':
resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1812,6 +1883,13 @@ packages:
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.2.15':
resolution: {integrity: sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.2.8':
resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1819,6 +1897,13 @@ packages:
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.2.15':
resolution: {integrity: sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-x64-musl@1.2.8':
resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1826,18 +1911,36 @@ packages:
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.2.15':
resolution: {integrity: sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-openharmony-arm64@1.2.8':
resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-win32-arm64-msvc@1.2.15':
resolution: {integrity: sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-arm64-msvc@1.2.8':
resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.2.15':
resolution: {integrity: sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.2.8':
resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2132,7 +2235,7 @@ packages:
resolution: {integrity: sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
peerDependencies:
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
vite: ^8.3.3
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -2261,8 +2364,8 @@ packages:
peerDependencies:
'@popperjs/core': ^2.11.8
brace-expansion@2.1.4:
resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==}
brace-expansion@2.1.7:
resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==}
brace-expansion@5.0.12:
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
@@ -2597,8 +2700,8 @@ packages:
fast-string-width@3.0.2:
resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
fast-uri@3.1.8:
resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==}
fast-uri@3.1.9:
resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==}
fast-wrap-ansi@0.2.2:
resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
@@ -3029,8 +3132,8 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@3.15.1:
resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==}
js-yaml@3.15.2:
resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
hasBin: true
jsdom@26.1.0:
@@ -3280,6 +3383,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@3.3.20:
resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
napi-postinstall@0.3.4:
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -3510,6 +3618,10 @@ packages:
resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.31:
resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -3601,6 +3713,11 @@ packages:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
rolldown@1.2.15:
resolution: {integrity: sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
rolldown@1.2.8:
resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -3773,8 +3890,8 @@ packages:
resolution: {integrity: sha512-aBY19bn/XA+hKOKX0qL7Z0UfdBsbQvc9hn993U8ALUjzxCvDcuZqLoRXjGJrUARWAlwMnsRVD9sw2AFNOYvalA==}
engines: {node: '>=22'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
source-map-js@1.2.2:
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
engines: {node: '>=0.10.0'}
source-map-support@0.5.21:
@@ -4005,13 +4122,13 @@ packages:
resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==}
engines: {node: '>=18.12.0'}
vite@8.3.0:
resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==}
vite@8.3.5:
resolution: {integrity: sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.7.1
'@vitejs/devtools': ^0.7.1 || ^0.8.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
@@ -4168,8 +4285,8 @@ packages:
resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==}
engines: {node: '>=18'}
zod@4.6.5:
resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
zod@4.6.7:
resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==}
snapshots:
@@ -4369,7 +4486,7 @@ snapshots:
'@babel/core': 8.0.5
'@inquirer/confirm': 6.3.2(@types/node@26.6.2)
'@parcel/watcher': 2.6.0
'@vitejs/plugin-basic-ssl': 2.3.0(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1))
'@vitejs/plugin-basic-ssl': 2.3.0(vite@8.3.5(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1))
beasties: 0.5.4
browserslist: 4.29.1
chokidar: 5.0.0
@@ -4391,7 +4508,7 @@ snapshots:
tinyglobby: 0.2.17
tslib: 2.8.1
typescript: 6.0.3
vite: 8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)
vite: 8.3.5(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)
xxhash-wasm: 1.1.0
optionalDependencies:
'@angular/core': 22.2.2(@angular/compiler@22.2.2)(rxjs@7.8.2)
@@ -4437,7 +4554,7 @@ snapshots:
parse5-html-rewriting-stream: 8.0.1
semver: 7.8.5
yargs: 18.1.0
zod: 4.6.5
zod: 4.6.7
transitivePeerDependencies:
- '@types/node'
- chokidar
@@ -4481,7 +4598,7 @@ snapshots:
'@standard-schema/spec': 1.1.0
rxjs: 7.8.2
tslib: 2.8.1
zod: 4.6.5
zod: 4.6.7
'@angular/localize@22.2.2(@angular/compiler-cli@22.2.2(@angular/compiler@22.2.2)(typescript@6.0.3))(@angular/compiler@22.2.2)':
dependencies:
@@ -5118,7 +5235,7 @@ snapshots:
camelcase: 5.3.1
find-up: 4.1.0
get-package-type: 0.1.0
js-yaml: 3.15.1
js-yaml: 3.15.2
resolve-from: 5.0.0
'@istanbuljs/schema@0.1.6': {}
@@ -5412,12 +5529,12 @@ snapshots:
'@modelcontextprotocol/core@2.3.1':
dependencies:
zod: 4.6.5
zod: 4.6.7
'@modelcontextprotocol/server@2.3.1':
dependencies:
'@modelcontextprotocol/core': 2.3.1
zod: 4.6.5
zod: 4.6.7
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4':
optional: true
@@ -5652,6 +5769,8 @@ snapshots:
'@oxc-project/types@0.150.0': {}
'@oxc-project/types@0.155.0': {}
'@parcel/watcher-android-arm64@2.6.0':
optional: true
@@ -5719,48 +5838,93 @@ snapshots:
'@popperjs/core@2.11.8': {}
'@rolldown/binding-android-arm-eabi@1.2.15':
optional: true
'@rolldown/binding-android-arm-eabi@1.2.8':
optional: true
'@rolldown/binding-android-arm64@1.2.15':
optional: true
'@rolldown/binding-android-arm64@1.2.8':
optional: true
'@rolldown/binding-darwin-arm64@1.2.15':
optional: true
'@rolldown/binding-darwin-arm64@1.2.8':
optional: true
'@rolldown/binding-darwin-x64@1.2.15':
optional: true
'@rolldown/binding-darwin-x64@1.2.8':
optional: true
'@rolldown/binding-freebsd-x64@1.2.15':
optional: true
'@rolldown/binding-freebsd-x64@1.2.8':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.2.15':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.2.8':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.2.15':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.2.8':
optional: true
'@rolldown/binding-linux-arm64-musl@1.2.15':
optional: true
'@rolldown/binding-linux-arm64-musl@1.2.8':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.2.15':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.2.8':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.2.15':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.2.8':
optional: true
'@rolldown/binding-linux-x64-gnu@1.2.15':
optional: true
'@rolldown/binding-linux-x64-gnu@1.2.8':
optional: true
'@rolldown/binding-linux-x64-musl@1.2.15':
optional: true
'@rolldown/binding-linux-x64-musl@1.2.8':
optional: true
'@rolldown/binding-openharmony-arm64@1.2.15':
optional: true
'@rolldown/binding-openharmony-arm64@1.2.8':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.2.15':
optional: true
'@rolldown/binding-win32-arm64-msvc@1.2.8':
optional: true
'@rolldown/binding-win32-x64-msvc@1.2.15':
optional: true
'@rolldown/binding-win32-x64-msvc@1.2.8':
optional: true
@@ -6051,9 +6215,9 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.12.2':
optional: true
'@vitejs/plugin-basic-ssl@2.3.0(vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1))':
'@vitejs/plugin-basic-ssl@2.3.0(vite@8.3.5(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1))':
dependencies:
vite: 8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)
vite: 8.3.5(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1)
acorn-jsx@5.3.2(acorn@8.18.0):
dependencies:
@@ -6083,7 +6247,7 @@ snapshots:
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.8
fast-uri: 3.1.9
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
@@ -6198,7 +6362,7 @@ snapshots:
dependencies:
'@popperjs/core': 2.11.8
brace-expansion@2.1.4:
brace-expansion@2.1.7:
dependencies:
balanced-match: 1.0.2
@@ -6543,7 +6707,7 @@ snapshots:
dependencies:
fast-string-truncated-width: 3.0.3
fast-uri@3.1.8: {}
fast-uri@3.1.9: {}
fast-wrap-ansi@0.2.2:
dependencies:
@@ -7193,7 +7357,7 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@3.15.1:
js-yaml@3.15.2:
dependencies:
argparse: 1.0.10
esprima: 4.0.1
@@ -7393,7 +7557,7 @@ snapshots:
minimatch@9.0.9:
dependencies:
brace-expansion: 2.1.4
brace-expansion: 2.1.7
minimist@1.2.8: {}
@@ -7428,6 +7592,8 @@ snapshots:
nanoid@3.3.18: {}
nanoid@3.3.20: {}
napi-postinstall@0.3.4: {}
natural-compare@1.4.0: {}
@@ -7674,7 +7840,13 @@ snapshots:
dependencies:
nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
source-map-js: 1.2.2
postcss@8.5.31:
dependencies:
nanoid: 3.3.20
picocolors: 1.1.1
source-map-js: 1.2.2
prelude-ls@1.2.1: {}
@@ -7742,6 +7914,27 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
rolldown@1.2.15:
dependencies:
'@oxc-project/types': 0.155.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm-eabi': 1.2.15
'@rolldown/binding-android-arm64': 1.2.15
'@rolldown/binding-darwin-arm64': 1.2.15
'@rolldown/binding-darwin-x64': 1.2.15
'@rolldown/binding-freebsd-x64': 1.2.15
'@rolldown/binding-linux-arm-gnueabihf': 1.2.15
'@rolldown/binding-linux-arm64-gnu': 1.2.15
'@rolldown/binding-linux-arm64-musl': 1.2.15
'@rolldown/binding-linux-ppc64-gnu': 1.2.15
'@rolldown/binding-linux-s390x-gnu': 1.2.15
'@rolldown/binding-linux-x64-gnu': 1.2.15
'@rolldown/binding-linux-x64-musl': 1.2.15
'@rolldown/binding-openharmony-arm64': 1.2.15
'@rolldown/binding-win32-arm64-msvc': 1.2.15
'@rolldown/binding-win32-x64-msvc': 1.2.15
rolldown@1.2.8:
dependencies:
'@oxc-project/types': 0.149.0
@@ -7862,7 +8055,7 @@ snapshots:
dependencies:
chokidar: 5.0.0
immutable: 5.1.9
source-map-js: 1.2.1
source-map-js: 1.2.2
optionalDependencies:
'@parcel/watcher': 2.6.0
@@ -7891,7 +8084,7 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
source-map-js@1.2.1: {}
source-map-js@1.2.2: {}
source-map-support@0.5.21:
dependencies:
@@ -8118,12 +8311,12 @@ snapshots:
verkit@0.3.2: {}
vite@8.3.0(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1):
vite@8.3.5(@types/node@26.6.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.104.1)(sass@1.104.1):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
postcss: 8.5.28
rolldown: 1.2.8
postcss: 8.5.31
rolldown: 1.2.15
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.6.2
@@ -8241,4 +8434,4 @@ snapshots:
yoctocolors@2.2.0: {}
zod@4.6.5: {}
zod@4.6.7: {}
+3
View File
@@ -24,3 +24,6 @@ onlyBuiltDependencies:
ignoredBuiltDependencies:
- normalize-diacritics
- unrs-resolver
overrides:
"vite@>=8.3.0 <8.3.3": "^8.3.3"
"zod@<4.6.6": "^4.6.6"
+1 -1
View File
@@ -8,7 +8,7 @@ export const environment = {
apiVersion: '10', // match src/paperless/settings.py
appTitle: DEFAULT_APP_TITLE,
tag: 'prod',
version: '3.3.4',
version: '3.4.0',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+38
View File
@@ -11,6 +11,8 @@ from typing import TYPE_CHECKING
import pytest
from paperless_testing import samples
if TYPE_CHECKING:
from collections.abc import Generator
from pathlib import Path
@@ -149,3 +151,39 @@ def fake_progress_manager(
monkeypatch.setattr("documents.tasks.ProgressManager", FakeProgressManager)
return FakeProgressManager
@pytest.fixture(scope="session")
def shared_samples_dir() -> Path:
"""Directory of sample files used by more than one app's tests."""
return samples.SHARED_SAMPLES_DIR
@pytest.fixture(scope="session")
def simple_digital_pdf_file() -> Path:
"""One-page PDF with a text layer."""
return samples.SIMPLE_DIGITAL_PDF
@pytest.fixture(scope="session")
def multi_page_digital_pdf_file() -> Path:
"""Three-page PDF with a text layer."""
return samples.MULTI_PAGE_DIGITAL_PDF
@pytest.fixture(scope="session")
def with_form_pdf_file() -> Path:
"""PDF containing a fillable form."""
return samples.WITH_FORM_PDF
@pytest.fixture(scope="session")
def multi_page_images_pdf_file() -> Path:
"""Multi-page PDF of scanned images, no text layer."""
return samples.MULTI_PAGE_IMAGES_PDF
@pytest.fixture(scope="session")
def thumbnail_webp_file() -> Path:
"""Small WebP thumbnail."""
return samples.THUMBNAIL_WEBP
+1 -2
View File
@@ -1,7 +1,6 @@
from django.conf import settings
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from treenode.admin import TreeNodeModelAdmin
from documents.models import Correspondent
from documents.models import CustomField
@@ -29,7 +28,7 @@ class CorrespondentAdmin(GuardedModelAdmin):
list_editable = ("match", "matching_algorithm")
class TagAdmin(GuardedModelAdmin, TreeNodeModelAdmin):
class TagAdmin(GuardedModelAdmin):
list_display = ("name", "color", "match", "matching_algorithm")
list_filter = ("matching_algorithm",)
list_editable = ("color", "match", "matching_algorithm")
+7 -13
View File
@@ -255,19 +255,13 @@ def modify_tags(
affected_docs = list(qs.values_list("pk", flat=True))
DocumentTagRelationship = Document.tags.through
# add with all ancestors
expanded_add_tags: set[int] = set()
add_tag_objects = Tag.objects.filter(pk__in=add_tags)
for t in add_tag_objects:
expanded_add_tags.add(int(t.id))
expanded_add_tags.update(int(pk) for pk in t.get_ancestors_pks())
# remove with all descendants
expanded_remove_tags: set[int] = set()
remove_tag_objects = Tag.objects.filter(pk__in=remove_tags)
for t in remove_tag_objects:
expanded_remove_tags.add(int(t.id))
expanded_remove_tags.update(int(pk) for pk in t.get_descendants_pks())
hierarchy = Tag.get_hierarchy()
expanded_add_tags = hierarchy.with_ancestors(
Tag.objects.filter(pk__in=add_tags).values_list("pk", flat=True),
)
expanded_remove_tags = hierarchy.with_descendants(
Tag.objects.filter(pk__in=remove_tags).values_list("pk", flat=True),
)
with transaction.atomic():
if expanded_remove_tags:
@@ -108,6 +108,24 @@ def _deserialize_record(
) from e
for field_name, field_value in record.get("fields", {}).items():
# Older exports include django-treenode's derived bookkeeping. The
# parent FK is authoritative; accept only these known removed fields.
if Model is Tag and field_name in {
"tn_ancestors_count",
"tn_ancestors_pks",
"tn_children_count",
"tn_children_pks",
"tn_depth",
"tn_descendants_count",
"tn_descendants_pks",
"tn_index",
"tn_level",
"tn_order",
"tn_priority",
"tn_siblings_count",
"tn_siblings_pks",
}:
continue
field = Model._meta.get_field(field_name)
remote = field.remote_field
@@ -0,0 +1,64 @@
# Generated by Django 5.2.17 on 2026-10-28 22:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("documents", "0027_documentbarcode"),
]
operations = [
migrations.RemoveField(
model_name="tag",
name="tn_ancestors_count",
),
migrations.RemoveField(
model_name="tag",
name="tn_ancestors_pks",
),
migrations.RemoveField(
model_name="tag",
name="tn_children_count",
),
migrations.RemoveField(
model_name="tag",
name="tn_children_pks",
),
migrations.RemoveField(
model_name="tag",
name="tn_depth",
),
migrations.RemoveField(
model_name="tag",
name="tn_descendants_count",
),
migrations.RemoveField(
model_name="tag",
name="tn_descendants_pks",
),
migrations.RemoveField(
model_name="tag",
name="tn_index",
),
migrations.RemoveField(
model_name="tag",
name="tn_level",
),
migrations.RemoveField(
model_name="tag",
name="tn_order",
),
migrations.RemoveField(
model_name="tag",
name="tn_priority",
),
migrations.RemoveField(
model_name="tag",
name="tn_siblings_count",
),
migrations.RemoveField(
model_name="tag",
name="tn_siblings_pks",
),
]
+53 -23
View File
@@ -14,7 +14,6 @@ from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from multiselectfield import MultiSelectField
from treenode.models import TreeNodeModel
if settings.AUDIT_LOG_ENABLED:
from auditlog.registry import auditlog
@@ -100,7 +99,17 @@ class Correspondent(MatchingModel):
verbose_name_plural = _("correspondents")
class Tag(MatchingModel, TreeNodeModel):
class Tag(MatchingModel):
# Keep the existing column and reverse relation during the transition.
tn_parent = models.ForeignKey(
"self",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="tn_children",
verbose_name=_("Parent"),
)
color = models.CharField(_("color"), max_length=7, default="#a6cee3")
# Maximum allowed nesting depth for tags (root = 1, max depth = 5)
MAX_NESTING_DEPTH: Final[int] = 5
@@ -114,27 +123,51 @@ class Tag(MatchingModel, TreeNodeModel):
),
)
class Meta(MatchingModel.Meta, TreeNodeModel.Meta):
class Meta(MatchingModel.Meta):
verbose_name = _("tag")
verbose_name_plural = _("tags")
@classmethod
def get_hierarchy(cls):
from documents.tag_hierarchy import TagHierarchy
return TagHierarchy(cls.objects.values_list("pk", "tn_parent_id"))
@property
def parent_pk(self):
return self.tn_parent_id
def get_parent(self):
return self.tn_parent
def get_ancestors_pks(self):
return self.get_hierarchy().ancestors(self.tn_parent_id)
def get_ancestors(self):
ids = self.get_ancestors_pks()
tags = self.__class__.objects.in_bulk(ids)
return [tags[pk] for pk in ids]
def get_descendants_pks(self):
return self.get_hierarchy().descendants([self.pk])
def get_descendants(self):
return self.__class__.objects.filter(pk__in=self.get_descendants_pks())
def get_children_queryset(self):
return self.__class__.objects.filter(tn_parent_id=self.pk)
def clean(self) -> None:
# Prevent self-parenting and assigning a descendant as parent
parent = self.get_parent()
if parent == self:
raise ValidationError({"parent": _("Cannot set itself as parent.")})
if parent and self.pk is not None and self.is_ancestor_of(parent):
raise ValidationError({"parent": _("Cannot set parent to a descendant.")})
# Enforce maximum nesting depth
new_parent_depth = 0
if parent:
new_parent_depth = parent.get_ancestors_count() + 1
height = 0 if self.pk is None else self.get_depth()
deepest_new_depth = (new_parent_depth + 1) + height
if deepest_new_depth > self.MAX_NESTING_DEPTH:
raise ValidationError({"parent": _("Maximum nesting depth exceeded.")})
hierarchy = self.get_hierarchy()
if self.pk is not None and self.tn_parent_id == self.pk:
raise ValidationError({"tn_parent": _("Cannot set itself as parent.")})
ancestors = hierarchy.ancestors(self.tn_parent_id)
if self.pk is not None and self.pk in ancestors:
raise ValidationError(
{"tn_parent": _("Cannot set parent to a descendant.")},
)
if len(ancestors) + 1 + hierarchy.height(self.pk) > self.MAX_NESTING_DEPTH:
raise ValidationError({"tn_parent": _("Maximum nesting depth exceeded.")})
return super().clean()
@@ -539,10 +572,7 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
return self.created
def add_nested_tags(self, tags) -> None:
tag_ids = set()
for tag in tags:
tag_ids.add(tag.id)
tag_ids.update(tag.get_ancestors_pks())
tag_ids = Tag.get_hierarchy().with_ancestors(tag.pk for tag in tags)
tags_to_add = self.tags.model.objects.filter(id__in=tag_ids)
self.tags.add(*tags_to_add)
+28 -50
View File
@@ -589,7 +589,7 @@ class TagSerializer(MatchingModelSerializer, OwnedObjectSerializer):
text_color = serializers.SerializerMethodField()
# map to treenode's tn_parent
# Preserve the public API parent field.
parent = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
allow_null=True,
@@ -692,7 +692,9 @@ class TagSerializer(MatchingModelSerializer, OwnedObjectSerializer):
self.instance.clean()
except ValidationError as e:
logger.debug("Tag parent validation failed: %s", e)
raise e
raise serializers.ValidationError(
{"parent": e.message_dict["tn_parent"]},
) from e
finally:
self.instance.tn_parent = original_parent
else:
@@ -702,7 +704,9 @@ class TagSerializer(MatchingModelSerializer, OwnedObjectSerializer):
temp.clean()
except ValidationError as e:
logger.debug("Tag parent validation failed: %s", e)
raise e
raise serializers.ValidationError(
{"parent": e.message_dict["tn_parent"]},
) from e
return super().validate(attrs)
@@ -1198,61 +1202,35 @@ class DocumentSerializer(
doc_id,
)
if "tags" in validated_data:
# Respect tag hierarchy on updates:
# - Adding a child adds its ancestors
# - Removing a parent removes all its descendants
prev_tags = set(instance.tags.all())
requested_tags = set(validated_data["tags"])
hierarchy = Tag.get_hierarchy()
previous_ids = set(instance.tags.values_list("pk", flat=True))
requested_ids = {tag.pk for tag in validated_data["tags"]}
required_ids = hierarchy.with_ancestors(requested_ids - previous_ids)
blocked_ids = hierarchy.with_descendants(previous_ids - requested_ids)
blocked_ids.difference_update(required_ids)
final_ids = hierarchy.with_ancestors(requested_ids) - blocked_ids
validated_data["tags"] = list(Tag.objects.filter(pk__in=final_ids))
# Tags newly added in this update and the ancestors they require
added_tags = requested_tags - prev_tags
required_by_add_tags = set(added_tags)
for t in added_tags:
required_by_add_tags.update(t.get_ancestors())
# Tags being removed in this update and all descendants, except
# those required by a tag that is being added in this same update
removed_tags = prev_tags - requested_tags
blocked_tags = set(removed_tags)
for t in removed_tags:
blocked_tags.update(t.get_descendants())
blocked_tags.difference_update(required_by_add_tags)
# Add all parent tags
final_tags = set(requested_tags)
for t in requested_tags:
final_tags.update(t.get_ancestors())
# Drop removed parents and their descendants
final_tags.difference_update(blocked_tags)
validated_data["tags"] = list(final_tags)
if validated_data.get("remove_inbox_tags"):
current_tag_ids = {t.pk for t in instance.tags.all()}
hierarchy = Tag.get_hierarchy()
tags = (
validated_data["tags"]
if "tags" in validated_data
else list(instance.tags.all())
)
# Tags newly added in this update, plus their ancestors, are kept
keep_ids: set[int] = set()
for tag in tags:
if tag.pk not in current_tag_ids:
keep_ids.add(tag.pk)
keep_ids.update(int(pk) for pk in tag.get_ancestors_pks())
# Remove inbox tags and their descendants, except those being kept
remove_ids: set[int] = set()
for inbox_tag in (
current_ids = set(instance.tags.values_list("pk", flat=True))
keep_ids = hierarchy.with_ancestors(
tag.pk for tag in tags if tag.pk not in current_ids
)
inbox_ids = (
Tag.objects.filter(is_inbox_tag=True)
.exclude(pk__in=keep_ids)
.only("pk", "tn_descendants_pks")
):
remove_ids.add(inbox_tag.pk)
remove_ids.update(int(pk) for pk in inbox_tag.get_descendants_pks())
validated_data["tags"] = [t for t in tags if t.pk not in remove_ids]
.exclude(
pk__in=keep_ids,
)
.values_list("pk", flat=True)
)
remove_ids = hierarchy.with_descendants(inbox_ids) - keep_ids
validated_data["tags"] = [tag for tag in tags if tag.pk not in remove_ids]
if settings.AUDIT_LOG_ENABLED:
with set_actor(self.user):
+61
View File
@@ -0,0 +1,61 @@
"""An operation-local snapshot of tag edges, with no persistent derived fields.
Never cache this globally: tasks, bulk updates and other processes can change
parents. Callers traversing multiple tags should share one snapshot.
"""
from collections import defaultdict
from collections.abc import Iterable
class TagHierarchy:
def __init__(self, edges: Iterable[tuple[int, int | None]]) -> None:
self.parents = dict(edges)
self.children: dict[int | None, list[int]] = defaultdict(list)
for pk, parent in self.parents.items():
self.children[parent].append(pk)
def ancestors(self, parent_id: int | None) -> list[int]:
result = []
seen = set()
while parent_id is not None and parent_id not in seen:
seen.add(parent_id)
result.append(parent_id)
parent_id = self.parents.get(parent_id)
return result[::-1]
def with_ancestors(self, roots: Iterable[int]) -> set[int]:
result = set(roots)
for pk in list(result):
result.update(self.ancestors(self.parents.get(pk)))
return result
def with_descendants(self, roots: Iterable[int]) -> set[int]:
roots = set(roots)
return roots | self.descendants(roots)
def descendants(self, roots: Iterable[int]) -> set[int]:
roots = {pk for pk in roots if pk is not None}
seen = set(roots)
pending = list(roots)
while pending:
for child in self.children.get(pending.pop(), ()):
if child not in seen:
seen.add(child)
pending.append(child)
return seen - roots
def height(self, pk: int | None) -> int:
if pk is None:
return 0
seen = {pk}
pending = [(pk, 0)]
height = 0
while pending:
parent, depth = pending.pop()
height = max(height, depth)
for child in self.children.get(parent, ()):
if child not in seen:
seen.add(child)
pending.append((child, depth + 1))
return height
+6 -10
View File
@@ -12,29 +12,25 @@ if TYPE_CHECKING:
from paperless_testing.dirs import PaperlessDirs
@pytest.fixture(scope="session")
def document_samples_dir() -> Path:
"""Path to the shared test sample documents."""
return Path(__file__).parent / "samples" / "documents"
@pytest.fixture()
def sample_doc(
paperless_dirs: "PaperlessDirs",
document_samples_dir: Path,
simple_digital_pdf_file: Path,
multi_page_images_pdf_file: Path,
thumbnail_webp_file: Path,
) -> "Document":
"""Create a document with valid files and matching checksums."""
with filelock.FileLock(paperless_dirs.media_lock):
shutil.copy(
document_samples_dir / "originals" / "0000001.pdf",
simple_digital_pdf_file,
paperless_dirs.originals_dir / "0000001.pdf",
)
shutil.copy(
document_samples_dir / "archive" / "0000001.pdf",
multi_page_images_pdf_file,
paperless_dirs.archive_dir / "0000001.pdf",
)
shutil.copy(
document_samples_dir / "thumbnails" / "0000001.webp",
thumbnail_webp_file,
paperless_dirs.thumbnail_dir / "0000001.webp",
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.
-1
View File
@@ -1 +0,0 @@
This is a test file.
@@ -53,7 +53,7 @@ class TestBulkDownload(DirectoriesMixin, SampleDirMixin, APITestCase):
archive_checksum="D",
)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", self.doc2.source_path)
shutil.copy(self.SIMPLE_PDF, self.doc2.source_path)
shutil.copy(self.SAMPLE_DIR / "simple.png", self.doc2b.source_path)
shutil.copy(self.SAMPLE_DIR / "simple.jpg", self.doc3.source_path)
shutil.copy(self.SAMPLE_DIR / "test_with_bom.pdf", self.doc3.archive_path)
+49 -33
View File
@@ -57,6 +57,8 @@ from paperless_testing.http import read_streaming_response
from paperless_testing.permissions import grant_all_global
from paperless_testing.permissions import grant_global
from paperless_testing.permissions import grant_object
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
from paperless_testing.samples import THUMBNAIL_WEBP
class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
@@ -1917,7 +1919,11 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SimpleUploadedFile(
"simple.pdf",
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
) as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f},
@@ -1945,7 +1951,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
payload = SimpleUploadedFile(
"../../outside.pdf",
(Path(__file__).parent / "samples" / "simple.pdf").read_bytes(),
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
)
@@ -1974,7 +1980,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
pdf_bytes = (Path(__file__).parent / "samples" / "simple.pdf").read_bytes()
pdf_bytes = SIMPLE_DIGITAL_PDF.read_bytes()
boundary = "paperless-boundary"
payload = (
(
@@ -2050,7 +2056,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
def test_upload_insufficient_permissions(self) -> None:
self.client.force_authenticate(user=UserFactory(username="testuser2"))
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f},
@@ -2063,7 +2069,11 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SimpleUploadedFile(
"simple.pdf",
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
) as f:
response = self.client.post(
"/api/documents/post_document/",
{
@@ -2096,7 +2106,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"documenst": f},
@@ -2122,7 +2132,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "title": "my custom title"},
@@ -2142,7 +2152,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
)
c = Correspondent.objects.create(name="test-corres")
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "correspondent": c.id},
@@ -2161,7 +2171,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "correspondent": 3456},
@@ -2176,7 +2186,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
)
dt = DocumentType.objects.create(name="invoice")
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "document_type": dt.id},
@@ -2195,7 +2205,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "document_type": 34578},
@@ -2210,7 +2220,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
)
sp = StoragePath.objects.create(name="invoices")
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "storage_path": sp.id},
@@ -2229,7 +2239,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "storage_path": 34578},
@@ -2245,7 +2255,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
t1 = Tag.objects.create(name="tag1")
t2 = Tag.objects.create(name="tag2")
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "tags": [t2.id, t1.id]},
@@ -2266,7 +2276,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
t1 = Tag.objects.create(name="tag1")
t2 = Tag.objects.create(name="tag2")
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "tags": [t2.id, t1.id, 734563]},
@@ -2290,7 +2300,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
0,
tzinfo=zoneinfo.ZoneInfo("America/Los_Angeles"),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "created": created},
@@ -2306,7 +2316,11 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SimpleUploadedFile(
"simple.pdf",
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
) as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "archive_serial_number": 500},
@@ -2333,7 +2347,11 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
data_type=CustomField.FieldDataType.STRING,
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SimpleUploadedFile(
"simple.pdf",
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
) as f:
response = self.client.post(
"/api/documents/post_document/",
{
@@ -2388,7 +2406,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
w1.actions.add(action1)
w1.save()
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{
@@ -2429,7 +2447,11 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
data_type=CustomField.FieldDataType.INT,
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SimpleUploadedFile(
"simple.pdf",
SIMPLE_DIGITAL_PDF.read_bytes(),
content_type="application/pdf",
) as f:
response = self.client.post(
"/api/documents/post_document/",
{
@@ -2478,7 +2500,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
]
for payload in error_payloads:
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
data = {"document": f, **payload}
response = self.client.post(
"/api/documents/post_document/",
@@ -2536,7 +2558,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
id=str(uuid.uuid4()),
)
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
with SIMPLE_DIGITAL_PDF.open("rb") as f:
response = self.client.post(
"/api/documents/post_document/",
{"document": f, "from_webui": True},
@@ -2575,14 +2597,8 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
archive_filename="archive.pdf",
)
source_file: Path = (
Path(__file__).parent
/ "samples"
/ "documents"
/ "thumbnails"
/ "0000001.webp"
)
archive_file: Path = Path(__file__).parent / "samples" / "simple.pdf"
source_file: Path = THUMBNAIL_WEBP
archive_file: Path = SIMPLE_DIGITAL_PDF
shutil.copy(source_file, doc.source_path)
shutil.copy(archive_file, doc.archive_path)
@@ -2615,7 +2631,7 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
mime_type="application/pdf",
)
shutil.copy(Path(__file__).parent / "samples" / "simple.pdf", doc.source_path)
shutil.copy(SIMPLE_DIGITAL_PDF, doc.source_path)
response = self.client.get(f"/api/documents/{doc.pk}/metadata/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
@@ -4381,8 +4397,8 @@ class TestDocumentApi(DirectoriesMixin, ConsumeTaskMixin, APITestCase):
filename="test2.pdf",
)
archive_file = Path(__file__).parent / "samples" / "simple.pdf"
source_file = Path(__file__).parent / "samples" / "simple.pdf"
archive_file = SIMPLE_DIGITAL_PDF
source_file = SIMPLE_DIGITAL_PDF
shutil.copy(archive_file, doc.archive_path)
shutil.copy(source_file, doc2.source_path)
+8 -5
View File
@@ -12,6 +12,9 @@ from documents.tests.utils import SampleDirMixin
from paperless_testing.dirs import DirectoriesMixin
from paperless_testing.factories import UserFactory
from paperless_testing.permissions import grant_global
from paperless_testing.samples import MULTI_PAGE_DIGITAL_PDF
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
from paperless_testing.samples import WITH_FORM_PDF
class TestEmail(DirectoriesMixin, SampleDirMixin, APITestCase):
@@ -42,15 +45,15 @@ class TestEmail(DirectoriesMixin, SampleDirMixin, APITestCase):
# Copy sample files to document paths (using different files to distinguish versions)
shutil.copy(
self.SAMPLE_DIR / "documents" / "originals" / "0000001.pdf",
SIMPLE_DIGITAL_PDF,
self.doc1.archive_path,
)
shutil.copy(
self.SAMPLE_DIR / "documents" / "originals" / "0000002.pdf",
MULTI_PAGE_DIGITAL_PDF,
self.doc1.source_path,
)
shutil.copy(
self.SAMPLE_DIR / "documents" / "originals" / "0000003.pdf",
WITH_FORM_PDF,
self.doc2.source_path,
)
@@ -377,7 +380,7 @@ class TestEmail(DirectoriesMixin, SampleDirMixin, APITestCase):
checksum="3",
filename="test3.pdf",
)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", doc3.source_path)
shutil.copy(self.SIMPLE_PDF, doc3.source_path)
doc4 = Document.objects.create(
title="test1",
@@ -386,7 +389,7 @@ class TestEmail(DirectoriesMixin, SampleDirMixin, APITestCase):
checksum="4",
filename="test4.pdf",
)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", doc4.source_path)
shutil.copy(self.SIMPLE_PDF, doc4.source_path)
response = self.client.post(
self.ENDPOINT,
+2 -2
View File
@@ -169,7 +169,7 @@ class TestBarcode(
- No barcodes detected
- No pages to split on
"""
test_file = self.SAMPLE_DIR / "simple.pdf"
test_file = self.SIMPLE_PDF
with self.get_reader(test_file) as reader:
reader.detect()
separator_page_numbers = reader.get_separation_pages()
@@ -461,7 +461,7 @@ class TestBarcode(
THEN:
- Nothing happens
"""
test_file = self.SAMPLE_DIR / "simple.pdf"
test_file = self.SIMPLE_PDF
with self.get_reader(test_file) as reader:
try:
+9 -30
View File
@@ -29,6 +29,9 @@ from documents.permissions import set_permissions_for_objects
from paperless_testing.dirs import DirectoriesMixin
from paperless_testing.factories import DocumentFactory
from paperless_testing.permissions import grant_object
from paperless_testing.samples import MULTI_PAGE_DIGITAL_PDF
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
from paperless_testing.samples import WITH_FORM_PDF
class TestBulkEdit(DirectoriesMixin, TestCase):
@@ -706,47 +709,27 @@ class TestPDFActions(DirectoriesMixin, TestCase):
super().setUp()
sample1 = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
sample1,
)
sample1_archive = self.dirs.archive_dir / "sample_archive.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
sample1_archive,
)
sample2 = self.dirs.scratch_dir / "sample2.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000002.pdf",
MULTI_PAGE_DIGITAL_PDF,
sample2,
)
sample2_archive = self.dirs.archive_dir / "sample2_archive.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000002.pdf",
MULTI_PAGE_DIGITAL_PDF,
sample2_archive,
)
sample3 = self.dirs.scratch_dir / "sample3.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000003.pdf",
WITH_FORM_PDF,
sample3,
)
self.doc1 = Document.objects.create(
@@ -780,11 +763,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
)
img_doc_archive = self.dirs.archive_dir / "sample_image.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
img_doc_archive,
)
self.img_doc = Document.objects.create(
+9 -28
View File
@@ -39,6 +39,9 @@ from paperless_testing.factories import TagFactory
from paperless_testing.factories import UserFactory
from paperless_testing.fakes.progress import FakeProgressManager
from paperless_testing.permissions import grant_object
from paperless_testing.samples import MULTI_PAGE_DIGITAL_PDF
from paperless_testing.samples import MULTI_PAGE_IMAGES_PDF
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
class _BaseNewStyleParser:
@@ -115,9 +118,7 @@ class _BaseNewStyleParser:
class DummyParser(_BaseNewStyleParser):
_ARCHIVE_SRC = (
Path(__file__).parent / "samples" / "documents" / "archive" / "0000001.pdf"
)
_ARCHIVE_SRC = MULTI_PAGE_IMAGES_PDF
def parse(self, document_path, mime_type, *, produce_archive: bool = True) -> None:
self._text = "The Text"
@@ -203,33 +204,19 @@ class TestConsumer(
self.addCleanup(patcher.stop)
def get_test_file(self):
src = (
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf"
)
src = SIMPLE_DIGITAL_PDF
dst = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(src, dst)
return dst
def get_test_file2(self):
src = (
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000002.pdf"
)
src = MULTI_PAGE_DIGITAL_PDF
dst = self.dirs.scratch_dir / "sample2.pdf"
shutil.copy(src, dst)
return dst
def get_test_archive_file(self):
src = (
Path(__file__).parent / "samples" / "documents" / "archive" / "0000001.pdf"
)
src = MULTI_PAGE_IMAGES_PDF
dst = self.dirs.scratch_dir / "sample_archive.pdf"
shutil.copy(src, dst)
return dst
@@ -1124,7 +1111,7 @@ class TestConsumer(
@mock.patch("documents.consumer.get_parser_registry")
def test_similar_filenames(self, m) -> None:
shutil.copy(
Path(__file__).parent / "samples" / "simple.pdf",
SIMPLE_DIGITAL_PDF,
settings.CONSUMPTION_DIR / "simple.pdf",
)
shutil.copy(
@@ -1665,13 +1652,7 @@ class TestConsumerRemoteOCR(
self.addCleanup(patcher.stop)
def _consume(self, *, overrides: DocumentMetadataOverrides | None = None) -> bool:
src = (
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf"
)
src = SIMPLE_DIGITAL_PDF
dst = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(src, dst)
+5 -2
View File
@@ -37,12 +37,15 @@ class TestDoubleSided(
self.double_sided_dir.mkdir()
self.staging_file = self.dirs.scratch_dir / STAGING_FILE_NAME
def _sample(self, name: str) -> Path:
return self.SIMPLE_PDF if name == "simple.pdf" else self.SAMPLE_DIR / name
def consume_file(self, srcname, dstname: str | Path = "foo.pdf"):
"""
Starts the consume process and also ensures the
destination file does not exist afterwards
"""
src = self.SAMPLE_DIR / srcname
src = self._sample(srcname)
dst = self.double_sided_dir / dstname
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(src, dst)
@@ -57,7 +60,7 @@ class TestDoubleSided(
return msg
def create_staging_file(self, src="double-sided-odd.pdf", datetime=None) -> None:
shutil.copy(self.SAMPLE_DIR / src, self.staging_file)
shutil.copy(self._sample(src), self.staging_file)
if datetime is None:
datetime = dt.datetime.now()
os.utime(str(self.staging_file), (datetime.timestamp(),) * 2)
+2 -1
View File
@@ -23,8 +23,9 @@ from documents.tasks import update_document_content_maybe_archive_file
from paperless_testing.assertions import FileSystemAssertsMixin
from paperless_testing.dirs import DirectoriesMixin
from paperless_testing.factories import DocumentFactory
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
sample_file: Path = Path(__file__).parent / "samples" / "simple.pdf"
sample_file: Path = SIMPLE_DIGITAL_PDF
@pytest.mark.management
+21 -80
View File
@@ -59,6 +59,7 @@ from paperless_testing.dirs import paperless_environment
from paperless_testing.factories import DocumentBarcodeFactory
from paperless_testing.factories import DocumentFactory
from paperless_testing.permissions import grant_object
from paperless_testing.samples import install_document_samples
@pytest.mark.management
@@ -204,10 +205,7 @@ class TestExportImport(
def test_exporter(self, *, use_filename_format=False) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
num_permission_objects = Permission.objects.count()
@@ -311,10 +309,7 @@ class TestExportImport(
def test_exporter_with_filename_format(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
with override_settings(
FILENAME_FORMAT="{created_year}/{correspondent}/{title}",
@@ -323,10 +318,7 @@ class TestExportImport(
def test_exporter_includes_share_links_and_bundles(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
share_link = ShareLink.objects.create(
slug="share-link-slug",
@@ -425,10 +417,7 @@ class TestExportImport(
def test_update_export_changed_time(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
self._do_export()
self.assertIsFile(self.target / "manifest.json")
@@ -464,10 +453,7 @@ class TestExportImport(
def test_update_export_changed_checksum(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
self._do_export()
@@ -494,10 +480,7 @@ class TestExportImport(
def test_update_export_deleted_document(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
manifest = self._do_export()
@@ -529,10 +512,7 @@ class TestExportImport(
@override_settings(FILENAME_FORMAT="{title}/{correspondent}")
def test_update_export_changed_location(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
self._do_export(use_filename_format=True)
self.assertIsFile(self.target / "wow1" / "c.pdf")
@@ -574,10 +554,7 @@ class TestExportImport(
- Zipfile contains exported files
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
args = ["document_exporter", self.target, "--zip"]
@@ -606,10 +583,7 @@ class TestExportImport(
- Zipfile contains exported files
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
args = ["document_exporter", self.target, "--zip", "--use-filename-format"]
@@ -645,10 +619,7 @@ class TestExportImport(
- The existing file and directory in target are removed
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
# Create stuff in target directory
existing_file = self.target / "test.txt"
@@ -744,10 +715,7 @@ class TestExportImport(
- Documents can be imported again
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
manifest = self._do_export()
has_archive = False
@@ -790,10 +758,7 @@ class TestExportImport(
- Documents can be imported again
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
manifest = self._do_export()
has_thumbnail = False
@@ -838,10 +803,7 @@ class TestExportImport(
- Documents can be imported again
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
manifest = self._do_export(split_manifest=True)
has_document = False
@@ -866,10 +828,7 @@ class TestExportImport(
def _export_import_barcodes(self, *, split_manifest: bool) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
DocumentBarcodeFactory(document=self.d1, value="https://example.com")
DocumentBarcodeFactory(document=self.d2, page=2, value="DE8937")
@@ -905,10 +864,7 @@ class TestExportImport(
- Documents can be imported again
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
self._do_export(use_folder_prefix=True)
@@ -935,10 +891,7 @@ class TestExportImport(
- Documents can be imported again
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
self._do_export(use_folder_prefix=True, split_manifest=True)
@@ -965,10 +918,7 @@ class TestExportImport(
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
num_content_type_objects = ContentType.objects.count()
num_permission_objects = Permission.objects.count()
@@ -1005,10 +955,7 @@ class TestExportImport(
def test_exporter_with_auditlog_disabled(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
with override_settings(
AUDIT_LOG_ENABLED=False,
@@ -1028,10 +975,7 @@ class TestExportImport(
survive the round-trip with deleted_at preserved
"""
shutil.rmtree(Path(self.dirs.media_dir) / "documents")
shutil.copytree(
Path(__file__).parent / "samples" / "documents",
Path(self.dirs.media_dir) / "documents",
)
install_document_samples(Path(self.dirs.media_dir) / "documents")
# d1 has self.note and self.cfi1 attached via setUp
self.d1.delete()
@@ -1075,10 +1019,7 @@ class TestExportImport(
"""
shutil.rmtree(self.dirs.media_dir / "documents")
shutil.copytree(
self.SAMPLE_DIR / "documents",
self.dirs.media_dir / "documents",
)
install_document_samples(self.dirs.media_dir / "documents")
_ = self._do_export(data_only=True)
@@ -11,6 +11,7 @@ from documents.models import Document
from documents.parsers import get_default_thumbnail
from paperless_testing.assertions import FileSystemAssertsMixin
from paperless_testing.dirs import DirectoriesMixin
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
@pytest.mark.management
@@ -24,7 +25,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
filename="test.pdf",
)
shutil.copy(
Path(__file__).parent / "samples" / "simple.pdf",
SIMPLE_DIGITAL_PDF,
self.d1.source_path,
)
@@ -36,7 +37,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
filename="test2.pdf",
)
shutil.copy(
Path(__file__).parent / "samples" / "simple.pdf",
SIMPLE_DIGITAL_PDF,
self.d2.source_path,
)
+2 -1
View File
@@ -15,10 +15,11 @@ import pytest
from documents import pdf_ops
from documents.pdf_ops import PageSpec
from paperless_testing.samples import MULTI_PAGE_DIGITAL_PDF
SRC_ROOT = Path(__file__).parents[2]
SAMPLES = Path(__file__).parent / "samples"
THREE_PAGES = SAMPLES / "documents" / "originals" / "0000002.pdf"
THREE_PAGES = MULTI_PAGE_DIGITAL_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"
+104
View File
@@ -0,0 +1,104 @@
from django.core.exceptions import FieldDoesNotExist
from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from documents.management.commands.document_importer import _deserialize_record
from documents.models import Tag
from documents.tag_hierarchy import TagHierarchy
from paperless_testing.migrations import TestMigrations
class TestTagAdjacency(TestCase):
def test_held_instance_uses_current_parent_edges(self):
first = Tag.objects.create(name="First")
second = Tag.objects.create(name="Second")
child = Tag.objects.create(name="Child", tn_parent=first)
Tag.objects.filter(pk=child.pk).update(tn_parent=second)
assert first.get_descendants_pks() == set()
assert second.get_descendants_pks() == {child.pk}
def test_queryset_delete_preserves_cascade(self):
root = Tag.objects.create(name="Root")
child = Tag.objects.create(name="Child", tn_parent=root)
Tag.objects.create(name="Grandchild", tn_parent=child)
survivor = Tag.objects.create(name="Other root")
Tag.objects.filter(pk=root.pk).delete()
assert list(Tag.objects.values_list("pk", flat=True)) == [survivor.pk]
def test_flat_writes_do_not_update_other_tags(self):
Tag.objects.bulk_create([Tag(name=f"Tag {i}") for i in range(100)])
with CaptureQueriesContext(connection) as queries:
new = Tag.objects.create(name="New")
assert not any(q["sql"].startswith("UPDATE") for q in queries)
with CaptureQueriesContext(connection) as queries:
new.name = "Renamed"
new.save()
tag_updates = [
q["sql"] for q in queries if q["sql"].startswith('UPDATE "documents_tag"')
]
assert len(tag_updates) == 1
assert 'WHERE "documents_tag"."id" =' in tag_updates[0]
def test_batch_snapshot_is_one_query_and_includes_overlapping_roots(self):
root = Tag.objects.create(name="Root")
child = Tag.objects.create(name="Child", tn_parent=root)
leaf = Tag.objects.create(name="Leaf", tn_parent=child)
with self.assertNumQueries(1):
hierarchy = Tag.get_hierarchy()
assert hierarchy.with_ancestors([leaf.pk]) == {root.pk, child.pk, leaf.pk}
assert hierarchy.with_descendants([root.pk, child.pk]) == {
root.pk,
child.pk,
leaf.pk,
}
assert hierarchy.height(None) == 0
def test_legacy_export_fields_are_ignored_but_parent_is_preserved(self):
model, tag, m2m = _deserialize_record(
{
"model": "documents.tag",
"pk": 2,
"fields": {
"name": "Child",
"tn_parent": 1,
"tn_order": 3,
"tn_siblings_pks": "4,5",
"tn_descendants_pks": "6",
},
},
)
assert model is Tag
assert tag.tn_parent_id == 1
assert tag.name == "Child"
assert not m2m
with self.assertRaises(FieldDoesNotExist):
_deserialize_record(
{"model": "documents.tag", "pk": 3, "fields": {"tn_typo": "bad"}},
)
def test_malformed_cycles_terminate(self):
hierarchy = TagHierarchy([(1, 2), (2, 1)])
assert hierarchy.with_descendants([1]) == {1, 2}
assert set(hierarchy.ancestors(2)) == {1, 2}
class TestTagAdjacencyMigration(TestMigrations):
migrate_from = "0027_documentbarcode"
migrate_to = "0028_replace_tag_tree_fields"
def setUpBeforeMigration(self, apps):
tag = apps.get_model("documents", "Tag")
root = tag.objects.create(name="Root")
child = tag.objects.create(name="Child", tn_parent=root)
self.edges = [(root.pk, None), (child.pk, root.pk)]
def test_parent_edges_survive(self):
tag = self.apps.get_model("documents", "Tag")
assert (
list(tag.objects.order_by("pk").values_list("pk", "tn_parent_id"))
== self.edges
)
assert {
field.name for field in tag._meta.fields if field.name.startswith("tn_")
} == {"tn_parent"}
+5 -23
View File
@@ -21,6 +21,7 @@ from documents.tests.helpers import dummy_preprocess
from paperless_testing.assertions import FileSystemAssertsMixin
from paperless_testing.dirs import DirectoriesMixin
from paperless_testing.factories import DocumentFactory
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
@pytest.mark.django_db
@@ -229,20 +230,12 @@ class TestUpdateContent(DirectoriesMixin, TestCase):
"""
sample1 = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
sample1,
)
sample1_archive = self.dirs.archive_dir / "sample_archive.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
sample1_archive,
)
doc = Document.objects.create(
@@ -270,11 +263,7 @@ class TestUpdateContent(DirectoriesMixin, TestCase):
"""
sample1 = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
SIMPLE_DIGITAL_PDF,
sample1,
)
doc = Document.objects.create(
@@ -290,14 +279,7 @@ class TestUpdateContent(DirectoriesMixin, TestCase):
def _create_root_with_version(self) -> tuple[Document, Document]:
sample1 = self.dirs.scratch_dir / "sample.pdf"
shutil.copy(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf",
sample1,
)
shutil.copy(SIMPLE_DIGITAL_PDF, sample1)
root = DocumentFactory(content="root content", mime_type="application/pdf")
version = DocumentFactory(
content="my document",
+27 -27
View File
@@ -178,7 +178,7 @@ class TestWorkflows(
self.assertEqual(action.__str__(), "WorkflowAction 1")
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -290,7 +290,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -409,7 +409,7 @@ class TestWorkflows(
w2.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -478,7 +478,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -531,7 +531,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -608,7 +608,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -687,7 +687,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -765,7 +765,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -876,7 +876,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -928,7 +928,7 @@ class TestWorkflows(
generated = generate_unique_filename(doc)
destination = (settings.ORIGINALS_DIR / generated).resolve()
create_source_path_directory(destination)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", destination)
shutil.copy(self.SIMPLE_PDF, destination)
Document.objects.filter(pk=doc.pk).update(filename=generated.as_posix())
doc.refresh_from_db()
@@ -2067,7 +2067,7 @@ class TestWorkflows(
superuser = UserFactory(username="superuser", superuser=True)
self.client.force_authenticate(user=superuser)
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
tasks.consume_file(
@@ -3053,7 +3053,7 @@ class TestWorkflows(
generated = generate_unique_filename(doc)
destination = (settings.ORIGINALS_DIR / generated).resolve()
create_source_path_directory(destination)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", destination)
shutil.copy(self.SIMPLE_PDF, destination)
Document.objects.filter(pk=doc.pk).update(filename=generated.as_posix())
doc.refresh_from_db()
doc.tags.set([self.t1, self.t2])
@@ -3117,7 +3117,7 @@ class TestWorkflows(
generated = generate_unique_filename(doc)
destination = (settings.ORIGINALS_DIR / generated).resolve()
create_source_path_directory(destination)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", destination)
shutil.copy(self.SIMPLE_PDF, destination)
Document.objects.filter(pk=doc.pk).update(filename=generated.as_posix())
doc.refresh_from_db()
doc.tags.set([self.t1])
@@ -3266,7 +3266,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -3390,7 +3390,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -3540,7 +3540,7 @@ class TestWorkflows(
workflow.actions.set([assignment_action, email_action])
temp_working_copy = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "working-copy.pdf",
)
@@ -3640,7 +3640,7 @@ class TestWorkflows(
# move the file
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -3748,7 +3748,7 @@ class TestWorkflows(
generated = generate_unique_filename(doc)
destination = (settings.ORIGINALS_DIR / generated).resolve()
create_source_path_directory(destination)
shutil.copy(self.SAMPLE_DIR / "simple.pdf", destination)
shutil.copy(self.SIMPLE_PDF, destination)
Document.objects.filter(pk=doc.pk).update(filename=generated.as_posix())
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
@@ -3959,7 +3959,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -4080,7 +4080,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -4431,7 +4431,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -4879,7 +4879,7 @@ class TestWorkflows(
w.save()
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
@@ -5071,7 +5071,7 @@ class TestWorkflows(
# Create a test file to be consumed
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
test_file_path = Path(test_file)
@@ -5135,7 +5135,7 @@ class TestWorkflows(
# Create a test file to be consumed
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple2.pdf",
)
test_file_path = Path(test_file)
@@ -5639,7 +5639,7 @@ class TestDateWorkflowLocalization(
)
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
tmp_path / "simple.pdf",
)
@@ -5706,7 +5706,7 @@ class TestRemoteOCRWorkflowAction(DirectoriesMixin, SampleDirMixin, APITestCase)
self._make_workflow(WorkflowTrigger.WorkflowTriggerType.CONSUMPTION)
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
overrides = DocumentMetadataOverrides()
@@ -5924,7 +5924,7 @@ class TestApplyAISuggestionsWorkflowAction(
)
test_file = shutil.copy(
self.SAMPLE_DIR / "simple.pdf",
self.SIMPLE_PDF,
self.dirs.scratch_dir / "simple.pdf",
)
+3
View File
@@ -11,6 +11,7 @@ from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource
from paperless_testing.fakes.progress import FakeProgressManager
from paperless_testing.samples import SIMPLE_DIGITAL_PDF
class ConsumeTaskMixin:
@@ -53,6 +54,8 @@ class SampleDirMixin:
BARCODE_SAMPLE_DIR = SAMPLE_DIR / "barcodes"
SIMPLE_PDF = SIMPLE_DIGITAL_PDF
class GetConsumerMixin:
@contextmanager
+2 -7
View File
@@ -627,7 +627,7 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]):
queryset = queryset.order_by(*ordering)
all_tags = list(queryset)
descendant_pks = {pk for tag in all_tags for pk in tag.get_descendants_pks()}
descendant_pks = Tag.get_hierarchy().descendants(t.pk for t in all_tags)
if descendant_pks:
user = getattr(getattr(self, "request", None), "user", None)
@@ -5060,12 +5060,7 @@ class BulkEditObjectsView(PassUserMixin):
if object_type == "tags":
editable_ids = set(user_permitted_objects.values_list("pk", flat=True))
all_ids = set(objs.values_list("pk", flat=True))
for tag in objs:
all_ids.update(
descendant.pk
for descendant in tag.get_descendants()
if descendant.pk in editable_ids
)
all_ids.update(Tag.get_hierarchy().descendants(all_ids) & editable_ids)
objs = object_class.objects.filter(pk__in=all_ids)
objs = objs.select_related("owner")
object_ids = list(objs.values_list("pk", flat=True))
+10 -12
View File
@@ -6,6 +6,7 @@ from guardian.shortcuts import remove_perm
from documents.data_models import DocumentMetadataOverrides
from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import Tag
from documents.models import WorkflowAction
from documents.permissions import PermissionsSpec
from documents.permissions import set_permissions_for_objects
@@ -134,10 +135,9 @@ def apply_assignment_to_overrides(
if action.has_assign_tags:
if overrides.tag_ids is None:
overrides.tag_ids = []
tag_ids_to_add: set[int] = set()
for tag in action.assign_tags.all():
tag_ids_to_add.add(tag.pk)
tag_ids_to_add.update(int(pk) for pk in tag.get_ancestors_pks())
tag_ids_to_add = Tag.get_hierarchy().with_ancestors(
tag.pk for tag in action.assign_tags.all()
)
overrides.tag_ids = list(set(overrides.tag_ids) | tag_ids_to_add)
@@ -216,10 +216,9 @@ def apply_removal_to_document(
if action.remove_all_tags:
Document.objects.get(pk=document.pk).tags.clear()
else:
tag_ids_to_remove: set[int] = set()
for tag in action.remove_tags.all():
tag_ids_to_remove.add(tag.pk)
tag_ids_to_remove.update(int(pk) for pk in tag.get_descendants_pks())
tag_ids_to_remove = Tag.get_hierarchy().with_descendants(
tag.pk for tag in action.remove_tags.all()
)
if tag_ids_to_remove:
Document.objects.get(pk=document.pk).tags.remove(*tag_ids_to_remove)
@@ -297,10 +296,9 @@ def apply_removal_to_overrides(
if action.remove_all_tags:
overrides.tag_ids = None
elif overrides.tag_ids:
tag_ids_to_remove: set[int] = set()
for tag in action.remove_tags.all():
tag_ids_to_remove.add(tag.pk)
tag_ids_to_remove.update(int(pk) for pk in tag.get_descendants_pks())
tag_ids_to_remove = Tag.get_hierarchy().with_descendants(
tag.pk for tag in action.remove_tags.all()
)
overrides.tag_ids = [t for t in overrides.tag_ids if t not in tag_ids_to_remove]
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Afrikaans\n"
"Language: af_ZA\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Dokumente"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "Waarde moet geldige JSON wees."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "Ongeldige gepasmaakte veldnavraaguitdrukking"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "Ongeldige uitdrukking lys. Moet nie leeg wees nie."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "Ongeldige logiese uitdrukking {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr ""
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr ""
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr ""
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr ""
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr ""
@@ -1635,48 +1639,48 @@ msgstr ""
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Ongeldige kleur."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Lêertipe %(type)s word nie ondersteun nie"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Ongeldige veranderlike bespeur."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1916,40 +1920,40 @@ msgstr ""
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2241,151 +2245,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Engels (VS)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr "Arabies"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Belorussies"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "Katalaans"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Tsjeggies"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Deens"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Duits"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Engels (GB)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Spaans"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "Fins"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Frans"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Italiaans"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Luxemburgs"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Nederlands"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Pools"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Portugees (Brasilië)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Portugees"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Roemeens"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Russies"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Sloweens"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Serwies"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Sweeds"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Turks"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Vereenvoudigde Sjinees"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr ""
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Amharic\n"
"Language: am_ET\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "መዝገባት"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "የሚሰራው እሴት \"JSON\" መሆን አለበት"
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "ልክ ያልሆነ የተወሰነ የቦታ መጠይቅ አገላለጽ"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "ልክ ያልሆነ የመግለጫ ዝርዝር። ባዶ መሆን የለበትም።"
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "ልክ ያልሆነ የሎጂክ ኦፕሬተር {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "ከፍተኛው የጥያቄ ሁኔታዎች/መጠን ብዛት አልፏል።"
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} ይሄ ታዐማኒነት ያለው ልማድ አይደለም።"
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "ጥያቄን አይደግፍም expr {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr "ከፍተኛው የጥገኝነት ጥልቀት አልፏል።"
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "ይህ ልማድ አልተገኘም"
@@ -1635,48 +1639,48 @@ msgstr ""
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr ""
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1916,40 +1920,40 @@ msgstr ""
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2241,151 +2245,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr ""
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr ""
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr ""
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr ""
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr ""
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr ""
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr ""
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr ""
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr ""
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr ""
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr ""
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr ""
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr ""
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr ""
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr ""
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr ""
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr ""
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr ""
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr ""
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr ""
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "المستندات"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "يجب أن تكون القيمة JSON."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "تعبير استعلام غير صالح للحقول المخصصة"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "قائمة عبارة خاطئة."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr ""
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "تجاوز الحد الأقصى لعدد شروط الاستعلام."
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} حقل مخصص غير صالح."
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "{data_type} لا يدعم تعبير الاستعلام {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr ""
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "لم يتم العثور على حقل مخصص"
@@ -1635,48 +1639,48 @@ msgstr ""
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "لون خاطئ."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "نوع الملف %(type)s غير مدعوم"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "اكتشاف متغير خاطئ."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1917,40 +1921,40 @@ msgstr "كخطوة أخيرة، يرجى ملء النموذج التالي:"
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2242,151 +2246,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "إعدادات التطبيق paperless"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "الإنجليزية (الولايات المتحدة)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr "العربية"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr "اللغة الأفريقانية"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "البيلاروسية"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr "البلغارية"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "اللغة الكتالونية"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "التشيكية"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "الدانماركية"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "الألمانية"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr "اليونانية"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "الإنجليزية (المملكة المتحدة)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "الإسبانية"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "الفنلندية"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "الفرنسية"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr "المجرية"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "الإيطالية"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr "اليابانية"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "اللوكسمبرجية"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr "النرويجية"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "الهولندية"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "البولندية"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "البرتغالية (البرازيل)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "البرتغالية"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "الرومانية"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "الروسية"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr "السلوفاكية"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "السلوفانية"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "الصربية"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "السويدية"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "التركية"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr "الأوكرانية"
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "الصينية المبسطة"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr ""
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Belarusian\n"
"Language: be_BY\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Дакументы"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr ""
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr ""
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr ""
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr ""
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr ""
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr ""
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr ""
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr ""
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr ""
@@ -1635,48 +1639,48 @@ msgstr ""
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Няправільны колер."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Тып файла %(type)s не падтрымліваецца"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Выяўлена няправільная зменная."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1916,40 +1920,40 @@ msgstr ""
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2241,151 +2245,151 @@ msgstr ""
msgid "paperless application settings"
msgstr ""
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Англійская (ЗША)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr ""
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Беларуская"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr ""
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr ""
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Чэшская"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Дацкая"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Нямецкая"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr ""
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Англійская (Вялікабрытанія)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Іспанская"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr ""
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Французская"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr ""
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Італьянская"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr ""
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr ""
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Люксембургская"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr ""
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Нідэрландская"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Польская"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Партугальская (Бразілія)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Партугальская"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Румынская"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Руская"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr ""
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Славенская"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Сербская"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Шведская"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Турэцкая"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr ""
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Кітайская спрошчаная"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr ""
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Документи"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "Стойността трябва да е валидна JSON."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "Невалидна заявка на персонализираното полето"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "Списък с невалиден израз. Не може да е празно."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "Невалиден логически оператор {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "Надвишен е максимален брой за заявки."
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} не е валидно персонализирано поле."
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "{data_type} не поддържа заявка expr {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr "Надвишена е максималната дълбочина на вмъкване."
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "Персонализирано поле не е намерено"
@@ -1635,48 +1639,48 @@ msgstr "стартиране на работния процес"
msgid "workflow runs"
msgstr "стартиране на работните процеси"
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Невалиден цвят."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Файловия тип %(type)s не се поддържа"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Засечена е невалидна променлива."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1917,40 +1921,40 @@ msgstr "Като последна стъпка, моля, попълнете с
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2242,151 +2246,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "настройки на софтуера"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Английски (САЩ)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr "Арабски"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr "Африканс"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Беларуски"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr "Български"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "Каталунски"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Чешки"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Датски"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Немски"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr "Гръцки"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Английски (Великобритания)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Испански"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "Финландски"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Френски"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr "Унгарски"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Италиански"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr "Японски"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr "Корейски"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Люксембургски"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr "Норвежки"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Холандски"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Полски"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Португалски (Бразилия)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Португалски"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Румънски"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Руски"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr "Словашки"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Словенски"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Сръбски"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Шведски"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Турски"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr "Украински"
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Китайски опростен"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr "Китайски традиционен"
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Language: ca_ES\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Documents "
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "Valor ha de ser un JSON valid."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "Expressió de camp de consulta invàlid"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "Expressió de llista invàlida. No ha d'estar buida."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "Invàlid operand lògic {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "Condicions de consulta excedits."
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} no és un camp personalitzat vàlid."
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "{data_type} no suporta expressió de consulta {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr "Màxima profunditat anidada excedida."
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "Camp personalitzat no trobat"
@@ -1635,48 +1639,48 @@ msgstr "data del flux"
msgid "workflow runs"
msgstr "flux corrents"
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr "Permisos insuficients."
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Color Invàlid."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Tipus arxiu %(type)s no suportat"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr "ID de camp personalizat ha de ser enter: %(id)s"
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr "Camp personalitzat amb ID %(id)s no existeix"
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr "Camps personalitzats han de ser una llista d'enters o un objecte que mapegi els identificadors amb els valors."
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr "Alguns camps personalitzats no existeixen o s'han especificat dues vegades."
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Variable detectada invàlida."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr "Duplicat d'identificadors de documents no permès."
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr "Documents no trobats: %(ids)s"
@@ -1917,40 +1921,40 @@ msgstr "Com a pas final, completa el següent formulari:"
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
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
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr "Invalid more_like_id"
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr "Configuració AI invàlida."
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr "Permisos insuficients per compartir document %(id)s."
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr "Paquet ja s'està processant."
#: documents/views.py:4945
#: documents/views.py:4909
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."
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr "El paquet d'enllaç no està disponible."
@@ -2242,151 +2246,151 @@ msgstr "Estableix temps d'espera en segons"
msgid "paperless application settings"
msgstr "configuració de l'aplicació paperless"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Anglès (US)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr "Àrab"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr "Africà"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Bielorús"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr "Búlgar"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "Català"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Txec"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Danès"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Alemany"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr "Grec"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Anglès (GB)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Espanyol"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr "Persa"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "Finès"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Francès"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr "Hongarès"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr "Indonesi"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Italià"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr "Japonès"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr "Coreà"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Luxemburguès"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr "Noruec"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Holandès"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Polonès"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Portuguès (Brasil)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Portuguès"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Romanès"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Rus"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr "Eslovac"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Eslovè"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Serbi"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Suec"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Turc"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr "Ucranià"
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr "Vietnamita"
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Xinès Simplificat"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr "Xinès tradicional"
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Dokumenty"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "Hodnota musí být platný JSON."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "Neplatný výraz dotazu na vlastní pole"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "Neplatný seznam výrazů. Nesmí být prázdný."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "Neplatný logický operátor {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "Překročen maximální počet podmínek dotazu."
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} není platné vlastní pole."
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "{data_type} nepodporuje výraz dotazu {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr "Překročena maximální hloubka větvení."
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "Vlastní pole nebylo nalezeno"
@@ -1635,48 +1639,48 @@ msgstr "spuštění pracovního postupu"
msgid "workflow runs"
msgstr "spuštění pracovních postupů"
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr "Nedostatečná oprávnění."
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Neplatná barva."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Typ souboru %(type)s není podporován"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr "Vlastní ID pole musí být celé číslo: %(id)s"
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr "Vlastní pole s ID %(id)s neexistuje"
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr "Vlastní pole musí být seznam celých čísel nebo ID pro mapování objektů na hodnoty."
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr "Některá vlastní pole neexistují nebo byla zadána dvakrát."
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Zjištěna neplatná proměnná."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1917,40 +1921,40 @@ msgstr "Jako poslední krok vyplňte následující formulář:"
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr "Nedostatečná oprávnění ke sdílení dokumentu %(id)s."
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2242,151 +2246,151 @@ msgstr "Nastaví časový limit LLM v sekundách"
msgid "paperless application settings"
msgstr "nastavení aplikace paperless"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Angličtina (US)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr "Arabština"
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr "Afrikánština"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Běloruština"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr "Bulharština"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "Katalánština"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Čeština"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Dánština"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Němčina"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr "Řečtina"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Angličtina (GB)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Španělština"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr "Perština"
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "Finština"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Francouzština"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr "Maďarština"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr "Indonéština"
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Italština"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr "Japonština"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr "Korejština"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Lucemburština"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr "Norština"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Holandština"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Polština"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Portugalština (Brazílie)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Portugalština"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Rumunština"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Ruština"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr "Slovenština"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Slovinština"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Srbština"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Švédština"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Turečtina"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr "Ukrajinština"
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr "Vietnamština"
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Čínština (zjednodušená)"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr "Čínština (tradiční)"
+72 -68
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-19 15:40+0000\n"
"PO-Revision-Date: 2026-10-19 15:42\n"
"POT-Creation-Date: 2026-10-29 17:25+0000\n"
"PO-Revision-Date: 2026-10-29 17:27\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Language: da_DK\n"
@@ -21,39 +21,43 @@ msgstr ""
msgid "Documents"
msgstr "Dokumenter"
#: documents/filters.py:464
#: documents/filters.py:337
msgid "Expected a date or a relative date such as \"today\" or \"in 30 days\"."
msgstr ""
#: documents/filters.py:513
msgid "Value must be valid JSON."
msgstr "Værdien skal være gyldig JSON."
#: documents/filters.py:483
#: documents/filters.py:534
msgid "Invalid custom field query expression"
msgstr "Ugyldigt tilpasset feltforespørgselsudtryk"
#: documents/filters.py:493
#: documents/filters.py:544
msgid "Invalid expression list. Must be nonempty."
msgstr "Ugyldig udtryksliste. Må ikke være tom."
#: documents/filters.py:514
#: documents/filters.py:565
msgid "Invalid logical operator {op!r}"
msgstr "Ugyldig logisk operatør {op!r}"
#: documents/filters.py:528
#: documents/filters.py:579
msgid "Maximum number of query conditions exceeded."
msgstr "Maksimalt antal forespørgselsbetingelser overskredet."
#: documents/filters.py:592
#: documents/filters.py:643
msgid "{name!r} is not a valid custom field."
msgstr "{name!r} er ikke et gyldigt tilpasset felt."
#: documents/filters.py:629
#: documents/filters.py:680
msgid "{data_type} does not support query expr {expr!r}."
msgstr "{data_type} understøtter ikke forespørgsel expr {expr!r}."
#: documents/filters.py:748 documents/models.py:137
#: documents/filters.py:801 documents/models.py:137
msgid "Maximum nesting depth exceeded."
msgstr "Maksimal indlejringsdybde overskredet."
#: documents/filters.py:1125
#: documents/filters.py:1178
msgid "Custom field not found"
msgstr "Tilpasset felt ikke fundet"
@@ -1635,48 +1639,48 @@ msgstr "workflow-kørsel"
msgid "workflow runs"
msgstr "workflow-kørsler"
#: documents/serialisers.py:515 documents/serialisers.py:872
#: documents/serialisers.py:2902 documents/views.py:363 documents/views.py:2768
#: documents/serialisers.py:483 documents/serialisers.py:840
#: documents/serialisers.py:2872 documents/views.py:360 documents/views.py:2730
#: paperless_mail/serialisers.py:156
msgid "Insufficient permissions."
msgstr ""
#: documents/serialisers.py:708
#: documents/serialisers.py:676
msgid "Invalid color."
msgstr "Ugyldig farve."
#: documents/serialisers.py:2369
#: documents/serialisers.py:2339
#, python-format
msgid "File type %(type)s not supported"
msgstr "Filtype %(type)s understøttes ikke"
#: documents/serialisers.py:2413
#: documents/serialisers.py:2383
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:2420
#: documents/serialisers.py:2390
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:2437 documents/serialisers.py:2447
#: documents/serialisers.py:2407 documents/serialisers.py:2417
msgid "Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:2442
#: documents/serialisers.py:2412
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2589
#: documents/serialisers.py:2559
msgid "Invalid variable detected."
msgstr "Ugyldig variabel fundet."
#: documents/serialisers.py:2958
#: documents/serialisers.py:2928
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2988 documents/views.py:4816
#: documents/serialisers.py:2958 documents/views.py:4780
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1917,40 +1921,40 @@ msgstr "Som sidste trin, udfyld venligst flg. formular:"
msgid ", "
msgstr ""
#: documents/views.py:318
#: documents/views.py:315
msgid "Specify only one of text, title_search, query, or more_like_id."
msgstr ""
#: documents/views.py:356 documents/views.py:2765
#: documents/views.py:353 documents/views.py:2727
msgid "Invalid more_like_id"
msgstr ""
#: documents/views.py:1703
#: documents/views.py:1665
msgid "Invalid AI configuration."
msgstr ""
#: documents/views.py:1714
#: documents/views.py:1676
msgid "AI backend request timed out."
msgstr ""
#: documents/views.py:1726
#: documents/views.py:1688
msgid "AI backend rejected the request. Check logs for details."
msgstr ""
#: documents/views.py:4832
#: documents/views.py:4796
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:4879
#: documents/views.py:4843
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:4945
#: documents/views.py:4909
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:4959
#: documents/views.py:4923
msgid "The share link bundle is unavailable."
msgstr ""
@@ -2242,151 +2246,151 @@ msgstr ""
msgid "paperless application settings"
msgstr "paperless-applikationsindstillinger"
#: paperless/settings/__init__.py:562
#: paperless/settings/__init__.py:566
msgid "English (US)"
msgstr "Engelsk (USA)"
#: paperless/settings/__init__.py:563
#: paperless/settings/__init__.py:567
msgid "Arabic"
msgstr ""
#: paperless/settings/__init__.py:564
#: paperless/settings/__init__.py:568
msgid "Afrikaans"
msgstr "Afrikaans"
#: paperless/settings/__init__.py:565
#: paperless/settings/__init__.py:569
msgid "Belarusian"
msgstr "Hviderussisk"
#: paperless/settings/__init__.py:566
#: paperless/settings/__init__.py:570
msgid "Bulgarian"
msgstr "Bulgarsk"
#: paperless/settings/__init__.py:567
#: paperless/settings/__init__.py:571
msgid "Catalan"
msgstr "Katalansk"
#: paperless/settings/__init__.py:568
#: paperless/settings/__init__.py:572
msgid "Czech"
msgstr "Tjekkisk"
#: paperless/settings/__init__.py:569
#: paperless/settings/__init__.py:573
msgid "Danish"
msgstr "Dansk"
#: paperless/settings/__init__.py:570
#: paperless/settings/__init__.py:574
msgid "German"
msgstr "Tysk"
#: paperless/settings/__init__.py:571
#: paperless/settings/__init__.py:575
msgid "Greek"
msgstr "Græsk"
#: paperless/settings/__init__.py:572
#: paperless/settings/__init__.py:576
msgid "English (GB)"
msgstr "Engelsk (GB)"
#: paperless/settings/__init__.py:573
#: paperless/settings/__init__.py:577
msgid "Spanish"
msgstr "Spansk"
#: paperless/settings/__init__.py:574
#: paperless/settings/__init__.py:578
msgid "Persian"
msgstr ""
#: paperless/settings/__init__.py:575
#: paperless/settings/__init__.py:579
msgid "Finnish"
msgstr "Finsk"
#: paperless/settings/__init__.py:576
#: paperless/settings/__init__.py:580
msgid "French"
msgstr "Fransk"
#: paperless/settings/__init__.py:577
#: paperless/settings/__init__.py:581
msgid "Hungarian"
msgstr "Ungarsk"
#: paperless/settings/__init__.py:578
#: paperless/settings/__init__.py:582
msgid "Indonesian"
msgstr ""
#: paperless/settings/__init__.py:579
#: paperless/settings/__init__.py:583
msgid "Italian"
msgstr "Italiensk"
#: paperless/settings/__init__.py:580
#: paperless/settings/__init__.py:584
msgid "Japanese"
msgstr "Japansk"
#: paperless/settings/__init__.py:581
#: paperless/settings/__init__.py:585
msgid "Korean"
msgstr "Koreansk"
#: paperless/settings/__init__.py:582
#: paperless/settings/__init__.py:586
msgid "Luxembourgish"
msgstr "Luxemburgsk"
#: paperless/settings/__init__.py:583
#: paperless/settings/__init__.py:587
msgid "Norwegian"
msgstr "Norsk"
#: paperless/settings/__init__.py:584
#: paperless/settings/__init__.py:588
msgid "Dutch"
msgstr "Hollandsk"
#: paperless/settings/__init__.py:585
#: paperless/settings/__init__.py:589
msgid "Polish"
msgstr "Polsk"
#: paperless/settings/__init__.py:586
#: paperless/settings/__init__.py:590
msgid "Portuguese (Brazil)"
msgstr "Portugisisk (Brasilien)"
#: paperless/settings/__init__.py:587
#: paperless/settings/__init__.py:591
msgid "Portuguese"
msgstr "Portugisisk"
#: paperless/settings/__init__.py:588
#: paperless/settings/__init__.py:592
msgid "Romanian"
msgstr "Romansk"
#: paperless/settings/__init__.py:589
#: paperless/settings/__init__.py:593
msgid "Russian"
msgstr "Russisk"
#: paperless/settings/__init__.py:590
#: paperless/settings/__init__.py:594
msgid "Slovak"
msgstr "Slovakisk"
#: paperless/settings/__init__.py:591
#: paperless/settings/__init__.py:595
msgid "Slovenian"
msgstr "Slovensk"
#: paperless/settings/__init__.py:592
#: paperless/settings/__init__.py:596
msgid "Serbian"
msgstr "Serbisk"
#: paperless/settings/__init__.py:593
#: paperless/settings/__init__.py:597
msgid "Swedish"
msgstr "Svensk"
#: paperless/settings/__init__.py:594
#: paperless/settings/__init__.py:598
msgid "Turkish"
msgstr "Tyrkisk"
#: paperless/settings/__init__.py:595
#: paperless/settings/__init__.py:599
msgid "Ukrainian"
msgstr "Ukrainsk"
#: paperless/settings/__init__.py:596
#: paperless/settings/__init__.py:600
msgid "Vietnamese"
msgstr ""
#: paperless/settings/__init__.py:597
#: paperless/settings/__init__.py:601
msgid "Chinese Simplified"
msgstr "Kinesisk, forenklet"
#: paperless/settings/__init__.py:598
#: paperless/settings/__init__.py:602
msgid "Chinese Traditional"
msgstr "Kinesisk, traditionelt"
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more