mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-30 16:25:20 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de2e2c5da | ||
|
|
e240c89f27 | ||
|
|
ced3cf9303 | ||
|
|
08a7d1d4b8 | ||
|
|
47052d6506 | ||
|
|
9bd28d3e57 |
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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paperless-ngx-ui",
|
||||
"version": "3.3.4",
|
||||
"version": "3.4.0",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"ng": "ng",
|
||||
|
||||
Generated
+228
-35
@@ -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: {}
|
||||
@@ -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"
|
||||
@@ -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
@@ -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,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")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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 +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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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 "Китайски традиционен"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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í)"
|
||||
|
||||
|
||||
@@ -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
Reference in new issue
Block a user