mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-03-13 20:51:24 +00:00
Compare commits
391 Commits
feature-un
...
fix-drop-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed3103227 | ||
|
|
ea55ec8bc5 | ||
|
|
c977445718 | ||
|
|
b313759903 | ||
|
|
5f0887046c | ||
|
|
047d4eca84 | ||
|
|
d86cfdb088 | ||
|
|
c2e1085418 | ||
|
|
ee0d1a3094 | ||
|
|
f15394fa5c | ||
|
|
773eb25f7d | ||
|
|
e2947ccff2 | ||
|
|
61841a767b | ||
|
|
15db023caa | ||
|
|
45b363659e | ||
|
|
7494161c95 | ||
|
|
5331312699 | ||
|
|
b5a002b8ed | ||
|
|
dd8573242d | ||
|
|
86fa74c115 | ||
|
|
b7b9e83f37 | ||
|
|
217b5df591 | ||
|
|
3efc9a5733 | ||
|
|
e19f341974 | ||
|
|
2b4ea570ef | ||
|
|
86573fc1a0 | ||
|
|
3856ec19c0 | ||
|
|
1221e7f21c | ||
|
|
3e32e90355 | ||
|
|
63cb75564e | ||
|
|
6955d6c07f | ||
|
|
d85ee29976 | ||
|
|
0c7d56c5e7 | ||
|
|
0bcf904e3a | ||
|
|
bcc2f11152 | ||
|
|
e18b1fd99d | ||
|
|
e30676f889 | ||
|
|
2a28549c5a | ||
|
|
4badf0e7c2 | ||
|
|
bc26d94593 | ||
|
|
93cbbf34b7 | ||
|
|
1e8622494d | ||
|
|
0c3298f030 | ||
|
|
2b288c094d | ||
|
|
2cdb1424ef | ||
|
|
f5c0c21922 | ||
|
|
91ddda9256 | ||
|
|
9d5e618de8 | ||
|
|
50ae49c7da | ||
|
|
ba023ef332 | ||
|
|
7345f2e81c | ||
|
|
731448a8f9 | ||
|
|
1c2d5483c2 | ||
|
|
815e598218 | ||
|
|
a5a267fe49 | ||
|
|
24a2cfd957 | ||
|
|
7cf2ef6398 | ||
|
|
df03207eef | ||
|
|
fa998ecd49 | ||
|
|
1e21bcd26e | ||
|
|
a9cb89c633 | ||
|
|
a37e24c1ad | ||
|
|
85a18e5911 | ||
|
|
ae182c459b | ||
|
|
d51a118aac | ||
|
|
d6a316b1df | ||
|
|
8f311c4b6b | ||
|
|
f25322600d | ||
|
|
615f27e6fb | ||
|
|
190fc70288 | ||
|
|
5b809122b5 | ||
|
|
c623234769 | ||
|
|
299dac21ee | ||
|
|
5498503d60 | ||
|
|
8b8307571a | ||
|
|
16b58c2de5 | ||
|
|
c724fbb5d9 | ||
|
|
9c0f112e94 | ||
|
|
43406f44f2 | ||
|
|
b7ca3550b1 | ||
|
|
0e97419e0e | ||
|
|
10cb2ac183 | ||
|
|
1d7cd5a7ad | ||
|
|
e58a35d40c | ||
|
|
20a9cd40e8 | ||
|
|
b94ce85b46 | ||
|
|
484bef00c1 | ||
|
|
317a177537 | ||
|
|
62efb4078f | ||
|
|
96ac7b2336 | ||
|
|
20173a2863 | ||
|
|
4e883ad70f | ||
|
|
fab3c04b3f | ||
|
|
2c87ba9bd6 | ||
|
|
3d843af64d | ||
|
|
8f3dece8a7 | ||
|
|
95484db71b | ||
|
|
2003fee401 | ||
|
|
8e7084eba7 | ||
|
|
cd2b5127db | ||
|
|
7ff51452f0 | ||
|
|
a700928dd5 | ||
|
|
709bcfd30d | ||
|
|
dd06627e43 | ||
|
|
f65807b906 | ||
|
|
a6c974589f | ||
|
|
47f9f642a9 | ||
|
|
8bfebc3b9b | ||
|
|
c7f83212a3 | ||
|
|
b010f65ae7 | ||
|
|
1dd3a62bc2 | ||
|
|
0bc032a67d | ||
|
|
8531078a54 | ||
|
|
5988d5896b | ||
|
|
89d3a53603 | ||
|
|
898dc578e5 | ||
|
|
c30ee1ec03 | ||
|
|
e67e28a509 | ||
|
|
5e135de85e | ||
|
|
c86ebc0260 | ||
|
|
9601b3d597 | ||
|
|
5e1202a416 | ||
|
|
fff1aa2a34 | ||
|
|
ceee769e26 | ||
|
|
c9278f88ca | ||
|
|
53ac338946 | ||
|
|
503845ce5b | ||
|
|
90a58dde18 | ||
|
|
13e07844fe | ||
|
|
90ae55252f | ||
|
|
c431370bde | ||
|
|
be82fcb70a | ||
|
|
ffbbe7986f | ||
|
|
6de7283cbf | ||
|
|
8a798e1ce2 | ||
|
|
e08287f791 | ||
|
|
c4ea332c61 | ||
|
|
fa13ca7a42 | ||
|
|
814f57b099 | ||
|
|
be7f1c6233 | ||
|
|
d6cd6d0311 | ||
|
|
095ea3cbd3 | ||
|
|
5b667621cd | ||
|
|
1b912c137a | ||
|
|
98298e37cd | ||
|
|
35be0850ec | ||
|
|
1bb4b9b473 | ||
|
|
f85094dc2b | ||
|
|
65ca78e9e7 | ||
|
|
57c5939d7b | ||
|
|
43fe932c57 | ||
|
|
83f68d6063 | ||
|
|
eda0e61cec | ||
|
|
426c0a8974 | ||
|
|
e54b69f7c4 | ||
|
|
4884b67714 | ||
|
|
2ccb315291 | ||
|
|
02896a15fd | ||
|
|
d8e07b8d84 | ||
|
|
c08d3aa962 | ||
|
|
be4e29a19c | ||
|
|
5c1bbcd06d | ||
|
|
bc734798e3 | ||
|
|
5ecbfc9df7 | ||
|
|
e63b62d531 | ||
|
|
dd3ec83569 | ||
|
|
7a23356898 | ||
|
|
afaf39e43a | ||
|
|
84163f4197 | ||
|
|
ae234d15c8 | ||
|
|
118afa82b3 | ||
|
|
56d1b5677a | ||
|
|
6622349b5f | ||
|
|
b050fab77f | ||
|
|
a467df0755 | ||
|
|
728c5ea07b | ||
|
|
4f2e16fdc7 | ||
|
|
8db1c4e08b | ||
|
|
1c70d3f8a8 | ||
|
|
491b5a4355 | ||
|
|
d41d4e12bf | ||
|
|
775e32bf3b | ||
|
|
e8e027abc0 | ||
|
|
c4ed4e7f36 | ||
|
|
0b89e2847e | ||
|
|
21623e4455 | ||
|
|
9d7231d2dc | ||
|
|
4208d9255a | ||
|
|
9e9e55758f | ||
|
|
6a87c3f4dd | ||
|
|
d7c64760ed | ||
|
|
750c77736b | ||
|
|
30b1d3c6d7 | ||
|
|
d3ff856202 | ||
|
|
3bc4631a0f | ||
|
|
ab328e0212 | ||
|
|
5c3d02e6d4 | ||
|
|
1d89ec402b | ||
|
|
6192915be7 | ||
|
|
b9b90ec9f7 | ||
|
|
0dc58cf686 | ||
|
|
505ff31748 | ||
|
|
3c51b3f9cd | ||
|
|
dfbac35f9c | ||
|
|
8f917555b1 | ||
|
|
734b5b9a45 | ||
|
|
0f1cae03ec | ||
|
|
71663fdbe2 | ||
|
|
5b45b89d35 | ||
|
|
1188a89369 | ||
|
|
b8e3b6590e | ||
|
|
4a5116adf8 | ||
|
|
bbf2e63f10 | ||
|
|
33cbe2ad54 | ||
|
|
261e10ebeb | ||
|
|
585c28b460 | ||
|
|
e77ab3357c | ||
|
|
05ab091ea4 | ||
|
|
fb7abf7a6e | ||
|
|
6ad2fc0356 | ||
|
|
2ec8ec96c8 | ||
|
|
276dc13e3f | ||
|
|
d0c02e7a8d | ||
|
|
e45fca475a | ||
|
|
63c0e2f72b | ||
|
|
00ef0837d2 | ||
|
|
231561ad55 | ||
|
|
4fa38708a1 | ||
|
|
5c2366fb24 | ||
|
|
e5edfd0f7f | ||
|
|
470c824684 | ||
|
|
3b5ffbf9fa | ||
|
|
a9c0b06e28 | ||
|
|
5fde91a891 | ||
|
|
3293655b0f | ||
|
|
36e07f5d40 | ||
|
|
fd78beff77 | ||
|
|
563156bd8e | ||
|
|
b26da51507 | ||
|
|
ff308a2010 | ||
|
|
6442fdc235 | ||
|
|
a42df003fb | ||
|
|
5b9bb147cf | ||
|
|
9ddd66ccbc | ||
|
|
c278f52fb2 | ||
|
|
72b861b5eb | ||
|
|
1513cbaaf5 | ||
|
|
aac6858aad | ||
|
|
c3b036e0d3 | ||
|
|
d27a5f688a | ||
|
|
c5bb5b237b | ||
|
|
11a5714cba | ||
|
|
4363567fa7 | ||
|
|
3e41d99a82 | ||
|
|
5cc3c087d9 | ||
|
|
c8c4c7c749 | ||
|
|
836c81e037 | ||
|
|
e4b861d76f | ||
|
|
a367b8ad1c | ||
|
|
d16d3fb618 | ||
|
|
5577f70c69 | ||
|
|
4d9aa2e943 | ||
|
|
6913f9d79c | ||
|
|
66593ec660 | ||
|
|
5af0d1da26 | ||
|
|
3281ec2401 | ||
|
|
dc9061eb97 | ||
|
|
6859e7e3c2 | ||
|
|
3e645bd9e2 | ||
|
|
09d39de200 | ||
|
|
94231dbb0f | ||
|
|
2f76350023 | ||
|
|
4cbe56e3af | ||
|
|
01b21377af | ||
|
|
56b5d838d7 | ||
|
|
d294508982 | ||
|
|
02002620d2 | ||
|
|
6d93ae93b4 | ||
|
|
c84f2f04b3 | ||
|
|
d9d83e3045 | ||
|
|
1f074390e4 | ||
|
|
50d676c592 | ||
|
|
94b0f4e114 | ||
|
|
045994042b | ||
|
|
6997a2ab8b | ||
|
|
f82f31f383 | ||
|
|
ac76710296 | ||
|
|
df07b8a03e | ||
|
|
cac1b721b9 | ||
|
|
4428354150 | ||
|
|
df1aa13551 | ||
|
|
e9e138e62c | ||
|
|
cafb0f2022 | ||
|
|
1d2e3393ac | ||
|
|
857aaca493 | ||
|
|
891f4a2faf | ||
|
|
ae816a01b2 | ||
|
|
b6531aed2f | ||
|
|
991d3cef88 | ||
|
|
f2bb6c9725 | ||
|
|
2312314aa7 | ||
|
|
72e8b73108 | ||
|
|
444ff6951e | ||
|
|
5c9ff367e3 | ||
|
|
aecf42d1ab | ||
|
|
45f5025f78 | ||
|
|
cf89d81b9e | ||
|
|
d0032c18be | ||
|
|
4271812c2d | ||
|
|
94f6b8d36d | ||
|
|
32d04e1fd3 | ||
|
|
56c744fd56 | ||
|
|
2d9717a330 | ||
|
|
213bd7e244 | ||
|
|
32b236cfa2 | ||
|
|
c06e1e7cba | ||
|
|
51b466a86b | ||
|
|
e3c29fc626 | ||
|
|
1f432a3378 | ||
|
|
d1aa76e4ce | ||
|
|
5381bc5907 | ||
|
|
6c45455384 | ||
|
|
2901693860 | ||
|
|
a527f5e244 | ||
|
|
16cc704539 | ||
|
|
245d9fb4a1 | ||
|
|
771f3f150a | ||
|
|
62248f5702 | ||
|
|
ecfeff5054 | ||
|
|
fa6a0a81f4 | ||
|
|
37477d391e | ||
|
|
b2541f3e8c | ||
|
|
f8ab81cef7 | ||
|
|
e9f7993ba5 | ||
|
|
3ea5e05137 | ||
|
|
56fddf1e58 | ||
|
|
d447a9fb32 | ||
|
|
155d69b211 | ||
|
|
4a7f9fa984 | ||
|
|
c471c201ee | ||
|
|
a9548afb42 | ||
|
|
2f1cd31e31 | ||
|
|
742c136773 | ||
|
|
939b2f7553 | ||
|
|
8b58718fff | ||
|
|
ad78c436c0 | ||
|
|
c6697cd82b | ||
|
|
0689c8ad3a | ||
|
|
825e9ca14c | ||
|
|
11cc2f8289 | ||
|
|
055ce9172c | ||
|
|
1b41559067 | ||
|
|
94a5af66eb | ||
|
|
948c664dcf | ||
|
|
eeb5639990 | ||
|
|
6cf8abc5d3 | ||
|
|
9c0de249a6 | ||
|
|
71ecdc528e | ||
|
|
00ec8a577b | ||
|
|
3618c50b62 | ||
|
|
6f4497185e | ||
|
|
e816269db5 | ||
|
|
d4e60e13bf | ||
|
|
cb091665e2 | ||
|
|
00bb92e3e1 | ||
|
|
11ec676909 | ||
|
|
7c457466b7 | ||
|
|
fb82146c10 | ||
|
|
e940764fe0 | ||
|
|
4347ba1f9c | ||
|
|
7b666e7569 | ||
|
|
07eb3c4761 | ||
|
|
d210f3091d | ||
|
|
402ed6b9e7 | ||
|
|
b748362509 | ||
|
|
505a2f0dc3 | ||
|
|
3261297910 | ||
|
|
b76d0dd616 | ||
|
|
ba4d88c801 | ||
|
|
58d88440f1 | ||
|
|
cb5f09c04e | ||
|
|
5b1e66be91 | ||
|
|
f3e3ba49d1 | ||
|
|
4c2f5f3473 | ||
|
|
39d46bc2df | ||
|
|
cf59853f34 | ||
|
|
9cce212910 | ||
|
|
ba42f0eb4f | ||
|
|
a0744f179f | ||
|
|
e7260838d6 | ||
|
|
65aed2405c |
67
.codecov.yml
67
.codecov.yml
@@ -1,6 +1,7 @@
|
|||||||
|
# https://docs.codecov.com/docs/codecovyml-reference#codecov
|
||||||
codecov:
|
codecov:
|
||||||
require_ci_to_pass: true
|
require_ci_to_pass: true
|
||||||
# https://docs.codecov.com/docs/components
|
# https://docs.codecov.com/docs/components
|
||||||
component_management:
|
component_management:
|
||||||
individual_components:
|
individual_components:
|
||||||
- component_id: backend
|
- component_id: backend
|
||||||
@@ -9,26 +10,76 @@ component_management:
|
|||||||
- component_id: frontend
|
- component_id: frontend
|
||||||
paths:
|
paths:
|
||||||
- src-ui/**
|
- src-ui/**
|
||||||
# https://docs.codecov.com/docs/pull-request-comments
|
# https://docs.codecov.com/docs/flags#step-2-flag-management-in-yaml
|
||||||
|
# https://docs.codecov.com/docs/carryforward-flags
|
||||||
|
flags:
|
||||||
|
# Backend Python versions
|
||||||
|
backend-python-3.11:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
|
backend-python-3.12:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
|
backend-python-3.13:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
|
backend-python-3.14:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
|
# Frontend (shards merge into single flag)
|
||||||
|
frontend-node-24.x:
|
||||||
|
paths:
|
||||||
|
- src-ui/**
|
||||||
|
carryforward: true
|
||||||
comment:
|
comment:
|
||||||
layout: "header, diff, components, flags, files"
|
layout: "header, diff, components, flags, files"
|
||||||
# https://docs.codecov.com/docs/javascript-bundle-analysis
|
|
||||||
require_bundle_changes: true
|
require_bundle_changes: true
|
||||||
bundle_change_threshold: "50Kb"
|
bundle_change_threshold: "50Kb"
|
||||||
coverage:
|
coverage:
|
||||||
|
# https://docs.codecov.com/docs/commit-status
|
||||||
status:
|
status:
|
||||||
project:
|
project:
|
||||||
default:
|
backend:
|
||||||
|
flags:
|
||||||
|
- backend-python-3.11
|
||||||
|
- backend-python-3.12
|
||||||
|
- backend-python-3.13
|
||||||
|
- backend-python-3.14
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
# https://docs.codecov.com/docs/commit-status#threshold
|
# https://docs.codecov.com/docs/commit-status#threshold
|
||||||
threshold: 1%
|
threshold: 1%
|
||||||
|
removed_code_behavior: adjust_base
|
||||||
|
frontend:
|
||||||
|
flags:
|
||||||
|
- frontend-node-24.x
|
||||||
|
paths:
|
||||||
|
- src-ui/**
|
||||||
|
threshold: 1%
|
||||||
|
removed_code_behavior: adjust_base
|
||||||
patch:
|
patch:
|
||||||
default:
|
backend:
|
||||||
# For the changed lines only, target 100% covered, but
|
flags:
|
||||||
# allow as low as 75%
|
- backend-python-3.11
|
||||||
|
- backend-python-3.12
|
||||||
|
- backend-python-3.13
|
||||||
|
- backend-python-3.14
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
target: 100%
|
||||||
|
threshold: 25%
|
||||||
|
frontend:
|
||||||
|
flags:
|
||||||
|
- frontend-node-24.x
|
||||||
|
paths:
|
||||||
|
- src-ui/**
|
||||||
target: 100%
|
target: 100%
|
||||||
threshold: 25%
|
threshold: 25%
|
||||||
# https://docs.codecov.com/docs/javascript-bundle-analysis
|
# https://docs.codecov.com/docs/javascript-bundle-analysis
|
||||||
bundle_analysis:
|
bundle_analysis:
|
||||||
# Fail if the bundle size increases by more than 1MB
|
|
||||||
warning_threshold: "1MB"
|
warning_threshold: "1MB"
|
||||||
status: true
|
status: true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-trixie-slim as main-app
|
FROM --platform=$BUILDPLATFORM docker.io/node:24-trixie-slim as main-app
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
@@ -64,8 +64,6 @@ ARG RUNTIME_PACKAGES="\
|
|||||||
libmagic1 \
|
libmagic1 \
|
||||||
media-types \
|
media-types \
|
||||||
zlib1g \
|
zlib1g \
|
||||||
# Barcode splitter
|
|
||||||
libzbar0 \
|
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
htop \
|
htop \
|
||||||
sudo"
|
sudo"
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ Additional tasks are available for common maintenance operations:
|
|||||||
- **Migrate Database**: To apply database migrations.
|
- **Migrate Database**: To apply database migrations.
|
||||||
- **Create Superuser**: To create an admin user for the application.
|
- **Create Superuser**: To create an admin user for the application.
|
||||||
|
|
||||||
|
## Committing from the Host Machine
|
||||||
|
|
||||||
|
The DevContainer automatically installs Git pre-commit hooks during setup. However, these hooks are configured for use inside the container.
|
||||||
|
|
||||||
|
If you want to commit changes from your host machine (outside the DevContainer), you need to set up prek on your host. This installs it as a standalone tool.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv tool install prek && prek install
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, you can commit either from inside the DevContainer or from your host machine.
|
||||||
|
|
||||||
## Let's Get Started!
|
## Let's Get Started!
|
||||||
|
|
||||||
Follow the steps above to get your development environment up and running. Happy coding!
|
Follow the steps above to get your development environment up and running. Happy coding!
|
||||||
|
|||||||
@@ -3,26 +3,31 @@
|
|||||||
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
||||||
"service": "paperless-development",
|
"service": "paperless-development",
|
||||||
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
||||||
"postCreateCommand": "/bin/bash -c 'rm -rf .venv/.* && uv sync --group dev && uv run pre-commit install'",
|
"forwardPorts": [4200, 8000],
|
||||||
|
"containerEnv": {
|
||||||
|
"UV_CACHE_DIR": "/usr/src/paperless/paperless-ngx/.uv-cache"
|
||||||
|
},
|
||||||
|
"postCreateCommand": "/bin/bash -c 'rm -rf .venv/.* && uv sync --group dev && uv run prek install'",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-vscode.js-debug-nightly",
|
"ms-vscode.js-debug-nightly",
|
||||||
"eamodio.gitlens",
|
"eamodio.gitlens",
|
||||||
"yzhang.markdown-all-in-one"
|
"yzhang.markdown-all-in-one",
|
||||||
],
|
"pnpm.pnpm"
|
||||||
"settings": {
|
],
|
||||||
"python.defaultInterpreterPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
"settings": {
|
||||||
"python.pythonPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
"python.defaultInterpreterPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
||||||
"python.terminal.activateEnvInCurrentTerminal": true,
|
"python.pythonPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
|
||||||
"editor.formatOnPaste": false,
|
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnPaste": false,
|
||||||
"editor.formatOnType": true,
|
"editor.formatOnSave": true,
|
||||||
"files.trimTrailingWhitespace": true
|
"editor.formatOnType": true,
|
||||||
}
|
"files.trimTrailingWhitespace": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"remoteUser": "paperless"
|
"remoteUser": "paperless"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"label": "Start: Frontend Angular",
|
"label": "Start: Frontend Angular",
|
||||||
"description": "Start the Frontend Angular Dev Server",
|
"description": "Start the Frontend Angular Dev Server",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pnpm start",
|
"command": "pnpm exec ng serve --host 0.0.0.0",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}/src-ui"
|
"cwd": "${workspaceFolder}/src-ui"
|
||||||
@@ -116,9 +116,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Maintenance: Build Documentation",
|
"label": "Maintenance: Build Documentation",
|
||||||
"description": "Build the documentation with MkDocs",
|
"description": "Build the documentation with Zensical",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "uv run mkdocs build --config-file mkdocs.yml && uv run mkdocs serve",
|
"command": "uv run zensical build && uv run zensical serve",
|
||||||
"group": "none",
|
"group": "none",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@@ -174,12 +174,22 @@
|
|||||||
{
|
{
|
||||||
"label": "Maintenance: Install Frontend Dependencies",
|
"label": "Maintenance: Install Frontend Dependencies",
|
||||||
"description": "Install frontend (pnpm) dependencies",
|
"description": "Install frontend (pnpm) dependencies",
|
||||||
"type": "pnpm",
|
"type": "shell",
|
||||||
"script": "install",
|
"command": "pnpm install",
|
||||||
"path": "src-ui",
|
|
||||||
"group": "clean",
|
"group": "clean",
|
||||||
"problemMatcher": [],
|
"problemMatcher": [],
|
||||||
"detail": "install dependencies from package"
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/src-ui"
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": true,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": false,
|
||||||
|
"clear": true,
|
||||||
|
"revealProblems": "onProblem"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Clean install frontend dependencies and build the frontend for production",
|
"description": "Clean install frontend dependencies and build the frontend for production",
|
||||||
|
|||||||
@@ -28,3 +28,4 @@
|
|||||||
./resources
|
./resources
|
||||||
# Other stuff
|
# Other stuff
|
||||||
**/*.drawio.png
|
**/*.drawio.png
|
||||||
|
.mypy_baseline
|
||||||
|
|||||||
@@ -39,3 +39,6 @@ max_line_length = off
|
|||||||
|
|
||||||
[Dockerfile*]
|
[Dockerfile*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[*.toml]
|
||||||
|
indent_style = space
|
||||||
|
|||||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -37,6 +37,6 @@ NOTE: PRs that do not address the following will not be merged, please do not sk
|
|||||||
- [ ] If applicable, I have included testing coverage for new code in this PR, for [backend](https://docs.paperless-ngx.com/development/#testing) and / or [front-end](https://docs.paperless-ngx.com/development/#testing-and-code-style) changes.
|
- [ ] If applicable, I have included testing coverage for new code in this PR, for [backend](https://docs.paperless-ngx.com/development/#testing) and / or [front-end](https://docs.paperless-ngx.com/development/#testing-and-code-style) changes.
|
||||||
- [ ] If applicable, I have tested my code for breaking changes & regressions on both mobile & desktop devices, using the latest version of major browsers.
|
- [ ] If applicable, I have tested my code for breaking changes & regressions on both mobile & desktop devices, using the latest version of major browsers.
|
||||||
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://docs.paperless-ngx.com/development/#back-end-development).
|
- [ ] If applicable, I have checked that all tests pass, see [documentation](https://docs.paperless-ngx.com/development/#back-end-development).
|
||||||
- [ ] I have run all `pre-commit` hooks, see [documentation](https://docs.paperless-ngx.com/development/#code-formatting-with-pre-commit-hooks).
|
- [ ] I have run all Git `pre-commit` hooks, see [documentation](https://docs.paperless-ngx.com/development/#code-formatting-with-pre-commit-hooks).
|
||||||
- [ ] I have made corresponding changes to the documentation as needed.
|
- [ ] I have made corresponding changes to the documentation as needed.
|
||||||
- [ ] In the description of the PR above I have disclosed the use of AI tools in the coding of this PR.
|
- [ ] In the description of the PR above I have disclosed the use of AI tools in the coding of this PR.
|
||||||
|
|||||||
29
.github/dependabot.yml
vendored
29
.github/dependabot.yml
vendored
@@ -12,6 +12,8 @@ updates:
|
|||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
labels:
|
labels:
|
||||||
- "frontend"
|
- "frontend"
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
@@ -36,7 +38,9 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
# Check for updates once a week
|
# Check for updates once a week
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "monthly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
labels:
|
labels:
|
||||||
- "backend"
|
- "backend"
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
@@ -46,8 +50,8 @@ updates:
|
|||||||
patterns:
|
patterns:
|
||||||
- "*pytest*"
|
- "*pytest*"
|
||||||
- "ruff"
|
- "ruff"
|
||||||
- "mkdocs-material"
|
- "zensical"
|
||||||
- "pre-commit*"
|
- "prek*"
|
||||||
# Django & DRF Ecosystem
|
# Django & DRF Ecosystem
|
||||||
django-ecosystem:
|
django-ecosystem:
|
||||||
patterns:
|
patterns:
|
||||||
@@ -69,7 +73,6 @@ updates:
|
|||||||
patterns:
|
patterns:
|
||||||
- "ocrmypdf"
|
- "ocrmypdf"
|
||||||
- "pdf2image"
|
- "pdf2image"
|
||||||
- "pyzbar"
|
|
||||||
- "zxing-cpp"
|
- "zxing-cpp"
|
||||||
- "tika-client"
|
- "tika-client"
|
||||||
- "gotenberg-client"
|
- "gotenberg-client"
|
||||||
@@ -98,6 +101,8 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
# Check for updates to GitHub Actions every month
|
# Check for updates to GitHub Actions every month
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
labels:
|
labels:
|
||||||
- "ci-cd"
|
- "ci-cd"
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
@@ -113,7 +118,9 @@ updates:
|
|||||||
- "/"
|
- "/"
|
||||||
- "/.devcontainer/"
|
- "/.devcontainer/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "monthly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
@@ -124,7 +131,9 @@ updates:
|
|||||||
- package-ecosystem: "docker-compose"
|
- package-ecosystem: "docker-compose"
|
||||||
directory: "/docker/compose/"
|
directory: "/docker/compose/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "monthly"
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
labels:
|
labels:
|
||||||
- "dependencies"
|
- "dependencies"
|
||||||
@@ -148,3 +157,11 @@ updates:
|
|||||||
postgres:
|
postgres:
|
||||||
patterns:
|
patterns:
|
||||||
- "docker.io/library/postgres*"
|
- "docker.io/library/postgres*"
|
||||||
|
- package-ecosystem: "pre-commit" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
groups:
|
||||||
|
pre-commit-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|||||||
1
.github/release-drafter.yml
vendored
1
.github/release-drafter.yml
vendored
@@ -44,6 +44,7 @@ include-labels:
|
|||||||
- 'notable'
|
- 'notable'
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- 'skip-changelog'
|
- 'skip-changelog'
|
||||||
|
filter-by-commitish: true
|
||||||
category-template: '### $TITLE'
|
category-template: '### $TITLE'
|
||||||
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||||
change-title-escapes: '\<*_&#@'
|
change-title-escapes: '\<*_&#@'
|
||||||
|
|||||||
214
.github/workflows/ci-backend.yml
vendored
Normal file
214
.github/workflows/ci-backend.yml
vendored
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
name: Backend Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: backend-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
env:
|
||||||
|
DEFAULT_UV_VERSION: "0.10.x"
|
||||||
|
NLTK_DATA: "/usr/share/nltk_data"
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
name: Detect Backend Changes
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
outputs:
|
||||||
|
backend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.backend == 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Decide run mode
|
||||||
|
id: force
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event_name }}" == "push" && ( "${{ github.ref_name }}" == "main" || "${{ github.ref_name }}" == "dev" ) ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "run_all=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
- name: Set diff range
|
||||||
|
id: range
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event.created }}" == "true" ]]; then
|
||||||
|
echo "base=${{ github.event.repository.default_branch }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "base=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
echo "ref=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Detect changes
|
||||||
|
id: filter
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
uses: dorny/paths-filter@v3.0.2
|
||||||
|
with:
|
||||||
|
base: ${{ steps.range.outputs.base }}
|
||||||
|
ref: ${{ steps.range.outputs.ref }}
|
||||||
|
filters: |
|
||||||
|
backend:
|
||||||
|
- 'src/**'
|
||||||
|
- 'pyproject.toml'
|
||||||
|
- 'uv.lock'
|
||||||
|
- 'docker/compose/docker-compose.ci-test.yml'
|
||||||
|
- '.github/workflows/ci-backend.yml'
|
||||||
|
test:
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.backend_changed == 'true'
|
||||||
|
name: "Python ${{ matrix.python-version }}"
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.11', '3.12', '3.13', '3.14']
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Start containers
|
||||||
|
run: |
|
||||||
|
docker compose --file docker/compose/docker-compose.ci-test.yml pull --quiet
|
||||||
|
docker compose --file docker/compose/docker-compose.ci-test.yml up --detach
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: "${{ matrix.python-version }}"
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq --no-install-recommends \
|
||||||
|
unpaper tesseract-ocr imagemagick ghostscript poppler-utils
|
||||||
|
- name: Configure ImageMagick
|
||||||
|
run: |
|
||||||
|
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
uv sync \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--group testing \
|
||||||
|
--frozen
|
||||||
|
- name: List installed Python dependencies
|
||||||
|
run: |
|
||||||
|
uv pip list
|
||||||
|
- name: Install NLTK data
|
||||||
|
run: |
|
||||||
|
uv run python -m nltk.downloader punkt punkt_tab snowball_data stopwords -d ${{ env.NLTK_DATA }}
|
||||||
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
NLTK_DATA: ${{ env.NLTK_DATA }}
|
||||||
|
PAPERLESS_CI_TEST: 1
|
||||||
|
run: |
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
pytest
|
||||||
|
- name: Upload test results to Codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v5.5.2
|
||||||
|
with:
|
||||||
|
flags: backend-python-${{ matrix.python-version }}
|
||||||
|
files: junit.xml
|
||||||
|
report_type: test_results
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v5.5.2
|
||||||
|
with:
|
||||||
|
flags: backend-python-${{ matrix.python-version }}
|
||||||
|
files: coverage.xml
|
||||||
|
report_type: coverage
|
||||||
|
- name: Stop containers
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose --file docker/compose/docker-compose.ci-test.yml logs
|
||||||
|
docker compose --file docker/compose/docker-compose.ci-test.yml down
|
||||||
|
typing:
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.backend_changed == 'true'
|
||||||
|
name: Check project typing
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
env:
|
||||||
|
DEFAULT_PYTHON: "3.12"
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: "${{ env.DEFAULT_PYTHON }}"
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
uv sync \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--group testing \
|
||||||
|
--group typing \
|
||||||
|
--frozen
|
||||||
|
- name: List installed Python dependencies
|
||||||
|
run: |
|
||||||
|
uv pip list
|
||||||
|
- name: Check typing (pyrefly)
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
uv run pyrefly \
|
||||||
|
check \
|
||||||
|
src/
|
||||||
|
- name: Cache Mypy
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: .mypy_cache
|
||||||
|
# Keyed by OS, Python version, and dependency hashes
|
||||||
|
key: ${{ runner.os }}-mypy-py${{ env.DEFAULT_PYTHON }}-${{ hashFiles('pyproject.toml', 'uv.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-mypy-py${{ env.DEFAULT_PYTHON }}-
|
||||||
|
${{ runner.os }}-mypy-
|
||||||
|
- name: Check typing (mypy)
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
uv run mypy \
|
||||||
|
--show-error-codes \
|
||||||
|
--warn-unused-configs \
|
||||||
|
src/ | uv run mypy-baseline filter
|
||||||
|
gate:
|
||||||
|
name: Backend CI Gate
|
||||||
|
needs: [changes, test, typing]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
steps:
|
||||||
|
- name: Check gate
|
||||||
|
run: |
|
||||||
|
if [[ "${{ needs.changes.outputs.backend_changed }}" != "true" ]]; then
|
||||||
|
echo "No backend-relevant changes detected."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs.test.result }}" != "success" ]]; then
|
||||||
|
echo "::error::Backend test job result: ${{ needs.test.result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs.typing.result }}" != "success" ]]; then
|
||||||
|
echo "::error::Backend typing job result: ${{ needs.typing.result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Backend checks passed."
|
||||||
254
.github/workflows/ci-docker.yml
vendored
Normal file
254
.github/workflows/ci-docker.yml
vendored
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
name: Docker Build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+'
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- beta
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: docker-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
jobs:
|
||||||
|
build-arch:
|
||||||
|
name: Build ${{ matrix.arch }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: ubuntu-24.04
|
||||||
|
arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
- runner: ubuntu-24.04-arm
|
||||||
|
arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
outputs:
|
||||||
|
should-push: ${{ steps.check-push.outputs.should-push }}
|
||||||
|
push-external: ${{ steps.check-push.outputs.push-external }}
|
||||||
|
repository: ${{ steps.repo.outputs.name }}
|
||||||
|
ref-name: ${{ steps.ref.outputs.name }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Determine ref name
|
||||||
|
id: ref
|
||||||
|
run: |
|
||||||
|
ref_name="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}"
|
||||||
|
# Sanitize by replacing / with - for use in tags and cache keys
|
||||||
|
sanitized_ref="${ref_name//\//-}"
|
||||||
|
|
||||||
|
echo "ref_name=${ref_name}"
|
||||||
|
echo "sanitized_ref=${sanitized_ref}"
|
||||||
|
|
||||||
|
echo "name=${sanitized_ref}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Check push permissions
|
||||||
|
id: check-push
|
||||||
|
env:
|
||||||
|
REF_NAME: ${{ steps.ref.outputs.name }}
|
||||||
|
run: |
|
||||||
|
# should-push: Should we push to GHCR?
|
||||||
|
# True for:
|
||||||
|
# 1. Pushes (tags/dev/beta) - filtered via the workflow triggers
|
||||||
|
# 2. Manual dispatch - always push to GHCR
|
||||||
|
# 3. Internal PRs where the branch name starts with 'feature-' or 'fix-'
|
||||||
|
|
||||||
|
should_push="false"
|
||||||
|
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||||
|
should_push="true"
|
||||||
|
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
should_push="true"
|
||||||
|
elif [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]]; then
|
||||||
|
if [[ "${REF_NAME}" == feature-* || "${REF_NAME}" == fix-* ]]; then
|
||||||
|
should_push="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "should-push=${should_push}"
|
||||||
|
echo "should-push=${should_push}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# push-external: Should we also push to Docker Hub and Quay.io?
|
||||||
|
# Only for main repo on dev/beta branches or version tags
|
||||||
|
push_external="false"
|
||||||
|
if [[ "${should_push}" == "true" && "${{ github.repository_owner }}" == "paperless-ngx" ]]; then
|
||||||
|
case "${REF_NAME}" in
|
||||||
|
dev|beta)
|
||||||
|
push_external="true"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "${{ github.ref }}" in
|
||||||
|
refs/tags/v*|*beta.rc*)
|
||||||
|
push_external="true"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
echo "push-external=${push_external}"
|
||||||
|
echo "push-external=${push_external}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Set repository name
|
||||||
|
id: repo
|
||||||
|
run: |
|
||||||
|
repo_name="${{ github.repository }}"
|
||||||
|
repo_name="${repo_name,,}"
|
||||||
|
|
||||||
|
echo "repository=${repo_name}"
|
||||||
|
echo "name=${repo_name}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4.0.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v4.0.0
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Maximize space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/share/dotnet
|
||||||
|
sudo rm -rf /opt/ghc
|
||||||
|
sudo rm -rf /usr/local/share/boost
|
||||||
|
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||||
|
- name: Docker metadata
|
||||||
|
id: docker-meta
|
||||||
|
uses: docker/metadata-action@v5.10.0
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=raw,value=${{ steps.ref.outputs.name }},enable=${{ github.event_name == 'pull_request' }}
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
- name: Build and push by digest
|
||||||
|
id: build
|
||||||
|
uses: docker/build-push-action@v6.19.2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: ${{ matrix.platform }}
|
||||||
|
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
PNGX_TAG_VERSION=${{ steps.docker-meta.outputs.version }}
|
||||||
|
outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }},push-by-digest=true,name-canonical=true,push=${{ steps.check-push.outputs.should-push }}
|
||||||
|
cache-from: |
|
||||||
|
type=registry,ref=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}/cache/app:${{ steps.ref.outputs.name }}-${{ matrix.arch }}
|
||||||
|
type=registry,ref=${{ env.REGISTRY }}/${{ steps.repo.outputs.name }}/cache/app:dev-${{ matrix.arch }}
|
||||||
|
cache-to: ${{ steps.check-push.outputs.should-push == 'true' && format('type=registry,mode=max,ref={0}/{1}/cache/app:{2}-{3}', env.REGISTRY, steps.repo.outputs.name, steps.ref.outputs.name, matrix.arch) || '' }}
|
||||||
|
- name: Export digest
|
||||||
|
if: steps.check-push.outputs.should-push == 'true'
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/digests
|
||||||
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
|
echo "digest=${digest}"
|
||||||
|
echo "${digest}" > "/tmp/digests/digest-${{ matrix.arch }}.txt"
|
||||||
|
- name: Upload digest
|
||||||
|
if: steps.check-push.outputs.should-push == 'true'
|
||||||
|
uses: actions/upload-artifact@v7.0.0
|
||||||
|
with:
|
||||||
|
name: digests-${{ matrix.arch }}
|
||||||
|
path: /tmp/digests/digest-${{ matrix.arch }}.txt
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
archive: false
|
||||||
|
merge-and-push:
|
||||||
|
name: Merge and Push Manifest
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: build-arch
|
||||||
|
if: needs.build-arch.outputs.should-push == 'true'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Download digests
|
||||||
|
uses: actions/download-artifact@v8.0.0
|
||||||
|
with:
|
||||||
|
path: /tmp/digests
|
||||||
|
pattern: digest-*.txt
|
||||||
|
merge-multiple: true
|
||||||
|
- name: List digests
|
||||||
|
run: |
|
||||||
|
echo "Downloaded digests:"
|
||||||
|
ls -la /tmp/digests/
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4.0.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v4.0.0
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
if: needs.build-arch.outputs.push-external == 'true'
|
||||||
|
uses: docker/login-action@v4.0.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to Quay.io
|
||||||
|
if: needs.build-arch.outputs.push-external == 'true'
|
||||||
|
uses: docker/login-action@v4.0.0
|
||||||
|
with:
|
||||||
|
registry: quay.io
|
||||||
|
username: ${{ secrets.QUAY_USERNAME }}
|
||||||
|
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
|
||||||
|
- name: Docker metadata
|
||||||
|
id: docker-meta
|
||||||
|
uses: docker/metadata-action@v5.10.0
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=raw,value=${{ needs.build-arch.outputs.ref-name }},enable=${{ github.event_name == 'pull_request' }}
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
- name: Create manifest list and push
|
||||||
|
working-directory: /tmp/digests
|
||||||
|
env:
|
||||||
|
REPOSITORY: ${{ needs.build-arch.outputs.repository }}
|
||||||
|
run: |
|
||||||
|
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "${DOCKER_METADATA_OUTPUT_JSON}")
|
||||||
|
|
||||||
|
digests=""
|
||||||
|
for digest_file in digest-*.txt; do
|
||||||
|
digest=$(cat "${digest_file}")
|
||||||
|
digests+="${{ env.REGISTRY }}/${REPOSITORY}@${digest} "
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Creating manifest with tags: ${tags}"
|
||||||
|
echo "From digests: ${digests}"
|
||||||
|
|
||||||
|
docker buildx imagetools create ${tags} ${digests}
|
||||||
|
- name: Inspect image
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
|
||||||
|
- name: Copy to Docker Hub
|
||||||
|
if: needs.build-arch.outputs.push-external == 'true'
|
||||||
|
env:
|
||||||
|
TAGS: ${{ steps.docker-meta.outputs.tags }}
|
||||||
|
GHCR_REPO: ${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }}
|
||||||
|
run: |
|
||||||
|
for tag in ${TAGS}; do
|
||||||
|
dockerhub_tag="${tag/${GHCR_REPO}/docker.io/paperlessngx/paperless-ngx}"
|
||||||
|
echo "Copying ${tag} to ${dockerhub_tag}"
|
||||||
|
skopeo copy --all "docker://${tag}" "docker://${dockerhub_tag}"
|
||||||
|
done
|
||||||
|
- name: Copy to Quay.io
|
||||||
|
if: needs.build-arch.outputs.push-external == 'true'
|
||||||
|
env:
|
||||||
|
TAGS: ${{ steps.docker-meta.outputs.tags }}
|
||||||
|
GHCR_REPO: ${{ env.REGISTRY }}/${{ needs.build-arch.outputs.repository }}
|
||||||
|
run: |
|
||||||
|
for tag in ${TAGS}; do
|
||||||
|
quay_tag="${tag/${GHCR_REPO}/quay.io/paperlessngx/paperless-ngx}"
|
||||||
|
echo "Copying ${tag} to ${quay_tag}"
|
||||||
|
skopeo copy --all "docker://${tag}" "docker://${quay_tag}"
|
||||||
|
done
|
||||||
132
.github/workflows/ci-docs.yml
vendored
Normal file
132
.github/workflows/ci-docs.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
name: Documentation
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: docs-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
env:
|
||||||
|
DEFAULT_UV_VERSION: "0.10.x"
|
||||||
|
DEFAULT_PYTHON_VERSION: "3.12"
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
name: Detect Docs Changes
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
outputs:
|
||||||
|
docs_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.docs == 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Decide run mode
|
||||||
|
id: force
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event_name }}" == "push" && ( "${{ github.ref_name }}" == "main" || "${{ github.ref_name }}" == "dev" ) ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "run_all=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
- name: Set diff range
|
||||||
|
id: range
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event.created }}" == "true" ]]; then
|
||||||
|
echo "base=${{ github.event.repository.default_branch }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "base=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
echo "ref=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Detect changes
|
||||||
|
id: filter
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
uses: dorny/paths-filter@v3.0.2
|
||||||
|
with:
|
||||||
|
base: ${{ steps.range.outputs.base }}
|
||||||
|
ref: ${{ steps.range.outputs.ref }}
|
||||||
|
filters: |
|
||||||
|
docs:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'zensical.toml'
|
||||||
|
- 'pyproject.toml'
|
||||||
|
- 'uv.lock'
|
||||||
|
- '.github/workflows/ci-docs.yml'
|
||||||
|
build:
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.docs_changed == 'true'
|
||||||
|
name: Build Documentation
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/configure-pages@v5.0.0
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||||
|
- name: Build documentation
|
||||||
|
run: |
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
zensical build --clean
|
||||||
|
- name: Upload GitHub Pages artifact
|
||||||
|
uses: actions/upload-pages-artifact@v4.0.0
|
||||||
|
with:
|
||||||
|
path: site
|
||||||
|
name: github-pages-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
deploy:
|
||||||
|
name: Deploy Documentation
|
||||||
|
needs: [changes, build]
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- name: Deploy GitHub Pages
|
||||||
|
uses: actions/deploy-pages@v4.0.5
|
||||||
|
id: deployment
|
||||||
|
with:
|
||||||
|
artifact_name: github-pages-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
gate:
|
||||||
|
name: Docs CI Gate
|
||||||
|
needs: [changes, build]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
steps:
|
||||||
|
- name: Check gate
|
||||||
|
run: |
|
||||||
|
if [[ "${{ needs.changes.outputs.docs_changed }}" != "true" ]]; then
|
||||||
|
echo "No docs-relevant changes detected."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs.build.result }}" != "success" ]]; then
|
||||||
|
echo "::error::Docs build job result: ${{ needs.build.result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Docs checks passed."
|
||||||
273
.github/workflows/ci-frontend.yml
vendored
Normal file
273
.github/workflows/ci-frontend.yml
vendored
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
name: Frontend Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: frontend-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
changes:
|
||||||
|
name: Detect Frontend Changes
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
outputs:
|
||||||
|
frontend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.frontend == 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Decide run mode
|
||||||
|
id: force
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event_name }}" == "push" && ( "${{ github.ref_name }}" == "main" || "${{ github.ref_name }}" == "dev" ) ]]; then
|
||||||
|
echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "run_all=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
- name: Set diff range
|
||||||
|
id: range
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
echo "base=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
elif [[ "${{ github.event.created }}" == "true" ]]; then
|
||||||
|
echo "base=${{ github.event.repository.default_branch }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "base=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
echo "ref=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Detect changes
|
||||||
|
id: filter
|
||||||
|
if: steps.force.outputs.run_all != 'true'
|
||||||
|
uses: dorny/paths-filter@v3.0.2
|
||||||
|
with:
|
||||||
|
base: ${{ steps.range.outputs.base }}
|
||||||
|
ref: ${{ steps.range.outputs.ref }}
|
||||||
|
filters: |
|
||||||
|
frontend:
|
||||||
|
- 'src-ui/**'
|
||||||
|
- '.github/workflows/ci-frontend.yml'
|
||||||
|
install-dependencies:
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.frontend_changed == 'true'
|
||||||
|
name: Install Dependencies
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Cache frontend dependencies
|
||||||
|
id: cache-frontend-deps
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd src-ui && pnpm install
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
needs: [changes, install-dependencies]
|
||||||
|
if: needs.changes.outputs.frontend_changed == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Cache frontend dependencies
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
- name: Re-link Angular CLI
|
||||||
|
run: cd src-ui && pnpm link @angular/cli
|
||||||
|
- name: Run lint
|
||||||
|
run: cd src-ui && pnpm run lint
|
||||||
|
unit-tests:
|
||||||
|
name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||||
|
needs: [changes, install-dependencies]
|
||||||
|
if: needs.changes.outputs.frontend_changed == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node-version: [24.x]
|
||||||
|
shard-index: [1, 2, 3, 4]
|
||||||
|
shard-count: [4]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Cache frontend dependencies
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
- name: Re-link Angular CLI
|
||||||
|
run: cd src-ui && pnpm link @angular/cli
|
||||||
|
- name: Run Jest unit tests
|
||||||
|
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
|
- name: Upload test results to Codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v5.5.2
|
||||||
|
with:
|
||||||
|
flags: frontend-node-${{ matrix.node-version }}
|
||||||
|
directory: src-ui/
|
||||||
|
report_type: test_results
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v5.5.2
|
||||||
|
with:
|
||||||
|
flags: frontend-node-${{ matrix.node-version }}
|
||||||
|
directory: src-ui/coverage/
|
||||||
|
e2e-tests:
|
||||||
|
name: "E2E Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
||||||
|
needs: [changes, install-dependencies]
|
||||||
|
if: needs.changes.outputs.frontend_changed == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container: mcr.microsoft.com/playwright:v1.58.2-noble
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node-version: [24.x]
|
||||||
|
shard-index: [1, 2]
|
||||||
|
shard-count: [2]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Cache frontend dependencies
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
- name: Re-link Angular CLI
|
||||||
|
run: cd src-ui && pnpm link @angular/cli
|
||||||
|
- name: Install dependencies
|
||||||
|
run: cd src-ui && pnpm install --no-frozen-lockfile
|
||||||
|
- name: Run Playwright E2E tests
|
||||||
|
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
|
bundle-analysis:
|
||||||
|
name: Bundle Analysis
|
||||||
|
needs: [changes, unit-tests, e2e-tests]
|
||||||
|
if: needs.changes.outputs.frontend_changed == 'true'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Cache frontend dependencies
|
||||||
|
uses: actions/cache@v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache
|
||||||
|
key: ${{ runner.os }}-frontend-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
||||||
|
- name: Re-link Angular CLI
|
||||||
|
run: cd src-ui && pnpm link @angular/cli
|
||||||
|
- name: Build and analyze
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
run: cd src-ui && pnpm run build --configuration=production
|
||||||
|
gate:
|
||||||
|
name: Frontend CI Gate
|
||||||
|
needs: [changes, install-dependencies, lint, unit-tests, e2e-tests, bundle-analysis]
|
||||||
|
if: always()
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
steps:
|
||||||
|
- name: Check gate
|
||||||
|
run: |
|
||||||
|
if [[ "${{ needs.changes.outputs.frontend_changed }}" != "true" ]]; then
|
||||||
|
echo "No frontend-relevant changes detected."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs['install-dependencies'].result }}" != "success" ]]; then
|
||||||
|
echo "::error::Frontend install job result: ${{ needs['install-dependencies'].result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs.lint.result }}" != "success" ]]; then
|
||||||
|
echo "::error::Frontend lint job result: ${{ needs.lint.result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs['unit-tests'].result }}" != "success" ]]; then
|
||||||
|
echo "::error::Frontend unit-tests job result: ${{ needs['unit-tests'].result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs['e2e-tests'].result }}" != "success" ]]; then
|
||||||
|
echo "::error::Frontend e2e-tests job result: ${{ needs['e2e-tests'].result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${{ needs['bundle-analysis'].result }}" != "success" ]]; then
|
||||||
|
echo "::error::Frontend bundle-analysis job result: ${{ needs['bundle-analysis'].result }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Frontend checks passed."
|
||||||
24
.github/workflows/ci-lint.yml
vendored
Normal file
24
.github/workflows/ci-lint.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Lint
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations**'
|
||||||
|
concurrency:
|
||||||
|
group: lint-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Linting via prek
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
- name: Install Python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: "3.14"
|
||||||
|
- name: Run prek
|
||||||
|
uses: j178/prek-action@v1.1.1
|
||||||
238
.github/workflows/ci-release.yml
vendored
Normal file
238
.github/workflows/ci-release.yml
vendored
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+'
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
env:
|
||||||
|
DEFAULT_UV_VERSION: "0.10.x"
|
||||||
|
DEFAULT_PYTHON_VERSION: "3.12"
|
||||||
|
jobs:
|
||||||
|
wait-for-docker:
|
||||||
|
name: Wait for Docker Build
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Wait for Docker build
|
||||||
|
uses: lewagon/wait-on-check-action@v1.5.0
|
||||||
|
with:
|
||||||
|
ref: ${{ github.sha }}
|
||||||
|
check-name: 'Build Docker Image'
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
wait-interval: 60
|
||||||
|
build-release:
|
||||||
|
name: Build Release
|
||||||
|
needs: wait-for-docker
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
# ---- Frontend Build ----
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4.2.0
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
- name: Use Node.js 24
|
||||||
|
uses: actions/setup-node@v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: cd src-ui && pnpm install
|
||||||
|
- name: Build frontend
|
||||||
|
run: cd src-ui && pnpm run build --configuration production
|
||||||
|
# ---- Backend Setup ----
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
||||||
|
# ---- Build Documentation ----
|
||||||
|
- name: Build documentation
|
||||||
|
run: |
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
zensical build --clean
|
||||||
|
# ---- Prepare Release ----
|
||||||
|
- name: Generate requirements file
|
||||||
|
run: |
|
||||||
|
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
|
||||||
|
- name: Compile messages
|
||||||
|
run: |
|
||||||
|
cd src/
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
manage.py compilemessages
|
||||||
|
- name: Collect static files
|
||||||
|
run: |
|
||||||
|
cd src/
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
manage.py collectstatic --no-input --clear
|
||||||
|
- name: Assemble release package
|
||||||
|
run: |
|
||||||
|
mkdir -p dist/paperless-ngx/scripts
|
||||||
|
|
||||||
|
for file_name in .dockerignore \
|
||||||
|
.env \
|
||||||
|
Dockerfile \
|
||||||
|
pyproject.toml \
|
||||||
|
uv.lock \
|
||||||
|
requirements.txt \
|
||||||
|
LICENSE \
|
||||||
|
README.md \
|
||||||
|
paperless.conf.example
|
||||||
|
do
|
||||||
|
cp --verbose ${file_name} dist/paperless-ngx/
|
||||||
|
done
|
||||||
|
mv dist/paperless-ngx/paperless.conf.example dist/paperless-ngx/paperless.conf
|
||||||
|
|
||||||
|
cp --recursive docker/ dist/paperless-ngx/docker
|
||||||
|
cp scripts/*.service scripts/*.sh scripts/*.socket dist/paperless-ngx/scripts/
|
||||||
|
cp --recursive src/ dist/paperless-ngx/src
|
||||||
|
cp --recursive site/ dist/paperless-ngx/docs
|
||||||
|
mv static dist/paperless-ngx/
|
||||||
|
|
||||||
|
find dist/paperless-ngx -name "__pycache__" -type d -exec rm -rf {} +
|
||||||
|
- name: Create release archive
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
sudo chown -R 1000:1000 paperless-ngx/
|
||||||
|
tar -cJf paperless-ngx.tar.xz paperless-ngx/
|
||||||
|
- name: Upload release artifact
|
||||||
|
uses: actions/upload-artifact@v7.0.0
|
||||||
|
with:
|
||||||
|
name: release
|
||||||
|
path: dist/paperless-ngx.tar.xz
|
||||||
|
retention-days: 7
|
||||||
|
publish-release:
|
||||||
|
name: Publish Release
|
||||||
|
needs: build-release
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
outputs:
|
||||||
|
prerelease: ${{ steps.get-version.outputs.prerelease }}
|
||||||
|
changelog: ${{ steps.create-release.outputs.body }}
|
||||||
|
version: ${{ steps.get-version.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Download release artifact
|
||||||
|
uses: actions/download-artifact@v8.0.0
|
||||||
|
with:
|
||||||
|
name: release
|
||||||
|
path: ./
|
||||||
|
- name: Get version info
|
||||||
|
id: get-version
|
||||||
|
run: |
|
||||||
|
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
||||||
|
if [[ "${{ github.ref_name }}" == *"-beta.rc"* ]]; then
|
||||||
|
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
- name: Create release and changelog
|
||||||
|
id: create-release
|
||||||
|
uses: release-drafter/release-drafter@v6.2.0
|
||||||
|
with:
|
||||||
|
name: Paperless-ngx ${{ steps.get-version.outputs.version }}
|
||||||
|
tag: ${{ steps.get-version.outputs.version }}
|
||||||
|
version: ${{ steps.get-version.outputs.version }}
|
||||||
|
prerelease: ${{ steps.get-version.outputs.prerelease }}
|
||||||
|
publish: true
|
||||||
|
commitish: main
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Upload release archive
|
||||||
|
uses: shogo82148/actions-upload-release-asset@v1.9.2
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||||
|
asset_path: ./paperless-ngx.tar.xz
|
||||||
|
asset_name: paperless-ngx-${{ steps.get-version.outputs.version }}.tar.xz
|
||||||
|
asset_content_type: application/x-xz
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Append changelog to docs (only on non-prerelease)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
append-changelog:
|
||||||
|
name: Append Changelog
|
||||||
|
needs: publish-release
|
||||||
|
if: needs.publish-release.outputs.prerelease == 'false'
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup-python
|
||||||
|
uses: actions/setup-python@v6.2.0
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
|
- name: Update changelog
|
||||||
|
working-directory: docs
|
||||||
|
run: |
|
||||||
|
git branch ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
|
git checkout ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
|
|
||||||
|
echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md
|
||||||
|
|
||||||
|
echo "Manually linking usernames"
|
||||||
|
sed -i -r 's|@([a-zA-Z0-9_]+) \(\[#|[@\1](https://github.com/\1) ([#|g' changelog-new.md
|
||||||
|
|
||||||
|
echo "Removing unneeded comment tags"
|
||||||
|
sed -i -r 's|@<!---->|@|g' changelog-new.md
|
||||||
|
|
||||||
|
CURRENT_CHANGELOG=$(tail --lines +2 changelog.md)
|
||||||
|
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
||||||
|
mv changelog-new.md changelog.md
|
||||||
|
|
||||||
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
prek run --files changelog.md || true
|
||||||
|
|
||||||
|
git config --global user.name "github-actions"
|
||||||
|
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
||||||
|
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
|
- name: Create pull request
|
||||||
|
uses: actions/github-script@v8.0.0
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { repo, owner } = context.repo;
|
||||||
|
const result = await github.rest.pulls.create({
|
||||||
|
title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog',
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
head: '${{ needs.publish-release.outputs.version }}-changelog',
|
||||||
|
base: 'main',
|
||||||
|
body: 'This PR is auto-generated by CI.'
|
||||||
|
});
|
||||||
|
github.rest.issues.addLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: result.data.number,
|
||||||
|
labels: ['documentation', 'skip-changelog']
|
||||||
|
});
|
||||||
693
.github/workflows/ci.yml
vendored
693
.github/workflows/ci.yml
vendored
@@ -1,693 +0,0 @@
|
|||||||
name: ci
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
# https://semver.org/#spec-item-2
|
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
|
||||||
# https://semver.org/#spec-item-9
|
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+'
|
|
||||||
branches-ignore:
|
|
||||||
- 'translations**'
|
|
||||||
pull_request:
|
|
||||||
branches-ignore:
|
|
||||||
- 'translations**'
|
|
||||||
env:
|
|
||||||
DEFAULT_UV_VERSION: "0.9.x"
|
|
||||||
# This is the default version of Python to use in most steps which aren't specific
|
|
||||||
DEFAULT_PYTHON_VERSION: "3.11"
|
|
||||||
NLTK_DATA: "/usr/share/nltk_data"
|
|
||||||
jobs:
|
|
||||||
detect-duplicate:
|
|
||||||
name: Detect Duplicate Run
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
outputs:
|
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
|
||||||
steps:
|
|
||||||
- name: Check if workflow should run
|
|
||||||
id: check
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
if (context.eventName !== 'push') {
|
|
||||||
core.info('Not a push event; running workflow.');
|
|
||||||
core.setOutput('should_run', 'true');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ref = context.ref || '';
|
|
||||||
if (!ref.startsWith('refs/heads/')) {
|
|
||||||
core.info('Push is not to a branch; running workflow.');
|
|
||||||
core.setOutput('should_run', 'true');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const branch = ref.substring('refs/heads/'.length);
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const prs = await github.paginate(github.rest.pulls.list, {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
state: 'open',
|
|
||||||
head: `${owner}:${branch}`,
|
|
||||||
per_page: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (prs.length === 0) {
|
|
||||||
core.info(`No open PR found for ${branch}; running workflow.`);
|
|
||||||
core.setOutput('should_run', 'true');
|
|
||||||
} else {
|
|
||||||
core.info(`Found ${prs.length} open PR(s) for ${branch}; skipping duplicate push run.`);
|
|
||||||
core.setOutput('should_run', 'false');
|
|
||||||
}
|
|
||||||
pre-commit:
|
|
||||||
needs:
|
|
||||||
- detect-duplicate
|
|
||||||
if: needs.detect-duplicate.outputs.should_run == 'true'
|
|
||||||
name: Linting Checks
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Install python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Check files
|
|
||||||
uses: pre-commit/action@v3.0.1
|
|
||||||
documentation:
|
|
||||||
name: "Build & Deploy Documentation"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- pre-commit
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Set up Python
|
|
||||||
id: setup-python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
|
||||||
enable-cache: true
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Install Python dependencies
|
|
||||||
run: |
|
|
||||||
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
|
||||||
- name: Make documentation
|
|
||||||
run: |
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
--dev \
|
|
||||||
--frozen \
|
|
||||||
mkdocs build --config-file ./mkdocs.yml
|
|
||||||
- name: Deploy documentation
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
run: |
|
|
||||||
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
|
|
||||||
git config --global user.name "${{ github.actor }}"
|
|
||||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
--dev \
|
|
||||||
--frozen \
|
|
||||||
mkdocs gh-deploy --force --no-history
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: documentation
|
|
||||||
path: site/
|
|
||||||
retention-days: 7
|
|
||||||
tests-backend:
|
|
||||||
name: "Backend Tests (Python ${{ matrix.python-version }})"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- pre-commit
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
python-version: ['3.10', '3.11', '3.12']
|
|
||||||
fail-fast: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Start containers
|
|
||||||
run: |
|
|
||||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml pull --quiet
|
|
||||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml up --detach
|
|
||||||
- name: Set up Python
|
|
||||||
id: setup-python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: "${{ matrix.python-version }}"
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
|
||||||
enable-cache: true
|
|
||||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript libzbar0 poppler-utils
|
|
||||||
- name: Configure ImageMagick
|
|
||||||
run: |
|
|
||||||
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
|
||||||
- name: Install Python dependencies
|
|
||||||
run: |
|
|
||||||
uv sync \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
--group testing \
|
|
||||||
--frozen
|
|
||||||
- name: List installed Python dependencies
|
|
||||||
run: |
|
|
||||||
uv pip list
|
|
||||||
- name: Install or update NLTK dependencies
|
|
||||||
run: uv run python -m nltk.downloader punkt punkt_tab snowball_data stopwords -d ${{ env.NLTK_DATA }}
|
|
||||||
- name: Tests
|
|
||||||
env:
|
|
||||||
NLTK_DATA: ${{ env.NLTK_DATA }}
|
|
||||||
PAPERLESS_CI_TEST: 1
|
|
||||||
# Enable paperless_mail testing against real server
|
|
||||||
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
|
|
||||||
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
|
||||||
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
|
||||||
run: |
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
--dev \
|
|
||||||
--frozen \
|
|
||||||
pytest
|
|
||||||
- name: Upload backend test results to Codecov
|
|
||||||
if: always()
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
flags: backend-python-${{ matrix.python-version }}
|
|
||||||
files: junit.xml
|
|
||||||
report_type: test_results
|
|
||||||
- name: Upload backend coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
flags: backend-python-${{ matrix.python-version }}
|
|
||||||
files: coverage.xml
|
|
||||||
- name: Stop containers
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs
|
|
||||||
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down
|
|
||||||
install-frontend-dependencies:
|
|
||||||
name: "Install Frontend Dependencies"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- pre-commit
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
- name: Use Node.js 20
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
|
||||||
- name: Cache frontend dependencies
|
|
||||||
id: cache-frontend-deps
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
|
||||||
- name: Install dependencies
|
|
||||||
run: cd src-ui && pnpm install
|
|
||||||
tests-frontend:
|
|
||||||
name: "Frontend Unit Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- install-frontend-dependencies
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node-version: [20.x]
|
|
||||||
shard-index: [1, 2, 3, 4]
|
|
||||||
shard-count: [4]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
- name: Use Node.js 20
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
|
||||||
- name: Cache frontend dependencies
|
|
||||||
id: cache-frontend-deps
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
|
||||||
- name: Re-link Angular cli
|
|
||||||
run: cd src-ui && pnpm link @angular/cli
|
|
||||||
- name: Linting checks
|
|
||||||
run: cd src-ui && pnpm run lint
|
|
||||||
- name: Run Jest unit tests
|
|
||||||
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
|
||||||
- name: Upload frontend test results to Codecov
|
|
||||||
if: always()
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
flags: frontend-node-${{ matrix.node-version }}
|
|
||||||
directory: src-ui/
|
|
||||||
report_type: test_results
|
|
||||||
- name: Upload frontend coverage to Codecov
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
flags: frontend-node-${{ matrix.node-version }}
|
|
||||||
directory: src-ui/coverage/
|
|
||||||
tests-frontend-e2e:
|
|
||||||
name: "Frontend E2E Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
container: mcr.microsoft.com/playwright:v1.57.0-noble
|
|
||||||
needs:
|
|
||||||
- install-frontend-dependencies
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node-version: [20.x]
|
|
||||||
shard-index: [1, 2]
|
|
||||||
shard-count: [2]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
- name: Use Node.js 20
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
|
||||||
- name: Cache frontend dependencies
|
|
||||||
id: cache-frontend-deps
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
|
|
||||||
- name: Re-link Angular cli
|
|
||||||
run: cd src-ui && pnpm link @angular/cli
|
|
||||||
- name: Install dependencies
|
|
||||||
run: cd src-ui && pnpm install --no-frozen-lockfile
|
|
||||||
- name: Run Playwright e2e tests
|
|
||||||
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
|
||||||
frontend-bundle-analysis:
|
|
||||||
name: "Frontend Bundle Analysis"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- tests-frontend
|
|
||||||
- tests-frontend-e2e
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
- name: Use Node.js 20
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: 20.x
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
|
||||||
- name: Cache frontend dependencies
|
|
||||||
id: cache-frontend-deps
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.pnpm-store
|
|
||||||
~/.cache
|
|
||||||
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }}
|
|
||||||
- name: Re-link Angular cli
|
|
||||||
run: cd src-ui && pnpm link @angular/cli
|
|
||||||
- name: Build frontend and upload analysis
|
|
||||||
env:
|
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
||||||
run: cd src-ui && pnpm run build --configuration=production
|
|
||||||
build-docker-image:
|
|
||||||
name: Build Docker image for ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
if: (github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || startsWith(github.ref, 'refs/heads/fix-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/l10n_'))) || (github.event_name == 'pull_request' && (startsWith(github.head_ref, 'feature-') || startsWith(github.head_ref, 'fix-') || github.head_ref == 'dev' || github.head_ref == 'beta' || contains(github.head_ref, 'beta.rc') || startsWith(github.head_ref, 'l10n_')))
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
needs:
|
|
||||||
- tests-backend
|
|
||||||
- tests-frontend
|
|
||||||
- tests-frontend-e2e
|
|
||||||
steps:
|
|
||||||
- name: Prepare build variables
|
|
||||||
id: build-vars
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
result-encoding: string
|
|
||||||
script: |
|
|
||||||
const isPR = context.eventName === 'pull_request';
|
|
||||||
const defaultRefName = context.ref.replace('refs/heads/', '');
|
|
||||||
const headRef = isPR ? context.payload.pull_request.head.ref : defaultRefName;
|
|
||||||
const buildRef = isPR ? `refs/heads/${headRef}` : context.ref;
|
|
||||||
const buildCacheKey = headRef.split('/').join('-');
|
|
||||||
const canPush = context.eventName === 'push' || (isPR && context.payload.pull_request.head.repo.full_name === `${context.repo.owner}/${context.repo.repo}`);
|
|
||||||
|
|
||||||
core.setOutput('build-ref', buildRef);
|
|
||||||
core.setOutput('build-ref-name', headRef);
|
|
||||||
core.setOutput('build-cache-key', buildCacheKey);
|
|
||||||
core.setOutput('can-push', canPush ? 'true' : 'false');
|
|
||||||
- name: Check pushing to Docker Hub
|
|
||||||
id: push-other-places
|
|
||||||
# Only push to Dockerhub from the main repo AND the ref is either:
|
|
||||||
# main
|
|
||||||
# dev
|
|
||||||
# beta
|
|
||||||
# a tag
|
|
||||||
# Otherwise forks would require a Docker Hub account and secrets setup
|
|
||||||
env:
|
|
||||||
BUILD_REF: ${{ steps.build-vars.outputs.build-ref }}
|
|
||||||
BUILD_REF_NAME: ${{ steps.build-vars.outputs.build-ref-name }}
|
|
||||||
run: |
|
|
||||||
if [[ ${{ github.repository_owner }} == "paperless-ngx" && ( "$BUILD_REF_NAME" == "dev" || "$BUILD_REF_NAME" == "beta" || $BUILD_REF == refs/tags/v* || $BUILD_REF == *beta.rc* ) ]] ; then
|
|
||||||
echo "Enabling DockerHub image push"
|
|
||||||
echo "enable=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "Not pushing to DockerHub"
|
|
||||||
echo "enable=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
- name: Set ghcr repository name
|
|
||||||
id: set-ghcr-repository
|
|
||||||
run: |
|
|
||||||
ghcr_name=$(echo "${{ github.repository }}" | awk '{ print tolower($0) }')
|
|
||||||
echo "Name is ${ghcr_name}"
|
|
||||||
echo "ghcr-repository=${ghcr_name}" >> $GITHUB_OUTPUT
|
|
||||||
- name: Gather Docker metadata
|
|
||||||
id: docker-meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}
|
|
||||||
name=paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
|
||||||
name=quay.io/paperlessngx/paperless-ngx,enable=${{ steps.push-other-places.outputs.enable }}
|
|
||||||
tags: |
|
|
||||||
# Tag branches with branch name
|
|
||||||
type=ref,event=branch
|
|
||||||
# Pull requests need a sanitized branch tag for pushing images
|
|
||||||
type=raw,value=${{ steps.build-vars.outputs.build-cache-key }},enable=${{ github.event_name == 'pull_request' }}
|
|
||||||
# Process semver tags
|
|
||||||
# For a tag x.y.z or vX.Y.Z, output an x.y.z and x.y image tag
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
# If https://github.com/docker/buildx/issues/1044 is resolved,
|
|
||||||
# the append input with a native arm64 arch could be used to
|
|
||||||
# significantly speed up building
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: arm64
|
|
||||||
- name: Login to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
# Don't attempt to login if not pushing to Docker Hub
|
|
||||||
if: steps.push-other-places.outputs.enable == 'true'
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to Quay.io
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
# Don't attempt to login if not pushing to Quay.io
|
|
||||||
if: steps.push-other-places.outputs.enable == 'true'
|
|
||||||
with:
|
|
||||||
registry: quay.io
|
|
||||||
username: ${{ secrets.QUAY_USERNAME }}
|
|
||||||
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: ${{ steps.build-vars.outputs.can-push == 'true' }}
|
|
||||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
|
||||||
build-args: |
|
|
||||||
PNGX_TAG_VERSION=${{ steps.docker-meta.outputs.version }}
|
|
||||||
# Get cache layers from this branch, then dev
|
|
||||||
# This allows new branches to get at least some cache benefits, generally from dev
|
|
||||||
cache-from: |
|
|
||||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ steps.build-vars.outputs.build-cache-key }}
|
|
||||||
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev
|
|
||||||
cache-to: ${{ steps.build-vars.outputs.can-push == 'true' && format('type=registry,mode=max,ref=ghcr.io/{0}/builder/cache/app:{1}', steps.set-ghcr-repository.outputs.ghcr-repository, steps.build-vars.outputs.build-cache-key) || '' }}
|
|
||||||
- name: Inspect image
|
|
||||||
if: steps.build-vars.outputs.can-push == 'true'
|
|
||||||
run: |
|
|
||||||
docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
|
|
||||||
- name: Export frontend artifact from docker
|
|
||||||
if: steps.build-vars.outputs.can-push == 'true'
|
|
||||||
run: |
|
|
||||||
docker create --name frontend-extract ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
|
|
||||||
docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/
|
|
||||||
- name: Upload frontend artifact
|
|
||||||
if: steps.build-vars.outputs.can-push == 'true'
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: frontend-compiled
|
|
||||||
path: src/documents/static/frontend/
|
|
||||||
retention-days: 7
|
|
||||||
build-release:
|
|
||||||
name: "Build Release"
|
|
||||||
needs:
|
|
||||||
- build-docker-image
|
|
||||||
- documentation
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Set up Python
|
|
||||||
id: setup-python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
|
||||||
enable-cache: true
|
|
||||||
python-version: ${{ steps.setup-python.outputs.python-version }}
|
|
||||||
- name: Install Python dependencies
|
|
||||||
run: |
|
|
||||||
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -qq --no-install-recommends gettext liblept5
|
|
||||||
- name: Download frontend artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: frontend-compiled
|
|
||||||
path: src/documents/static/frontend/
|
|
||||||
- name: Download documentation artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: documentation
|
|
||||||
path: docs/_build/html/
|
|
||||||
- name: Generate requirements file
|
|
||||||
run: |
|
|
||||||
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
|
|
||||||
- name: Compile messages
|
|
||||||
run: |
|
|
||||||
cd src/
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
manage.py compilemessages
|
|
||||||
- name: Collect static files
|
|
||||||
run: |
|
|
||||||
cd src/
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
manage.py collectstatic --no-input
|
|
||||||
- name: Move files
|
|
||||||
run: |
|
|
||||||
echo "Making dist folders"
|
|
||||||
for directory in dist \
|
|
||||||
dist/paperless-ngx \
|
|
||||||
dist/paperless-ngx/scripts;
|
|
||||||
do
|
|
||||||
mkdir --verbose --parents ${directory}
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Copying basic files"
|
|
||||||
for file_name in .dockerignore \
|
|
||||||
.env \
|
|
||||||
Dockerfile \
|
|
||||||
pyproject.toml \
|
|
||||||
uv.lock \
|
|
||||||
requirements.txt \
|
|
||||||
LICENSE \
|
|
||||||
README.md \
|
|
||||||
paperless.conf.example
|
|
||||||
do
|
|
||||||
cp --verbose ${file_name} dist/paperless-ngx/
|
|
||||||
done
|
|
||||||
mv --verbose dist/paperless-ngx/paperless.conf.example dist/paperless-ngx/paperless.conf
|
|
||||||
|
|
||||||
echo "Copying Docker related files"
|
|
||||||
cp --recursive docker/ dist/paperless-ngx/docker
|
|
||||||
|
|
||||||
echo "Copying startup scripts"
|
|
||||||
cp --verbose scripts/*.service scripts/*.sh scripts/*.socket dist/paperless-ngx/scripts/
|
|
||||||
|
|
||||||
echo "Copying source files"
|
|
||||||
cp --recursive src/ dist/paperless-ngx/src
|
|
||||||
echo "Copying documentation"
|
|
||||||
cp --recursive docs/_build/html/ dist/paperless-ngx/docs
|
|
||||||
|
|
||||||
mv --verbose static dist/paperless-ngx
|
|
||||||
- name: Make release package
|
|
||||||
run: |
|
|
||||||
echo "Creating release archive"
|
|
||||||
cd dist
|
|
||||||
sudo chown -R 1000:1000 paperless-ngx/
|
|
||||||
tar -cJf paperless-ngx.tar.xz paperless-ngx/
|
|
||||||
- name: Upload release artifact
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: release
|
|
||||||
path: dist/paperless-ngx.tar.xz
|
|
||||||
retention-days: 7
|
|
||||||
publish-release:
|
|
||||||
name: "Publish Release"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
outputs:
|
|
||||||
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
|
||||||
changelog: ${{ steps.create-release.outputs.body }}
|
|
||||||
version: ${{ steps.get_version.outputs.version }}
|
|
||||||
needs:
|
|
||||||
- build-release
|
|
||||||
if: github.ref_type == 'tag' && (startsWith(github.ref_name, 'v') || contains(github.ref_name, '-beta.rc'))
|
|
||||||
steps:
|
|
||||||
- name: Download release artifact
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
name: release
|
|
||||||
path: ./
|
|
||||||
- name: Get version
|
|
||||||
id: get_version
|
|
||||||
run: |
|
|
||||||
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
|
||||||
if [[ ${{ contains(github.ref_name, '-beta.rc') }} == 'true' ]]; then
|
|
||||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
- name: Create Release and Changelog
|
|
||||||
id: create-release
|
|
||||||
uses: release-drafter/release-drafter@v6
|
|
||||||
with:
|
|
||||||
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
|
|
||||||
tag: ${{ steps.get_version.outputs.version }}
|
|
||||||
version: ${{ steps.get_version.outputs.version }}
|
|
||||||
prerelease: ${{ steps.get_version.outputs.prerelease }}
|
|
||||||
publish: true # ensures release is not marked as draft
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Upload release archive
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: shogo82148/actions-upload-release-asset@v1
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
|
||||||
asset_path: ./paperless-ngx.tar.xz
|
|
||||||
asset_name: paperless-ngx-${{ steps.get_version.outputs.version }}.tar.xz
|
|
||||||
asset_content_type: application/x-xz
|
|
||||||
append-changelog:
|
|
||||||
name: "Append Changelog"
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs:
|
|
||||||
- publish-release
|
|
||||||
if: needs.publish-release.outputs.prerelease == 'false'
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
- name: Set up Python
|
|
||||||
id: setup-python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Install uv
|
|
||||||
uses: astral-sh/setup-uv@v7
|
|
||||||
with:
|
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
|
||||||
enable-cache: true
|
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
|
||||||
- name: Append Changelog to docs
|
|
||||||
id: append-Changelog
|
|
||||||
working-directory: docs
|
|
||||||
run: |
|
|
||||||
git branch ${{ needs.publish-release.outputs.version }}-changelog
|
|
||||||
git checkout ${{ needs.publish-release.outputs.version }}-changelog
|
|
||||||
echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md
|
|
||||||
echo "Manually linking usernames"
|
|
||||||
sed -i -r 's|@([a-zA-Z0-9_]+) \(\[#|[@\1](https://github.com/\1) ([#|g' changelog-new.md
|
|
||||||
echo "Removing unneeded comment tags"
|
|
||||||
sed -i -r 's|@<!---->|@|g' changelog-new.md
|
|
||||||
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
|
||||||
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
|
||||||
mv changelog-new.md changelog.md
|
|
||||||
uv run \
|
|
||||||
--python ${{ steps.setup-python.outputs.python-version }} \
|
|
||||||
--dev \
|
|
||||||
pre-commit run --files changelog.md || true
|
|
||||||
git config --global user.name "github-actions"
|
|
||||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
|
||||||
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { repo, owner } = context.repo;
|
|
||||||
const result = await github.rest.pulls.create({
|
|
||||||
title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog',
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
head: '${{ needs.publish-release.outputs.version }}-changelog',
|
|
||||||
base: 'main',
|
|
||||||
body: 'This PR is auto-generated by CI.'
|
|
||||||
});
|
|
||||||
github.rest.issues.addLabels({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: result.data.number,
|
|
||||||
labels: ['documentation', 'skip-changelog']
|
|
||||||
});
|
|
||||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -34,10 +34,10 @@ jobs:
|
|||||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6.0.2
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v4
|
uses: github/codeql-action/init@v4.32.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -45,4 +45,4 @@ jobs:
|
|||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v4
|
uses: github/codeql-action/analyze@v4.32.5
|
||||||
|
|||||||
4
.github/workflows/crowdin.yml
vendored
4
.github/workflows/crowdin.yml
vendored
@@ -13,11 +13,11 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||||
- name: crowdin action
|
- name: crowdin action
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2.15.0
|
||||||
with:
|
with:
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
|||||||
25
.github/workflows/pr-bot.yml
vendored
25
.github/workflows/pr-bot.yml
vendored
@@ -2,17 +2,28 @@ name: PR Bot
|
|||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
jobs:
|
jobs:
|
||||||
|
anti-slop:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: read
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: peakoss/anti-slop@v0.2.1
|
||||||
|
with:
|
||||||
|
max-failures: 4
|
||||||
|
failure-add-pr-labels: 'ai'
|
||||||
pr-bot:
|
pr-bot:
|
||||||
name: Automated PR Bot
|
name: Automated PR Bot
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Label PR by file path or branch name
|
- name: Label PR by file path or branch name
|
||||||
# see .github/labeler.yml for the labeler config
|
# see .github/labeler.yml for the labeler config
|
||||||
uses: actions/labeler@v6
|
uses: actions/labeler@v6.0.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Label by size
|
- name: Label by size
|
||||||
@@ -26,7 +37,7 @@ jobs:
|
|||||||
fail_if_xl: 'false'
|
fail_if_xl: 'false'
|
||||||
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
|
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
|
||||||
- name: Label by PR title
|
- name: Label by PR title
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
@@ -52,7 +63,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
- name: Label bot-generated PRs
|
- name: Label bot-generated PRs
|
||||||
if: ${{ contains(github.actor, 'dependabot') || contains(github.actor, 'crowdin-bot') }}
|
if: ${{ contains(github.actor, 'dependabot') || contains(github.actor, 'crowdin-bot') }}
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
@@ -77,7 +88,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
- name: Welcome comment
|
- name: Welcome comment
|
||||||
if: ${{ !contains(github.actor, 'bot') }}
|
if: ${{ !contains(github.actor, 'bot') }}
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
|
|||||||
2
.github/workflows/project-actions.yml
vendored
2
.github/workflows/project-actions.yml
vendored
@@ -19,6 +19,6 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||||
steps:
|
steps:
|
||||||
- name: Label PR with release-drafter
|
- name: Label PR with release-drafter
|
||||||
uses: release-drafter/release-drafter@v6
|
uses: release-drafter/release-drafter@v6.2.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
10
.github/workflows/repo-maintenance.yml
vendored
10
.github/workflows/repo-maintenance.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'paperless-ngx'
|
if: github.repository_owner == 'paperless-ngx'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v10
|
- uses: actions/stale@v10.2.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 7
|
days-before-stale: 7
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'paperless-ngx'
|
if: github.repository_owner == 'paperless-ngx'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v6
|
- uses: dessant/lock-threads@v6.0.0
|
||||||
with:
|
with:
|
||||||
issue-inactive-days: '30'
|
issue-inactive-days: '30'
|
||||||
pr-inactive-days: '30'
|
pr-inactive-days: '30'
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'paperless-ngx'
|
if: github.repository_owner == 'paperless-ngx'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v8
|
- uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'paperless-ngx'
|
if: github.repository_owner == 'paperless-ngx'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v8
|
- uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
@@ -206,7 +206,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'paperless-ngx'
|
if: github.repository_owner == 'paperless-ngx'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v8
|
- uses: actions/github-script@v8.0.0
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
|
|||||||
18
.github/workflows/translate-strings.yml
vendored
18
.github/workflows/translate-strings.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6.0.2
|
||||||
env:
|
env:
|
||||||
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
|
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
|
||||||
with:
|
with:
|
||||||
@@ -19,13 +19,13 @@ jobs:
|
|||||||
ref: ${{ env.GH_REF }}
|
ref: ${{ env.GH_REF }}
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6.2.0
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
sudo apt-get install -qq --no-install-recommends gettext
|
sudo apt-get install -qq --no-install-recommends gettext
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@v7
|
uses: astral-sh/setup-uv@v7.3.1
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
- name: Install backend python dependencies
|
- name: Install backend python dependencies
|
||||||
@@ -36,18 +36,18 @@ jobs:
|
|||||||
- name: Generate backend translation strings
|
- name: Generate backend translation strings
|
||||||
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4.2.0
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 24
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 24.x
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
id: cache-frontend-deps
|
id: cache-frontend-deps
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
cd src-ui
|
cd src-ui
|
||||||
pnpm run ng extract-i18n
|
pnpm run ng extract-i18n
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: stefanzweifel/git-auto-commit-action@v7
|
uses: stefanzweifel/git-auto-commit-action@v7.1.0
|
||||||
with:
|
with:
|
||||||
file_pattern: 'src-ui/messages.xlf src/locale/en_US/LC_MESSAGES/django.po'
|
file_pattern: 'src-ui/messages.xlf src/locale/en_US/LC_MESSAGES/django.po'
|
||||||
commit_message: "Auto translate strings"
|
commit_message: "Auto translate strings"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,6 +40,7 @@ htmlcov/
|
|||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
|
.uv-cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*,cover
|
||||||
@@ -53,7 +54,7 @@ junit.xml
|
|||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# MkDocs documentation
|
# Zensical documentation
|
||||||
site/
|
site/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
|
|||||||
2453
.mypy-baseline.txt
Normal file
2453
.mypy-baseline.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
# This file configures pre-commit hooks.
|
# This file configures pre-commit hooks.
|
||||||
# See https://pre-commit.com/ for general information
|
# See https://pre-commit.com/ for general information
|
||||||
# See https://pre-commit.com/hooks.html for a listing of possible hooks
|
# See https://pre-commit.com/hooks.html for a listing of possible hooks
|
||||||
|
# We actually run via https://github.com/j178/prek which is compatible
|
||||||
repos:
|
repos:
|
||||||
# General hooks
|
# General hooks
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
@@ -28,7 +29,7 @@ repos:
|
|||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.4.1
|
rev: v2.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
additional_dependencies: [tomli]
|
additional_dependencies: [tomli]
|
||||||
@@ -37,7 +38,7 @@ repos:
|
|||||||
- json
|
- json
|
||||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: 'v3.6.2'
|
rev: 'v3.8.1'
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or:
|
types_or:
|
||||||
@@ -45,16 +46,16 @@ repos:
|
|||||||
- ts
|
- ts
|
||||||
- markdown
|
- markdown
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- prettier@3.3.3
|
- prettier@3.8.1
|
||||||
- 'prettier-plugin-organize-imports@4.1.0'
|
- 'prettier-plugin-organize-imports@4.3.0'
|
||||||
# Python hooks
|
# Python hooks
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.14.5
|
rev: v0.15.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: "v2.11.1"
|
rev: "v2.12.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
# Dockerfile hooks
|
# Dockerfile hooks
|
||||||
@@ -64,7 +65,7 @@ repos:
|
|||||||
- id: hadolint
|
- id: hadolint
|
||||||
# Shell script hooks
|
# Shell script hooks
|
||||||
- repo: https://github.com/lovesegfault/beautysh
|
- repo: https://github.com/lovesegfault/beautysh
|
||||||
rev: v6.4.2
|
rev: v6.4.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: beautysh
|
- id: beautysh
|
||||||
types: [file]
|
types: [file]
|
||||||
@@ -76,7 +77,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
- repo: https://github.com/google/yamlfmt
|
- repo: https://github.com/google/yamlfmt
|
||||||
rev: v0.20.0
|
rev: v0.21.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamlfmt
|
- id: yamlfmt
|
||||||
exclude: "^src-ui/pnpm-lock.yaml"
|
exclude: "^src-ui/pnpm-lock.yaml"
|
||||||
|
|||||||
@@ -5,14 +5,6 @@ const config = {
|
|||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
// https://prettier.io/docs/en/options.html#trailing-commas
|
// https://prettier.io/docs/en/options.html#trailing-commas
|
||||||
trailingComma: 'es5',
|
trailingComma: 'es5',
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['docs/*.md'],
|
|
||||||
options: {
|
|
||||||
tabWidth: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: [require('prettier-plugin-organize-imports')],
|
plugins: [require('prettier-plugin-organize-imports')],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17368
.pyrefly-baseline.json
Normal file
17368
.pyrefly-baseline.json
Normal file
File diff suppressed because one or more lines are too long
@@ -13,7 +13,9 @@ If you want to implement something big:
|
|||||||
|
|
||||||
## Python
|
## Python
|
||||||
|
|
||||||
Paperless supports python 3.10 - 3.12 at this time. We format Python code with [ruff](https://docs.astral.sh/ruff/formatter/).
|
Paperless-ngx currently supports Python 3.11, 3.12, 3.13, and 3.14. As a policy, we aim to support at least the three most recent Python versions, and drop support for versions as they reach end-of-life. Older versions may be supported if dependencies permit, but this is not guaranteed.
|
||||||
|
|
||||||
|
We format Python code with [ruff](https://docs.astral.sh/ruff/formatter/).
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
|
|||||||
22
Dockerfile
22
Dockerfile
@@ -5,14 +5,12 @@
|
|||||||
# Purpose: Compiles the frontend
|
# Purpose: Compiles the frontend
|
||||||
# Notes:
|
# Notes:
|
||||||
# - Does PNPM stuff with Typescript and such
|
# - Does PNPM stuff with Typescript and such
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-trixie-slim AS compile-frontend
|
FROM --platform=$BUILDPLATFORM docker.io/node:24-trixie-slim AS compile-frontend
|
||||||
|
|
||||||
COPY ./src-ui /src/src-ui
|
COPY ./src-ui /src/src-ui
|
||||||
|
|
||||||
WORKDIR /src/src-ui
|
WORKDIR /src/src-ui
|
||||||
RUN set -eux \
|
RUN set -eux \
|
||||||
&& npm update -g pnpm \
|
|
||||||
&& npm install -g corepack@latest \
|
|
||||||
&& corepack enable \
|
&& corepack enable \
|
||||||
&& pnpm install
|
&& pnpm install
|
||||||
|
|
||||||
@@ -32,7 +30,7 @@ RUN set -eux \
|
|||||||
# Purpose: Installs s6-overlay and rootfs
|
# Purpose: Installs s6-overlay and rootfs
|
||||||
# Comments:
|
# Comments:
|
||||||
# - Don't leave anything extra in here either
|
# - Don't leave anything extra in here either
|
||||||
FROM ghcr.io/astral-sh/uv:0.9.15-python3.12-trixie-slim AS s6-overlay-base
|
FROM ghcr.io/astral-sh/uv:0.10.9-python3.12-trixie-slim AS s6-overlay-base
|
||||||
|
|
||||||
WORKDIR /usr/src/s6
|
WORKDIR /usr/src/s6
|
||||||
|
|
||||||
@@ -47,7 +45,7 @@ ENV \
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
# Lock this version
|
# Lock this version
|
||||||
ARG S6_OVERLAY_VERSION=3.2.1.0
|
ARG S6_OVERLAY_VERSION=3.2.2.0
|
||||||
|
|
||||||
ARG S6_BUILD_TIME_PKGS="curl \
|
ARG S6_BUILD_TIME_PKGS="curl \
|
||||||
xz-utils"
|
xz-utils"
|
||||||
@@ -110,8 +108,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||||
PNGX_CONTAINERIZED=1 \
|
PNGX_CONTAINERIZED=1 \
|
||||||
# https://docs.astral.sh/uv/reference/settings/#link-mode
|
# https://docs.astral.sh/uv/reference/settings/#link-mode
|
||||||
UV_LINK_MODE=copy \
|
UV_LINK_MODE=copy
|
||||||
UV_CACHE_DIR=/cache/uv/
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Begin installation and configuration
|
# Begin installation and configuration
|
||||||
@@ -157,8 +154,6 @@ ARG RUNTIME_PACKAGES="\
|
|||||||
libmagic1 \
|
libmagic1 \
|
||||||
media-types \
|
media-types \
|
||||||
zlib1g \
|
zlib1g \
|
||||||
# Barcode splitter
|
|
||||||
libzbar0 \
|
|
||||||
poppler-utils"
|
poppler-utils"
|
||||||
|
|
||||||
# Install basic runtime packages.
|
# Install basic runtime packages.
|
||||||
@@ -193,14 +188,17 @@ ARG BUILD_PACKAGES="\
|
|||||||
pkg-config"
|
pkg-config"
|
||||||
|
|
||||||
# hadolint ignore=DL3042
|
# hadolint ignore=DL3042
|
||||||
RUN --mount=type=cache,target=${UV_CACHE_DIR},id=python-cache \
|
RUN set -eux \
|
||||||
set -eux \
|
|
||||||
&& echo "Installing build system packages" \
|
&& echo "Installing build system packages" \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||||
&& echo "Installing Python requirements" \
|
&& echo "Installing Python requirements" \
|
||||||
&& uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt \
|
&& uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt \
|
||||||
&& uv pip install --system --no-python-downloads --python-preference system --requirements requirements.txt \
|
&& uv pip install --no-cache --system --no-python-downloads --python-preference system \
|
||||||
|
--index https://pypi.org/simple \
|
||||||
|
--index https://download.pytorch.org/whl/cpu \
|
||||||
|
--index-strategy unsafe-best-match \
|
||||||
|
--requirements requirements.txt \
|
||||||
&& echo "Installing NLTK data" \
|
&& echo "Installing NLTK data" \
|
||||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
||||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# correct networking for the tests
|
# correct networking for the tests
|
||||||
services:
|
services:
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: docker.io/gotenberg/gotenberg:8.25
|
image: docker.io/gotenberg/gotenberg:8.27
|
||||||
hostname: gotenberg
|
hostname: gotenberg
|
||||||
container_name: gotenberg
|
container_name: gotenberg
|
||||||
network_mode: host
|
network_mode: host
|
||||||
@@ -23,3 +23,24 @@ services:
|
|||||||
container_name: tika
|
container_name: tika
|
||||||
network_mode: host
|
network_mode: host
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
greenmail:
|
||||||
|
image: greenmail/standalone:2.1.8
|
||||||
|
hostname: greenmail
|
||||||
|
container_name: greenmail
|
||||||
|
environment:
|
||||||
|
# Enable only IMAP for now (SMTP available via 3025 if needed later)
|
||||||
|
GREENMAIL_OPTS: >-
|
||||||
|
-Dgreenmail.setup.test.imap -Dgreenmail.users=test@localhost:test -Dgreenmail.users.login=test@localhost -Dgreenmail.verbose
|
||||||
|
ports:
|
||||||
|
- "3143:3143" # IMAP
|
||||||
|
restart: unless-stopped
|
||||||
|
nginx:
|
||||||
|
image: docker.io/nginx:1.29.5-alpine
|
||||||
|
hostname: nginx
|
||||||
|
container_name: nginx
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ../../docs/assets:/usr/share/nginx/html/assets:ro
|
||||||
|
- ./test-nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ services:
|
|||||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: docker.io/gotenberg/gotenberg:8.25
|
image: docker.io/gotenberg/gotenberg:8.27
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||||
# want to allow external content like tracking pixels or even javascript.
|
# want to allow external content like tracking pixels or even javascript.
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PAPERLESS_REDIS: redis://broker:6379
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
PAPERLESS_DBHOST: db
|
PAPERLESS_DBHOST: db
|
||||||
|
PAPERLESS_DBENGINE: postgres
|
||||||
env_file:
|
env_file:
|
||||||
- stack.env
|
- stack.env
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -62,11 +62,12 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PAPERLESS_REDIS: redis://broker:6379
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
PAPERLESS_DBHOST: db
|
PAPERLESS_DBHOST: db
|
||||||
|
PAPERLESS_DBENGINE: postgresql
|
||||||
PAPERLESS_TIKA_ENABLED: 1
|
PAPERLESS_TIKA_ENABLED: 1
|
||||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: docker.io/gotenberg/gotenberg:8.25
|
image: docker.io/gotenberg/gotenberg:8.27
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||||
# want to allow external content like tracking pixels or even javascript.
|
# want to allow external content like tracking pixels or even javascript.
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PAPERLESS_REDIS: redis://broker:6379
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
PAPERLESS_DBHOST: db
|
PAPERLESS_DBHOST: db
|
||||||
|
PAPERLESS_DBENGINE: postgresql
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
media:
|
media:
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ services:
|
|||||||
env_file: docker-compose.env
|
env_file: docker-compose.env
|
||||||
environment:
|
environment:
|
||||||
PAPERLESS_REDIS: redis://broker:6379
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
|
PAPERLESS_DBENGINE: sqlite
|
||||||
PAPERLESS_TIKA_ENABLED: 1
|
PAPERLESS_TIKA_ENABLED: 1
|
||||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: docker.io/gotenberg/gotenberg:8.25
|
image: docker.io/gotenberg/gotenberg:8.27
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||||
# want to allow external content like tracking pixels or even javascript.
|
# want to allow external content like tracking pixels or even javascript.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ services:
|
|||||||
env_file: docker-compose.env
|
env_file: docker-compose.env
|
||||||
environment:
|
environment:
|
||||||
PAPERLESS_REDIS: redis://broker:6379
|
PAPERLESS_REDIS: redis://broker:6379
|
||||||
|
PAPERLESS_DBENGINE: sqlite
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
media:
|
media:
|
||||||
|
|||||||
14
docker/compose/test-nginx.conf
Normal file
14
docker/compose/test-nginx.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
# Enable CORS for test requests
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, OPTIONS' always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
for command in decrypt_documents \
|
for command in document_archiver \
|
||||||
document_archiver \
|
|
||||||
document_exporter \
|
document_exporter \
|
||||||
document_importer \
|
document_importer \
|
||||||
mail_fetcher \
|
mail_fetcher \
|
||||||
document_create_classifier \
|
document_create_classifier \
|
||||||
document_index \
|
document_index \
|
||||||
|
document_llmindex \
|
||||||
document_renamer \
|
document_renamer \
|
||||||
document_retagger \
|
document_retagger \
|
||||||
document_thumbnails \
|
document_thumbnails \
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py management_command "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py management_command "$@"
|
s6-setuidgid paperless python3 manage.py management_command "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py management_command "$@"
|
python3 manage.py management_command "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ cd "${PAPERLESS_SRC_DIR}"
|
|||||||
|
|
||||||
# The whole migrate, with flock, needs to run as the right user
|
# The whole migrate, with flock, needs to run as the right user
|
||||||
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py check --tag compatibility paperless || exit 1
|
||||||
exec s6-setlock -n "${data_dir}/migration_lock" python3 manage.py migrate --skip-checks --no-input
|
exec s6-setlock -n "${data_dir}/migration_lock" python3 manage.py migrate --skip-checks --no-input
|
||||||
else
|
else
|
||||||
|
s6-setuidgid paperless python3 manage.py check --tag compatibility paperless || exit 1
|
||||||
exec s6-setuidgid paperless \
|
exec s6-setuidgid paperless \
|
||||||
s6-setlock -n "${data_dir}/migration_lock" \
|
s6-setlock -n "${data_dir}/migration_lock" \
|
||||||
python3 manage.py migrate --skip-checks --no-input
|
python3 manage.py migrate --skip-checks --no-input
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py convert_mariadb_uuid "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
|
s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py convert_mariadb_uuid "$@"
|
python3 manage.py convert_mariadb_uuid "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py createsuperuser "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
|
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py createsuperuser "$@"
|
python3 manage.py createsuperuser "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_archiver "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_archiver "$@"
|
s6-setuidgid paperless python3 manage.py document_archiver "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_archiver "$@"
|
python3 manage.py document_archiver "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_create_classifier "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
|
s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_create_classifier "$@"
|
python3 manage.py document_create_classifier "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_exporter "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_exporter "$@"
|
s6-setuidgid paperless python3 manage.py document_exporter "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_exporter "$@"
|
python3 manage.py document_exporter "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_fuzzy_match "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
|
s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_fuzzy_match "$@"
|
python3 manage.py document_fuzzy_match "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_importer "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_importer "$@"
|
s6-setuidgid paperless python3 manage.py document_importer "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_importer "$@"
|
python3 manage.py document_importer "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_index "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_index "$@"
|
s6-setuidgid paperless python3 manage.py document_index "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_index "$@"
|
python3 manage.py document_index "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ set -e
|
|||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py decrypt_documents "$@"
|
s6-setuidgid paperless python3 manage.py document_llmindex "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py decrypt_documents "$@"
|
python3 manage.py document_llmindex "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
fi
|
fi
|
||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_renamer "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_renamer "$@"
|
s6-setuidgid paperless python3 manage.py document_renamer "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_renamer "$@"
|
python3 manage.py document_renamer "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_retagger "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_retagger "$@"
|
s6-setuidgid paperless python3 manage.py document_retagger "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_retagger "$@"
|
python3 manage.py document_retagger "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_sanity_checker "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
|
s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_sanity_checker "$@"
|
python3 manage.py document_sanity_checker "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py document_thumbnails "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
|
s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py document_thumbnails "$@"
|
python3 manage.py document_thumbnails "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py mail_fetcher "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
|
s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py mail_fetcher "$@"
|
python3 manage.py mail_fetcher "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py manage_superuser "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py manage_superuser "$@"
|
s6-setuidgid paperless python3 manage.py manage_superuser "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py manage_superuser "$@"
|
python3 manage.py manage_superuser "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ set -e
|
|||||||
|
|
||||||
cd "${PAPERLESS_SRC_DIR}"
|
cd "${PAPERLESS_SRC_DIR}"
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]]; then
|
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
|
||||||
|
python3 manage.py prune_audit_logs "$@"
|
||||||
|
elif [[ $(id -u) == 0 ]]; then
|
||||||
s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
|
s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
|
||||||
elif [[ $(id -un) == "paperless" ]]; then
|
elif [[ $(id -un) == "paperless" ]]; then
|
||||||
python3 manage.py prune_audit_logs "$@"
|
python3 manage.py prune_audit_logs "$@"
|
||||||
else
|
else
|
||||||
echo "Unknown user."
|
echo "Unknown user."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ consuming documents at that time.
|
|||||||
|
|
||||||
Options available to any installation of paperless:
|
Options available to any installation of paperless:
|
||||||
|
|
||||||
- Use the [document exporter](#exporter). The document exporter exports all your documents,
|
- Use the [document exporter](#exporter). The document exporter exports all your documents,
|
||||||
thumbnails, metadata, and database contents to a specific folder. You may import your
|
thumbnails, metadata, and database contents to a specific folder. You may import your
|
||||||
documents and settings into a fresh instance of paperless again or store your
|
documents and settings into a fresh instance of paperless again or store your
|
||||||
documents in another DMS with this export.
|
documents in another DMS with this export.
|
||||||
|
|
||||||
The document exporter is also able to update an already existing
|
The document exporter is also able to update an already existing
|
||||||
export. Therefore, incremental backups with `rsync` are entirely
|
export. Therefore, incremental backups with `rsync` are entirely
|
||||||
possible.
|
possible.
|
||||||
|
|
||||||
The exporter does not include API tokens and they will need to be re-generated after importing.
|
The exporter does not include API tokens and they will need to be re-generated after importing.
|
||||||
|
|
||||||
!!! caution
|
!!! caution
|
||||||
|
|
||||||
@@ -29,28 +29,27 @@ Options available to any installation of paperless:
|
|||||||
|
|
||||||
Options available to docker installations:
|
Options available to docker installations:
|
||||||
|
|
||||||
- Backup the docker volumes. These usually reside within
|
- Backup the docker volumes. These usually reside within
|
||||||
`/var/lib/docker/volumes` on the host and you need to be root in
|
`/var/lib/docker/volumes` on the host and you need to be root in
|
||||||
order to access them.
|
order to access them.
|
||||||
|
|
||||||
Paperless uses 4 volumes:
|
Paperless uses 4 volumes:
|
||||||
|
- `paperless_media`: This is where your documents are stored.
|
||||||
- `paperless_media`: This is where your documents are stored.
|
- `paperless_data`: This is where auxiliary data is stored. This
|
||||||
- `paperless_data`: This is where auxiliary data is stored. This
|
folder also contains the SQLite database, if you use it.
|
||||||
folder also contains the SQLite database, if you use it.
|
- `paperless_pgdata`: Exists only if you use PostgreSQL and
|
||||||
- `paperless_pgdata`: Exists only if you use PostgreSQL and
|
contains the database.
|
||||||
contains the database.
|
- `paperless_dbdata`: Exists only if you use MariaDB and contains
|
||||||
- `paperless_dbdata`: Exists only if you use MariaDB and contains
|
the database.
|
||||||
the database.
|
|
||||||
|
|
||||||
Options available to bare-metal and non-docker installations:
|
Options available to bare-metal and non-docker installations:
|
||||||
|
|
||||||
- Backup the entire paperless folder. This ensures that if your
|
- Backup the entire paperless folder. This ensures that if your
|
||||||
paperless instance crashes at some point or your disk fails, you can
|
paperless instance crashes at some point or your disk fails, you can
|
||||||
simply copy the folder back into place and it works.
|
simply copy the folder back into place and it works.
|
||||||
|
|
||||||
When using PostgreSQL or MariaDB, you'll also have to backup the
|
When using PostgreSQL or MariaDB, you'll also have to backup the
|
||||||
database.
|
database.
|
||||||
|
|
||||||
### Restoring {#migrating-restoring}
|
### Restoring {#migrating-restoring}
|
||||||
|
|
||||||
@@ -62,6 +61,10 @@ copies you created in the steps above.
|
|||||||
|
|
||||||
## Updating Paperless {#updating}
|
## Updating Paperless {#updating}
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
Please review the [migration instructions](migration-v3.md) before upgrading Paperless-ngx to v3.0, it includes some breaking changes that require manual intervention before upgrading.
|
||||||
|
|
||||||
### Docker Route {#docker-updating}
|
### Docker Route {#docker-updating}
|
||||||
|
|
||||||
If a new release of paperless-ngx is available, upgrading depends on how
|
If a new release of paperless-ngx is available, upgrading depends on how
|
||||||
@@ -505,19 +508,19 @@ collection for issues.
|
|||||||
|
|
||||||
The issues detected by the sanity checker are as follows:
|
The issues detected by the sanity checker are as follows:
|
||||||
|
|
||||||
- Missing original files.
|
- Missing original files.
|
||||||
- Missing archive files.
|
- Missing archive files.
|
||||||
- Inaccessible original files due to improper permissions.
|
- Inaccessible original files due to improper permissions.
|
||||||
- Inaccessible archive files due to improper permissions.
|
- Inaccessible archive files due to improper permissions.
|
||||||
- Corrupted original documents by comparing their checksum against
|
- Corrupted original documents by comparing their checksum against
|
||||||
what is stored in the database.
|
what is stored in the database.
|
||||||
- Corrupted archive documents by comparing their checksum against what
|
- Corrupted archive documents by comparing their checksum against what
|
||||||
is stored in the database.
|
is stored in the database.
|
||||||
- Missing thumbnails.
|
- Missing thumbnails.
|
||||||
- Inaccessible thumbnails due to improper permissions.
|
- Inaccessible thumbnails due to improper permissions.
|
||||||
- Documents without any content (warning).
|
- Documents without any content (warning).
|
||||||
- Orphaned files in the media directory (warning). These are files
|
- Orphaned files in the media directory (warning). These are files
|
||||||
that are not referenced by any document in paperless.
|
that are not referenced by any document in paperless.
|
||||||
|
|
||||||
```
|
```
|
||||||
document_sanity_checker
|
document_sanity_checker
|
||||||
@@ -580,39 +583,9 @@ document.
|
|||||||
documents, such as encrypted PDF documents. The archiver will skip over
|
documents, such as encrypted PDF documents. The archiver will skip over
|
||||||
these documents each time it sees them.
|
these documents each time it sees them.
|
||||||
|
|
||||||
### Managing encryption {#encryption}
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
Encryption was removed in [paperless-ng 0.9](changelog.md#paperless-ng-090)
|
|
||||||
because it did not really provide any additional security, the passphrase
|
|
||||||
was stored in a configuration file on the same system as the documents.
|
|
||||||
Furthermore, the entire text content of the documents is stored plain in
|
|
||||||
the database, even if your documents are encrypted. Filenames are not
|
|
||||||
encrypted as well. Finally, the web server provides transparent access to
|
|
||||||
your encrypted documents.
|
|
||||||
|
|
||||||
Consider running paperless on an encrypted filesystem instead, which
|
|
||||||
will then at least provide security against physical hardware theft.
|
|
||||||
|
|
||||||
#### Enabling encryption
|
|
||||||
|
|
||||||
Enabling encryption is no longer supported.
|
|
||||||
|
|
||||||
#### Disabling encryption
|
|
||||||
|
|
||||||
Basic usage to disable encryption of your document store:
|
|
||||||
|
|
||||||
(Note: If `PAPERLESS_PASSPHRASE` isn't set already, you need to specify
|
|
||||||
it here)
|
|
||||||
|
|
||||||
```
|
|
||||||
decrypt_documents [--passphrase SECR3TP4SSPHRA$E]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Detecting duplicates {#fuzzy_duplicate}
|
### Detecting duplicates {#fuzzy_duplicate}
|
||||||
|
|
||||||
Paperless already catches and prevents upload of exactly matching documents,
|
Paperless-ngx already catches and warns of exactly matching documents,
|
||||||
however a new scan of an existing document may not produce an exact bit for bit
|
however a new scan of an existing document may not produce an exact bit for bit
|
||||||
duplicate. But the content should be exact or close, allowing detection.
|
duplicate. But the content should be exact or close, allowing detection.
|
||||||
|
|
||||||
|
|||||||
@@ -25,20 +25,20 @@ documents.
|
|||||||
|
|
||||||
The following algorithms are available:
|
The following algorithms are available:
|
||||||
|
|
||||||
- **None:** No matching will be performed.
|
- **None:** No matching will be performed.
|
||||||
- **Any:** Looks for any occurrence of any word provided in match in
|
- **Any:** Looks for any occurrence of any word provided in match in
|
||||||
the PDF. If you define the match as `Bank1 Bank2`, it will match
|
the PDF. If you define the match as `Bank1 Bank2`, it will match
|
||||||
documents containing either of these terms.
|
documents containing either of these terms.
|
||||||
- **All:** Requires that every word provided appears in the PDF,
|
- **All:** Requires that every word provided appears in the PDF,
|
||||||
albeit not in the order provided.
|
albeit not in the order provided.
|
||||||
- **Exact:** Matches only if the match appears exactly as provided
|
- **Exact:** Matches only if the match appears exactly as provided
|
||||||
(i.e. preserve ordering) in the PDF.
|
(i.e. preserve ordering) in the PDF.
|
||||||
- **Regular expression:** Parses the match as a regular expression and
|
- **Regular expression:** Parses the match as a regular expression and
|
||||||
tries to find a match within the document.
|
tries to find a match within the document.
|
||||||
- **Fuzzy match:** Uses a partial matching based on locating the tag text
|
- **Fuzzy match:** Uses a partial matching based on locating the tag text
|
||||||
inside the document, using a [partial ratio](https://rapidfuzz.github.io/RapidFuzz/Usage/fuzz.html#partial-ratio)
|
inside the document, using a [partial ratio](https://rapidfuzz.github.io/RapidFuzz/Usage/fuzz.html#partial-ratio)
|
||||||
- **Auto:** Tries to automatically match new documents. This does not
|
- **Auto:** Tries to automatically match new documents. This does not
|
||||||
require you to set a match. See the [notes below](#automatic-matching).
|
require you to set a match. See the [notes below](#automatic-matching).
|
||||||
|
|
||||||
When using the _any_ or _all_ matching algorithms, you can search for
|
When using the _any_ or _all_ matching algorithms, you can search for
|
||||||
terms that consist of multiple words by enclosing them in double quotes.
|
terms that consist of multiple words by enclosing them in double quotes.
|
||||||
@@ -69,33 +69,33 @@ Paperless tries to hide much of the involved complexity with this
|
|||||||
approach. However, there are a couple caveats you need to keep in mind
|
approach. However, there are a couple caveats you need to keep in mind
|
||||||
when using this feature:
|
when using this feature:
|
||||||
|
|
||||||
- Changes to your documents are not immediately reflected by the
|
- Changes to your documents are not immediately reflected by the
|
||||||
matching algorithm. The neural network needs to be _trained_ on your
|
matching algorithm. The neural network needs to be _trained_ on your
|
||||||
documents after changes. Paperless periodically (default: once each
|
documents after changes. Paperless periodically (default: once each
|
||||||
hour) checks for changes and does this automatically for you.
|
hour) checks for changes and does this automatically for you.
|
||||||
- The Auto matching algorithm only takes documents into account which
|
- The Auto matching algorithm only takes documents into account which
|
||||||
are NOT placed in your inbox (i.e. have any inbox tags assigned to
|
are NOT placed in your inbox (i.e. have any inbox tags assigned to
|
||||||
them). This ensures that the neural network only learns from
|
them). This ensures that the neural network only learns from
|
||||||
documents which you have correctly tagged before.
|
documents which you have correctly tagged before.
|
||||||
- The matching algorithm can only work if there is a correlation
|
- The matching algorithm can only work if there is a correlation
|
||||||
between the tag, correspondent, document type, or storage path and
|
between the tag, correspondent, document type, or storage path and
|
||||||
the document itself. Your bank statements usually contain your bank
|
the document itself. Your bank statements usually contain your bank
|
||||||
account number and the name of the bank, so this works reasonably
|
account number and the name of the bank, so this works reasonably
|
||||||
well, However, tags such as "TODO" cannot be automatically
|
well, However, tags such as "TODO" cannot be automatically
|
||||||
assigned.
|
assigned.
|
||||||
- The matching algorithm needs a reasonable number of documents to
|
- The matching algorithm needs a reasonable number of documents to
|
||||||
identify when to assign tags, correspondents, storage paths, and
|
identify when to assign tags, correspondents, storage paths, and
|
||||||
types. If one out of a thousand documents has the correspondent
|
types. If one out of a thousand documents has the correspondent
|
||||||
"Very obscure web shop I bought something five years ago", it will
|
"Very obscure web shop I bought something five years ago", it will
|
||||||
probably not assign this correspondent automatically if you buy
|
probably not assign this correspondent automatically if you buy
|
||||||
something from them again. The more documents, the better.
|
something from them again. The more documents, the better.
|
||||||
- Paperless also needs a reasonable amount of negative examples to
|
- Paperless also needs a reasonable amount of negative examples to
|
||||||
decide when not to assign a certain tag, correspondent, document
|
decide when not to assign a certain tag, correspondent, document
|
||||||
type, or storage path. This will usually be the case as you start
|
type, or storage path. This will usually be the case as you start
|
||||||
filling up paperless with documents. Example: If all your documents
|
filling up paperless with documents. Example: If all your documents
|
||||||
are either from "Webshop" or "Bank", paperless will assign one
|
are either from "Webshop" or "Bank", paperless will assign one
|
||||||
of these correspondents to ANY new document, if both are set to
|
of these correspondents to ANY new document, if both are set to
|
||||||
automatic matching.
|
automatic matching.
|
||||||
|
|
||||||
## Hooking into the consumption process {#consume-hooks}
|
## Hooking into the consumption process {#consume-hooks}
|
||||||
|
|
||||||
@@ -243,12 +243,12 @@ webserver:
|
|||||||
|
|
||||||
Troubleshooting:
|
Troubleshooting:
|
||||||
|
|
||||||
- Monitor the Docker Compose log
|
- Monitor the Docker Compose log
|
||||||
`cd ~/paperless-ngx; docker compose logs -f`
|
`cd ~/paperless-ngx; docker compose logs -f`
|
||||||
- Check your script's permission e.g. in case of permission error
|
- Check your script's permission e.g. in case of permission error
|
||||||
`sudo chmod 755 post-consumption-example.sh`
|
`sudo chmod 755 post-consumption-example.sh`
|
||||||
- Pipe your scripts's output to a log file e.g.
|
- Pipe your scripts's output to a log file e.g.
|
||||||
`echo "${DOCUMENT_ID}" | tee --append /usr/src/paperless/scripts/post-consumption-example.log`
|
`echo "${DOCUMENT_ID}" | tee --append /usr/src/paperless/scripts/post-consumption-example.log`
|
||||||
|
|
||||||
## File name handling {#file-name-handling}
|
## File name handling {#file-name-handling}
|
||||||
|
|
||||||
@@ -262,6 +262,10 @@ your files differently, you can do that by adjusting the
|
|||||||
or using [storage paths (see below)](#storage-paths). Paperless adds the
|
or using [storage paths (see below)](#storage-paths). Paperless adds the
|
||||||
correct file extension e.g. `.pdf`, `.jpg` automatically.
|
correct file extension e.g. `.pdf`, `.jpg` automatically.
|
||||||
|
|
||||||
|
When a document has file versions, each version uses the same naming rules and
|
||||||
|
storage path resolution as any other document file, with an added version suffix
|
||||||
|
such as `_v1`, `_v2`, etc.
|
||||||
|
|
||||||
This variable allows you to configure the filename (folders are allowed)
|
This variable allows you to configure the filename (folders are allowed)
|
||||||
using placeholders. For example, configuring this to
|
using placeholders. For example, configuring this to
|
||||||
|
|
||||||
@@ -303,35 +307,35 @@ will create a directory structure as follows:
|
|||||||
|
|
||||||
Paperless provides the following variables for use within filenames:
|
Paperless provides the following variables for use within filenames:
|
||||||
|
|
||||||
- `{{ asn }}`: The archive serial number of the document, or "none".
|
- `{{ asn }}`: The archive serial number of the document, or "none".
|
||||||
- `{{ correspondent }}`: The name of the correspondent, or "none".
|
- `{{ correspondent }}`: The name of the correspondent, or "none".
|
||||||
- `{{ document_type }}`: The name of the document type, or "none".
|
- `{{ document_type }}`: The name of the document type, or "none".
|
||||||
- `{{ tag_list }}`: A comma separated list of all tags assigned to the
|
- `{{ tag_list }}`: A comma separated list of all tags assigned to the
|
||||||
document.
|
document.
|
||||||
- `{{ title }}`: The title of the document.
|
- `{{ title }}`: The title of the document.
|
||||||
- `{{ created }}`: The full date (ISO 8601 format, e.g. `2024-03-14`) the document was created.
|
- `{{ created }}`: The full date (ISO 8601 format, e.g. `2024-03-14`) the document was created.
|
||||||
- `{{ created_year }}`: Year created only, formatted as the year with
|
- `{{ created_year }}`: Year created only, formatted as the year with
|
||||||
century.
|
century.
|
||||||
- `{{ created_year_short }}`: Year created only, formatted as the year
|
- `{{ created_year_short }}`: Year created only, formatted as the year
|
||||||
without century, zero padded.
|
without century, zero padded.
|
||||||
- `{{ created_month }}`: Month created only (number 01-12).
|
- `{{ created_month }}`: Month created only (number 01-12).
|
||||||
- `{{ created_month_name }}`: Month created name, as per locale
|
- `{{ created_month_name }}`: Month created name, as per locale
|
||||||
- `{{ created_month_name_short }}`: Month created abbreviated name, as per
|
- `{{ created_month_name_short }}`: Month created abbreviated name, as per
|
||||||
locale
|
locale
|
||||||
- `{{ created_day }}`: Day created only (number 01-31).
|
- `{{ created_day }}`: Day created only (number 01-31).
|
||||||
- `{{ added }}`: The full date (ISO format) the document was added to
|
- `{{ added }}`: The full date (ISO format) the document was added to
|
||||||
paperless.
|
paperless.
|
||||||
- `{{ added_year }}`: Year added only.
|
- `{{ added_year }}`: Year added only.
|
||||||
- `{{ added_year_short }}`: Year added only, formatted as the year without
|
- `{{ added_year_short }}`: Year added only, formatted as the year without
|
||||||
century, zero padded.
|
century, zero padded.
|
||||||
- `{{ added_month }}`: Month added only (number 01-12).
|
- `{{ added_month }}`: Month added only (number 01-12).
|
||||||
- `{{ added_month_name }}`: Month added name, as per locale
|
- `{{ added_month_name }}`: Month added name, as per locale
|
||||||
- `{{ added_month_name_short }}`: Month added abbreviated name, as per
|
- `{{ added_month_name_short }}`: Month added abbreviated name, as per
|
||||||
locale
|
locale
|
||||||
- `{{ added_day }}`: Day added only (number 01-31).
|
- `{{ added_day }}`: Day added only (number 01-31).
|
||||||
- `{{ owner_username }}`: Username of document owner, if any, or "none"
|
- `{{ owner_username }}`: Username of document owner, if any, or "none"
|
||||||
- `{{ original_name }}`: Document original filename, minus the extension, if any, or "none"
|
- `{{ original_name }}`: Document original filename, minus the extension, if any, or "none"
|
||||||
- `{{ doc_pk }}`: The paperless identifier (primary key) for the document.
|
- `{{ doc_pk }}`: The paperless identifier (primary key) for the document.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
@@ -353,6 +357,8 @@ If paperless detects that two documents share the same filename,
|
|||||||
paperless will automatically append `_01`, `_02`, etc to the filename.
|
paperless will automatically append `_01`, `_02`, etc to the filename.
|
||||||
This happens if all the placeholders in a filename evaluate to the same
|
This happens if all the placeholders in a filename evaluate to the same
|
||||||
value.
|
value.
|
||||||
|
For versioned files, this counter is appended after the version suffix
|
||||||
|
(for example `statement_v2_01.pdf`).
|
||||||
|
|
||||||
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
|
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
|
||||||
paperless will fall back to using the default naming scheme instead.
|
paperless will fall back to using the default naming scheme instead.
|
||||||
@@ -382,10 +388,10 @@ before empty placeholders are removed as well, empty directories are omitted.
|
|||||||
When a single storage layout is not sufficient for your use case, storage paths allow for more complex
|
When a single storage layout is not sufficient for your use case, storage paths allow for more complex
|
||||||
structure to set precisely where each document is stored in the file system.
|
structure to set precisely where each document is stored in the file system.
|
||||||
|
|
||||||
- Each storage path is a [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) and
|
- Each storage path is a [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) and
|
||||||
follows the rules described above
|
follows the rules described above
|
||||||
- Each document is assigned a storage path using the matching algorithms described above, but can be
|
- Each document is assigned a storage path using the matching algorithms described above, but can be
|
||||||
overwritten at any time
|
overwritten at any time
|
||||||
|
|
||||||
For example, you could define the following two storage paths:
|
For example, you could define the following two storage paths:
|
||||||
|
|
||||||
@@ -431,8 +437,10 @@ This allows for complex logic to be included in the format, including [logical s
|
|||||||
and [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#id11) to manipulate the [variables](#filename-format-variables)
|
and [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#id11) to manipulate the [variables](#filename-format-variables)
|
||||||
provided. The template is provided as a string, potentially multiline, and rendered into a single line.
|
provided. The template is provided as a string, potentially multiline, and rendered into a single line.
|
||||||
|
|
||||||
In addition, the entire Document instance is available to be utilized in a more advanced way, as well as some variables which only make sense to be accessed
|
In addition, a limited `document` object is available for advanced templates.
|
||||||
with more complex logic.
|
This object includes common metadata fields such as `id`, `pk`, `title`, `content`, `page_count`, `created`, `added`, `modified`, `mime_type`,
|
||||||
|
`checksum`, `archive_checksum`, `archive_serial_number`, `filename`, `archive_filename`, and `original_filename`.
|
||||||
|
Related values are available as nested objects with limited fields, for example document.correspondent.name, etc.
|
||||||
|
|
||||||
#### Custom Jinja2 Filters
|
#### Custom Jinja2 Filters
|
||||||
|
|
||||||
@@ -449,13 +457,13 @@ The `get_cf_value` filter retrieves a value from custom field data with optional
|
|||||||
|
|
||||||
###### Parameters
|
###### Parameters
|
||||||
|
|
||||||
- `custom_fields`: This _must_ be the provided custom field data
|
- `custom_fields`: This _must_ be the provided custom field data
|
||||||
- `name` (str): Name of the custom field to retrieve
|
- `name` (str): Name of the custom field to retrieve
|
||||||
- `default` (str, optional): Default value to return if field is not found or has no value
|
- `default` (str, optional): Default value to return if field is not found or has no value
|
||||||
|
|
||||||
###### Returns
|
###### Returns
|
||||||
|
|
||||||
- `str | None`: The field value, default value, or `None` if neither exists
|
- `str | None`: The field value, default value, or `None` if neither exists
|
||||||
|
|
||||||
###### Examples
|
###### Examples
|
||||||
|
|
||||||
@@ -479,12 +487,12 @@ The `datetime` filter formats a datetime string or datetime object using Python'
|
|||||||
|
|
||||||
###### Parameters
|
###### Parameters
|
||||||
|
|
||||||
- `value` (str | datetime): Date/time value to format (strings will be parsed automatically)
|
- `value` (str | datetime): Date/time value to format (strings will be parsed automatically)
|
||||||
- `format` (str): Python strftime format string
|
- `format` (str): Python strftime format string
|
||||||
|
|
||||||
###### Returns
|
###### Returns
|
||||||
|
|
||||||
- `str`: Formatted datetime string
|
- `str`: Formatted datetime string
|
||||||
|
|
||||||
###### Examples
|
###### Examples
|
||||||
|
|
||||||
@@ -501,7 +509,7 @@ The `datetime` filter formats a datetime string or datetime object using Python'
|
|||||||
See the [strftime format code documentation](https://docs.python.org/3.13/library/datetime.html#strftime-and-strptime-format-codes)
|
See the [strftime format code documentation](https://docs.python.org/3.13/library/datetime.html#strftime-and-strptime-format-codes)
|
||||||
for the possible codes and their meanings.
|
for the possible codes and their meanings.
|
||||||
|
|
||||||
##### Date Localization
|
##### Date Localization {#date-localization}
|
||||||
|
|
||||||
The `localize_date` filter formats a date or datetime object into a localized string using Babel internationalization.
|
The `localize_date` filter formats a date or datetime object into a localized string using Babel internationalization.
|
||||||
This takes into account the provided locale for translation. Since this must be used on a date or datetime object,
|
This takes into account the provided locale for translation. Since this must be used on a date or datetime object,
|
||||||
@@ -517,13 +525,13 @@ An ISO string can also be provided to control the output format.
|
|||||||
|
|
||||||
###### Parameters
|
###### Parameters
|
||||||
|
|
||||||
- `value` (date | datetime | str): Date, datetime object or ISO string to format (datetime should be timezone-aware)
|
- `value` (date | datetime | str): Date, datetime object or ISO string to format (datetime should be timezone-aware)
|
||||||
- `format` (str): Format type - either a Babel preset ('short', 'medium', 'long', 'full') or custom pattern
|
- `format` (str): Format type - either a Babel preset ('short', 'medium', 'long', 'full') or custom pattern
|
||||||
- `locale` (str): Locale code for localization (e.g., 'en_US', 'fr_FR', 'de_DE')
|
- `locale` (str): Locale code for localization (e.g., 'en_US', 'fr_FR', 'de_DE')
|
||||||
|
|
||||||
###### Returns
|
###### Returns
|
||||||
|
|
||||||
- `str`: Localized, formatted date string
|
- `str`: Localized, formatted date string
|
||||||
|
|
||||||
###### Examples
|
###### Examples
|
||||||
|
|
||||||
@@ -557,15 +565,15 @@ See the [supported format codes](https://unicode.org/reports/tr35/tr35-dates.htm
|
|||||||
|
|
||||||
### Format Presets
|
### Format Presets
|
||||||
|
|
||||||
- **short**: Abbreviated format (e.g., "1/15/24")
|
- **short**: Abbreviated format (e.g., "1/15/24")
|
||||||
- **medium**: Medium-length format (e.g., "Jan 15, 2024")
|
- **medium**: Medium-length format (e.g., "Jan 15, 2024")
|
||||||
- **long**: Long format with full month name (e.g., "January 15, 2024")
|
- **long**: Long format with full month name (e.g., "January 15, 2024")
|
||||||
- **full**: Full format including day of week (e.g., "Monday, January 15, 2024")
|
- **full**: Full format including day of week (e.g., "Monday, January 15, 2024")
|
||||||
|
|
||||||
#### Additional Variables
|
#### Additional Variables
|
||||||
|
|
||||||
- `{{ tag_name_list }}`: A list of tag names applied to the document, ordered by the tag name. Note this is a list, not a single string
|
- `{{ tag_name_list }}`: A list of tag names applied to the document, ordered by the tag name. Note this is a list, not a single string
|
||||||
- `{{ custom_fields }}`: A mapping of custom field names to their type and value. A user can access the mapping by field name or check if a field is applied by checking its existence in the variable.
|
- `{{ custom_fields }}`: A mapping of custom field names to their type and value. A user can access the mapping by field name or check if a field is applied by checking its existence in the variable.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
@@ -667,15 +675,15 @@ installation, you can use volumes to accomplish this:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
|
# ...
|
||||||
|
webserver:
|
||||||
|
environment:
|
||||||
|
- PAPERLESS_ENABLE_FLOWER
|
||||||
|
ports:
|
||||||
|
- 5555:5555 # (2)!
|
||||||
# ...
|
# ...
|
||||||
webserver:
|
volumes:
|
||||||
environment:
|
- /path/to/my/flowerconfig.py:/usr/src/paperless/src/paperless/flowerconfig.py:ro # (1)!
|
||||||
- PAPERLESS_ENABLE_FLOWER
|
|
||||||
ports:
|
|
||||||
- 5555:5555 # (2)!
|
|
||||||
# ...
|
|
||||||
volumes:
|
|
||||||
- /path/to/my/flowerconfig.py:/usr/src/paperless/src/paperless/flowerconfig.py:ro # (1)!
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Note the `:ro` tag means the file will be mounted as read only.
|
1. Note the `:ro` tag means the file will be mounted as read only.
|
||||||
@@ -706,11 +714,11 @@ For example, using Docker Compose:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
|
# ...
|
||||||
|
webserver:
|
||||||
# ...
|
# ...
|
||||||
webserver:
|
volumes:
|
||||||
# ...
|
- /path/to/my/scripts:/custom-cont-init.d:ro # (1)!
|
||||||
volumes:
|
|
||||||
- /path/to/my/scripts:/custom-cont-init.d:ro # (1)!
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Note the `:ro` tag means the folder will be mounted as read only. This is for extra security against changes
|
1. Note the `:ro` tag means the folder will be mounted as read only. This is for extra security against changes
|
||||||
@@ -763,18 +771,17 @@ Paperless is able to utilize barcodes for automatically performing some tasks.
|
|||||||
|
|
||||||
At this time, the library utilized for detection of barcodes supports the following types:
|
At this time, the library utilized for detection of barcodes supports the following types:
|
||||||
|
|
||||||
- AN-13/UPC-A
|
- AN-13/UPC-A
|
||||||
- UPC-E
|
- UPC-E
|
||||||
- EAN-8
|
- EAN-8
|
||||||
- Code 128
|
- Code 128
|
||||||
- Code 93
|
- Code 93
|
||||||
- Code 39
|
- Code 39
|
||||||
- Codabar
|
- Codabar
|
||||||
- Interleaved 2 of 5
|
- Interleaved 2 of 5
|
||||||
- QR Code
|
- QR Code
|
||||||
- SQ Code
|
- SQ Code
|
||||||
|
|
||||||
You may check for updates on the [zbar library homepage](https://github.com/mchehab/zbar).
|
|
||||||
For usage in Paperless, the type of barcode does not matter, only the contents of it.
|
For usage in Paperless, the type of barcode does not matter, only the contents of it.
|
||||||
|
|
||||||
For how to enable barcode usage, see [the configuration](configuration.md#barcodes).
|
For how to enable barcode usage, see [the configuration](configuration.md#barcodes).
|
||||||
@@ -783,9 +790,17 @@ below.
|
|||||||
|
|
||||||
### Document Splitting {#document-splitting}
|
### Document Splitting {#document-splitting}
|
||||||
|
|
||||||
When enabled, Paperless will look for a barcode with the configured value and create a new document
|
If document splitting is enabled, Paperless splits _after_ a separator barcode by default.
|
||||||
starting from the next page. The page with the barcode on it will _not_ be retained. It
|
This means:
|
||||||
is expected to be a page existing only for triggering the split.
|
|
||||||
|
- any page containing the configured separator barcode starts a new document, starting with the **next** page
|
||||||
|
- pages containing the separator barcode are discarded
|
||||||
|
|
||||||
|
This is intended for dedicated separator sheets such as PATCH-T pages.
|
||||||
|
|
||||||
|
If [`PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES`](configuration.md#PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES)
|
||||||
|
is enabled, the page containing the separator barcode is retained instead. In this mode,
|
||||||
|
each page containing the separator barcode becomes the **first** page of a new document.
|
||||||
|
|
||||||
### Archive Serial Number Assignment
|
### Archive Serial Number Assignment
|
||||||
|
|
||||||
@@ -794,8 +809,9 @@ archive serial number, allowing quick reference back to the original, paper docu
|
|||||||
|
|
||||||
If document splitting via barcode is also enabled, documents will be split when an ASN
|
If document splitting via barcode is also enabled, documents will be split when an ASN
|
||||||
barcode is located. However, differing from the splitting, the page with the
|
barcode is located. However, differing from the splitting, the page with the
|
||||||
barcode _will_ be retained. This allows application of a barcode to any page, including
|
barcode _will_ be retained. Each detected ASN barcode starts a new document _starting with
|
||||||
one which holds data to keep in the document.
|
that page_. This allows placing ASN barcodes on content pages that should remain part of
|
||||||
|
the document.
|
||||||
|
|
||||||
### Tag Assignment
|
### Tag Assignment
|
||||||
|
|
||||||
@@ -805,6 +821,27 @@ See the relevant settings [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE`](configuratio
|
|||||||
and [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING)
|
and [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING)
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
#### Splitting on Tag Barcodes
|
||||||
|
|
||||||
|
By default, tag barcodes only assign tags to documents without splitting them. However,
|
||||||
|
you can enable document splitting on tag barcodes by setting
|
||||||
|
[`PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT)
|
||||||
|
to `true`.
|
||||||
|
|
||||||
|
When enabled, documents will be split at pages containing tag barcodes, similar to how
|
||||||
|
ASN barcodes work. Key features:
|
||||||
|
|
||||||
|
- The page with the tag barcode is **retained** in the resulting document
|
||||||
|
- **Each split document extracts its own tags** - only tags on pages within that document are assigned
|
||||||
|
- Multiple tag barcodes can trigger multiple splits in the same document
|
||||||
|
- Works seamlessly with ASN barcodes - each split document gets its own ASN and tags
|
||||||
|
|
||||||
|
This is useful for batch scanning where you place tag barcode pages between different
|
||||||
|
documents to both separate and categorize them in a single operation.
|
||||||
|
|
||||||
|
**Example:** A 6-page scan with TAG:invoice on page 3 and TAG:receipt on page 5 will create
|
||||||
|
three documents: pages 1-2 (no tags), pages 3-4 (tagged "invoice"), and pages 5-6 (tagged "receipt").
|
||||||
|
|
||||||
## Automatic collation of double-sided documents {#collate}
|
## Automatic collation of double-sided documents {#collate}
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
@@ -851,8 +888,8 @@ followed by the even pages.
|
|||||||
|
|
||||||
It's important that the scan files get consumed in the correct order, and one at a time.
|
It's important that the scan files get consumed in the correct order, and one at a time.
|
||||||
You therefore need to make sure that Paperless is running while you upload the files into
|
You therefore need to make sure that Paperless is running while you upload the files into
|
||||||
the directory; and if you're using [polling](configuration.md#polling), make sure that
|
the directory; and if you're using polling, make sure that
|
||||||
`CONSUMER_POLLING` is set to a value lower than it takes for the second scan to appear,
|
`CONSUMER_POLLING_INTERVAL` is set to a value lower than it takes for the second scan to appear,
|
||||||
like 5-10 or even lower.
|
like 5-10 or even lower.
|
||||||
|
|
||||||
Another thing that might happen is that you start a double sided scan, but then forget
|
Another thing that might happen is that you start a double sided scan, but then forget
|
||||||
@@ -959,9 +996,9 @@ If using docker, you'll need to add the following volume mounts to your `docker-
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
webserver:
|
webserver:
|
||||||
volumes:
|
volumes:
|
||||||
- /home/user/.gnupg/pubring.gpg:/usr/src/paperless/.gnupg/pubring.gpg
|
- /home/user/.gnupg/pubring.gpg:/usr/src/paperless/.gnupg/pubring.gpg
|
||||||
- <path to gpg-agent socket>:/usr/src/paperless/.gnupg/S.gpg-agent
|
- <path to gpg-agent socket>:/usr/src/paperless/.gnupg/S.gpg-agent
|
||||||
```
|
```
|
||||||
|
|
||||||
For a 'bare-metal' installation no further configuration is necessary. If you
|
For a 'bare-metal' installation no further configuration is necessary. If you
|
||||||
@@ -969,9 +1006,9 @@ want to use a separate `GNUPG_HOME`, you can do so by configuring the [PAPERLESS
|
|||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
- Make sure, that `gpg-agent` is running on your host machine
|
- Make sure, that `gpg-agent` is running on your host machine
|
||||||
- Make sure, that encryption and decryption works from inside the container using the `gpg` commands from above.
|
- Make sure, that encryption and decryption works from inside the container using the `gpg` commands from above.
|
||||||
- Check that all files in `/usr/src/paperless/.gnupg` have correct permissions
|
- Check that all files in `/usr/src/paperless/.gnupg` have correct permissions
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
paperless@9da1865df327:~/.gnupg$ ls -al
|
paperless@9da1865df327:~/.gnupg$ ls -al
|
||||||
|
|||||||
277
docs/api.md
277
docs/api.md
@@ -1,4 +1,4 @@
|
|||||||
# The REST API
|
# REST API
|
||||||
|
|
||||||
Paperless-ngx now ships with a fully-documented REST API and a browsable
|
Paperless-ngx now ships with a fully-documented REST API and a browsable
|
||||||
web interface to explore it. The API browsable interface is available at
|
web interface to explore it. The API browsable interface is available at
|
||||||
@@ -8,7 +8,7 @@ Further documentation is provided here for some endpoints and features.
|
|||||||
|
|
||||||
## Authorization
|
## Authorization
|
||||||
|
|
||||||
The REST api provides four different forms of authentication.
|
The REST api provides five different forms of authentication.
|
||||||
|
|
||||||
1. Basic authentication
|
1. Basic authentication
|
||||||
|
|
||||||
@@ -52,16 +52,24 @@ The REST api provides four different forms of authentication.
|
|||||||
[configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API)),
|
[configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API)),
|
||||||
you can authenticate against the API using Remote User auth.
|
you can authenticate against the API using Remote User auth.
|
||||||
|
|
||||||
|
5. Headless OIDC via [`django-allauth`](https://codeberg.org/allauth/django-allauth)
|
||||||
|
|
||||||
|
`django-allauth` exposes API endpoints under `api/auth/` which enable tools
|
||||||
|
like third-party apps to authenticate with social accounts that are
|
||||||
|
configured. See
|
||||||
|
[here](advanced_usage.md#openid-connect-and-social-authentication) for more
|
||||||
|
information on social accounts.
|
||||||
|
|
||||||
## Searching for documents
|
## Searching for documents
|
||||||
|
|
||||||
Full text searching is available on the `/api/documents/` endpoint. Two
|
Full text searching is available on the `/api/documents/` endpoint. Two
|
||||||
specific query parameters cause the API to return full text search
|
specific query parameters cause the API to return full text search
|
||||||
results:
|
results:
|
||||||
|
|
||||||
- `/api/documents/?query=your%20search%20query`: Search for a document
|
- `/api/documents/?query=your%20search%20query`: Search for a document
|
||||||
using a full text query. For details on the syntax, see [Basic Usage - Searching](usage.md#basic-usage_searching).
|
using a full text query. For details on the syntax, see [Basic Usage - Searching](usage.md#basic-usage_searching).
|
||||||
- `/api/documents/?more_like_id=1234`: Search for documents similar to
|
- `/api/documents/?more_like_id=1234`: Search for documents similar to
|
||||||
the document with id 1234.
|
the document with id 1234.
|
||||||
|
|
||||||
Pagination works exactly the same as it does for normal requests on this
|
Pagination works exactly the same as it does for normal requests on this
|
||||||
endpoint.
|
endpoint.
|
||||||
@@ -98,12 +106,12 @@ attribute with various information about the search results:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `score` is an indication how well this document matches the query
|
- `score` is an indication how well this document matches the query
|
||||||
relative to the other search results.
|
relative to the other search results.
|
||||||
- `highlights` is an excerpt from the document content and highlights
|
- `highlights` is an excerpt from the document content and highlights
|
||||||
the search terms with `<span>` tags as shown above.
|
the search terms with `<span>` tags as shown above.
|
||||||
- `rank` is the index of the search results. The first result will
|
- `rank` is the index of the search results. The first result will
|
||||||
have rank 0.
|
have rank 0.
|
||||||
|
|
||||||
### Filtering by custom fields
|
### Filtering by custom fields
|
||||||
|
|
||||||
@@ -114,33 +122,33 @@ use cases:
|
|||||||
1. Documents with a custom field "due" (date) between Aug 1, 2024 and
|
1. Documents with a custom field "due" (date) between Aug 1, 2024 and
|
||||||
Sept 1, 2024 (inclusive):
|
Sept 1, 2024 (inclusive):
|
||||||
|
|
||||||
`?custom_field_query=["due", "range", ["2024-08-01", "2024-09-01"]]`
|
`?custom_field_query=["due", "range", ["2024-08-01", "2024-09-01"]]`
|
||||||
|
|
||||||
2. Documents with a custom field "customer" (text) that equals "bob"
|
2. Documents with a custom field "customer" (text) that equals "bob"
|
||||||
(case sensitive):
|
(case sensitive):
|
||||||
|
|
||||||
`?custom_field_query=["customer", "exact", "bob"]`
|
`?custom_field_query=["customer", "exact", "bob"]`
|
||||||
|
|
||||||
3. Documents with a custom field "answered" (boolean) set to `true`:
|
3. Documents with a custom field "answered" (boolean) set to `true`:
|
||||||
|
|
||||||
`?custom_field_query=["answered", "exact", true]`
|
`?custom_field_query=["answered", "exact", true]`
|
||||||
|
|
||||||
4. Documents with a custom field "favorite animal" (select) set to either
|
4. Documents with a custom field "favorite animal" (select) set to either
|
||||||
"cat" or "dog":
|
"cat" or "dog":
|
||||||
|
|
||||||
`?custom_field_query=["favorite animal", "in", ["cat", "dog"]]`
|
`?custom_field_query=["favorite animal", "in", ["cat", "dog"]]`
|
||||||
|
|
||||||
5. Documents with a custom field "address" (text) that is empty:
|
5. Documents with a custom field "address" (text) that is empty:
|
||||||
|
|
||||||
`?custom_field_query=["OR", [["address", "isnull", true], ["address", "exact", ""]]]`
|
`?custom_field_query=["OR", [["address", "isnull", true], ["address", "exact", ""]]]`
|
||||||
|
|
||||||
6. Documents that don't have a field called "foo":
|
6. Documents that don't have a field called "foo":
|
||||||
|
|
||||||
`?custom_field_query=["foo", "exists", false]`
|
`?custom_field_query=["foo", "exists", false]`
|
||||||
|
|
||||||
7. Documents that have document links "references" to both document 3 and 7:
|
7. Documents that have document links "references" to both document 3 and 7:
|
||||||
|
|
||||||
`?custom_field_query=["references", "contains", [3, 7]]`
|
`?custom_field_query=["references", "contains", [3, 7]]`
|
||||||
|
|
||||||
All field types support basic operations including `exact`, `in`, `isnull`,
|
All field types support basic operations including `exact`, `in`, `isnull`,
|
||||||
and `exists`. String, URL, and monetary fields support case-insensitive
|
and `exists`. String, URL, and monetary fields support case-insensitive
|
||||||
@@ -156,8 +164,8 @@ Get auto completions for a partial search term.
|
|||||||
|
|
||||||
Query parameters:
|
Query parameters:
|
||||||
|
|
||||||
- `term`: The incomplete term.
|
- `term`: The incomplete term.
|
||||||
- `limit`: Amount of results. Defaults to 10.
|
- `limit`: Amount of results. Defaults to 10.
|
||||||
|
|
||||||
Results returned by the endpoint are ordered by importance of the term
|
Results returned by the endpoint are ordered by importance of the term
|
||||||
in the document index. The first result is the term that has the highest
|
in the document index. The first result is the term that has the highest
|
||||||
@@ -181,19 +189,19 @@ from there.
|
|||||||
|
|
||||||
The endpoint supports the following optional form fields:
|
The endpoint supports the following optional form fields:
|
||||||
|
|
||||||
- `title`: Specify a title that the consumer should use for the
|
- `title`: Specify a title that the consumer should use for the
|
||||||
document.
|
document.
|
||||||
- `created`: Specify a DateTime where the document was created (e.g.
|
- `created`: Specify a DateTime where the document was created (e.g.
|
||||||
"2016-04-19" or "2016-04-19 06:15:00+02:00").
|
"2016-04-19" or "2016-04-19 06:15:00+02:00").
|
||||||
- `correspondent`: Specify the ID of a correspondent that the consumer
|
- `correspondent`: Specify the ID of a correspondent that the consumer
|
||||||
should use for the document.
|
should use for the document.
|
||||||
- `document_type`: Similar to correspondent.
|
- `document_type`: Similar to correspondent.
|
||||||
- `storage_path`: Similar to correspondent.
|
- `storage_path`: Similar to correspondent.
|
||||||
- `tags`: Similar to correspondent. Specify this multiple times to
|
- `tags`: Similar to correspondent. Specify this multiple times to
|
||||||
have multiple tags added to the document.
|
have multiple tags added to the document.
|
||||||
- `archive_serial_number`: An optional archive serial number to set.
|
- `archive_serial_number`: An optional archive serial number to set.
|
||||||
- `custom_fields`: Either an array of custom field ids to assign (with an empty
|
- `custom_fields`: Either an array of custom field ids to assign (with an empty
|
||||||
value) to the document or an object mapping field id -> value.
|
value) to the document or an object mapping field id -> value.
|
||||||
|
|
||||||
The endpoint will immediately return HTTP 200 if the document consumption
|
The endpoint will immediately return HTTP 200 if the document consumption
|
||||||
process was started successfully, with the UUID of the consumption task
|
process was started successfully, with the UUID of the consumption task
|
||||||
@@ -203,6 +211,21 @@ However, querying the tasks endpoint with the returned UUID e.g.
|
|||||||
`/api/tasks/?task_id={uuid}` will provide information on the state of the
|
`/api/tasks/?task_id={uuid}` will provide information on the state of the
|
||||||
consumption including the ID of a created document if consumption succeeded.
|
consumption including the ID of a created document if consumption succeeded.
|
||||||
|
|
||||||
|
## Document Versions
|
||||||
|
|
||||||
|
Document versions are file-level versions linked to one root document.
|
||||||
|
|
||||||
|
- Root document metadata (title, tags, correspondent, document type, storage path, custom fields, permissions) remains shared.
|
||||||
|
- Version-specific file data (file, mime type, checksums, archive info, extracted text content) belongs to the selected/latest version.
|
||||||
|
|
||||||
|
Version-aware endpoints:
|
||||||
|
|
||||||
|
- `GET /api/documents/{id}/`: returns root document data; `content` resolves to latest version content by default. Use `?version={version_id}` to resolve content for a specific version.
|
||||||
|
- `PATCH /api/documents/{id}/`: content updates target the selected version (`?version={version_id}`) or latest version by default; non-content metadata updates target the root document.
|
||||||
|
- `GET /api/documents/{id}/download/`, `GET /api/documents/{id}/preview/`, `GET /api/documents/{id}/thumb/`, `GET /api/documents/{id}/metadata/`: accept `?version={version_id}`.
|
||||||
|
- `POST /api/documents/{id}/update_version/`: uploads a new version using multipart form field `document` and optional `version_label`.
|
||||||
|
- `DELETE /api/documents/{root_id}/versions/{version_id}/`: deletes a non-root version.
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
|
|
||||||
All objects (documents, tags, etc.) allow setting object-level permissions
|
All objects (documents, tags, etc.) allow setting object-level permissions
|
||||||
@@ -259,67 +282,38 @@ a json payload of the format:
|
|||||||
|
|
||||||
The following methods are supported:
|
The following methods are supported:
|
||||||
|
|
||||||
- `set_correspondent`
|
- `set_correspondent`
|
||||||
- Requires `parameters`: `{ "correspondent": CORRESPONDENT_ID }`
|
- Requires `parameters`: `{ "correspondent": CORRESPONDENT_ID }`
|
||||||
- `set_document_type`
|
- `set_document_type`
|
||||||
- Requires `parameters`: `{ "document_type": DOCUMENT_TYPE_ID }`
|
- Requires `parameters`: `{ "document_type": DOCUMENT_TYPE_ID }`
|
||||||
- `set_storage_path`
|
- `set_storage_path`
|
||||||
- Requires `parameters`: `{ "storage_path": STORAGE_PATH_ID }`
|
- Requires `parameters`: `{ "storage_path": STORAGE_PATH_ID }`
|
||||||
- `add_tag`
|
- `add_tag`
|
||||||
- Requires `parameters`: `{ "tag": TAG_ID }`
|
- Requires `parameters`: `{ "tag": TAG_ID }`
|
||||||
- `remove_tag`
|
- `remove_tag`
|
||||||
- Requires `parameters`: `{ "tag": TAG_ID }`
|
- Requires `parameters`: `{ "tag": TAG_ID }`
|
||||||
- `modify_tags`
|
- `modify_tags`
|
||||||
- Requires `parameters`: `{ "add_tags": [LIST_OF_TAG_IDS] }` and `{ "remove_tags": [LIST_OF_TAG_IDS] }`
|
- Requires `parameters`: `{ "add_tags": [LIST_OF_TAG_IDS] }` and `{ "remove_tags": [LIST_OF_TAG_IDS] }`
|
||||||
- `delete`
|
- `delete`
|
||||||
- No `parameters` required
|
- No `parameters` required
|
||||||
- `reprocess`
|
- `reprocess`
|
||||||
- No `parameters` required
|
- No `parameters` required
|
||||||
- `set_permissions`
|
- `set_permissions`
|
||||||
- Requires `parameters`:
|
- Requires `parameters`:
|
||||||
- `"set_permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
|
- `"set_permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
|
||||||
- `"owner": OWNER_ID or null`
|
- `"owner": OWNER_ID or null`
|
||||||
- `"merge": true or false` (defaults to false)
|
- `"merge": true or false` (defaults to false)
|
||||||
- The `merge` flag determines if the supplied permissions will overwrite all existing permissions (including
|
- The `merge` flag determines if the supplied permissions will overwrite all existing permissions (including
|
||||||
removing them) or be merged with existing permissions.
|
removing them) or be merged with existing permissions.
|
||||||
- `edit_pdf`
|
- `modify_custom_fields`
|
||||||
- Requires `parameters`:
|
- Requires `parameters`:
|
||||||
- `"doc_ids": [DOCUMENT_ID]` A list of a single document ID to edit.
|
- `"add_custom_fields": { CUSTOM_FIELD_ID: VALUE }`: JSON object consisting of custom field id:value pairs to add to the document, can also be a list of custom field IDs
|
||||||
- `"operations": [OPERATION, ...]` A list of operations to perform on the documents. Each operation is a dictionary
|
to add with empty values.
|
||||||
with the following keys:
|
- `"remove_custom_fields": [CUSTOM_FIELD_ID]`: custom field ids to remove from the document.
|
||||||
- `"page": PAGE_NUMBER` The page number to edit (1-based).
|
|
||||||
- `"rotate": DEGREES` Optional rotation in degrees (90, 180, 270).
|
#### Document-editing operations
|
||||||
- `"doc": OUTPUT_DOCUMENT_INDEX` Optional index of the output document for split operations.
|
|
||||||
- Optional `parameters`:
|
Beginning with version 10+, the API supports individual endpoints for document-editing operations (`merge`, `rotate`, `edit_pdf`, etc), thus their documentation can be found in the API spec / viewer. Legacy document-editing methods via `/api/documents/bulk_edit/` are still supported for compatibility, are deprecated and clients should migrate to the individual endpoints before they are removed in a future version.
|
||||||
- `"delete_original": true` to delete the original documents after editing.
|
|
||||||
- `"update_document": true` to update the existing document with the edited PDF.
|
|
||||||
- `"include_metadata": true` to copy metadata from the original document to the edited document.
|
|
||||||
- `merge`
|
|
||||||
- No additional `parameters` required.
|
|
||||||
- The ordering of the merged document is determined by the list of IDs.
|
|
||||||
- Optional `parameters`:
|
|
||||||
- `"metadata_document_id": DOC_ID` apply metadata (tags, correspondent, etc.) from this document to the merged document.
|
|
||||||
- `"delete_originals": true` to delete the original documents. This requires the calling user being the owner of
|
|
||||||
all documents that are merged.
|
|
||||||
- `split`
|
|
||||||
- Requires `parameters`:
|
|
||||||
- `"pages": [..]` The list should be a list of pages and/or a ranges, separated by commas e.g. `"[1,2-3,4,5-7]"`
|
|
||||||
- Optional `parameters`:
|
|
||||||
- `"delete_originals": true` to delete the original document after consumption. This requires the calling user being the owner of
|
|
||||||
the document.
|
|
||||||
- The split operation only accepts a single document.
|
|
||||||
- `rotate`
|
|
||||||
- Requires `parameters`:
|
|
||||||
- `"degrees": DEGREES`. Must be an integer i.e. 90, 180, 270
|
|
||||||
- `delete_pages`
|
|
||||||
- Requires `parameters`:
|
|
||||||
- `"pages": [..]` The list should be a list of integers e.g. `"[2,3,4]"`
|
|
||||||
- The delete_pages operation only accepts a single document.
|
|
||||||
- `modify_custom_fields`
|
|
||||||
- Requires `parameters`:
|
|
||||||
- `"add_custom_fields": { CUSTOM_FIELD_ID: VALUE }`: JSON object consisting of custom field id:value pairs to add to the document, can also be a list of custom field IDs
|
|
||||||
to add with empty values.
|
|
||||||
- `"remove_custom_fields": [CUSTOM_FIELD_ID]`: custom field ids to remove from the document.
|
|
||||||
|
|
||||||
### Objects
|
### Objects
|
||||||
|
|
||||||
@@ -339,41 +333,38 @@ operations, using the endpoint: `/api/bulk_edit_objects/`, which requires a json
|
|||||||
|
|
||||||
## API Versioning
|
## API Versioning
|
||||||
|
|
||||||
The REST API is versioned since Paperless-ngx 1.3.0.
|
The REST API is versioned.
|
||||||
|
|
||||||
- Versioning ensures that changes to the API don't break older
|
- Versioning ensures that changes to the API don't break older
|
||||||
clients.
|
clients.
|
||||||
- Clients specify the specific version of the API they wish to use
|
- Clients specify the specific version of the API they wish to use
|
||||||
with every request and Paperless will handle the request using the
|
with every request and Paperless will handle the request using the
|
||||||
specified API version.
|
specified API version.
|
||||||
- Even if the underlying data model changes, older API versions will
|
- Even if the underlying data model changes, supported older API
|
||||||
always serve compatible data.
|
versions continue to serve compatible data.
|
||||||
- If no version is specified, Paperless will serve version 1 to ensure
|
- If no version is specified, Paperless serves the configured default
|
||||||
compatibility with older clients that do not request a specific API
|
API version (currently `10`).
|
||||||
version.
|
- Supported API versions are currently `9` and `10`.
|
||||||
|
|
||||||
API versions are specified by submitting an additional HTTP `Accept`
|
API versions are specified by submitting an additional HTTP `Accept`
|
||||||
header with every request:
|
header with every request:
|
||||||
|
|
||||||
```
|
```
|
||||||
Accept: application/json; version=6
|
Accept: application/json; version=10
|
||||||
```
|
```
|
||||||
|
|
||||||
If an invalid version is specified, Paperless 1.3.0 will respond with
|
If an invalid version is specified, Paperless responds with
|
||||||
"406 Not Acceptable" and an error message in the body. Earlier
|
`406 Not Acceptable` and an error message in the body.
|
||||||
versions of Paperless will serve API version 1 regardless of whether a
|
|
||||||
version is specified via the `Accept` header.
|
|
||||||
|
|
||||||
If a client wishes to verify whether it is compatible with any given
|
If a client wishes to verify whether it is compatible with any given
|
||||||
server, the following procedure should be performed:
|
server, the following procedure should be performed:
|
||||||
|
|
||||||
1. Perform an _authenticated_ request against any API endpoint. If the
|
1. Perform an _authenticated_ request against any API endpoint. The
|
||||||
server is on version 1.3.0 or newer, the server will add two custom
|
server will add two custom headers to the response:
|
||||||
headers to the response:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
X-Api-Version: 2
|
X-Api-Version: 10
|
||||||
X-Version: 1.3.0
|
X-Version: <server-version>
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Determine whether the client is compatible with this server based on
|
2. Determine whether the client is compatible with this server based on
|
||||||
@@ -393,46 +384,56 @@ Initial API version.
|
|||||||
|
|
||||||
#### Version 2
|
#### Version 2
|
||||||
|
|
||||||
- Added field `Tag.color`. This read/write string field contains a hex
|
- Added field `Tag.color`. This read/write string field contains a hex
|
||||||
color such as `#a6cee3`.
|
color such as `#a6cee3`.
|
||||||
- Added read-only field `Tag.text_color`. This field contains the text
|
- Added read-only field `Tag.text_color`. This field contains the text
|
||||||
color to use for a specific tag, which is either black or white
|
color to use for a specific tag, which is either black or white
|
||||||
depending on the brightness of `Tag.color`.
|
depending on the brightness of `Tag.color`.
|
||||||
- Removed field `Tag.colour`.
|
- Removed field `Tag.colour`.
|
||||||
|
|
||||||
#### Version 3
|
#### Version 3
|
||||||
|
|
||||||
- Permissions endpoints have been added.
|
- Permissions endpoints have been added.
|
||||||
- The format of the `/api/ui_settings/` has changed.
|
- The format of the `/api/ui_settings/` has changed.
|
||||||
|
|
||||||
#### Version 4
|
#### Version 4
|
||||||
|
|
||||||
- Consumption templates were refactored to workflows and API endpoints
|
- Consumption templates were refactored to workflows and API endpoints
|
||||||
changed as such.
|
changed as such.
|
||||||
|
|
||||||
#### Version 5
|
#### Version 5
|
||||||
|
|
||||||
- Added bulk deletion methods for documents and objects.
|
- Added bulk deletion methods for documents and objects.
|
||||||
|
|
||||||
#### Version 6
|
#### Version 6
|
||||||
|
|
||||||
- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.
|
- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.
|
||||||
|
|
||||||
#### Version 7
|
#### Version 7
|
||||||
|
|
||||||
- The format of select type custom fields has changed to return the options
|
- The format of select type custom fields has changed to return the options
|
||||||
as an array of objects with `id` and `label` fields as opposed to a simple
|
as an array of objects with `id` and `label` fields as opposed to a simple
|
||||||
list of strings. When creating or updating a custom field value of a
|
list of strings. When creating or updating a custom field value of a
|
||||||
document for a select type custom field, the value should be the `id` of
|
document for a select type custom field, the value should be the `id` of
|
||||||
the option whereas previously was the index of the option.
|
the option whereas previously was the index of the option.
|
||||||
|
|
||||||
#### Version 8
|
#### Version 8
|
||||||
|
|
||||||
- The user field of document notes now returns a simplified user object
|
- The user field of document notes now returns a simplified user object
|
||||||
rather than just the user ID.
|
rather than just the user ID.
|
||||||
|
|
||||||
#### Version 9
|
#### Version 9
|
||||||
|
|
||||||
- The document `created` field is now a date, not a datetime. The
|
- The document `created` field is now a date, not a datetime. The
|
||||||
`created_date` field is considered deprecated and will be removed in a
|
`created_date` field is considered deprecated and will be removed in a
|
||||||
future version.
|
future version.
|
||||||
|
|
||||||
|
#### Version 10
|
||||||
|
|
||||||
|
- The `show_on_dashboard` and `show_in_sidebar` fields of saved views have been
|
||||||
|
removed. Relevant settings are now stored in the UISettings model. Compatibility is maintained
|
||||||
|
for versions < 10 until support for API v9 is dropped.
|
||||||
|
- Document-editing operations such as `merge`, `rotate`, and `edit_pdf` have been
|
||||||
|
moved from the bulk edit endpoint to their own individual endpoints. Using these methods via
|
||||||
|
the bulk edit endpoint is still supported for compatibility with versions < 10 until support
|
||||||
|
for API v9 is dropped.
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
:root > * {
|
:root>* {
|
||||||
--md-primary-fg-color: #17541f;
|
--paperless-green: #17541f;
|
||||||
--md-primary-fg-color--dark: #17541f;
|
--paperless-green-accent: #2b8a38;
|
||||||
--md-primary-fg-color--light: #17541f;
|
--md-primary-fg-color: var(--paperless-green);
|
||||||
--md-accent-fg-color: #2b8a38;
|
--md-primary-fg-color--dark: var(--paperless-green);
|
||||||
|
--md-primary-fg-color--light: var(--paperless-green-accent);
|
||||||
|
--md-accent-fg-color: var(--paperless-green-accent);
|
||||||
--md-typeset-a-color: #21652a;
|
--md-typeset-a-color: #21652a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-header,
|
||||||
|
.md-tabs {
|
||||||
|
background-color: var(--paperless-green);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-tabs__link {
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-tabs__link:hover,
|
||||||
|
.md-tabs__link--active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
[data-md-color-scheme="slate"] {
|
[data-md-color-scheme="slate"] {
|
||||||
--md-hue: 222;
|
--md-hue: 222;
|
||||||
|
--md-default-bg-color: hsla(var(--md-hue), 15%, 10%, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -69,8 +87,8 @@ h4 code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Hide config vars from sidebar, toc and move the border on mobile case their hidden */
|
/* Hide config vars from sidebar, toc and move the border on mobile case their hidden */
|
||||||
.md-nav.md-nav--secondary .md-nav__item .md-nav__link[href*="PAPERLESS_"],
|
.md-nav.md-nav--secondary .md-nav__item:has(> .md-nav__link[href*="PAPERLESS_"]),
|
||||||
.md-nav.md-nav--secondary .md-nav__item .md-nav__link[href*="USERMAP_"] {
|
.md-nav.md-nav--secondary .md-nav__item:has(> .md-nav__link[href*="USERMAP_"]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,18 +101,3 @@ h4 code {
|
|||||||
border-top: .05rem solid var(--md-default-fg-color--lightest);
|
border-top: .05rem solid var(--md-default-fg-color--lightest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show search shortcut key */
|
|
||||||
[data-md-toggle="search"]:not(:checked) ~ .md-header .md-search__form::after {
|
|
||||||
position: absolute;
|
|
||||||
top: .3rem;
|
|
||||||
right: .3rem;
|
|
||||||
display: block;
|
|
||||||
padding: .1rem .4rem;
|
|
||||||
color: var(--md-default-fg-color--lighter);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: .8rem;
|
|
||||||
border: .05rem solid var(--md-default-fg-color--lighter);
|
|
||||||
border-radius: .1rem;
|
|
||||||
content: "/";
|
|
||||||
}
|
|
||||||
|
|||||||
BIN
docs/assets/logo_full_black.png
Normal file
BIN
docs/assets/logo_full_black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/assets/logo_full_white.png
Normal file
BIN
docs/assets/logo_full_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
10038
docs/changelog.md
10038
docs/changelog.md
File diff suppressed because it is too large
Load Diff
@@ -8,17 +8,17 @@ common [OCR](#ocr) related settings and some frontend settings. If set, these wi
|
|||||||
preference over the settings via environment variables. If not set, the environment setting
|
preference over the settings via environment variables. If not set, the environment setting
|
||||||
or applicable default will be utilized instead.
|
or applicable default will be utilized instead.
|
||||||
|
|
||||||
- If you run paperless on docker, `paperless.conf` is not used.
|
- If you run paperless on docker, `paperless.conf` is not used.
|
||||||
Rather, configure paperless by copying necessary options to
|
Rather, configure paperless by copying necessary options to
|
||||||
`docker-compose.env`.
|
`docker-compose.env`.
|
||||||
|
|
||||||
- If you are running paperless on anything else, paperless will search
|
- If you are running paperless on anything else, paperless will search
|
||||||
for the configuration file in these locations and use the first one
|
for the configuration file in these locations and use the first one
|
||||||
it finds:
|
it finds:
|
||||||
- The environment variable `PAPERLESS_CONFIGURATION_PATH`
|
- The environment variable `PAPERLESS_CONFIGURATION_PATH`
|
||||||
- `/path/to/paperless/paperless.conf`
|
- `/path/to/paperless/paperless.conf`
|
||||||
- `/etc/paperless.conf`
|
- `/etc/paperless.conf`
|
||||||
- `/usr/local/etc/paperless.conf`
|
- `/usr/local/etc/paperless.conf`
|
||||||
|
|
||||||
## Required services
|
## Required services
|
||||||
|
|
||||||
@@ -51,130 +51,172 @@ matcher.
|
|||||||
### Database
|
### Database
|
||||||
|
|
||||||
By default, Paperless uses **SQLite** with a database stored at `data/db.sqlite3`.
|
By default, Paperless uses **SQLite** with a database stored at `data/db.sqlite3`.
|
||||||
To switch to **PostgreSQL** or **MariaDB**, set [`PAPERLESS_DBHOST`](#PAPERLESS_DBHOST) and optionally configure other
|
For multi-user or higher-throughput deployments, **PostgreSQL** (recommended) or
|
||||||
database-related environment variables.
|
**MariaDB** can be used instead by setting [`PAPERLESS_DBENGINE`](#PAPERLESS_DBENGINE)
|
||||||
|
and the relevant connection variables.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_DBENGINE=<engine>`](#PAPERLESS_DBENGINE) {#PAPERLESS_DBENGINE}
|
||||||
|
|
||||||
|
: Specifies the database engine to use. Accepted values are `sqlite`, `postgresql`,
|
||||||
|
and `mariadb`.
|
||||||
|
|
||||||
|
Defaults to `sqlite` if not set.
|
||||||
|
|
||||||
|
PostgreSQL and MariaDB both require [`PAPERLESS_DBHOST`](#PAPERLESS_DBHOST) to be
|
||||||
|
set. SQLite does not use any other connection variables; the database file is always
|
||||||
|
located at `<PAPERLESS_DATA_DIR>/db.sqlite3`.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Using MariaDB comes with some caveats.
|
||||||
|
See [MySQL Caveats](advanced_usage.md#mysql-caveats).
|
||||||
|
|
||||||
#### [`PAPERLESS_DBHOST=<hostname>`](#PAPERLESS_DBHOST) {#PAPERLESS_DBHOST}
|
#### [`PAPERLESS_DBHOST=<hostname>`](#PAPERLESS_DBHOST) {#PAPERLESS_DBHOST}
|
||||||
|
|
||||||
: If unset, Paperless uses **SQLite** by default.
|
: Hostname of the PostgreSQL or MariaDB database server. Required when
|
||||||
|
`PAPERLESS_DBENGINE` is `postgresql` or `mariadb`.
|
||||||
Set `PAPERLESS_DBHOST` to switch to PostgreSQL or MariaDB instead.
|
|
||||||
|
|
||||||
#### [`PAPERLESS_DBENGINE=<engine_name>`](#PAPERLESS_DBENGINE) {#PAPERLESS_DBENGINE}
|
|
||||||
|
|
||||||
: Optional. Specifies the database engine to use when connecting to a remote database.
|
|
||||||
Available options are `postgresql` and `mariadb`.
|
|
||||||
|
|
||||||
Defaults to `postgresql` if `PAPERLESS_DBHOST` is set.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
Using MariaDB comes with some caveats. See [MySQL Caveats](advanced_usage.md#mysql-caveats).
|
|
||||||
|
|
||||||
#### [`PAPERLESS_DBPORT=<port>`](#PAPERLESS_DBPORT) {#PAPERLESS_DBPORT}
|
#### [`PAPERLESS_DBPORT=<port>`](#PAPERLESS_DBPORT) {#PAPERLESS_DBPORT}
|
||||||
|
|
||||||
: Port to use when connecting to PostgreSQL or MariaDB.
|
: Port to use when connecting to PostgreSQL or MariaDB.
|
||||||
|
|
||||||
Default is `5432` for PostgreSQL and `3306` for MariaDB.
|
Defaults to `5432` for PostgreSQL and `3306` for MariaDB.
|
||||||
|
|
||||||
#### [`PAPERLESS_DBNAME=<name>`](#PAPERLESS_DBNAME) {#PAPERLESS_DBNAME}
|
#### [`PAPERLESS_DBNAME=<name>`](#PAPERLESS_DBNAME) {#PAPERLESS_DBNAME}
|
||||||
|
|
||||||
: Name of the database to connect to when using PostgreSQL or MariaDB.
|
: Name of the PostgreSQL or MariaDB database to connect to.
|
||||||
|
|
||||||
Defaults to "paperless".
|
Defaults to `paperless`.
|
||||||
|
|
||||||
#### [`PAPERLESS_DBUSER=<name>`](#PAPERLESS_DBUSER) {#PAPERLESS_DBUSER}
|
#### [`PAPERLESS_DBUSER=<user>`](#PAPERLESS_DBUSER) {#PAPERLESS_DBUSER}
|
||||||
|
|
||||||
: Username for authenticating with the PostgreSQL or MariaDB database.
|
: Username for authenticating with the PostgreSQL or MariaDB database.
|
||||||
|
|
||||||
Defaults to "paperless".
|
Defaults to `paperless`.
|
||||||
|
|
||||||
#### [`PAPERLESS_DBPASS=<password>`](#PAPERLESS_DBPASS) {#PAPERLESS_DBPASS}
|
#### [`PAPERLESS_DBPASS=<password>`](#PAPERLESS_DBPASS) {#PAPERLESS_DBPASS}
|
||||||
|
|
||||||
: Password for the PostgreSQL or MariaDB database user.
|
: Password for the PostgreSQL or MariaDB database user.
|
||||||
|
|
||||||
Defaults to "paperless".
|
Defaults to `paperless`.
|
||||||
|
|
||||||
#### [`PAPERLESS_DBSSLMODE=<mode>`](#PAPERLESS_DBSSLMODE) {#PAPERLESS_DBSSLMODE}
|
#### [`PAPERLESS_DB_OPTIONS=<options>`](#PAPERLESS_DB_OPTIONS) {#PAPERLESS_DB_OPTIONS}
|
||||||
|
|
||||||
: SSL mode to use when connecting to PostgreSQL or MariaDB.
|
: Advanced database connection options as a semicolon-delimited key-value string.
|
||||||
|
Keys and values are separated by `=`. Dot-notation produces nested option
|
||||||
|
dictionaries; for example, `pool.max_size=20` sets
|
||||||
|
`OPTIONS["pool"]["max_size"] = 20`.
|
||||||
|
|
||||||
See [the official documentation about
|
Options specified here are merged over the engine defaults. Unrecognised keys
|
||||||
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
|
are passed through to the underlying database driver without validation, so a
|
||||||
|
typo will be silently ignored rather than producing an error.
|
||||||
|
|
||||||
See [the official documentation about
|
Refer to your database driver's documentation for the full set of accepted keys:
|
||||||
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode).
|
|
||||||
|
|
||||||
*Note*: SSL mode values differ between PostgreSQL and MariaDB.
|
- PostgreSQL: [libpq connection parameters](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS)
|
||||||
|
- MariaDB: [MariaDB Connector/Python](https://mariadb.com/kb/en/mariadb-connector-python/)
|
||||||
|
- SQLite: [SQLite PRAGMA statements](https://www.sqlite.org/pragma.html)
|
||||||
|
|
||||||
Default is `prefer` for PostgreSQL and `PREFERRED` for MariaDB.
|
!!! note "PostgreSQL connection pooling"
|
||||||
|
|
||||||
#### [`PAPERLESS_DBSSLROOTCERT=<ca-path>`](#PAPERLESS_DBSSLROOTCERT) {#PAPERLESS_DBSSLROOTCERT}
|
Pool size is controlled via `pool.min_size` and `pool.max_size`. When
|
||||||
|
configuring pooling, ensure your PostgreSQL `max_connections` is large enough
|
||||||
|
to handle all pool connections across all workers:
|
||||||
|
`(web_workers + celery_workers) * pool.max_size + safety_margin`.
|
||||||
|
|
||||||
: Path to the SSL root certificate used to verify the database server.
|
**Examples:**
|
||||||
|
|
||||||
See [the official documentation about
|
```bash title="PostgreSQL: require SSL, set a custom CA certificate, and limit the pool size"
|
||||||
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
|
PAPERLESS_DB_OPTIONS="sslmode=require;sslrootcert=/certs/ca.pem;pool.max_size=5"
|
||||||
Changes the location of `root.crt`.
|
```
|
||||||
|
|
||||||
See [the official documentation about
|
```bash title="MariaDB: require SSL with a custom CA certificate"
|
||||||
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-ca).
|
PAPERLESS_DB_OPTIONS="ssl_mode=REQUIRED;ssl.ca=/certs/ca.pem"
|
||||||
|
```
|
||||||
|
|
||||||
Defaults to unset, using the standard location in the home directory.
|
```bash title="SQLite: set a busy timeout of 30 seconds"
|
||||||
|
# PostgreSQL: set a connection timeout
|
||||||
|
PAPERLESS_DB_OPTIONS="connect_timeout=10"
|
||||||
|
```
|
||||||
|
|
||||||
#### [`PAPERLESS_DBSSLCERT=<client-cert-path>`](#PAPERLESS_DBSSLCERT) {#PAPERLESS_DBSSLCERT}
|
#### ~~[`PAPERLESS_DBSSLMODE`](#PAPERLESS_DBSSLMODE)~~ {#PAPERLESS_DBSSLMODE}
|
||||||
|
|
||||||
: Path to the client SSL certificate used when connecting securely.
|
!!! failure "Removed in v3"
|
||||||
|
|
||||||
See [the official documentation about
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
|
|
||||||
|
|
||||||
See [the official documentation about
|
```bash title="PostgreSQL"
|
||||||
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-cert).
|
PAPERLESS_DB_OPTIONS="sslmode=require"
|
||||||
|
```
|
||||||
|
|
||||||
Changes the location of `postgresql.crt`.
|
```bash title="MariaDB"
|
||||||
|
PAPERLESS_DB_OPTIONS="ssl_mode=REQUIRED"
|
||||||
|
```
|
||||||
|
|
||||||
Defaults to unset, using the standard location in the home directory.
|
#### ~~[`PAPERLESS_DBSSLROOTCERT`](#PAPERLESS_DBSSLROOTCERT)~~ {#PAPERLESS_DBSSLROOTCERT}
|
||||||
|
|
||||||
#### [`PAPERLESS_DBSSLKEY=<client-cert-key>`](#PAPERLESS_DBSSLKEY) {#PAPERLESS_DBSSLKEY}
|
!!! failure "Removed in v3"
|
||||||
|
|
||||||
: Path to the client SSL private key used when connecting securely.
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
|
|
||||||
See [the official documentation about
|
```bash title="PostgreSQL"
|
||||||
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
|
PAPERLESS_DB_OPTIONS="sslrootcert=/path/to/ca.pem"
|
||||||
|
```
|
||||||
|
|
||||||
See [the official documentation about
|
```bash title="MariaDB"
|
||||||
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-key).
|
PAPERLESS_DB_OPTIONS="ssl.ca=/path/to/ca.pem"
|
||||||
|
```
|
||||||
|
|
||||||
Changes the location of `postgresql.key`.
|
#### ~~[`PAPERLESS_DBSSLCERT`](#PAPERLESS_DBSSLCERT)~~ {#PAPERLESS_DBSSLCERT}
|
||||||
|
|
||||||
Defaults to unset, using the standard location in the home directory.
|
!!! failure "Removed in v3"
|
||||||
|
|
||||||
#### [`PAPERLESS_DB_TIMEOUT=<int>`](#PAPERLESS_DB_TIMEOUT) {#PAPERLESS_DB_TIMEOUT}
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
|
|
||||||
: Sets how long a database connection should wait before timing out.
|
```bash title="PostgreSQL"
|
||||||
|
PAPERLESS_DB_OPTIONS="sslcert=/path/to/client.crt"
|
||||||
|
```
|
||||||
|
|
||||||
For SQLite, this sets how long to wait if the database is locked.
|
```bash title="MariaDB"
|
||||||
For PostgreSQL or MariaDB, this sets the connection timeout.
|
PAPERLESS_DB_OPTIONS="ssl.cert=/path/to/client.crt"
|
||||||
|
```
|
||||||
|
|
||||||
Defaults to unset, which uses Django’s built-in defaults.
|
#### ~~[`PAPERLESS_DBSSLKEY`](#PAPERLESS_DBSSLKEY)~~ {#PAPERLESS_DBSSLKEY}
|
||||||
|
|
||||||
#### [`PAPERLESS_DB_POOLSIZE=<int>`](#PAPERLESS_DB_POOLSIZE) {#PAPERLESS_DB_POOLSIZE}
|
!!! failure "Removed in v3"
|
||||||
|
|
||||||
: Defines the maximum number of database connections to keep in the pool.
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
|
|
||||||
Only applies to PostgreSQL. This setting is ignored for other database engines.
|
```bash title="PostgreSQL"
|
||||||
|
PAPERLESS_DB_OPTIONS="sslkey=/path/to/client.key"
|
||||||
|
```
|
||||||
|
|
||||||
The value must be greater than or equal to 1 to be used.
|
```bash title="MariaDB"
|
||||||
Defaults to unset, which disables connection pooling.
|
PAPERLESS_DB_OPTIONS="ssl.key=/path/to/client.key"
|
||||||
|
```
|
||||||
|
|
||||||
!!! note
|
#### ~~[`PAPERLESS_DB_TIMEOUT`](#PAPERLESS_DB_TIMEOUT)~~ {#PAPERLESS_DB_TIMEOUT}
|
||||||
|
|
||||||
A small pool is typically sufficient — for example, a size of 4.
|
!!! failure "Removed in v3"
|
||||||
Make sure your PostgreSQL server's max_connections setting is large enough to handle:
|
|
||||||
```(Paperless workers + Celery workers) × pool size + safety margin```
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
For example, with 4 Paperless workers and 2 Celery workers, and a pool size of 4:
|
|
||||||
(4 + 2) × 4 + 10 = 34 connections required.
|
```bash title="SQLite"
|
||||||
|
PAPERLESS_DB_OPTIONS="timeout=30"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash title="PostgreSQL or MariaDB"
|
||||||
|
PAPERLESS_DB_OPTIONS="connect_timeout=30"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ~~[`PAPERLESS_DB_POOLSIZE`](#PAPERLESS_DB_POOLSIZE)~~ {#PAPERLESS_DB_POOLSIZE}
|
||||||
|
|
||||||
|
!!! failure "Removed in v3"
|
||||||
|
|
||||||
|
Use [`PAPERLESS_DB_OPTIONS`](#PAPERLESS_DB_OPTIONS) instead.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PAPERLESS_DB_OPTIONS="pool.max_size=10"
|
||||||
|
```
|
||||||
|
|
||||||
#### [`PAPERLESS_DB_READ_CACHE_ENABLED=<bool>`](#PAPERLESS_DB_READ_CACHE_ENABLED) {#PAPERLESS_DB_READ_CACHE_ENABLED}
|
#### [`PAPERLESS_DB_READ_CACHE_ENABLED=<bool>`](#PAPERLESS_DB_READ_CACHE_ENABLED) {#PAPERLESS_DB_READ_CACHE_ENABLED}
|
||||||
|
|
||||||
@@ -652,7 +694,7 @@ system. See the corresponding
|
|||||||
|
|
||||||
: Sync groups from the third party authentication system (e.g. OIDC) to Paperless-ngx. When enabled, users will be added or removed from groups based on their group membership in the third party authentication system. Groups must already exist in Paperless-ngx and have the same name as in the third party authentication system. Groups are updated upon logging in via the third party authentication system, see the corresponding [django-allauth documentation](https://docs.allauth.org/en/dev/socialaccount/signals.html).
|
: Sync groups from the third party authentication system (e.g. OIDC) to Paperless-ngx. When enabled, users will be added or removed from groups based on their group membership in the third party authentication system. Groups must already exist in Paperless-ngx and have the same name as in the third party authentication system. Groups are updated upon logging in via the third party authentication system, see the corresponding [django-allauth documentation](https://docs.allauth.org/en/dev/socialaccount/signals.html).
|
||||||
|
|
||||||
: In order to pass groups from the authentication system you will need to update your [PAPERLESS_SOCIALACCOUNT_PROVIDERS](#PAPERLESS_SOCIALACCOUNT_PROVIDERS) setting by adding a top-level "SCOPES" setting which includes "groups", e.g.:
|
: In order to pass groups from the authentication system you will need to update your [PAPERLESS_SOCIALACCOUNT_PROVIDERS](#PAPERLESS_SOCIALACCOUNT_PROVIDERS) setting by adding a top-level "SCOPES" setting which includes "groups", or the custom groups claim configured in [`PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS_CLAIM`](#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS_CLAIM) e.g.:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"openid_connect":{"SCOPE": ["openid","profile","email","groups"]...
|
{"openid_connect":{"SCOPE": ["openid","profile","email","groups"]...
|
||||||
@@ -660,6 +702,12 @@ system. See the corresponding
|
|||||||
|
|
||||||
Defaults to False
|
Defaults to False
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS_CLAIM=<str>`](#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS_CLAIM) {#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS_CLAIM}
|
||||||
|
|
||||||
|
: Allows you to define a custom groups claim. See [PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS](#PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS) which is required for this setting to take effect.
|
||||||
|
|
||||||
|
Defaults to "groups"
|
||||||
|
|
||||||
#### [`PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS=<comma-separated-list>`](#PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS) {#PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS}
|
#### [`PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS=<comma-separated-list>`](#PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS) {#PAPERLESS_SOCIAL_ACCOUNT_DEFAULT_GROUPS}
|
||||||
|
|
||||||
: A list of group names that users who signup via social accounts will be added to upon signup. Groups listed here must already exist.
|
: A list of group names that users who signup via social accounts will be added to upon signup. Groups listed here must already exist.
|
||||||
@@ -1139,8 +1187,9 @@ via the consumption directory, you can disable the consumer to save resources.
|
|||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
|
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
|
||||||
|
|
||||||
: When the consumer detects a duplicate document, it will not touch
|
: As of version 3.0 Paperless-ngx allows duplicate documents to be consumed by default, _except_ when
|
||||||
the original document. This default behavior can be changed here.
|
this setting is enabled. When enabled, Paperless will check if a document with the same hash already
|
||||||
|
exists in the system and delete the duplicate file from the consumption directory without consuming it.
|
||||||
|
|
||||||
Defaults to false.
|
Defaults to false.
|
||||||
|
|
||||||
@@ -1168,29 +1217,45 @@ don't exist yet.
|
|||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_IGNORE_PATTERNS=<json>`](#PAPERLESS_CONSUMER_IGNORE_PATTERNS) {#PAPERLESS_CONSUMER_IGNORE_PATTERNS}
|
#### [`PAPERLESS_CONSUMER_IGNORE_PATTERNS=<json>`](#PAPERLESS_CONSUMER_IGNORE_PATTERNS) {#PAPERLESS_CONSUMER_IGNORE_PATTERNS}
|
||||||
|
|
||||||
: By default, paperless ignores certain files and folders in the
|
: Additional regex patterns for files to ignore in the consumption directory. Patterns are matched against filenames only (not full paths)
|
||||||
consumption directory, such as system files created by the Mac OS
|
using Python's `re.match()`, which anchors at the start of the filename.
|
||||||
or hidden folders some tools use to store data.
|
|
||||||
|
|
||||||
This can be adjusted by configuring a custom json array with
|
See the [watchfiles documentation](https://watchfiles.helpmanual.io/api/filters/#watchfiles.BaseFilter.ignore_entity_patterns)
|
||||||
patterns to exclude.
|
|
||||||
|
|
||||||
For example, `.DS_STORE/*` will ignore any files found in a folder
|
This setting is for additional patterns beyond the built-in defaults. Common system files and directories are already ignored automatically.
|
||||||
named `.DS_STORE`, including `.DS_STORE/bar.pdf` and `foo/.DS_STORE/bar.pdf`
|
The patterns will be compiled via Python's standard `re` module.
|
||||||
|
|
||||||
A pattern like `._*` will ignore anything starting with `._`, including:
|
Example custom patterns:
|
||||||
`._foo.pdf` and `._bar/foo.pdf`
|
|
||||||
|
|
||||||
Defaults to
|
```json
|
||||||
`[".DS_Store", ".DS_STORE", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*", "Thumbs.db"]`.
|
["^temp_", "\\.bak$", "^~"]
|
||||||
|
```
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_BARCODE_SCANNER=<string>`](#PAPERLESS_CONSUMER_BARCODE_SCANNER) {#PAPERLESS_CONSUMER_BARCODE_SCANNER}
|
This would ignore:
|
||||||
|
|
||||||
: Sets the barcode scanner used for barcode functionality.
|
- Files starting with `temp_` (e.g., `temp_scan.pdf`)
|
||||||
|
- Files ending with `.bak` (e.g., `document.pdf.bak`)
|
||||||
|
- Files starting with `~` (e.g., `~$document.docx`)
|
||||||
|
|
||||||
Currently, "PYZBAR" (the default) or "ZXING" might be selected.
|
Defaults to `[]` (empty list, uses only built-in defaults).
|
||||||
If you have problems that your Barcodes/QR-Codes are not detected
|
|
||||||
(especially with bad scan quality and/or small codes), try the other one.
|
The default ignores are `[.DS_Store, .DS_STORE, ._*, desktop.ini, Thumbs.db]` and cannot be overridden.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_CONSUMER_IGNORE_DIRS=<json>`](#PAPERLESS_CONSUMER_IGNORE_DIRS) {#PAPERLESS_CONSUMER_IGNORE_DIRS}
|
||||||
|
|
||||||
|
: Additional directory names to ignore in the consumption directory. Directories matching these names (and all their contents) will be skipped.
|
||||||
|
|
||||||
|
This setting is for additional directories beyond the built-in defaults. Matching is done by directory name only, not full path.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
["temp", "incoming", ".hidden"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults to `[]` (empty list, uses only built-in defaults).
|
||||||
|
|
||||||
|
The default ignores are `[.stfolder, .stversions, .localized, @eaDir, .Spotlight-V100, .Trashes, __MACOSX]` and cannot be overridden.
|
||||||
|
|
||||||
#### [`PAPERLESS_PRE_CONSUME_SCRIPT=<filename>`](#PAPERLESS_PRE_CONSUME_SCRIPT) {#PAPERLESS_PRE_CONSUME_SCRIPT}
|
#### [`PAPERLESS_PRE_CONSUME_SCRIPT=<filename>`](#PAPERLESS_PRE_CONSUME_SCRIPT) {#PAPERLESS_PRE_CONSUME_SCRIPT}
|
||||||
|
|
||||||
@@ -1281,48 +1346,24 @@ within your documents.
|
|||||||
|
|
||||||
Defaults to false.
|
Defaults to false.
|
||||||
|
|
||||||
### Polling {#polling}
|
#### [`PAPERLESS_CONSUMER_POLLING_INTERVAL=<num>`](#PAPERLESS_CONSUMER_POLLING_INTERVAL) {#PAPERLESS_CONSUMER_POLLING_INTERVAL}
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_POLLING=<num>`](#PAPERLESS_CONSUMER_POLLING) {#PAPERLESS_CONSUMER_POLLING}
|
: Configures how the consumer detects new files in the consumption directory.
|
||||||
|
|
||||||
: If paperless won't find documents added to your consume folder, it
|
When set to `0` (default), paperless uses native filesystem notifications for efficient, immediate detection of new files.
|
||||||
might not be able to automatically detect filesystem changes. In
|
|
||||||
that case, specify a polling interval in seconds here, which will
|
|
||||||
then cause paperless to periodically check your consumption
|
|
||||||
directory for changes. This will also disable listening for file
|
|
||||||
system changes with `inotify`.
|
|
||||||
|
|
||||||
Defaults to 0, which disables polling and uses filesystem
|
When set to a positive number, paperless polls the consumption directory at that interval in seconds. Use polling for network filesystems (NFS, SMB/CIFS) where native notifications may not work reliably.
|
||||||
notifications.
|
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_POLLING_RETRY_COUNT=<num>`](#PAPERLESS_CONSUMER_POLLING_RETRY_COUNT) {#PAPERLESS_CONSUMER_POLLING_RETRY_COUNT}
|
Defaults to 0.
|
||||||
|
|
||||||
: If consumer polling is enabled, sets the maximum number of times
|
#### [`PAPERLESS_CONSUMER_STABILITY_DELAY=<num>`](#PAPERLESS_CONSUMER_STABILITY_DELAY) {#PAPERLESS_CONSUMER_STABILITY_DELAY}
|
||||||
paperless will check for a file to remain unmodified. If a file's
|
|
||||||
modification time and size are identical for two consecutive checks, it
|
|
||||||
will be consumed.
|
|
||||||
|
|
||||||
Defaults to 5.
|
: Sets the time in seconds that a file must remain unchanged (same size and modification time) before paperless will begin consuming it.
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_POLLING_DELAY=<num>`](#PAPERLESS_CONSUMER_POLLING_DELAY) {#PAPERLESS_CONSUMER_POLLING_DELAY}
|
Increase this value if you experience issues with files being consumed before they are fully written, particularly on slower network storage or
|
||||||
|
with certain scanner quirks
|
||||||
|
|
||||||
: If consumer polling is enabled, sets the delay in seconds between
|
Defaults to 5.0 seconds.
|
||||||
each check (above) paperless will do while waiting for a file to
|
|
||||||
remain unmodified.
|
|
||||||
|
|
||||||
Defaults to 5.
|
|
||||||
|
|
||||||
### iNotify {#inotify}
|
|
||||||
|
|
||||||
#### [`PAPERLESS_CONSUMER_INOTIFY_DELAY=<num>`](#PAPERLESS_CONSUMER_INOTIFY_DELAY) {#PAPERLESS_CONSUMER_INOTIFY_DELAY}
|
|
||||||
|
|
||||||
: Sets the time in seconds the consumer will wait for additional
|
|
||||||
events from inotify before the consumer will consider a file ready
|
|
||||||
and begin consumption. Certain scanners or network setups may
|
|
||||||
generate multiple events for a single file, leading to multiple
|
|
||||||
consumers working on the same file. Configure this to prevent that.
|
|
||||||
|
|
||||||
Defaults to 0.5 seconds.
|
|
||||||
|
|
||||||
## Workflow webhooks
|
## Workflow webhooks
|
||||||
|
|
||||||
@@ -1543,6 +1584,20 @@ assigns or creates tags if a properly formatted barcode is detected.
|
|||||||
|
|
||||||
Please refer to the Python regex documentation for more information.
|
Please refer to the Python regex documentation for more information.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT=<bool>`](#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT) {#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT}
|
||||||
|
|
||||||
|
: Enables splitting of documents on tag barcodes, similar to how ASN barcodes work.
|
||||||
|
|
||||||
|
When enabled, documents will be split into separate PDFs at pages containing
|
||||||
|
tag barcodes that match the configured `PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`
|
||||||
|
patterns. The page with the tag barcode will be retained in the new document.
|
||||||
|
|
||||||
|
Each split document will have the detected tags assigned to it.
|
||||||
|
|
||||||
|
This only has an effect if `PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE` is also enabled.
|
||||||
|
|
||||||
|
Defaults to false.
|
||||||
|
|
||||||
## Audit Trail
|
## Audit Trail
|
||||||
|
|
||||||
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
|
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
|
||||||
@@ -1603,6 +1658,16 @@ processing. This only has an effect if
|
|||||||
|
|
||||||
Defaults to `0 1 * * *`, once per day.
|
Defaults to `0 1 * * *`, once per day.
|
||||||
|
|
||||||
|
## Share links
|
||||||
|
|
||||||
|
#### [`PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON=<cron expression>`](#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON) {#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON}
|
||||||
|
|
||||||
|
: Controls how often Paperless-ngx removes expired share link bundles (and their generated ZIP archives).
|
||||||
|
|
||||||
|
: If set to the string "disable", expired bundles are not cleaned up automatically.
|
||||||
|
|
||||||
|
Defaults to `0 2 * * *`, once per day at 02:00.
|
||||||
|
|
||||||
## Binaries
|
## Binaries
|
||||||
|
|
||||||
There are a few external software packages that Paperless expects to
|
There are a few external software packages that Paperless expects to
|
||||||
@@ -1804,3 +1869,87 @@ password. All of these options come from their similarly-named [Django settings]
|
|||||||
#### [`PAPERLESS_EMAIL_USE_SSL=<bool>`](#PAPERLESS_EMAIL_USE_SSL) {#PAPERLESS_EMAIL_USE_SSL}
|
#### [`PAPERLESS_EMAIL_USE_SSL=<bool>`](#PAPERLESS_EMAIL_USE_SSL) {#PAPERLESS_EMAIL_USE_SSL}
|
||||||
|
|
||||||
: Defaults to false.
|
: Defaults to false.
|
||||||
|
|
||||||
|
## Remote OCR
|
||||||
|
|
||||||
|
#### [`PAPERLESS_REMOTE_OCR_ENGINE=<str>`](#PAPERLESS_REMOTE_OCR_ENGINE) {#PAPERLESS_REMOTE_OCR_ENGINE}
|
||||||
|
|
||||||
|
: The remote OCR engine to use. Currently only Azure AI is supported as "azureai".
|
||||||
|
|
||||||
|
Defaults to None, which disables remote OCR.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_REMOTE_OCR_API_KEY=<str>`](#PAPERLESS_REMOTE_OCR_API_KEY) {#PAPERLESS_REMOTE_OCR_API_KEY}
|
||||||
|
|
||||||
|
: The API key to use for the remote OCR engine.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_REMOTE_OCR_ENDPOINT=<str>`](#PAPERLESS_REMOTE_OCR_ENDPOINT) {#PAPERLESS_REMOTE_OCR_ENDPOINT}
|
||||||
|
|
||||||
|
: The endpoint to use for the remote OCR engine. This is required for Azure AI.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
## AI {#ai}
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_ENABLED=<bool>`](#PAPERLESS_AI_ENABLED) {#PAPERLESS_AI_ENABLED}
|
||||||
|
|
||||||
|
: Enables the AI features in Paperless. This includes the AI-based
|
||||||
|
suggestions. This setting is required to be set to true in order to use the AI features.
|
||||||
|
|
||||||
|
Defaults to false.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_EMBEDDING_BACKEND=<str>`](#PAPERLESS_AI_LLM_EMBEDDING_BACKEND) {#PAPERLESS_AI_LLM_EMBEDDING_BACKEND}
|
||||||
|
|
||||||
|
: The embedding backend to use for RAG. This can be either "openai" or "huggingface".
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_EMBEDDING_MODEL=<str>`](#PAPERLESS_AI_LLM_EMBEDDING_MODEL) {#PAPERLESS_AI_LLM_EMBEDDING_MODEL}
|
||||||
|
|
||||||
|
: The model to use for the embedding backend for RAG. This can be set to any of the embedding models supported by the current embedding backend. If not supplied, defaults to "text-embedding-3-small" for OpenAI and "sentence-transformers/all-MiniLM-L6-v2" for Huggingface.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_BACKEND=<str>`](#PAPERLESS_AI_LLM_BACKEND) {#PAPERLESS_AI_LLM_BACKEND}
|
||||||
|
|
||||||
|
: The AI backend to use. This can be either "openai" or "ollama". If set to "ollama", the AI
|
||||||
|
features will be run locally on your machine. If set to "openai", the AI features will be run
|
||||||
|
using the OpenAI API. This setting is required to be set to use the AI features.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
The OpenAI API is a paid service. You will need to set up an OpenAI account and
|
||||||
|
will be charged for usage incurred by Paperless-ngx features and your document data
|
||||||
|
will (of course) be sent to the OpenAI API. Paperless-ngx does not endorse the use of the
|
||||||
|
OpenAI API in any way.
|
||||||
|
|
||||||
|
Refer to the OpenAI terms of service, and use at your own risk.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_MODEL=<str>`](#PAPERLESS_AI_LLM_MODEL) {#PAPERLESS_AI_LLM_MODEL}
|
||||||
|
|
||||||
|
: The model to use for the AI backend, i.e. "gpt-3.5-turbo", "gpt-4" or any of the models supported by the
|
||||||
|
current backend. If not supplied, defaults to "gpt-3.5-turbo" for OpenAI and "llama3.1" for Ollama.
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_API_KEY=<str>`](#PAPERLESS_AI_LLM_API_KEY) {#PAPERLESS_AI_LLM_API_KEY}
|
||||||
|
|
||||||
|
: The API key to use for the AI backend. This is required for the OpenAI backend (optional for others).
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_ENDPOINT=<str>`](#PAPERLESS_AI_LLM_ENDPOINT) {#PAPERLESS_AI_LLM_ENDPOINT}
|
||||||
|
|
||||||
|
: The endpoint / url to use for the AI backend. This is required for the Ollama backend (optional for others).
|
||||||
|
|
||||||
|
Defaults to None.
|
||||||
|
|
||||||
|
#### [`PAPERLESS_AI_LLM_INDEX_TASK_CRON=<cron expression>`](#PAPERLESS_AI_LLM_INDEX_TASK_CRON) {#PAPERLESS_AI_LLM_INDEX_TASK_CRON}
|
||||||
|
|
||||||
|
: Configures the schedule to update the AI embeddings of text content and metadata for all documents. Only performed if
|
||||||
|
AI is enabled and the LLM embedding backend is set.
|
||||||
|
|
||||||
|
Defaults to `10 2 * * *`, once per day.
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ on Paperless-ngx.
|
|||||||
Check out the source from GitHub. The repository is organized in the
|
Check out the source from GitHub. The repository is organized in the
|
||||||
following way:
|
following way:
|
||||||
|
|
||||||
- `main` always represents the latest release and will only see
|
- `main` always represents the latest release and will only see
|
||||||
changes when a new release is made.
|
changes when a new release is made.
|
||||||
- `dev` contains the code that will be in the next release.
|
- `dev` contains the code that will be in the next release.
|
||||||
- `feature-X` contains bigger changes that will be in some release, but
|
- `feature-X` contains bigger changes that will be in some release, but
|
||||||
not necessarily the next one.
|
not necessarily the next one.
|
||||||
|
|
||||||
When making functional changes to Paperless-ngx, _always_ make your changes
|
When making functional changes to Paperless-ngx, _always_ make your changes
|
||||||
on the `dev` branch.
|
on the `dev` branch.
|
||||||
|
|
||||||
Apart from that, the folder structure is as follows:
|
Apart from that, the folder structure is as follows:
|
||||||
|
|
||||||
- `docs/` - Documentation.
|
- `docs/` - Documentation.
|
||||||
- `src-ui/` - Code of the front end.
|
- `src-ui/` - Code of the front end.
|
||||||
- `src/` - Code of the back end.
|
- `src/` - Code of the back end.
|
||||||
- `scripts/` - Various scripts that help with different parts of
|
- `scripts/` - Various scripts that help with different parts of
|
||||||
development.
|
development.
|
||||||
- `docker/` - Files required to build the docker image.
|
- `docker/` - Files required to build the docker image.
|
||||||
|
|
||||||
## Contributing to Paperless-ngx
|
## Contributing to Paperless-ngx
|
||||||
|
|
||||||
@@ -75,13 +75,13 @@ first-time setup.
|
|||||||
4. Install the Python dependencies:
|
4. Install the Python dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uv sync --group dev
|
uv sync --group dev
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Install pre-commit hooks:
|
5. Install pre-commit hooks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uv run pre-commit install
|
uv run prek install
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Apply migrations and create a superuser (also can be done via the web UI) for your development instance:
|
6. Apply migrations and create a superuser (also can be done via the web UI) for your development instance:
|
||||||
@@ -89,23 +89,22 @@ first-time setup.
|
|||||||
```bash
|
```bash
|
||||||
# src/
|
# src/
|
||||||
|
|
||||||
$ uv run manage.py migrate
|
uv run manage.py migrate
|
||||||
$ uv run manage.py createsuperuser
|
uv run manage.py createsuperuser
|
||||||
```
|
```
|
||||||
|
|
||||||
7. You can now either ...
|
7. You can now either ...
|
||||||
|
- install Redis or
|
||||||
|
|
||||||
- install Redis or
|
- use the included `scripts/start_services.sh` to use Docker to fire
|
||||||
|
up a Redis instance (and some other services such as Tika,
|
||||||
|
Gotenberg and a database server) or
|
||||||
|
|
||||||
- use the included `scripts/start_services.sh` to use Docker to fire
|
- spin up a bare Redis container
|
||||||
up a Redis instance (and some other services such as Tika,
|
|
||||||
Gotenberg and a database server) or
|
|
||||||
|
|
||||||
- spin up a bare Redis container
|
```bash
|
||||||
|
docker run -d -p 6379:6379 --restart unless-stopped redis:latest
|
||||||
```
|
```
|
||||||
docker run -d -p 6379:6379 --restart unless-stopped redis:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
8. Continue with either back-end or front-end development – or both :-).
|
8. Continue with either back-end or front-end development – or both :-).
|
||||||
|
|
||||||
@@ -118,18 +117,18 @@ work well for development, but you can use whatever you want.
|
|||||||
Configure the IDE to use the `src/`-folder as the base source folder.
|
Configure the IDE to use the `src/`-folder as the base source folder.
|
||||||
Configure the following launch configurations in your IDE:
|
Configure the following launch configurations in your IDE:
|
||||||
|
|
||||||
- `python3 manage.py runserver`
|
- `uv run manage.py runserver`
|
||||||
- `python3 manage.py document_consumer`
|
- `uv run manage.py document_consumer`
|
||||||
- `celery --app paperless worker -l DEBUG` (or any other log level)
|
- `uv run celery --app paperless worker -l DEBUG` (or any other log level)
|
||||||
|
|
||||||
To start them all:
|
To start them all:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# src/
|
# src/
|
||||||
|
|
||||||
$ python3 manage.py runserver & \
|
uv run manage.py runserver & \
|
||||||
python3 manage.py document_consumer & \
|
uv run manage.py document_consumer & \
|
||||||
celery --app paperless worker -l DEBUG
|
uv run celery --app paperless worker -l DEBUG
|
||||||
```
|
```
|
||||||
|
|
||||||
You might need the front end to test your back end code.
|
You might need the front end to test your back end code.
|
||||||
@@ -140,17 +139,17 @@ To build the front end once use this command:
|
|||||||
```bash
|
```bash
|
||||||
# src-ui/
|
# src-ui/
|
||||||
|
|
||||||
$ pnpm install
|
pnpm install
|
||||||
$ ng build --configuration production
|
pnpm ng build --configuration production
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- Run `pytest` in the `src/` directory to execute all tests. This also
|
- Run `pytest` in the `src/` directory to execute all tests. This also
|
||||||
generates a HTML coverage report. When running tests, `paperless.conf`
|
generates a HTML coverage report. When running tests, `paperless.conf`
|
||||||
is loaded as well. However, the tests rely on the default
|
is loaded as well. However, the tests rely on the default
|
||||||
configuration. This is not ideal. But for now, make sure no settings
|
configuration. This is not ideal. But for now, make sure no settings
|
||||||
except for DEBUG are overridden when testing.
|
except for DEBUG are overridden when testing.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
@@ -175,7 +174,7 @@ To add a new development package `uv add --dev <package>`
|
|||||||
|
|
||||||
## Front end development
|
## Front end development
|
||||||
|
|
||||||
The front end is built using AngularJS. In order to get started, you need Node.js (version 14.15+) and
|
The front end is built using AngularJS. In order to get started, you need Node.js (version 24+) and
|
||||||
`pnpm`.
|
`pnpm`.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
@@ -199,7 +198,7 @@ The front end is built using AngularJS. In order to get started, you need Node.j
|
|||||||
4. You can launch a development server by running:
|
4. You can launch a development server by running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ng serve
|
pnpm ng serve
|
||||||
```
|
```
|
||||||
|
|
||||||
This will automatically update whenever you save. However, in-place
|
This will automatically update whenever you save. However, in-place
|
||||||
@@ -217,21 +216,21 @@ commit. See [above](#code-formatting-with-pre-commit-hooks) for installation ins
|
|||||||
command such as
|
command such as
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git ls-files -- '*.ts' | xargs pre-commit run prettier --files
|
git ls-files -- '*.ts' | xargs uv run prek run prettier --files
|
||||||
```
|
```
|
||||||
|
|
||||||
Front end testing uses Jest and Playwright. Unit tests and e2e tests,
|
Front end testing uses Jest and Playwright. Unit tests and e2e tests,
|
||||||
respectively, can be run non-interactively with:
|
respectively, can be run non-interactively with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ng test
|
pnpm ng test
|
||||||
$ npx playwright test
|
pnpm playwright test
|
||||||
```
|
```
|
||||||
|
|
||||||
Playwright also includes a UI which can be run with:
|
Playwright also includes a UI which can be run with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npx playwright test --ui
|
pnpm playwright test --ui
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building the frontend
|
### Building the frontend
|
||||||
@@ -239,7 +238,7 @@ $ npx playwright test --ui
|
|||||||
In order to build the front end and serve it as part of Django, execute:
|
In order to build the front end and serve it as part of Django, execute:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ng build --configuration production
|
pnpm ng build --configuration production
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build the front end and put it in a location from which the
|
This will build the front end and put it in a location from which the
|
||||||
@@ -254,14 +253,14 @@ these parts have to be translated separately.
|
|||||||
|
|
||||||
### Front end localization
|
### Front end localization
|
||||||
|
|
||||||
- The AngularJS front end does localization according to the [Angular
|
- The AngularJS front end does localization according to the [Angular
|
||||||
documentation](https://angular.io/guide/i18n).
|
documentation](https://angular.io/guide/i18n).
|
||||||
- The source language of the project is "en_US".
|
- The source language of the project is "en_US".
|
||||||
- The source strings end up in the file `src-ui/messages.xlf`.
|
- The source strings end up in the file `src-ui/messages.xlf`.
|
||||||
- The translated strings need to be placed in the
|
- The translated strings need to be placed in the
|
||||||
`src-ui/src/locale/` folder.
|
`src-ui/src/locale/` folder.
|
||||||
- In order to extract added or changed strings from the source files,
|
- In order to extract added or changed strings from the source files,
|
||||||
call `ng extract-i18n`.
|
call `ng extract-i18n`.
|
||||||
|
|
||||||
Adding new languages requires adding the translated files in the
|
Adding new languages requires adding the translated files in the
|
||||||
`src-ui/src/locale/` folder and adjusting a couple files.
|
`src-ui/src/locale/` folder and adjusting a couple files.
|
||||||
@@ -307,18 +306,18 @@ A majority of the strings that appear in the back end appear only when
|
|||||||
the admin is used. However, some of these are still shown on the front
|
the admin is used. However, some of these are still shown on the front
|
||||||
end (such as error messages).
|
end (such as error messages).
|
||||||
|
|
||||||
- The django application does localization according to the [Django
|
- The django application does localization according to the [Django
|
||||||
documentation](https://docs.djangoproject.com/en/3.1/topics/i18n/translation/).
|
documentation](https://docs.djangoproject.com/en/3.1/topics/i18n/translation/).
|
||||||
- The source language of the project is "en_US".
|
- The source language of the project is "en_US".
|
||||||
- Localization files end up in the folder `src/locale/`.
|
- Localization files end up in the folder `src/locale/`.
|
||||||
- In order to extract strings from the application, call
|
- In order to extract strings from the application, call
|
||||||
`python3 manage.py makemessages -l en_US`. This is important after
|
`uv run manage.py makemessages -l en_US`. This is important after
|
||||||
making changes to translatable strings.
|
making changes to translatable strings.
|
||||||
- The message files need to be compiled for them to show up in the
|
- The message files need to be compiled for them to show up in the
|
||||||
application. Call `python3 manage.py compilemessages` to do this.
|
application. Call `uv run manage.py compilemessages` to do this.
|
||||||
The generated files don't get committed into git, since these are
|
The generated files don't get committed into git, since these are
|
||||||
derived artifacts. The build pipeline takes care of executing this
|
derived artifacts. The build pipeline takes care of executing this
|
||||||
command.
|
command.
|
||||||
|
|
||||||
Adding new languages requires adding the translated files in the
|
Adding new languages requires adding the translated files in the
|
||||||
`src/locale/`-folder and adjusting the file
|
`src/locale/`-folder and adjusting the file
|
||||||
@@ -338,13 +337,13 @@ LANGUAGES = [
|
|||||||
|
|
||||||
## Building the documentation
|
## Building the documentation
|
||||||
|
|
||||||
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
|
The documentation is built using Zensical, see their [documentation](https://zensical.org/docs/).
|
||||||
If you want to build the documentation locally, this is how you do it:
|
If you want to build the documentation locally, this is how you do it:
|
||||||
|
|
||||||
1. Build the documentation
|
1. Build the documentation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uv run mkdocs build --config-file mkdocs.yml
|
$ uv run zensical build
|
||||||
```
|
```
|
||||||
|
|
||||||
_alternatively..._
|
_alternatively..._
|
||||||
@@ -355,10 +354,10 @@ If you want to build the documentation locally, this is how you do it:
|
|||||||
something.
|
something.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ uv run mkdocs serve
|
$ uv run zensical serve
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building the Docker image
|
## Building the Docker image {#docker_build}
|
||||||
|
|
||||||
The docker image is primarily built by the GitHub actions workflow, but
|
The docker image is primarily built by the GitHub actions workflow, but
|
||||||
it can be faster when developing to build and tag an image locally.
|
it can be faster when developing to build and tag an image locally.
|
||||||
@@ -381,10 +380,10 @@ base code.
|
|||||||
Paperless-ngx uses parsers to add documents. A parser is
|
Paperless-ngx uses parsers to add documents. A parser is
|
||||||
responsible for:
|
responsible for:
|
||||||
|
|
||||||
- Retrieving the content from the original
|
- Retrieving the content from the original
|
||||||
- Creating a thumbnail
|
- Creating a thumbnail
|
||||||
- _optional:_ Retrieving a created date from the original
|
- _optional:_ Retrieving a created date from the original
|
||||||
- _optional:_ Creating an archived document from the original
|
- _optional:_ Creating an archived document from the original
|
||||||
|
|
||||||
Custom parsers can be added to Paperless-ngx to support more file types. In
|
Custom parsers can be added to Paperless-ngx to support more file types. In
|
||||||
order to do that, you need to write the parser itself and announce its
|
order to do that, you need to write the parser itself and announce its
|
||||||
@@ -442,17 +441,17 @@ def myparser_consumer_declaration(sender, **kwargs):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `parser` is a reference to a class that extends `DocumentParser`.
|
- `parser` is a reference to a class that extends `DocumentParser`.
|
||||||
- `weight` is used whenever two or more parsers are able to parse a
|
- `weight` is used whenever two or more parsers are able to parse a
|
||||||
file: The parser with the higher weight wins. This can be used to
|
file: The parser with the higher weight wins. This can be used to
|
||||||
override the parsers provided by Paperless-ngx.
|
override the parsers provided by Paperless-ngx.
|
||||||
- `mime_types` is a dictionary. The keys are the mime types your
|
- `mime_types` is a dictionary. The keys are the mime types your
|
||||||
parser supports and the value is the default file extension that
|
parser supports and the value is the default file extension that
|
||||||
Paperless-ngx should use when storing files and serving them for
|
Paperless-ngx should use when storing files and serving them for
|
||||||
download. We could guess that from the file extensions, but some
|
download. We could guess that from the file extensions, but some
|
||||||
mime types have many extensions associated with them and the Python
|
mime types have many extensions associated with them and the Python
|
||||||
methods responsible for guessing the extension do not always return
|
methods responsible for guessing the extension do not always return
|
||||||
the same value.
|
the same value.
|
||||||
|
|
||||||
## Using Visual Studio Code devcontainer
|
## Using Visual Studio Code devcontainer
|
||||||
|
|
||||||
@@ -471,9 +470,8 @@ To get started:
|
|||||||
2. VS Code will prompt you with "Reopen in container". Do so and wait for the environment to start.
|
2. VS Code will prompt you with "Reopen in container". Do so and wait for the environment to start.
|
||||||
|
|
||||||
3. In case your host operating system is Windows:
|
3. In case your host operating system is Windows:
|
||||||
|
- The Source Control view in Visual Studio Code might show: "The detected Git repository is potentially unsafe as the folder is owned by someone other than the current user." Use "Manage Unsafe Repositories" to fix this.
|
||||||
- The Source Control view in Visual Studio Code might show: "The detected Git repository is potentially unsafe as the folder is owned by someone other than the current user." Use "Manage Unsafe Repositories" to fix this.
|
- Git might have detecteded modifications for all files, because Windows is using CRLF line endings. Run `git checkout .` in the containers terminal to fix this issue.
|
||||||
- Git might have detecteded modifications for all files, because Windows is using CRLF line endings. Run `git checkout .` in the containers terminal to fix this issue.
|
|
||||||
|
|
||||||
4. Initialize the project by running the task **Project Setup: Run all Init Tasks**. This
|
4. Initialize the project by running the task **Project Setup: Run all Init Tasks**. This
|
||||||
will initialize the database tables and create a superuser. Then you can compile the front end
|
will initialize the database tables and create a superuser. Then you can compile the front end
|
||||||
@@ -481,3 +479,147 @@ To get started:
|
|||||||
|
|
||||||
5. The project is ready for debugging, start either run the fullstack debug or individual debug
|
5. The project is ready for debugging, start either run the fullstack debug or individual debug
|
||||||
processes. Yo spin up the project without debugging run the task **Project Start: Run all Services**
|
processes. Yo spin up the project without debugging run the task **Project Start: Run all Services**
|
||||||
|
|
||||||
|
## Developing Date Parser Plugins
|
||||||
|
|
||||||
|
Paperless-ngx uses a plugin system for date parsing, allowing you to extend or replace the default date parsing behavior. Plugins are discovered using [Python entry points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html).
|
||||||
|
|
||||||
|
### Creating a Date Parser Plugin
|
||||||
|
|
||||||
|
To create a custom date parser plugin, you need to:
|
||||||
|
|
||||||
|
1. Create a class that inherits from `DateParserPluginBase`
|
||||||
|
2. Implement the required abstract method
|
||||||
|
3. Register your plugin via an entry point
|
||||||
|
|
||||||
|
#### 1. Implementing the Parser Class
|
||||||
|
|
||||||
|
Your parser must extend `documents.plugins.date_parsing.DateParserPluginBase` and implement the `parse` method:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections.abc import Iterator
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from documents.plugins.date_parsing import DateParserPluginBase
|
||||||
|
|
||||||
|
|
||||||
|
class MyDateParserPlugin(DateParserPluginBase):
|
||||||
|
"""
|
||||||
|
Custom date parser implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parse(self, filename: str, content: str) -> Iterator[datetime.datetime]:
|
||||||
|
"""
|
||||||
|
Parse dates from the document's filename and content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: The original filename of the document
|
||||||
|
content: The extracted text content of the document
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
datetime.datetime: Valid datetime objects found in the document
|
||||||
|
"""
|
||||||
|
# Your parsing logic here
|
||||||
|
# Use self.config to access configuration settings
|
||||||
|
|
||||||
|
# Example: parse dates from filename first
|
||||||
|
if self.config.filename_date_order:
|
||||||
|
# Your filename parsing logic
|
||||||
|
yield some_datetime
|
||||||
|
|
||||||
|
# Then parse dates from content
|
||||||
|
# Your content parsing logic
|
||||||
|
yield another_datetime
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Configuration and Helper Methods
|
||||||
|
|
||||||
|
Your parser instance is initialized with a `DateParserConfig` object accessible via `self.config`. This provides:
|
||||||
|
|
||||||
|
- `languages: list[str]` - List of language codes for date parsing
|
||||||
|
- `timezone_str: str` - Timezone string for date localization
|
||||||
|
- `ignore_dates: set[datetime.date]` - Dates that should be filtered out
|
||||||
|
- `reference_time: datetime.datetime` - Current time for filtering future dates
|
||||||
|
- `filename_date_order: str | None` - Date order preference for filenames (e.g., "DMY", "MDY")
|
||||||
|
- `content_date_order: str` - Date order preference for content
|
||||||
|
|
||||||
|
The base class provides two helper methods you can use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _parse_string(
|
||||||
|
self,
|
||||||
|
date_string: str,
|
||||||
|
date_order: str,
|
||||||
|
) -> datetime.datetime | None:
|
||||||
|
"""
|
||||||
|
Parse a single date string using dateparser with configured settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _filter_date(
|
||||||
|
self,
|
||||||
|
date: datetime.datetime | None,
|
||||||
|
) -> datetime.datetime | None:
|
||||||
|
"""
|
||||||
|
Validate a parsed datetime against configured rules.
|
||||||
|
Filters out dates before 1900, future dates, and ignored dates.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Resource Management (Optional)
|
||||||
|
|
||||||
|
If your plugin needs to acquire or release resources (database connections, API clients, etc.), override the context manager methods. Paperless-ngx will always use plugins as context managers, ensuring resources can be released even in the event of errors.
|
||||||
|
|
||||||
|
#### 4. Registering Your Plugin
|
||||||
|
|
||||||
|
Register your plugin using a setuptools entry point in your package's `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project.entry-points."paperless_ngx.date_parsers"]
|
||||||
|
my_parser = "my_package.parsers:MyDateParserPlugin"
|
||||||
|
```
|
||||||
|
|
||||||
|
The entry point name (e.g., `"my_parser"`) is used for sorting when multiple plugins are found. Paperless-ngx will use the first plugin alphabetically by name if multiple plugins are discovered.
|
||||||
|
|
||||||
|
### Plugin Discovery
|
||||||
|
|
||||||
|
Paperless-ngx automatically discovers and loads date parser plugins at runtime. The discovery process:
|
||||||
|
|
||||||
|
1. Queries the `paperless_ngx.date_parsers` entry point group
|
||||||
|
2. Validates that each plugin is a subclass of `DateParserPluginBase`
|
||||||
|
3. Sorts valid plugins alphabetically by entry point name
|
||||||
|
4. Uses the first valid plugin, or falls back to the default `RegexDateParserPlugin` if none are found
|
||||||
|
|
||||||
|
If multiple plugins are installed, a warning is logged indicating which plugin was selected.
|
||||||
|
|
||||||
|
### Example: Simple Date Parser
|
||||||
|
|
||||||
|
Here's a minimal example that only looks for ISO 8601 dates:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
from documents.plugins.date_parsing.base import DateParserPluginBase
|
||||||
|
|
||||||
|
|
||||||
|
class ISODateParserPlugin(DateParserPluginBase):
|
||||||
|
"""
|
||||||
|
Parser that only matches ISO 8601 formatted dates (YYYY-MM-DD).
|
||||||
|
"""
|
||||||
|
|
||||||
|
ISO_REGEX = re.compile(r"\b(\d{4}-\d{2}-\d{2})\b")
|
||||||
|
|
||||||
|
def parse(self, filename: str, content: str) -> Iterator[datetime.datetime]:
|
||||||
|
# Combine filename and content for searching
|
||||||
|
text = f"{filename} {content}"
|
||||||
|
|
||||||
|
for match in self.ISO_REGEX.finditer(text):
|
||||||
|
date_string = match.group(1)
|
||||||
|
# Use helper method to parse with configured timezone
|
||||||
|
date = self._parse_string(date_string, "YMD")
|
||||||
|
# Use helper method to validate the date
|
||||||
|
filtered_date = self._filter_date(date)
|
||||||
|
if filtered_date is not None:
|
||||||
|
yield filtered_date
|
||||||
|
```
|
||||||
|
|||||||
44
docs/faq.md
44
docs/faq.md
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: FAQs
|
||||||
|
---
|
||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
## _What's the general plan for Paperless-ngx?_
|
## _What's the general plan for Paperless-ngx?_
|
||||||
@@ -40,31 +44,33 @@ system. On Linux, chances are high that this location is
|
|||||||
You can always drag those files out of that folder to use them
|
You can always drag those files out of that folder to use them
|
||||||
elsewhere. Here are a couple notes about that.
|
elsewhere. Here are a couple notes about that.
|
||||||
|
|
||||||
- Paperless-ngx never modifies your original documents. It keeps
|
- Paperless-ngx never modifies your original documents. It keeps
|
||||||
checksums of all documents and uses a scheduled sanity checker to
|
checksums of all documents and uses a scheduled sanity checker to
|
||||||
check that they remain the same.
|
check that they remain the same.
|
||||||
- By default, paperless uses the internal ID of each document as its
|
- By default, paperless uses the internal ID of each document as its
|
||||||
filename. This might not be very convenient for export. However, you
|
filename. This might not be very convenient for export. However, you
|
||||||
can adjust the way files are stored in paperless by
|
can adjust the way files are stored in paperless by
|
||||||
[configuring the filename format](advanced_usage.md#file-name-handling).
|
[configuring the filename format](advanced_usage.md#file-name-handling).
|
||||||
- [The exporter](administration.md#exporter) is
|
- [The exporter](administration.md#exporter) is
|
||||||
another easy way to get your files out of paperless with reasonable
|
another easy way to get your files out of paperless with reasonable
|
||||||
file names.
|
file names.
|
||||||
|
|
||||||
## _What file types does paperless-ngx support?_
|
## _What file types does paperless-ngx support?_
|
||||||
|
|
||||||
**A:** Currently, the following files are supported:
|
**A:** Currently, the following files are supported:
|
||||||
|
|
||||||
- PDF documents, PNG images, JPEG images, TIFF images, GIF images and
|
- PDF documents, PNG images, JPEG images, TIFF images, GIF images and
|
||||||
WebP images are processed with OCR and converted into PDF documents.
|
WebP images are processed with OCR and converted into PDF documents.
|
||||||
- Plain text documents are supported as well and are added verbatim to
|
- Plain text documents are supported as well and are added verbatim to
|
||||||
paperless.
|
paperless.
|
||||||
- With the optional Tika integration enabled (see [Tika configuration](https://docs.paperless-ngx.com/configuration#tika)),
|
- With the optional Tika integration enabled (see [Tika configuration](https://docs.paperless-ngx.com/configuration#tika)),
|
||||||
Paperless also supports various Office documents (.docx, .doc, odt,
|
Paperless also supports various Office documents (.docx, .doc, odt,
|
||||||
.ppt, .pptx, .odp, .xls, .xlsx, .ods).
|
.ppt, .pptx, .odp, .xls, .xlsx, .ods).
|
||||||
|
|
||||||
Paperless-ngx determines the type of a file by inspecting its content.
|
Paperless-ngx determines the type of a file by inspecting its content
|
||||||
The file extensions do not matter.
|
rather than its file extensions. However, files processed via the
|
||||||
|
consumption directory will be rejected if they have a file extension that
|
||||||
|
not supported by any of the available parsers.
|
||||||
|
|
||||||
## _Will paperless-ngx run on Raspberry Pi?_
|
## _Will paperless-ngx run on Raspberry Pi?_
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Home
|
||||||
|
---
|
||||||
|
|
||||||
<div class="grid-left" markdown>
|
<div class="grid-left" markdown>
|
||||||
{.index-logo}
|
{.index-logo}
|
||||||
{.index-logo}
|
{.index-logo}
|
||||||
@@ -24,34 +28,36 @@ physical documents into a searchable online archive so you can keep, well, _less
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Organize and index** your scanned documents with tags, correspondents, types, and more.
|
- **Organize and index** your scanned documents with tags, correspondents, types, and more.
|
||||||
- _Your_ data is stored locally on _your_ server and is never transmitted or shared in any way.
|
- _Your_ data is stored locally on _your_ server and is never transmitted or shared in any way, unless you explicitly choose to do so.
|
||||||
- Performs **OCR** on your documents, adding searchable and selectable text, even to documents scanned with only images.
|
- Performs **OCR** on your documents, adding searchable and selectable text, even to documents scanned with only images.
|
||||||
- Utilizes the open-source Tesseract engine to recognize more than 100 languages.
|
- Utilizes the open-source Tesseract engine to recognize more than 100 languages.
|
||||||
- Documents are saved as PDF/A format which is designed for long term storage, alongside the unaltered originals.
|
- _New!_ Supports remote OCR with Azure AI (opt-in).
|
||||||
- Uses machine-learning to automatically add tags, correspondents and document types to your documents.
|
- Documents are saved as PDF/A format which is designed for long term storage, alongside the unaltered originals.
|
||||||
- Supports PDF documents, images, plain text files, Office documents (Word, Excel, PowerPoint, and LibreOffice equivalents)[^1] and more.
|
- Uses machine-learning to automatically add tags, correspondents and document types to your documents.
|
||||||
- Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely with different configurations assigned to different documents.
|
- **New**: Paperless-ngx can now leverage AI (Large Language Models or LLMs) for document suggestions. This is an optional feature that can be enabled (and is disabled by default).
|
||||||
- **Beautiful, modern web application** that features:
|
- Supports PDF documents, images, plain text files, Office documents (Word, Excel, PowerPoint, and LibreOffice equivalents)[^1] and more.
|
||||||
- Customizable dashboard with statistics.
|
- Paperless stores your documents plain on disk. Filenames and folders are managed by paperless and their format can be configured freely with different configurations assigned to different documents.
|
||||||
- Filtering by tags, correspondents, types, and more.
|
- **Beautiful, modern web application** that features:
|
||||||
- Bulk editing of tags, correspondents, types and more.
|
- Customizable dashboard with statistics.
|
||||||
- Drag-and-drop uploading of documents throughout the app.
|
- Filtering by tags, correspondents, types, and more.
|
||||||
- Customizable views can be saved and displayed on the dashboard and / or sidebar.
|
- Bulk editing of tags, correspondents, types and more.
|
||||||
- Support for custom fields of various data types.
|
- Drag-and-drop uploading of documents throughout the app.
|
||||||
- Shareable public links with optional expiration.
|
- Customizable views can be saved and displayed on the dashboard and / or sidebar.
|
||||||
- **Full text search** helps you find what you need:
|
- Support for custom fields of various data types.
|
||||||
- Auto completion suggests relevant words from your documents.
|
- Shareable public links with optional expiration.
|
||||||
- Results are sorted by relevance to your search query.
|
- **Full text search** helps you find what you need:
|
||||||
- Highlighting shows you which parts of the document matched the query.
|
- Auto completion suggests relevant words from your documents.
|
||||||
- Searching for similar documents ("More like this")
|
- Results are sorted by relevance to your search query.
|
||||||
- **Email processing**[^1]: import documents from your email accounts:
|
- Highlighting shows you which parts of the document matched the query.
|
||||||
- Configure multiple accounts and rules for each account.
|
- Searching for similar documents ("More like this")
|
||||||
- After processing, paperless can perform actions on the messages such as marking as read, deleting and more.
|
- **Email processing**[^1]: import documents from your email accounts:
|
||||||
- A built-in robust **multi-user permissions** system that supports 'global' permissions as well as per document or object.
|
- Configure multiple accounts and rules for each account.
|
||||||
- A powerful workflow system that gives you even more control.
|
- After processing, paperless can perform actions on the messages such as marking as read, deleting and more.
|
||||||
- **Optimized** for multi core systems: Paperless-ngx consumes multiple documents in parallel.
|
- A built-in robust **multi-user permissions** system that supports 'global' permissions as well as per document or object.
|
||||||
- The integrated sanity checker makes sure that your document archive is in good health.
|
- A powerful workflow system that gives you even more control.
|
||||||
|
- **Optimized** for multi core systems: Paperless-ngx consumes multiple documents in parallel.
|
||||||
|
- The integrated sanity checker makes sure that your document archive is in good health.
|
||||||
|
|
||||||
[^1]: Office document and email consumption support is optional and provided by Apache Tika (see [configuration](https://docs.paperless-ngx.com/configuration/#tika))
|
[^1]: Office document and email consumption support is optional and provided by Apache Tika (see [configuration](https://docs.paperless-ngx.com/configuration/#tika))
|
||||||
|
|
||||||
|
|||||||
105
docs/migration-v3.md
Normal file
105
docs/migration-v3.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# v3 Migration Guide
|
||||||
|
|
||||||
|
## Consumer Settings Changes
|
||||||
|
|
||||||
|
The v3 consumer command uses a [different library](https://watchfiles.helpmanual.io/) to unify
|
||||||
|
the watching for new files in the consume directory. For the user, this removes several configuration options related to delays and retries
|
||||||
|
and replaces with a single unified setting. It also adjusts how the consumer ignore filtering happens, replaced `fnmatch` with `regex` and
|
||||||
|
separating the directory ignore from the file ignore.
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Old Setting | New Setting | Notes |
|
||||||
|
| ------------------------------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||||
|
| `CONSUMER_POLLING` | [`CONSUMER_POLLING_INTERVAL`](configuration.md#PAPERLESS_CONSUMER_POLLING_INTERVAL) | Renamed for clarity |
|
||||||
|
| `CONSUMER_INOTIFY_DELAY` | [`CONSUMER_STABILITY_DELAY`](configuration.md#PAPERLESS_CONSUMER_STABILITY_DELAY) | Unified for all modes |
|
||||||
|
| `CONSUMER_POLLING_DELAY` | _Removed_ | Use `CONSUMER_STABILITY_DELAY` |
|
||||||
|
| `CONSUMER_POLLING_RETRY_COUNT` | _Removed_ | Automatic with stability tracking |
|
||||||
|
| `CONSUMER_IGNORE_PATTERNS` | [`CONSUMER_IGNORE_PATTERNS`](configuration.md#PAPERLESS_CONSUMER_IGNORE_PATTERNS) | **Now regex, not fnmatch**; user patterns are added to (not replacing) default ones |
|
||||||
|
| _New_ | [`CONSUMER_IGNORE_DIRS`](configuration.md#PAPERLESS_CONSUMER_IGNORE_DIRS) | Additional directories to ignore; user entries are added to (not replacing) defaults |
|
||||||
|
|
||||||
|
## Encryption Support
|
||||||
|
|
||||||
|
Document and thumbnail encryption is no longer supported. This was previously deprecated in [paperless-ng 0.9.3](https://github.com/paperless-ngx/paperless-ngx/blob/dev/docs/changelog.md#paperless-ng-093)
|
||||||
|
|
||||||
|
Users must decrypt their document using the `decrypt_documents` command before upgrading.
|
||||||
|
|
||||||
|
## Barcode Scanner Changes
|
||||||
|
|
||||||
|
Support for [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) has been removed. The underlying libzbar library has
|
||||||
|
seen no updates in 16 years and is largely unmaintained, and the pyzbar Python wrapper last saw a release in March 2022. In
|
||||||
|
practice, pyzbar struggled with barcode detection reliability, particularly on skewed, low-contrast, or partially
|
||||||
|
obscured barcodes. [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) is actively maintained, significantly more
|
||||||
|
reliable at finding barcodes, and now ships pre-built wheels for both x86_64 and arm64, removing the need to build the library.
|
||||||
|
|
||||||
|
The `CONSUMER_BARCODE_SCANNER` setting has been removed. zxing-cpp is now the only backend.
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Old Setting | New Setting | Notes |
|
||||||
|
| -------------------------- | ----------- | --------------------------------- |
|
||||||
|
| `CONSUMER_BARCODE_SCANNER` | _Removed_ | zxing-cpp is now the only backend |
|
||||||
|
|
||||||
|
### Action Required
|
||||||
|
|
||||||
|
- If you were already using `CONSUMER_BARCODE_SCANNER=ZXING`, simply remove the setting.
|
||||||
|
- If you had `CONSUMER_BARCODE_SCANNER=PYZBAR` or were using the default, no functional changes are needed beyond
|
||||||
|
removing the setting. zxing-cpp supports all the same barcode formats and you should see improved detection
|
||||||
|
reliability.
|
||||||
|
- The `libzbar0` / `libzbar-dev` system packages are no longer required and can be removed from any custom Docker
|
||||||
|
images or host installations.
|
||||||
|
|
||||||
|
## Database Engine
|
||||||
|
|
||||||
|
`PAPERLESS_DBENGINE` is now required to use PostgreSQL or MariaDB. Previously, the
|
||||||
|
engine was inferred from the presence of `PAPERLESS_DBHOST`, with `PAPERLESS_DBENGINE`
|
||||||
|
only needed to select MariaDB over PostgreSQL.
|
||||||
|
|
||||||
|
SQLite users require no changes, though they may explicitly set their engine if desired.
|
||||||
|
|
||||||
|
#### Action Required
|
||||||
|
|
||||||
|
PostgreSQL and MariaDB users must add `PAPERLESS_DBENGINE` to their environment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# v2 (PostgreSQL inferred from PAPERLESS_DBHOST)
|
||||||
|
PAPERLESS_DBHOST: postgres
|
||||||
|
|
||||||
|
# v3 (engine must be explicit)
|
||||||
|
PAPERLESS_DBENGINE: postgresql
|
||||||
|
PAPERLESS_DBHOST: postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
See [`PAPERLESS_DBENGINE`](configuration.md#PAPERLESS_DBENGINE) for accepted values.
|
||||||
|
|
||||||
|
## Database Advanced Options
|
||||||
|
|
||||||
|
The individual SSL, timeout, and pooling variables have been removed in favor of a
|
||||||
|
single [`PAPERLESS_DB_OPTIONS`](configuration.md#PAPERLESS_DB_OPTIONS) string. This
|
||||||
|
consolidates a growing set of engine-specific variables into one place, and allows
|
||||||
|
any option supported by the underlying database driver to be set without requiring a
|
||||||
|
dedicated environment variable for each.
|
||||||
|
|
||||||
|
The removed variables and their replacements are:
|
||||||
|
|
||||||
|
| Removed Variable | Replacement in `PAPERLESS_DB_OPTIONS` |
|
||||||
|
| ------------------------- | ---------------------------------------------------------------------------- |
|
||||||
|
| `PAPERLESS_DBSSLMODE` | `sslmode=<value>` (PostgreSQL) or `ssl_mode=<value>` (MariaDB) |
|
||||||
|
| `PAPERLESS_DBSSLROOTCERT` | `sslrootcert=<path>` (PostgreSQL) or `ssl.ca=<path>` (MariaDB) |
|
||||||
|
| `PAPERLESS_DBSSLCERT` | `sslcert=<path>` (PostgreSQL) or `ssl.cert=<path>` (MariaDB) |
|
||||||
|
| `PAPERLESS_DBSSLKEY` | `sslkey=<path>` (PostgreSQL) or `ssl.key=<path>` (MariaDB) |
|
||||||
|
| `PAPERLESS_DB_POOLSIZE` | `pool.max_size=<value>` (PostgreSQL only) |
|
||||||
|
| `PAPERLESS_DB_TIMEOUT` | `timeout=<value>` (SQLite) or `connect_timeout=<value>` (PostgreSQL/MariaDB) |
|
||||||
|
|
||||||
|
The deprecated variables will continue to function for now but will be removed in a
|
||||||
|
future release. A deprecation warning is logged at startup for each deprecated variable
|
||||||
|
that is still set.
|
||||||
|
|
||||||
|
#### Action Required
|
||||||
|
|
||||||
|
Users with any of the deprecated variables set should migrate to `PAPERLESS_DB_OPTIONS`.
|
||||||
|
Multiple options are combined in a single value:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PAPERLESS_DB_OPTIONS="sslmode=require;sslrootcert=/certs/ca.pem;pool.max_size=10"
|
||||||
|
```
|
||||||
730
docs/setup.md
730
docs/setup.md
File diff suppressed because it is too large
Load Diff
@@ -4,27 +4,27 @@
|
|||||||
|
|
||||||
Check for the following issues:
|
Check for the following issues:
|
||||||
|
|
||||||
- Ensure that the directory you're putting your documents in is the
|
- Ensure that the directory you're putting your documents in is the
|
||||||
folder paperless is watching. With docker, this setting is performed
|
folder paperless is watching. With docker, this setting is performed
|
||||||
in the `docker-compose.yml` file. Without Docker, look at the
|
in the `docker-compose.yml` file. Without Docker, look at the
|
||||||
`CONSUMPTION_DIR` setting. Don't adjust this setting if you're
|
`CONSUMPTION_DIR` setting. Don't adjust this setting if you're
|
||||||
using docker.
|
using docker.
|
||||||
|
|
||||||
- Ensure that redis is up and running. Paperless does its task
|
- Ensure that redis is up and running. Paperless does its task
|
||||||
processing asynchronously, and for documents to arrive at the task
|
processing asynchronously, and for documents to arrive at the task
|
||||||
processor, it needs redis to run.
|
processor, it needs redis to run.
|
||||||
|
|
||||||
- Ensure that the task processor is running. Docker does this
|
- Ensure that the task processor is running. Docker does this
|
||||||
automatically. Manually invoke the task processor by executing
|
automatically. Manually invoke the task processor by executing
|
||||||
|
|
||||||
```shell-session
|
```shell-session
|
||||||
celery --app paperless worker
|
celery --app paperless worker
|
||||||
```
|
```
|
||||||
|
|
||||||
- Look at the output of paperless and inspect it for any errors.
|
- Look at the output of paperless and inspect it for any errors.
|
||||||
|
|
||||||
- Go to the admin interface, and check if there are failed tasks. If
|
- Go to the admin interface, and check if there are failed tasks. If
|
||||||
so, the tasks will contain an error message.
|
so, the tasks will contain an error message.
|
||||||
|
|
||||||
## Consumer warns `OCR for XX failed`
|
## Consumer warns `OCR for XX failed`
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ run:
|
|||||||
If you notice that the consumer will only pickup files in the
|
If you notice that the consumer will only pickup files in the
|
||||||
consumption directory at startup, but won't find any other files added
|
consumption directory at startup, but won't find any other files added
|
||||||
later, you will need to enable filesystem polling with the configuration
|
later, you will need to enable filesystem polling with the configuration
|
||||||
option [`PAPERLESS_CONSUMER_POLLING`](configuration.md#PAPERLESS_CONSUMER_POLLING).
|
option [`PAPERLESS_CONSUMER_POLLING_INTERVAL`](configuration.md#PAPERLESS_CONSUMER_POLLING_INTERVAL).
|
||||||
|
|
||||||
This will disable listening to filesystem changes with inotify and
|
This will disable automatic listening for filesystem changes and
|
||||||
paperless will manually check the consumption directory for changes
|
paperless will manually check the consumption directory for changes
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
@@ -78,12 +78,12 @@ Ensure that `chown` is possible on these directories.
|
|||||||
This indicates that the Auto matching algorithm found no documents to
|
This indicates that the Auto matching algorithm found no documents to
|
||||||
learn from. This may have two reasons:
|
learn from. This may have two reasons:
|
||||||
|
|
||||||
- You don't use the Auto matching algorithm: The error can be safely
|
- You don't use the Auto matching algorithm: The error can be safely
|
||||||
ignored in this case.
|
ignored in this case.
|
||||||
- You are using the Auto matching algorithm: The classifier explicitly
|
- You are using the Auto matching algorithm: The classifier explicitly
|
||||||
excludes documents with Inbox tags. Verify that there are documents
|
excludes documents with Inbox tags. Verify that there are documents
|
||||||
in your archive without inbox tags. The algorithm will only learn
|
in your archive without inbox tags. The algorithm will only learn
|
||||||
from documents not in your inbox.
|
from documents not in your inbox.
|
||||||
|
|
||||||
## UserWarning in sklearn on every single document
|
## UserWarning in sklearn on every single document
|
||||||
|
|
||||||
@@ -127,10 +127,10 @@ change in the `docker-compose.yml` file:
|
|||||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||||
# want to allow external content like tracking pixels or even javascript.
|
# want to allow external content like tracking pixels or even javascript.
|
||||||
command:
|
command:
|
||||||
- 'gotenberg'
|
- 'gotenberg'
|
||||||
- '--chromium-disable-javascript=true'
|
- '--chromium-disable-javascript=true'
|
||||||
- '--chromium-allow-list=file:///tmp/.*'
|
- '--chromium-allow-list=file:///tmp/.*'
|
||||||
- '--api-timeout=60s'
|
- '--api-timeout=60s'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Permission denied errors in the consumption directory
|
## Permission denied errors in the consumption directory
|
||||||
@@ -234,47 +234,9 @@ FileNotFoundError: [Errno 2] No such file or directory: '/tmp/ocrmypdf.io.yhk3zb
|
|||||||
|
|
||||||
This probably indicates paperless tried to consume the same file twice.
|
This probably indicates paperless tried to consume the same file twice.
|
||||||
This can happen for a number of reasons, depending on how documents are
|
This can happen for a number of reasons, depending on how documents are
|
||||||
placed into the consume folder. If paperless is using inotify (the
|
placed into the consume folder, such as how a scanner may modify a file multiple times as it scans.
|
||||||
default) to check for documents, try adjusting the
|
Try adjusting the
|
||||||
[inotify configuration](configuration.md#inotify). If polling is enabled, try adjusting the
|
[file stability delay](configuration.md#PAPERLESS_CONSUMER_STABILITY_DELAY) to a larger value.
|
||||||
[polling configuration](configuration.md#polling).
|
|
||||||
|
|
||||||
## Consumer fails waiting for file to remain unmodified.
|
|
||||||
|
|
||||||
You might find messages like these in your log files:
|
|
||||||
|
|
||||||
```
|
|
||||||
[ERROR] [paperless.management.consumer] Timeout while waiting on file /usr/src/paperless/src/../consume/SCN_0001.pdf to remain unmodified.
|
|
||||||
```
|
|
||||||
|
|
||||||
This indicates paperless timed out while waiting for the file to be
|
|
||||||
completely written to the consume folder. Adjusting
|
|
||||||
[polling configuration](configuration.md#polling) values should resolve the issue.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
The user will need to manually move the file out of the consume folder
|
|
||||||
and back in, for the initial failing file to be consumed.
|
|
||||||
|
|
||||||
## Consumer fails reporting "OS reports file as busy still".
|
|
||||||
|
|
||||||
You might find messages like these in your log files:
|
|
||||||
|
|
||||||
```
|
|
||||||
[WARNING] [paperless.management.consumer] Not consuming file /usr/src/paperless/src/../consume/SCN_0001.pdf: OS reports file as busy still
|
|
||||||
```
|
|
||||||
|
|
||||||
This indicates paperless was unable to open the file, as the OS reported
|
|
||||||
the file as still being in use. To prevent a crash, paperless did not
|
|
||||||
try to consume the file. If paperless is using inotify (the default) to
|
|
||||||
check for documents, try adjusting the
|
|
||||||
[inotify configuration](configuration.md#inotify). If polling is enabled, try adjusting the
|
|
||||||
[polling configuration](configuration.md#polling).
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
The user will need to manually move the file out of the consume folder
|
|
||||||
and back in, for the initial failing file to be consumed.
|
|
||||||
|
|
||||||
## Log reports "Creating PaperlessTask failed".
|
## Log reports "Creating PaperlessTask failed".
|
||||||
|
|
||||||
|
|||||||
468
docs/usage.md
468
docs/usage.md
@@ -1,4 +1,8 @@
|
|||||||
# Usage Overview
|
---
|
||||||
|
title: Basic Usage
|
||||||
|
---
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
Paperless-ngx is an application that manages your personal documents. With
|
Paperless-ngx is an application that manages your personal documents. With
|
||||||
the (optional) help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)), Paperless-ngx transforms your unwieldy
|
the (optional) help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)), Paperless-ngx transforms your unwieldy
|
||||||
@@ -10,42 +14,42 @@ for finding and managing your documents.
|
|||||||
Paperless essentially consists of two different parts for managing your
|
Paperless essentially consists of two different parts for managing your
|
||||||
documents:
|
documents:
|
||||||
|
|
||||||
- The _consumer_ watches a specified folder and adds all documents in
|
- The _consumer_ watches a specified folder and adds all documents in
|
||||||
that folder to paperless.
|
that folder to paperless.
|
||||||
- The _web server_ (web UI) provides a UI that you use to manage and
|
- The _web server_ (web UI) provides a UI that you use to manage and
|
||||||
search documents.
|
search documents.
|
||||||
|
|
||||||
Each document has data fields that you can assign to them:
|
Each document has data fields that you can assign to them:
|
||||||
|
|
||||||
- A _Document_ is a piece of paper that sometimes contains valuable
|
- A _Document_ is a piece of paper that sometimes contains valuable
|
||||||
information.
|
information.
|
||||||
- The _correspondent_ of a document is the person, institution or
|
- The _correspondent_ of a document is the person, institution or
|
||||||
company that a document either originates from, or is sent to.
|
company that a document either originates from, or is sent to.
|
||||||
- A _tag_ is a label that you can assign to documents. Think of labels
|
- A _tag_ is a label that you can assign to documents. Think of labels
|
||||||
as more powerful folders: Multiple documents can be grouped together
|
as more powerful folders: Multiple documents can be grouped together
|
||||||
with a single tag, however, a single document can also have multiple
|
with a single tag, however, a single document can also have multiple
|
||||||
tags. This is not possible with folders. The reason folders are not
|
tags. This is not possible with folders. The reason folders are not
|
||||||
implemented in paperless is simply that tags are much more versatile
|
implemented in paperless is simply that tags are much more versatile
|
||||||
than folders.
|
than folders.
|
||||||
- A _document type_ is used to demarcate the type of a document such
|
- A _document type_ is used to demarcate the type of a document such
|
||||||
as letter, bank statement, invoice, contract, etc. It is used to
|
as letter, bank statement, invoice, contract, etc. It is used to
|
||||||
identify what a document is about.
|
identify what a document is about.
|
||||||
- The document _storage path_ is the location where the document files
|
- The document _storage path_ is the location where the document files
|
||||||
are stored. See [Storage Paths](advanced_usage.md#storage-paths) for
|
are stored. See [Storage Paths](advanced_usage.md#storage-paths) for
|
||||||
more information.
|
more information.
|
||||||
- The _date added_ of a document is the date the document was scanned
|
- The _date added_ of a document is the date the document was scanned
|
||||||
into paperless. You cannot and should not change this date.
|
into paperless. You cannot and should not change this date.
|
||||||
- The _date created_ of a document is the date the document was
|
- The _date created_ of a document is the date the document was
|
||||||
initially issued. This can be the date you bought a product, the
|
initially issued. This can be the date you bought a product, the
|
||||||
date you signed a contract, or the date a letter was sent to you.
|
date you signed a contract, or the date a letter was sent to you.
|
||||||
- The _archive serial number_ (short: ASN) of a document is the
|
- The _archive serial number_ (short: ASN) of a document is the
|
||||||
identifier of the document in your physical document binders. See
|
identifier of the document in your physical document binders. See
|
||||||
[recommended workflow](#usage-recommended-workflow) below.
|
[recommended workflow](#usage-recommended-workflow) below.
|
||||||
- The _content_ of a document is the text that was OCR'ed from the
|
- The _content_ of a document is the text that was OCR'ed from the
|
||||||
document. This text is fed into the search engine and is used for
|
document. This text is fed into the search engine and is used for
|
||||||
matching tags, correspondents and document types.
|
matching tags, correspondents and document types.
|
||||||
- Paperless-ngx also supports _custom fields_ which can be used to
|
- Paperless-ngx also supports _custom fields_ which can be used to
|
||||||
store additional metadata about a document.
|
store additional metadata about a document.
|
||||||
|
|
||||||
## The Web UI
|
## The Web UI
|
||||||
|
|
||||||
@@ -85,6 +89,17 @@ You can view the document, edit its metadata, assign tags, correspondents,
|
|||||||
document types, and custom fields. You can also view the document history,
|
document types, and custom fields. You can also view the document history,
|
||||||
download the document or share it via a share link.
|
download the document or share it via a share link.
|
||||||
|
|
||||||
|
### Document File Versions
|
||||||
|
|
||||||
|
Think of versions as **file history** for a document.
|
||||||
|
|
||||||
|
- Versions track the underlying file and extracted text content (OCR/text).
|
||||||
|
- Metadata such as tags, correspondent, document type, storage path and custom fields stay on the "root" document.
|
||||||
|
- Version files follow normal filename formatting (including storage paths) and add a `_vN` suffix (for example `_v1`, `_v2`).
|
||||||
|
- By default, search and document content use the latest version.
|
||||||
|
- In document detail, selecting a version switches the preview, file metadata and content (and download etc buttons) to that version.
|
||||||
|
- Deleting a non-root version keeps metadata and falls back to the latest remaining version.
|
||||||
|
|
||||||
### Management Lists
|
### Management Lists
|
||||||
|
|
||||||
Paperless-ngx includes management lists for tags, correspondents, document types
|
Paperless-ngx includes management lists for tags, correspondents, document types
|
||||||
@@ -203,21 +218,20 @@ patterns can include wildcards and multiple patterns separated by a comma.
|
|||||||
The actions all ensure that the same mail is not consumed twice by
|
The actions all ensure that the same mail is not consumed twice by
|
||||||
different means. These are as follows:
|
different means. These are as follows:
|
||||||
|
|
||||||
- **Delete:** Immediately deletes mail that paperless has consumed
|
- **Delete:** Immediately deletes mail that paperless has consumed
|
||||||
documents from. Use with caution.
|
documents from. Use with caution.
|
||||||
- **Mark as read:** Mark consumed mail as read. Paperless will not
|
- **Mark as read:** Mark consumed mail as read. Paperless will not
|
||||||
consume documents from already read mails. If you read a mail before
|
consume documents from already read mails. If you read a mail before
|
||||||
paperless sees it, it will be ignored.
|
paperless sees it, it will be ignored.
|
||||||
- **Flag:** Sets the 'important' flag on mails with consumed
|
- **Flag:** Sets the 'important' flag on mails with consumed
|
||||||
documents. Paperless will not consume flagged mails.
|
documents. Paperless will not consume flagged mails.
|
||||||
- **Move to folder:** Moves consumed mails out of the way so that
|
- **Move to folder:** Moves consumed mails out of the way so that
|
||||||
paperless won't consume them again.
|
paperless won't consume them again.
|
||||||
- **Add custom Tag:** Adds a custom tag to mails with consumed
|
- **Add custom Tag:** Adds a custom tag to mails with consumed
|
||||||
documents (the IMAP standard calls these "keywords"). Paperless
|
documents (the IMAP standard calls these "keywords"). Paperless
|
||||||
will not consume mails already tagged. Not all mail servers support
|
will not consume mails already tagged. Not all mail servers support
|
||||||
this feature!
|
this feature!
|
||||||
|
- **Apple Mail support:** Apple Mail clients allow differently colored tags. For this to work use `apple:<color>` (e.g. _apple:green_) as a custom tag. Available colors are _red_, _orange_, _yellow_, _blue_, _green_, _violet_ and _grey_.
|
||||||
- **Apple Mail support:** Apple Mail clients allow differently colored tags. For this to work use `apple:<color>` (e.g. _apple:green_) as a custom tag. Available colors are _red_, _orange_, _yellow_, _blue_, _green_, _violet_ and _grey_.
|
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
@@ -278,6 +292,28 @@ Once setup, navigating to the email settings page in Paperless-ngx will allow yo
|
|||||||
You can also submit a document using the REST API, see [POSTing documents](api.md#file-uploads)
|
You can also submit a document using the REST API, see [POSTing documents](api.md#file-uploads)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
## Document Suggestions
|
||||||
|
|
||||||
|
Paperless-ngx can suggest tags, correspondents, document types and storage paths for documents based on the content of the document. This is done using a (non-LLM) machine learning model that is trained on the documents in your database. The suggestions are shown in the document detail page and can be accepted or rejected by the user.
|
||||||
|
|
||||||
|
## AI Features
|
||||||
|
|
||||||
|
Paperless-ngx includes several features that use AI to enhance the document management experience. These features are optional and can be enabled or disabled in the settings. If you are using the AI features, you may want to also enable the "LLM index" feature, which supports Retrieval-Augmented Generation (RAG) designed to improve the quality of AI responses. The LLM index feature is not enabled by default and requires additional configuration.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
Remember that Paperless-ngx will send document content to the AI provider you have configured, so consider the privacy implications of using these features, especially if using a remote model (e.g. OpenAI), instead of the default local model.
|
||||||
|
|
||||||
|
The AI features work by creating an embedding of the text content and metadata of documents, which is then used for various tasks such as similarity search and question answering. This uses the FAISS vector store.
|
||||||
|
|
||||||
|
### AI-Enhanced Suggestions
|
||||||
|
|
||||||
|
If enabled, Paperless-ngx can use an AI LLM model to suggest document titles, dates, tags, correspondents and document types for documents. This feature will always be "opt-in" and does not disable the existing classifier-based suggestion system. Currently, both remote (via the OpenAI API) and local (via Ollama) models are supported, see [configuration](configuration.md#ai) for details.
|
||||||
|
|
||||||
|
### Document Chat
|
||||||
|
|
||||||
|
Paperless-ngx can use an AI LLM model to answer questions about a document or across multiple documents. Again, this feature works best when RAG is enabled. The chat feature is available in the upper app toolbar and will switch between chatting across multiple documents or a single document based on the current view.
|
||||||
|
|
||||||
## Sharing documents from Paperless-ngx
|
## Sharing documents from Paperless-ngx
|
||||||
|
|
||||||
Paperless-ngx supports sharing documents with other users by assigning them [permissions](#object-permissions)
|
Paperless-ngx supports sharing documents with other users by assigning them [permissions](#object-permissions)
|
||||||
@@ -286,12 +322,14 @@ or using [email](#workflow-action-email) or [webhook](#workflow-action-webhook)
|
|||||||
|
|
||||||
### Share Links
|
### Share Links
|
||||||
|
|
||||||
"Share links" are shareable public links to files and can be created and managed under the 'Send' button on the document detail screen.
|
"Share links" are public links to files (or an archive of files) and can be created and managed under the 'Send' button on the document detail screen or from the bulk editor.
|
||||||
|
|
||||||
- Share links do not require a user to login and thus link directly to a file.
|
- Share links do not require a user to login and thus link directly to a file or bundled download.
|
||||||
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
|
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
|
||||||
- Links can optionally have an expiration time set.
|
- Links can optionally have an expiration time set.
|
||||||
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
|
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
|
||||||
|
- From the document detail screen you can create a share link for that single document.
|
||||||
|
- From the bulk editor you can create a **share link bundle** for any selection. Paperless-ngx prepares a ZIP archive in the background and exposes a single share link. You can revisit the "Manage share link bundles" dialog to monitor progress, retry failed bundles, or delete links.
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
@@ -344,6 +382,11 @@ permissions can be granted to limit access to certain parts of the UI (and corre
|
|||||||
|
|
||||||
Superusers can access all parts of the front and backend application as well as any and all objects. Superuser status can only be granted by another superuser.
|
Superusers can access all parts of the front and backend application as well as any and all objects. Superuser status can only be granted by another superuser.
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
|
||||||
|
Because superuser accounts can see all objects and documents, you may want to use a regular account for day-to-day use. Additional superuser accounts can
|
||||||
|
be created via [cli](administration.md#create-superuser) or granted superuser status from an existing superuser account.
|
||||||
|
|
||||||
#### Admin Status
|
#### Admin Status
|
||||||
|
|
||||||
Admin status (Django 'staff status') grants access to viewing the paperless logs and the system status dialog
|
Admin status (Django 'staff status') grants access to viewing the paperless logs and the system status dialog
|
||||||
@@ -470,25 +513,25 @@ flowchart TD
|
|||||||
|
|
||||||
Workflows allow you to filter by:
|
Workflows allow you to filter by:
|
||||||
|
|
||||||
- Source, e.g. documents uploaded via consume folder, API (& the web UI) and mail fetch
|
- Source, e.g. documents uploaded via consume folder, API (& the web UI) and mail fetch
|
||||||
- File name, including wildcards e.g. \*.pdf will apply to all pdfs.
|
- File name, including wildcards e.g. \*.pdf will apply to all pdfs.
|
||||||
- File path, including wildcards. Note that enabling `PAPERLESS_CONSUMER_RECURSIVE` would allow, for
|
- File path, including wildcards. Note that enabling `PAPERLESS_CONSUMER_RECURSIVE` would allow, for
|
||||||
example, automatically assigning documents to different owners based on the upload directory.
|
example, automatically assigning documents to different owners based on the upload directory.
|
||||||
- Mail rule. Choosing this option will force 'mail fetch' to be the workflow source.
|
- Mail rule. Choosing this option will force 'mail fetch' to be the workflow source.
|
||||||
- Content matching (`Added`, `Updated` and `Scheduled` triggers only). Filter document content using the matching settings.
|
- Content matching (`Added`, `Updated` and `Scheduled` triggers only). Filter document content using the matching settings.
|
||||||
|
|
||||||
There are also 'advanced' filters available for `Added`, `Updated` and `Scheduled` triggers:
|
There are also 'advanced' filters available for `Added`, `Updated` and `Scheduled` triggers:
|
||||||
|
|
||||||
- Any Tags: Filter for documents with any of the specified tags.
|
- Any Tags: Filter for documents with any of the specified tags.
|
||||||
- All Tags: Filter for documents with all of the specified tags.
|
- All Tags: Filter for documents with all of the specified tags.
|
||||||
- No Tags: Filter for documents with none of the specified tags.
|
- No Tags: Filter for documents with none of the specified tags.
|
||||||
- Document type: Filter documents with this document type.
|
- Document type: Filter documents with this document type.
|
||||||
- Not Document types: Filter documents without any of these document types.
|
- Not Document types: Filter documents without any of these document types.
|
||||||
- Correspondent: Filter documents with this correspondent.
|
- Correspondent: Filter documents with this correspondent.
|
||||||
- Not Correspondents: Filter documents without any of these correspondents.
|
- Not Correspondents: Filter documents without any of these correspondents.
|
||||||
- Storage path: Filter documents with this storage path.
|
- Storage path: Filter documents with this storage path.
|
||||||
- Not Storage paths: Filter documents without any of these storage paths.
|
- Not Storage paths: Filter documents without any of these storage paths.
|
||||||
- Custom field query: Filter documents with a custom field query (the same as used for the document list filters).
|
- Custom field query: Filter documents with a custom field query (the same as used for the document list filters).
|
||||||
|
|
||||||
### Workflow Actions
|
### Workflow Actions
|
||||||
|
|
||||||
@@ -500,81 +543,94 @@ The following workflow action types are available:
|
|||||||
|
|
||||||
"Assignment" actions can assign:
|
"Assignment" actions can assign:
|
||||||
|
|
||||||
- Title, see [workflow placeholders](usage.md#workflow-placeholders) below
|
- Title, see [workflow placeholders](usage.md#workflow-placeholders) below
|
||||||
- Tags, correspondent, document type and storage path
|
- Tags, correspondent, document type and storage path
|
||||||
- Document owner
|
- Document owner
|
||||||
- View and / or edit permissions to users or groups
|
- View and / or edit permissions to users or groups
|
||||||
- Custom fields. Note that no value for the field will be set
|
- Custom fields. Note that no value for the field will be set
|
||||||
|
|
||||||
##### Removal {#workflow-action-removal}
|
##### Removal {#workflow-action-removal}
|
||||||
|
|
||||||
"Removal" actions can remove either all of or specific sets of the following:
|
"Removal" actions can remove either all of or specific sets of the following:
|
||||||
|
|
||||||
- Tags, correspondents, document types or storage paths
|
- Tags, correspondents, document types or storage paths
|
||||||
- Document owner
|
- Document owner
|
||||||
- View and / or edit permissions
|
- View and / or edit permissions
|
||||||
- Custom fields
|
- Custom fields
|
||||||
|
|
||||||
##### Email {#workflow-action-email}
|
##### Email {#workflow-action-email}
|
||||||
|
|
||||||
"Email" actions can send documents via email. This action requires a mail server to be [configured](configuration.md#email-sending). You can specify:
|
"Email" actions can send documents via email. This action requires a mail server to be [configured](configuration.md#email-sending). You can specify:
|
||||||
|
|
||||||
- The recipient email address(es) separated by commas
|
- The recipient email address(es) separated by commas
|
||||||
- The subject and body of the email, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below
|
- The subject and body of the email, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below
|
||||||
- Whether to include the document as an attachment
|
- Whether to include the document as an attachment
|
||||||
|
|
||||||
##### Webhook {#workflow-action-webhook}
|
##### Webhook {#workflow-action-webhook}
|
||||||
|
|
||||||
"Webhook" actions send a POST request to a specified URL. You can specify:
|
"Webhook" actions send a POST request to a specified URL. You can specify:
|
||||||
|
|
||||||
- The URL to send the request to
|
- The URL to send the request to
|
||||||
- The request body as text or as key-value pairs, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below.
|
- The request body as text or as key-value pairs, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below.
|
||||||
- Encoding for the request body, either JSON or form data
|
- Encoding for the request body, either JSON or form data
|
||||||
- The request headers as key-value pairs
|
- The request headers as key-value pairs
|
||||||
|
|
||||||
For security reasons, webhooks can be limited to specific ports and disallowed from connecting to local URLs. See the relevant
|
For security reasons, webhooks can be limited to specific ports and disallowed from connecting to local URLs. See the relevant
|
||||||
[configuration settings](configuration.md#workflow-webhooks) to change this behavior. If you are allowing non-admins to create workflows,
|
[configuration settings](configuration.md#workflow-webhooks) to change this behavior. If you are allowing non-admins to create workflows,
|
||||||
you may want to adjust these settings to prevent abuse.
|
you may want to adjust these settings to prevent abuse.
|
||||||
|
|
||||||
|
##### Move to Trash {#workflow-action-move-to-trash}
|
||||||
|
|
||||||
|
"Move to Trash" actions move the document to the trash. The document can be restored
|
||||||
|
from the trash until the trash is emptied (after the configured delay or manually).
|
||||||
|
|
||||||
|
The "Move to Trash" action will always be executed at the end of the workflow run,
|
||||||
|
regardless of its position in the action list. After a "Move to Trash" action is executed
|
||||||
|
no other workflow will be executed on the document.
|
||||||
|
|
||||||
|
If a "Move to Trash" action is executed in a consume pipeline, the consumption
|
||||||
|
will be aborted and the file will be deleted.
|
||||||
|
|
||||||
#### Workflow placeholders
|
#### Workflow placeholders
|
||||||
|
|
||||||
Titles can be assigned by workflows using [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/).
|
Titles and webhook payloads can be generated by workflows using [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/).
|
||||||
This allows for complex logic to be used to generate the title, including [logical structures](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-control-structures)
|
This allows for complex logic to be used, including [logical structures](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-control-structures)
|
||||||
and [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#id11).
|
and [filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#id11).
|
||||||
The template is provided as a string.
|
The template is provided as a string.
|
||||||
|
|
||||||
Using Jinja2 Templates is also useful for [Date localization](advanced_usage.md#Date-Localization) in the title.
|
Using Jinja2 Templates is also useful for [Date localization](advanced_usage.md#date-localization) in the title.
|
||||||
|
|
||||||
The available inputs differ depending on the type of workflow trigger.
|
The available inputs differ depending on the type of workflow trigger.
|
||||||
This is because at the time of consumption (when the text is to be set), no automatic tags etc. have been
|
This is because at the time of consumption (when the text is to be set), no automatic tags etc. have been
|
||||||
applied. You can use the following placeholders in the template with any trigger type:
|
applied. You can use the following placeholders in the template with any trigger type:
|
||||||
|
|
||||||
- `{{correspondent}}`: assigned correspondent name
|
- `{{correspondent}}`: assigned correspondent name
|
||||||
- `{{document_type}}`: assigned document type name
|
- `{{document_type}}`: assigned document type name
|
||||||
- `{{owner_username}}`: assigned owner username
|
- `{{owner_username}}`: assigned owner username
|
||||||
- `{{added}}`: added datetime
|
- `{{added}}`: added datetime
|
||||||
- `{{added_year}}`: added year
|
- `{{added_year}}`: added year
|
||||||
- `{{added_year_short}}`: added year
|
- `{{added_year_short}}`: added year
|
||||||
- `{{added_month}}`: added month
|
- `{{added_month}}`: added month
|
||||||
- `{{added_month_name}}`: added month name
|
- `{{added_month_name}}`: added month name
|
||||||
- `{{added_month_name_short}}`: added month short name
|
- `{{added_month_name_short}}`: added month short name
|
||||||
- `{{added_day}}`: added day
|
- `{{added_day}}`: added day
|
||||||
- `{{added_time}}`: added time in HH:MM format
|
- `{{added_time}}`: added time in HH:MM format
|
||||||
- `{{original_filename}}`: original file name without extension
|
- `{{original_filename}}`: original file name without extension
|
||||||
- `{{filename}}`: current file name without extension
|
- `{{filename}}`: current file name without extension (for "added" workflows this may not be final yet, you can use `{{original_filename}}`)
|
||||||
- `{{doc_title}}`: current document title
|
- `{{doc_title}}`: current document title (cannot be used in title assignment)
|
||||||
|
|
||||||
The following placeholders are only available for "added" or "updated" triggers
|
The following placeholders are only available for "added" or "updated" triggers
|
||||||
|
|
||||||
- `{{created}}`: created datetime
|
- `{{created}}`: created datetime
|
||||||
- `{{created_year}}`: created year
|
- `{{created_year}}`: created year
|
||||||
- `{{created_year_short}}`: created year
|
- `{{created_year_short}}`: created year
|
||||||
- `{{created_month}}`: created month
|
- `{{created_month}}`: created month
|
||||||
- `{{created_month_name}}`: created month name
|
- `{{created_month_name}}`: created month name
|
||||||
- `{created_month_name_short}}`: created month short name
|
- `{{created_month_name_short}}`: created month short name
|
||||||
- `{{created_day}}`: created day
|
- `{{created_day}}`: created day
|
||||||
- `{{created_time}}`: created time in HH:MM format
|
- `{{created_time}}`: created time in HH:MM format
|
||||||
- `{{doc_url}}`: URL to the document in the web UI. Requires the `PAPERLESS_URL` setting to be set.
|
- `{{doc_url}}`: URL to the document in the web UI. Requires the `PAPERLESS_URL` setting to be set.
|
||||||
|
- `{{doc_id}}`: Document ID
|
||||||
|
|
||||||
##### Examples
|
##### Examples
|
||||||
|
|
||||||
@@ -619,26 +675,26 @@ Multiple fields may be attached to a document but the same field name cannot be
|
|||||||
|
|
||||||
The following custom field types are supported:
|
The following custom field types are supported:
|
||||||
|
|
||||||
- `Text`: any text
|
- `Text`: any text
|
||||||
- `Boolean`: true / false (check / unchecked) field
|
- `Boolean`: true / false (check / unchecked) field
|
||||||
- `Date`: date
|
- `Date`: date
|
||||||
- `URL`: a valid url
|
- `URL`: a valid url
|
||||||
- `Integer`: integer number e.g. 12
|
- `Integer`: integer number e.g. 12
|
||||||
- `Number`: float number e.g. 12.3456
|
- `Number`: float number e.g. 12.3456
|
||||||
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
|
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
|
||||||
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
|
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
|
||||||
- `Select`: a pre-defined list of strings from which the user can choose
|
- `Select`: a pre-defined list of strings from which the user can choose
|
||||||
|
|
||||||
## PDF Actions
|
## PDF Actions
|
||||||
|
|
||||||
Paperless-ngx supports basic editing operations for PDFs (these operations currently cannot be performed on non-PDF files). When viewing an individual document you can
|
Paperless-ngx supports basic editing operations for PDFs (these operations currently cannot be performed on non-PDF files). When viewing an individual document you can
|
||||||
open the 'PDF Editor' to use a simple UI for re-arranging, rotating, deleting pages and splitting documents.
|
open the 'PDF Editor' to use a simple UI for re-arranging, rotating, deleting pages and splitting documents.
|
||||||
|
|
||||||
- Merging documents: available when selecting multiple documents for 'bulk editing'.
|
- Merging documents: available when selecting multiple documents for 'bulk editing'.
|
||||||
- Rotating documents: available when selecting multiple documents for 'bulk editing' and via the pdf editor on an individual document's details page.
|
- Rotating documents: available when selecting multiple documents for 'bulk editing' and via the pdf editor on an individual document's details page.
|
||||||
- Splitting documents: via the pdf editor on an individual document's details page.
|
- Splitting documents: via the pdf editor on an individual document's details page.
|
||||||
- Deleting pages: via the pdf editor on an individual document's details page.
|
- Deleting pages: via the pdf editor on an individual document's details page.
|
||||||
- Re-arranging pages: via the pdf editor on an individual document's details page.
|
- Re-arranging pages: via the pdf editor on an individual document's details page.
|
||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
|
|
||||||
@@ -716,18 +772,18 @@ the system.
|
|||||||
Here are a couple examples of tags and types that you could use in your
|
Here are a couple examples of tags and types that you could use in your
|
||||||
collection.
|
collection.
|
||||||
|
|
||||||
- An `inbox` tag for newly added documents that you haven't manually
|
- An `inbox` tag for newly added documents that you haven't manually
|
||||||
edited yet.
|
edited yet.
|
||||||
- A tag `car` for everything car related (repairs, registration,
|
- A tag `car` for everything car related (repairs, registration,
|
||||||
insurance, etc)
|
insurance, etc)
|
||||||
- A tag `todo` for documents that you still need to do something with,
|
- A tag `todo` for documents that you still need to do something with,
|
||||||
such as reply, or perform some task online.
|
such as reply, or perform some task online.
|
||||||
- A tag `bank account x` for all bank statement related to that
|
- A tag `bank account x` for all bank statement related to that
|
||||||
account.
|
account.
|
||||||
- A tag `mail` for anything that you added to paperless via its mail
|
- A tag `mail` for anything that you added to paperless via its mail
|
||||||
processing capabilities.
|
processing capabilities.
|
||||||
- A tag `missing_metadata` when you still need to add some metadata to
|
- A tag `missing_metadata` when you still need to add some metadata to
|
||||||
a document, but can't or don't want to do this right now.
|
a document, but can't or don't want to do this right now.
|
||||||
|
|
||||||
## Searching {#basic-usage_searching}
|
## Searching {#basic-usage_searching}
|
||||||
|
|
||||||
@@ -816,8 +872,8 @@ The following diagram shows how easy it is to manage your documents.
|
|||||||
|
|
||||||
### Preparations in paperless
|
### Preparations in paperless
|
||||||
|
|
||||||
- Create an inbox tag that gets assigned to all new documents.
|
- Create an inbox tag that gets assigned to all new documents.
|
||||||
- Create a TODO tag.
|
- Create a TODO tag.
|
||||||
|
|
||||||
### Processing of the physical documents
|
### Processing of the physical documents
|
||||||
|
|
||||||
@@ -891,78 +947,92 @@ Some documents require attention and require you to act on the document.
|
|||||||
You may take two different approaches to handle these documents based on
|
You may take two different approaches to handle these documents based on
|
||||||
how regularly you intend to scan documents and use paperless.
|
how regularly you intend to scan documents and use paperless.
|
||||||
|
|
||||||
- If you scan and process your documents in paperless regularly,
|
- If you scan and process your documents in paperless regularly,
|
||||||
assign a TODO tag to all scanned documents that you need to process.
|
assign a TODO tag to all scanned documents that you need to process.
|
||||||
Create a saved view on the dashboard that shows all documents with
|
Create a saved view on the dashboard that shows all documents with
|
||||||
this tag.
|
this tag.
|
||||||
- If you do not scan documents regularly and use paperless solely for
|
- If you do not scan documents regularly and use paperless solely for
|
||||||
archiving, create a physical todo box next to your physical inbox
|
archiving, create a physical todo box next to your physical inbox
|
||||||
and put documents you need to process in the TODO box. When you
|
and put documents you need to process in the TODO box. When you
|
||||||
performed the task associated with the document, move it to the
|
performed the task associated with the document, move it to the
|
||||||
inbox.
|
inbox.
|
||||||
|
|
||||||
|
## Remote OCR
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
|
||||||
|
This feature is disabled by default and will always remain strictly "opt-in".
|
||||||
|
|
||||||
|
Paperless-ngx supports performing OCR on documents using remote services. At the moment, this is limited to
|
||||||
|
[Microsoft's Azure "Document Intelligence" service](https://azure.microsoft.com/en-us/products/ai-services/ai-document-intelligence).
|
||||||
|
This is of course a paid service (with a free tier) which requires an Azure account and subscription. Azure AI is not affiliated with
|
||||||
|
Paperless-ngx in any way. When enabled, Paperless-ngx will automatically send appropriate documents to Azure for OCR processing, bypassing
|
||||||
|
the local OCR engine. See the [configuration](configuration.md#PAPERLESS_REMOTE_OCR_ENGINE) options for more details.
|
||||||
|
|
||||||
|
Additionally, when using a commercial service with this feature, consider both potential costs as well as any associated file size
|
||||||
|
or page limitations (e.g. with a free tier).
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Paperless-ngx consists of the following components:
|
Paperless-ngx consists of the following components:
|
||||||
|
|
||||||
- **The webserver:** This serves the administration pages, the API,
|
- **The webserver:** This serves the administration pages, the API,
|
||||||
and the new frontend. This is the main tool you'll be using to interact
|
and the new frontend. This is the main tool you'll be using to interact
|
||||||
with paperless. You may start the webserver directly with
|
with paperless. You may start the webserver directly with
|
||||||
|
|
||||||
```shell-session
|
```shell-session
|
||||||
cd /path/to/paperless/src/
|
cd /path/to/paperless/src/
|
||||||
granian --interface asginl --ws "paperless.asgi:application"
|
granian --interface asginl --ws "paperless.asgi:application"
|
||||||
```
|
```
|
||||||
|
|
||||||
or by any other means such as Apache `mod_wsgi`.
|
or by any other means such as Apache `mod_wsgi`.
|
||||||
|
|
||||||
- **The consumer:** This is what watches your consumption folder for
|
- **The consumer:** This is what watches your consumption folder for
|
||||||
documents. However, the consumer itself does not really consume your
|
documents. However, the consumer itself does not really consume your
|
||||||
documents. Now it notifies a task processor that a new file is ready
|
documents. Now it notifies a task processor that a new file is ready
|
||||||
for consumption. I suppose it should be named differently. This was
|
for consumption. I suppose it should be named differently. This was
|
||||||
also used to check your emails, but that's now done elsewhere as
|
also used to check your emails, but that's now done elsewhere as
|
||||||
well.
|
well.
|
||||||
|
|
||||||
Start the consumer with the management command `document_consumer`:
|
Start the consumer with the management command `document_consumer`:
|
||||||
|
|
||||||
```shell-session
|
```shell-session
|
||||||
cd /path/to/paperless/src/
|
cd /path/to/paperless/src/
|
||||||
python3 manage.py document_consumer
|
python3 manage.py document_consumer
|
||||||
```
|
```
|
||||||
|
|
||||||
- **The task processor:** Paperless relies on [Celery - Distributed
|
- **The task processor:** Paperless relies on [Celery - Distributed
|
||||||
Task Queue](https://docs.celeryq.dev/en/stable/index.html) for doing
|
Task Queue](https://docs.celeryq.dev/en/stable/index.html) for doing
|
||||||
most of the heavy lifting. This is a task queue that accepts tasks
|
most of the heavy lifting. This is a task queue that accepts tasks
|
||||||
from multiple sources and processes these in parallel. It also comes
|
from multiple sources and processes these in parallel. It also comes
|
||||||
with a scheduler that executes certain commands periodically.
|
with a scheduler that executes certain commands periodically.
|
||||||
|
|
||||||
This task processor is responsible for:
|
This task processor is responsible for:
|
||||||
|
- Consuming documents. When the consumer finds new documents, it
|
||||||
|
notifies the task processor to start a consumption task.
|
||||||
|
- The task processor also performs the consumption of any
|
||||||
|
documents you upload through the web interface.
|
||||||
|
- Consuming emails. It periodically checks your configured
|
||||||
|
accounts for new emails and notifies the task processor to
|
||||||
|
consume the attachment of an email.
|
||||||
|
- Maintaining the search index and the automatic matching
|
||||||
|
algorithm. These are things that paperless needs to do from time
|
||||||
|
to time in order to operate properly.
|
||||||
|
|
||||||
- Consuming documents. When the consumer finds new documents, it
|
This allows paperless to process multiple documents from your
|
||||||
notifies the task processor to start a consumption task.
|
consumption folder in parallel! On a modern multi core system, this
|
||||||
- The task processor also performs the consumption of any
|
makes the consumption process with full OCR blazingly fast.
|
||||||
documents you upload through the web interface.
|
|
||||||
- Consuming emails. It periodically checks your configured
|
|
||||||
accounts for new emails and notifies the task processor to
|
|
||||||
consume the attachment of an email.
|
|
||||||
- Maintaining the search index and the automatic matching
|
|
||||||
algorithm. These are things that paperless needs to do from time
|
|
||||||
to time in order to operate properly.
|
|
||||||
|
|
||||||
This allows paperless to process multiple documents from your
|
The task processor comes with a built-in admin interface that you
|
||||||
consumption folder in parallel! On a modern multi core system, this
|
can use to check whenever any of the tasks fail and inspect the
|
||||||
makes the consumption process with full OCR blazingly fast.
|
errors (i.e., wrong email credentials, errors during consuming a
|
||||||
|
specific file, etc).
|
||||||
|
|
||||||
The task processor comes with a built-in admin interface that you
|
- A [redis](https://redis.io/) message broker: This is a really
|
||||||
can use to check whenever any of the tasks fail and inspect the
|
lightweight service that is responsible for getting the tasks from
|
||||||
errors (i.e., wrong email credentials, errors during consuming a
|
the webserver and the consumer to the task scheduler. These run in a
|
||||||
specific file, etc).
|
different process (maybe even on different machines!), and
|
||||||
|
therefore, this is necessary.
|
||||||
|
|
||||||
- A [redis](https://redis.io/) message broker: This is a really
|
- Optional: A database server. Paperless supports PostgreSQL, MariaDB
|
||||||
lightweight service that is responsible for getting the tasks from
|
and SQLite for storing its data.
|
||||||
the webserver and the consumer to the task scheduler. These run in a
|
|
||||||
different process (maybe even on different machines!), and
|
|
||||||
therefore, this is necessary.
|
|
||||||
|
|
||||||
- Optional: A database server. Paperless supports PostgreSQL, MariaDB
|
|
||||||
and SQLite for storing its data.
|
|
||||||
|
|||||||
86
mkdocs.yml
86
mkdocs.yml
@@ -1,86 +0,0 @@
|
|||||||
site_name: Paperless-ngx
|
|
||||||
theme:
|
|
||||||
name: material
|
|
||||||
logo: assets/logo.svg
|
|
||||||
font:
|
|
||||||
text: Roboto
|
|
||||||
code: Roboto Mono
|
|
||||||
palette:
|
|
||||||
# Palette toggle for automatic mode
|
|
||||||
- media: "(prefers-color-scheme)"
|
|
||||||
toggle:
|
|
||||||
icon: material/brightness-auto
|
|
||||||
name: Switch to light mode
|
|
||||||
# Palette toggle for light mode
|
|
||||||
- media: "(prefers-color-scheme: light)"
|
|
||||||
scheme: default
|
|
||||||
toggle:
|
|
||||||
icon: material/brightness-7
|
|
||||||
name: Switch to dark mode
|
|
||||||
# Palette toggle for dark mode
|
|
||||||
- media: "(prefers-color-scheme: dark)"
|
|
||||||
scheme: slate
|
|
||||||
toggle:
|
|
||||||
icon: material/brightness-4
|
|
||||||
name: Switch to system preference
|
|
||||||
features:
|
|
||||||
- navigation.tabs
|
|
||||||
- navigation.top
|
|
||||||
- toc.integrate
|
|
||||||
- content.code.annotate
|
|
||||||
icon:
|
|
||||||
repo: fontawesome/brands/github
|
|
||||||
favicon: assets/favicon.png
|
|
||||||
repo_url: https://github.com/paperless-ngx/paperless-ngx
|
|
||||||
repo_name: paperless-ngx/paperless-ngx
|
|
||||||
edit_uri: blob/main/docs/
|
|
||||||
extra_css:
|
|
||||||
- assets/extra.css
|
|
||||||
markdown_extensions:
|
|
||||||
- attr_list
|
|
||||||
- md_in_html
|
|
||||||
- def_list
|
|
||||||
- admonition
|
|
||||||
- tables
|
|
||||||
- pymdownx.highlight:
|
|
||||||
anchor_linenums: true
|
|
||||||
- pymdownx.superfences
|
|
||||||
- pymdownx.inlinehilite
|
|
||||||
- pymdownx.snippets
|
|
||||||
- pymdownx.tilde
|
|
||||||
- footnotes
|
|
||||||
- pymdownx.superfences:
|
|
||||||
custom_fences:
|
|
||||||
- name: mermaid
|
|
||||||
class: mermaid
|
|
||||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
||||||
- pymdownx.emoji:
|
|
||||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
|
||||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
|
||||||
strict: true
|
|
||||||
nav:
|
|
||||||
- index.md
|
|
||||||
- setup.md
|
|
||||||
- 'Basic Usage': usage.md
|
|
||||||
- configuration.md
|
|
||||||
- administration.md
|
|
||||||
- advanced_usage.md
|
|
||||||
- 'REST API': api.md
|
|
||||||
- development.md
|
|
||||||
- 'FAQs': faq.md
|
|
||||||
- troubleshooting.md
|
|
||||||
- changelog.md
|
|
||||||
copyright: Copyright © 2016 - 2023 Daniel Quinn, Jonas Winkler, and the Paperless-ngx team
|
|
||||||
extra:
|
|
||||||
social:
|
|
||||||
- icon: fontawesome/brands/github
|
|
||||||
link: https://github.com/paperless-ngx/paperless-ngx
|
|
||||||
- icon: fontawesome/brands/docker
|
|
||||||
link: https://hub.docker.com/r/paperlessngx/paperless-ngx
|
|
||||||
- icon: material/chat
|
|
||||||
link: https://matrix.to/#/#paperless:matrix.org
|
|
||||||
plugins:
|
|
||||||
- search
|
|
||||||
- glightbox:
|
|
||||||
skip_classes:
|
|
||||||
- no-lightbox
|
|
||||||
@@ -33,6 +33,8 @@
|
|||||||
"**/coverage.json": true
|
"**/coverage.json": true
|
||||||
},
|
},
|
||||||
"python.defaultInterpreterPath": ".venv/bin/python3",
|
"python.defaultInterpreterPath": ".venv/bin/python3",
|
||||||
|
"python.analysis.inlayHints.pytestParameters": true,
|
||||||
|
"python.testing.pytestEnabled": true,
|
||||||
},
|
},
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"recommendations": ["ms-python.python", "charliermarsh.ruff", "editorconfig.editorconfig"],
|
"recommendations": ["ms-python.python", "charliermarsh.ruff", "editorconfig.editorconfig"],
|
||||||
|
|||||||
@@ -55,10 +55,10 @@
|
|||||||
#PAPERLESS_TASK_WORKERS=1
|
#PAPERLESS_TASK_WORKERS=1
|
||||||
#PAPERLESS_THREADS_PER_WORKER=1
|
#PAPERLESS_THREADS_PER_WORKER=1
|
||||||
#PAPERLESS_TIME_ZONE=UTC
|
#PAPERLESS_TIME_ZONE=UTC
|
||||||
#PAPERLESS_CONSUMER_POLLING=10
|
#PAPERLESS_CONSUMER_POLLING_INTERVAL=10
|
||||||
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
|
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
|
||||||
#PAPERLESS_CONSUMER_RECURSIVE=false
|
#PAPERLESS_CONSUMER_RECURSIVE=false
|
||||||
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]
|
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[] # Defaults are built in; add filename regexes, e.g. ["^\\.DS_Store$", "^desktop\\.ini$"]
|
||||||
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
||||||
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
|
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
|
||||||
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
#PAPERLESS_CONSUMER_BARCODE_DPI=300
|
#PAPERLESS_CONSUMER_BARCODE_DPI=300
|
||||||
#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false
|
#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false
|
||||||
#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}
|
#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}
|
||||||
|
#PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT=false
|
||||||
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
|
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
|
||||||
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
|
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
|
||||||
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false
|
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false
|
||||||
|
|||||||
164
pyproject.toml
164
pyproject.toml
@@ -1,12 +1,11 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "paperless-ngx"
|
name = "paperless-ngx"
|
||||||
version = "2.20.3"
|
version = "2.20.10"
|
||||||
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
@@ -16,43 +15,53 @@ classifiers = [
|
|||||||
# This will allow testing to not install a webserver, mysql, etc
|
# This will allow testing to not install a webserver, mysql, etc
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"azure-ai-documentintelligence>=1.0.2",
|
||||||
"babel>=2.17",
|
"babel>=2.17",
|
||||||
"bleach~=6.3.0",
|
"bleach~=6.3.0",
|
||||||
"celery[redis]~=5.5.1",
|
"celery[redis]~=5.6.2",
|
||||||
"channels~=4.2",
|
"channels~=4.2",
|
||||||
"channels-redis~=4.2",
|
"channels-redis~=4.2",
|
||||||
"concurrent-log-handler~=0.9.25",
|
"concurrent-log-handler~=0.9.25",
|
||||||
"dateparser~=1.2",
|
"dateparser~=1.2",
|
||||||
# WARNING: django does not use semver.
|
# WARNING: django does not use semver.
|
||||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||||
"django~=5.2.5",
|
"django~=5.2.10",
|
||||||
"django-allauth[mfa,socialaccount]~=65.12.1",
|
"django-allauth[mfa,socialaccount]~=65.14.0",
|
||||||
"django-auditlog~=3.3.0",
|
"django-auditlog~=3.4.1",
|
||||||
"django-cachalot~=2.8.0",
|
"django-cachalot~=2.9.0",
|
||||||
"django-celery-results~=2.6.0",
|
"django-celery-results~=2.6.0",
|
||||||
"django-compression-middleware~=0.5.0",
|
"django-compression-middleware~=0.5.0",
|
||||||
"django-cors-headers~=4.9.0",
|
"django-cors-headers~=4.9.0",
|
||||||
"django-extensions~=4.1",
|
"django-extensions~=4.1",
|
||||||
"django-filter~=25.1",
|
"django-filter~=25.1",
|
||||||
"django-guardian~=3.2.0",
|
"django-guardian~=3.3.0",
|
||||||
"django-multiselectfield~=1.0.1",
|
"django-multiselectfield~=1.0.1",
|
||||||
|
"django-rich~=2.2.0",
|
||||||
"django-soft-delete~=1.0.18",
|
"django-soft-delete~=1.0.18",
|
||||||
"django-treenode>=0.23.2",
|
"django-treenode>=0.23.2",
|
||||||
"djangorestframework~=3.16",
|
"djangorestframework~=3.16",
|
||||||
"djangorestframework-guardian~=0.4.0",
|
"djangorestframework-guardian~=0.4.0",
|
||||||
"drf-spectacular~=0.28",
|
"drf-spectacular~=0.28",
|
||||||
"drf-spectacular-sidecar~=2025.10.1",
|
"drf-spectacular-sidecar~=2026.3.1",
|
||||||
"drf-writable-nested~=0.7.1",
|
"drf-writable-nested~=0.7.1",
|
||||||
"filelock~=3.20.0",
|
"faiss-cpu>=1.10",
|
||||||
|
"filelock~=3.25.2",
|
||||||
"flower~=2.0.1",
|
"flower~=2.0.1",
|
||||||
"gotenberg-client~=0.12.0",
|
"gotenberg-client~=0.13.1",
|
||||||
"httpx-oauth~=0.16",
|
"httpx-oauth~=0.16",
|
||||||
|
"ijson>=3.2",
|
||||||
"imap-tools~=1.11.0",
|
"imap-tools~=1.11.0",
|
||||||
"inotifyrecursive~=0.3",
|
|
||||||
"jinja2~=3.1.5",
|
"jinja2~=3.1.5",
|
||||||
"langdetect~=1.0.9",
|
"langdetect~=1.0.9",
|
||||||
|
"llama-index-core>=0.14.12",
|
||||||
|
"llama-index-embeddings-huggingface>=0.6.1",
|
||||||
|
"llama-index-embeddings-openai>=0.5.1",
|
||||||
|
"llama-index-llms-ollama>=0.9.1",
|
||||||
|
"llama-index-llms-openai>=0.6.13",
|
||||||
|
"llama-index-vector-stores-faiss>=0.5.2",
|
||||||
"nltk~=3.9.1",
|
"nltk~=3.9.1",
|
||||||
"ocrmypdf~=16.12.0",
|
"ocrmypdf~=16.13.0",
|
||||||
|
"openai>=1.76",
|
||||||
"pathvalidate~=3.3.1",
|
"pathvalidate~=3.3.1",
|
||||||
"pdf2image~=1.17.0",
|
"pdf2image~=1.17.0",
|
||||||
"python-dateutil~=2.9.0",
|
"python-dateutil~=2.9.0",
|
||||||
@@ -60,31 +69,31 @@ dependencies = [
|
|||||||
"python-gnupg~=0.5.4",
|
"python-gnupg~=0.5.4",
|
||||||
"python-ipware~=3.0.0",
|
"python-ipware~=3.0.0",
|
||||||
"python-magic~=0.4.27",
|
"python-magic~=0.4.27",
|
||||||
"pyzbar~=0.1.9",
|
|
||||||
"rapidfuzz~=3.14.0",
|
"rapidfuzz~=3.14.0",
|
||||||
"redis[hiredis]~=5.2.1",
|
"redis[hiredis]~=5.2.1",
|
||||||
"regex>=2025.9.18",
|
"regex>=2025.9.18",
|
||||||
"scikit-learn~=1.7.0",
|
"scikit-learn~=1.8.0",
|
||||||
|
"sentence-transformers>=4.1",
|
||||||
"setproctitle~=1.3.4",
|
"setproctitle~=1.3.4",
|
||||||
"tika-client~=0.10.0",
|
"tika-client~=0.10.0",
|
||||||
"tqdm~=4.67.1",
|
"torch~=2.10.0",
|
||||||
"watchdog~=6.0",
|
"watchfiles>=1.1.1",
|
||||||
"whitenoise~=6.9",
|
"whitenoise~=6.11",
|
||||||
"whoosh-reloaded>=2.7.5",
|
"whoosh-reloaded>=2.7.5",
|
||||||
"zxing-cpp~=2.3.0",
|
"zxing-cpp~=3.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
optional-dependencies.mariadb = [
|
optional-dependencies.mariadb = [
|
||||||
"mysqlclient~=2.2.7",
|
"mysqlclient~=2.2.7",
|
||||||
]
|
]
|
||||||
optional-dependencies.postgres = [
|
optional-dependencies.postgres = [
|
||||||
"psycopg[c,pool]==3.2.12",
|
"psycopg[c,pool]==3.3",
|
||||||
# Direct dependency for proper resolution of the pre-built wheels
|
# Direct dependency for proper resolution of the pre-built wheels
|
||||||
"psycopg-c==3.2.12",
|
"psycopg-c==3.3",
|
||||||
"psycopg-pool==3.2.7",
|
"psycopg-pool==3.3",
|
||||||
]
|
]
|
||||||
optional-dependencies.webserver = [
|
optional-dependencies.webserver = [
|
||||||
"granian[uvloop]~=2.5.1",
|
"granian[uvloop]~=2.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
@@ -96,29 +105,29 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
docs = [
|
docs = [
|
||||||
"mkdocs-glightbox~=0.5.1",
|
"zensical>=0.0.21",
|
||||||
"mkdocs-material~=9.7.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
testing = [
|
testing = [
|
||||||
"daphne",
|
"daphne",
|
||||||
"factory-boy~=3.3.1",
|
"factory-boy~=3.3.1",
|
||||||
|
"faker~=40.8.0",
|
||||||
"imagehash",
|
"imagehash",
|
||||||
"pytest~=8.4.1",
|
"pytest~=9.0.0",
|
||||||
"pytest-cov~=7.0.0",
|
"pytest-cov~=7.0.0",
|
||||||
"pytest-django~=4.11.1",
|
"pytest-django~=4.12.0",
|
||||||
"pytest-env",
|
"pytest-env~=1.5.0",
|
||||||
"pytest-httpx",
|
"pytest-httpx",
|
||||||
"pytest-mock",
|
"pytest-mock~=3.15.1",
|
||||||
"pytest-rerunfailures",
|
#"pytest-randomly~=4.0.1",
|
||||||
|
"pytest-rerunfailures~=16.1",
|
||||||
"pytest-sugar",
|
"pytest-sugar",
|
||||||
"pytest-xdist",
|
"pytest-xdist~=3.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
lint = [
|
lint = [
|
||||||
"pre-commit~=4.4.0",
|
"prek~=0.3.0",
|
||||||
"pre-commit-uv~=4.2.0",
|
"ruff~=0.15.0",
|
||||||
"ruff~=0.14.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
typing = [
|
typing = [
|
||||||
@@ -127,8 +136,12 @@ typing = [
|
|||||||
"django-stubs[compatible-mypy]",
|
"django-stubs[compatible-mypy]",
|
||||||
"djangorestframework-stubs[compatible-mypy]",
|
"djangorestframework-stubs[compatible-mypy]",
|
||||||
"lxml-stubs",
|
"lxml-stubs",
|
||||||
|
"microsoft-python-type-stubs @ git+https://github.com/microsoft/python-type-stubs.git",
|
||||||
"mypy",
|
"mypy",
|
||||||
|
"mypy-baseline",
|
||||||
|
"pyrefly",
|
||||||
"types-bleach",
|
"types-bleach",
|
||||||
|
"types-channels",
|
||||||
"types-colorama",
|
"types-colorama",
|
||||||
"types-dateparser",
|
"types-dateparser",
|
||||||
"types-markdown",
|
"types-markdown",
|
||||||
@@ -137,30 +150,34 @@ typing = [
|
|||||||
"types-pytz",
|
"types-pytz",
|
||||||
"types-redis",
|
"types-redis",
|
||||||
"types-setuptools",
|
"types-setuptools",
|
||||||
"types-tqdm",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
required-version = ">=0.5.14"
|
required-version = ">=0.9.0"
|
||||||
package = false
|
package = false
|
||||||
environments = [
|
environments = [
|
||||||
"sys_platform == 'darwin'",
|
"sys_platform == 'darwin'",
|
||||||
"sys_platform == 'linux'",
|
"sys_platform == 'linux'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[tool.uv.index]]
|
||||||
|
name = "pytorch-cpu"
|
||||||
|
url = "https://download.pytorch.org/whl/cpu"
|
||||||
|
explicit = true
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
# Markers are chosen to select these almost exclusively when building the Docker image
|
# Markers are chosen to select these almost exclusively when building the Docker image
|
||||||
psycopg-c = [
|
psycopg-c = [
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-bookworm-3.2.12/psycopg_c-3.2.12-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-bookworm-3.2.12/psycopg_c-3.2.12-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||||
]
|
]
|
||||||
zxing-cpp = [
|
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
torch = [
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
{ index = "pytorch-cpu" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py310"
|
target-version = "py311"
|
||||||
line-length = 88
|
line-length = 88
|
||||||
src = [
|
src = [
|
||||||
"src",
|
"src",
|
||||||
@@ -238,14 +255,18 @@ lint.isort.force-single-line = true
|
|||||||
|
|
||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
write-changes = true
|
write-changes = true
|
||||||
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober"
|
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober,commitish"
|
||||||
skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
|
skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest]
|
||||||
minversion = "8.0"
|
minversion = "9.0"
|
||||||
pythonpath = [
|
pythonpath = [ "src" ]
|
||||||
"src",
|
|
||||||
]
|
strict_config = true
|
||||||
|
strict_markers = true
|
||||||
|
strict_parametrization_ids = true
|
||||||
|
strict_xfail = true
|
||||||
|
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"src/documents/tests/",
|
"src/documents/tests/",
|
||||||
"src/paperless/tests/",
|
"src/paperless/tests/",
|
||||||
@@ -253,7 +274,10 @@ testpaths = [
|
|||||||
"src/paperless_tesseract/tests/",
|
"src/paperless_tesseract/tests/",
|
||||||
"src/paperless_tika/tests",
|
"src/paperless_tika/tests",
|
||||||
"src/paperless_text/tests/",
|
"src/paperless_text/tests/",
|
||||||
|
"src/paperless_remote/tests/",
|
||||||
|
"src/paperless_ai/tests",
|
||||||
]
|
]
|
||||||
|
|
||||||
addopts = [
|
addopts = [
|
||||||
"--pythonwarnings=all",
|
"--pythonwarnings=all",
|
||||||
"--cov",
|
"--cov",
|
||||||
@@ -261,18 +285,39 @@ addopts = [
|
|||||||
"--cov-report=xml",
|
"--cov-report=xml",
|
||||||
"--numprocesses=auto",
|
"--numprocesses=auto",
|
||||||
"--maxprocesses=16",
|
"--maxprocesses=16",
|
||||||
"--quiet",
|
"--dist=loadscope",
|
||||||
"--durations=50",
|
"--durations=50",
|
||||||
|
"--durations-min=0.5",
|
||||||
"--junitxml=junit.xml",
|
"--junitxml=junit.xml",
|
||||||
"-o junit_family=legacy",
|
"-o",
|
||||||
|
"junit_family=legacy",
|
||||||
]
|
]
|
||||||
|
|
||||||
norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ]
|
norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ]
|
||||||
|
|
||||||
DJANGO_SETTINGS_MODULE = "paperless.settings"
|
DJANGO_SETTINGS_MODULE = "paperless.settings"
|
||||||
|
|
||||||
|
markers = [
|
||||||
|
"live: Integration tests requiring external services (Gotenberg, Tika, nginx, etc)",
|
||||||
|
"nginx: Tests that make HTTP requests to the local nginx service",
|
||||||
|
"gotenberg: Tests requiring Gotenberg service",
|
||||||
|
"tika: Tests requiring Tika service",
|
||||||
|
"greenmail: Tests requiring Greenmail service",
|
||||||
|
"date_parsing: Tests which cover date parsing from content or filename",
|
||||||
|
"management: Tests which cover management commands/functionality",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.pytest_env]
|
[tool.pytest_env]
|
||||||
PAPERLESS_DISABLE_DBHANDLER = "true"
|
PAPERLESS_DISABLE_DBHANDLER = "true"
|
||||||
PAPERLESS_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache"
|
PAPERLESS_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache"
|
||||||
|
PAPERLESS_CHANNELS_BACKEND = "channels.layers.InMemoryChannelLayer"
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_also = [
|
||||||
|
"if settings.AUDIT_LOG_ENABLED:",
|
||||||
|
"if AUDIT_LOG_ENABLED:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = [
|
source = [
|
||||||
@@ -285,13 +330,6 @@ omit = [
|
|||||||
"paperless/auth.py",
|
"paperless/auth.py",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.coverage.report]
|
|
||||||
exclude_also = [
|
|
||||||
"if settings.AUDIT_LOG_ENABLED:",
|
|
||||||
"if AUDIT_LOG_ENABLED:",
|
|
||||||
"if TYPE_CHECKING:",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
mypy_path = "src"
|
mypy_path = "src"
|
||||||
plugins = [
|
plugins = [
|
||||||
@@ -305,5 +343,15 @@ disallow_untyped_defs = true
|
|||||||
warn_redundant_casts = true
|
warn_redundant_casts = true
|
||||||
warn_unused_ignores = true
|
warn_unused_ignores = true
|
||||||
|
|
||||||
|
[tool.pyrefly]
|
||||||
|
search-path = [ "src" ]
|
||||||
|
baseline = ".pyrefly-baseline.json"
|
||||||
|
python-platform = "linux"
|
||||||
|
|
||||||
[tool.django-stubs]
|
[tool.django-stubs]
|
||||||
django_settings_module = "paperless.settings"
|
django_settings_module = "paperless.settings"
|
||||||
|
|
||||||
|
[tool.mypy-baseline]
|
||||||
|
baseline_path = ".mypy-baseline.txt"
|
||||||
|
sort_baseline = true
|
||||||
|
ignore_categories = [ "note" ]
|
||||||
|
|||||||
@@ -19,6 +19,4 @@ following additional information about it:
|
|||||||
* Correspondent: ${DOCUMENT_CORRESPONDENT}
|
* Correspondent: ${DOCUMENT_CORRESPONDENT}
|
||||||
* Tags: ${DOCUMENT_TAGS}
|
* Tags: ${DOCUMENT_TAGS}
|
||||||
|
|
||||||
It was consumed with the passphrase ${PASSPHRASE}
|
|
||||||
|
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"ignorePatterns": [
|
|
||||||
"projects/**/*",
|
|
||||||
"/src/app/components/common/pdf-viewer/**"
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.ts"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": [
|
|
||||||
"tsconfig.json"
|
|
||||||
],
|
|
||||||
"createDefaultProgram": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:@angular-eslint/recommended",
|
|
||||||
"plugin:@angular-eslint/template/process-inline-templates"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "attribute",
|
|
||||||
"prefix": "pngx",
|
|
||||||
"style": "camelCase"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "element",
|
|
||||||
"prefix": "pngx",
|
|
||||||
"style": "kebab-case"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.html"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"plugin:@angular-eslint/template/recommended"
|
|
||||||
],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
],
|
],
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"allowedCommonJsDependencies": [
|
"allowedCommonJsDependencies": [
|
||||||
"ng2-pdf-viewer",
|
|
||||||
"file-saver",
|
"file-saver",
|
||||||
"utif"
|
"utif"
|
||||||
],
|
],
|
||||||
@@ -156,16 +155,7 @@
|
|||||||
"builder": "@angular-builders/jest:run",
|
"builder": "@angular-builders/jest:run",
|
||||||
"options": {
|
"options": {
|
||||||
"tsConfig": "tsconfig.spec.json",
|
"tsConfig": "tsconfig.spec.json",
|
||||||
"assets": [
|
"zoneless": false
|
||||||
"src/favicon.ico",
|
|
||||||
"src/apple-touch-icon.png",
|
|
||||||
"src/assets",
|
|
||||||
"src/manifest.webmanifest"
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"src/styles.scss"
|
|
||||||
],
|
|
||||||
"scripts": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ test('dashboard saved view document links', async ({ page }) => {
|
|||||||
test('test slim sidebar', async ({ page }) => {
|
test('test slim sidebar', async ({ page }) => {
|
||||||
await page.routeFromHAR(REQUESTS_HAR1, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR1, { notFound: 'fallback' })
|
||||||
await page.goto('/dashboard')
|
await page.goto('/dashboard')
|
||||||
await page.locator('#sidebarMenu').getByRole('button').click()
|
await page.locator('.sidebar-slim-toggler').click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Dashboard' }).getByText('Dashboard')
|
page.getByRole('link', { name: 'Dashboard' }).getByText('Dashboard')
|
||||||
).toBeHidden()
|
).toBeHidden()
|
||||||
await page.locator('#sidebarMenu').getByRole('button').click()
|
await page.locator('.sidebar-slim-toggler').click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Dashboard' }).getByText('Dashboard')
|
page.getByRole('link', { name: 'Dashboard' }).getByText('Dashboard')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
@@ -468,7 +468,7 @@
|
|||||||
"time": 0.951,
|
"time": 0.951,
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=9",
|
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true&tags__id__in=9",
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
@@ -534,7 +534,7 @@
|
|||||||
"time": 0.653,
|
"time": 0.653,
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9",
|
"url": "http://localhost:8000/api/documents/?page=1&page_size=10&ordering=-created&truncate_content=true&include_selection_data=true&tags__id__all=9",
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"size": -1,
|
"size": -1,
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
"text": "{\"user\":{\"id\":2,\"username\":\"testuser\",\"is_superuser\":false,\"groups\":[]},\"settings\":{\"language\":\"\",\"bulk_edit\":{\"confirmation_dialogs\":true,\"apply_on_close\":false},\"documentListSize\":50,\"dark_mode\":{\"use_system\":false,\"enabled\":\"false\",\"thumb_inverted\":\"true\"},\"theme\":{\"color\":\"#9fbf2f\"},\"document_details\":{\"native_pdf_viewer\":false},\"date_display\":{\"date_locale\":\"\",\"date_format\":\"mediumDate\"},\"notifications\":{\"consumer_new_documents\":true,\"consumer_success\":true,\"consumer_failed\":true,\"consumer_suppress_on_dashboard\":true},\"comments_enabled\":true,\"slim_sidebar\":false,\"update_checking\":{\"enabled\":false,\"backend_setting\":\"default\"},\"saved_views\":{\"warn_on_unsaved_change\":true,\"dashboard_views_visible_ids\":[7,4],\"sidebar_views_visible_ids\":[7,4,11]},\"notes_enabled\":true,\"tour_complete\":true},\"permissions\":[\"delete_schedule\",\"view_note\",\"view_taskattributes\",\"delete_ormq\",\"add_ormq\",\"change_tag\",\"change_chordcounter\",\"delete_savedviewfilterrule\",\"delete_comment\",\"add_session\",\"add_mailrule\",\"add_tokenproxy\",\"delete_taskresult\",\"view_failure\",\"add_tag\",\"view_savedviewfilterrule\",\"view_paperlesstask\",\"change_mailaccount\",\"change_frontendsettings\",\"delete_group\",\"add_userobjectpermission\",\"add_failure\",\"delete_mailrule\",\"view_userobjectpermission\",\"change_schedule\",\"delete_note\",\"view_ormq\",\"add_note\",\"add_chordcounter\",\"delete_token\",\"change_failure\",\"add_savedviewfilterrule\",\"delete_user\",\"view_correspondent\",\"view_schedule\",\"change_tokenproxy\",\"view_user\",\"delete_task\",\"delete_correspondent\",\"delete_chordcounter\",\"delete_document\",\"view_taskresult\",\"change_document\",\"add_frontendsettings\",\"view_mailrule\",\"change_ormq\",\"delete_taskattributes\",\"view_logentry\",\"view_mailaccount\",\"view_log\",\"delete_success\",\"view_frontendsettings\",\"view_documenttype\",\"change_taskresult\",\"view_permission\",\"add_groupobjectpermission\",\"change_user\",\"view_document\",\"change_userobjectpermission\",\"add_user\",\"add_correspondent\",\"add_token\",\"add_mailaccount\",\"change_group\",\"add_group\",\"delete_processedmail\",\"delete_contenttype\",\"add_savedview\",\"view_chordcounter\",\"delete_tokenproxy\",\"change_groupresult\",\"delete_session\",\"view_savedview\",\"view_processedmail\",\"add_comment\",\"view_storagepath\",\"delete_documenttype\",\"add_processedmail\",\"view_group\",\"change_processedmail\",\"view_session\",\"delete_storagepath\",\"delete_paperlesstask\",\"add_groupresult\",\"delete_savedview\",\"delete_userobjectpermission\",\"view_tokenproxy\",\"add_task\",\"view_tag\",\"add_taskresult\",\"change_documenttype\",\"change_mailrule\",\"add_document\",\"change_comment\",\"view_task\",\"view_groupresult\",\"change_contenttype\",\"view_groupobjectpermission\",\"change_task\",\"add_log\",\"add_success\",\"change_savedview\",\"delete_frontendsettings\",\"view_success\",\"add_permission\",\"change_correspondent\",\"add_paperlesstask\",\"change_paperlesstask\",\"add_contenttype\",\"view_comment\",\"change_logentry\",\"delete_logentry\",\"delete_mailaccount\",\"change_session\",\"delete_groupresult\",\"add_logentry\",\"change_savedviewfilterrule\",\"change_success\",\"delete_tag\",\"add_taskattributes\",\"change_groupobjectpermission\",\"delete_failure\",\"add_uisettings\",\"view_token\",\"add_schedule\",\"delete_log\",\"delete_uisettings\",\"change_permission\",\"delete_groupobjectpermission\",\"change_token\",\"view_uisettings\",\"change_uisettings\",\"delete_permission\",\"add_storagepath\",\"change_storagepath\",\"view_contenttype\",\"change_note\",\"change_log\",\"change_taskattributes\",\"add_documenttype\"]}"
|
||||||
},
|
},
|
||||||
"headersSize": -1,
|
"headersSize": -1,
|
||||||
"bodySize": -1,
|
"bodySize": -1,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ test('should show a mobile preview', async ({ page }) => {
|
|||||||
await page.setViewportSize({ width: 400, height: 1000 })
|
await page.setViewportSize({ width: 400, height: 1000 })
|
||||||
await expect(page.getByRole('tab', { name: 'Preview' })).toBeVisible()
|
await expect(page.getByRole('tab', { name: 'Preview' })).toBeVisible()
|
||||||
await page.getByRole('tab', { name: 'Preview' }).click()
|
await page.getByRole('tab', { name: 'Preview' }).click()
|
||||||
await page.waitForSelector('pdf-viewer')
|
await page.waitForSelector('pngx-pdf-viewer')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should show a list of notes', async ({ page }) => {
|
test('should show a list of notes', async ({ page }) => {
|
||||||
|
|||||||
@@ -883,7 +883,7 @@
|
|||||||
"time": 0.93,
|
"time": 0.93,
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=4",
|
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true&tags__id__all=4",
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
@@ -961,7 +961,7 @@
|
|||||||
"time": -1,
|
"time": -1,
|
||||||
"request": {
|
"request": {
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=4",
|
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&include_selection_data=true&tags__id__all=4",
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ test('basic filtering', async ({ page }) => {
|
|||||||
await expect(page).toHaveURL(/tags__id__all=9/)
|
await expect(page).toHaveURL(/tags__id__all=9/)
|
||||||
await expect(page.locator('pngx-document-list')).toHaveText(/8 documents/)
|
await expect(page.locator('pngx-document-list')).toHaveText(/8 documents/)
|
||||||
await page.getByRole('button', { name: 'Document type' }).click()
|
await page.getByRole('button', { name: 'Document type' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Invoice Test 3' }).click()
|
await page.getByRole('menuitem', { name: /^Invoice Test/ }).click()
|
||||||
await expect(page).toHaveURL(/document_type__id__in=1/)
|
await expect(page).toHaveURL(/document_type__id__in=1/)
|
||||||
await expect(page.locator('pngx-document-list')).toHaveText(/3 documents/)
|
await expect(page.locator('pngx-document-list')).toHaveText(/3 documents/)
|
||||||
await page.getByRole('button', { name: 'Reset filters' }).first().click()
|
await page.getByRole('button', { name: 'Reset filters' }).first().click()
|
||||||
@@ -180,6 +180,9 @@ test('bulk edit', async ({ page }) => {
|
|||||||
await page.locator('pngx-document-card-small').nth(2).click()
|
await page.locator('pngx-document-card-small').nth(2).click()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tags' }).click()
|
await page.getByRole('button', { name: 'Tags' }).click()
|
||||||
|
await page
|
||||||
|
.getByRole('textbox', { name: 'Filter tags' })
|
||||||
|
.fill('TagWithPartial')
|
||||||
await page.getByRole('menuitem', { name: 'TagWithPartial' }).click()
|
await page.getByRole('menuitem', { name: 'TagWithPartial' }).click()
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Apply' }).click()
|
await page.getByRole('button', { name: 'Apply' }).click()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -33,9 +33,9 @@ test('should not allow user to view correspondents', async ({ page }) => {
|
|||||||
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
||||||
await page.goto('/dashboard')
|
await page.goto('/dashboard')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Correspondents' })
|
page.getByRole('link', { name: 'Attributes' })
|
||||||
).not.toBeAttached()
|
).not.toBeAttached()
|
||||||
await page.goto('/correspondents')
|
await page.goto('/attributes/correspondents')
|
||||||
await expect(page.locator('body')).toHaveText(
|
await expect(page.locator('body')).toHaveText(
|
||||||
/You don't have permissions to do that/i
|
/You don't have permissions to do that/i
|
||||||
)
|
)
|
||||||
@@ -44,8 +44,10 @@ test('should not allow user to view correspondents', async ({ page }) => {
|
|||||||
test('should not allow user to view tags', async ({ page }) => {
|
test('should not allow user to view tags', async ({ page }) => {
|
||||||
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
||||||
await page.goto('/dashboard')
|
await page.goto('/dashboard')
|
||||||
await expect(page.getByRole('link', { name: 'Tags' })).not.toBeAttached()
|
await expect(
|
||||||
await page.goto('/tags')
|
page.getByRole('link', { name: 'Attributes' })
|
||||||
|
).not.toBeAttached()
|
||||||
|
await page.goto('/attributes/tags')
|
||||||
await expect(page.locator('body')).toHaveText(
|
await expect(page.locator('body')).toHaveText(
|
||||||
/You don't have permissions to do that/i
|
/You don't have permissions to do that/i
|
||||||
)
|
)
|
||||||
@@ -55,9 +57,9 @@ test('should not allow user to view document types', async ({ page }) => {
|
|||||||
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
||||||
await page.goto('/dashboard')
|
await page.goto('/dashboard')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Document Types' })
|
page.getByRole('link', { name: 'Attributes' })
|
||||||
).not.toBeAttached()
|
).not.toBeAttached()
|
||||||
await page.goto('/documenttypes')
|
await page.goto('/attributes/documenttypes')
|
||||||
await expect(page.locator('body')).toHaveText(
|
await expect(page.locator('body')).toHaveText(
|
||||||
/You don't have permissions to do that/i
|
/You don't have permissions to do that/i
|
||||||
)
|
)
|
||||||
@@ -67,9 +69,9 @@ test('should not allow user to view storage paths', async ({ page }) => {
|
|||||||
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
||||||
await page.goto('/dashboard')
|
await page.goto('/dashboard')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: 'Storage Paths' })
|
page.getByRole('link', { name: 'Attributes' })
|
||||||
).not.toBeAttached()
|
).not.toBeAttached()
|
||||||
await page.goto('/storagepaths')
|
await page.goto('/attributes/storagepaths')
|
||||||
await expect(page.locator('body')).toHaveText(
|
await expect(page.locator('body')).toHaveText(
|
||||||
/You don't have permissions to do that/i
|
/You don't have permissions to do that/i
|
||||||
)
|
)
|
||||||
|
|||||||
58
src-ui/eslint.config.js
Normal file
58
src-ui/eslint.config.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const angularEslintPlugin = require('@angular-eslint/eslint-plugin')
|
||||||
|
const angularTemplatePlugin = require('@angular-eslint/eslint-plugin-template')
|
||||||
|
const angularTemplateParser = require('@angular-eslint/template-parser')
|
||||||
|
const tsParser = require('@typescript-eslint/parser')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['projects/**/*', 'src/app/components/common/pdf-viewer/**'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: ['tsconfig.json'],
|
||||||
|
createDefaultProgram: true,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@angular-eslint': angularEslintPlugin,
|
||||||
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
|
},
|
||||||
|
processor: '@angular-eslint/template/extract-inline-html',
|
||||||
|
rules: {
|
||||||
|
...angularEslintPlugin.configs.recommended.rules,
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'pngx',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'pngx',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: angularTemplateParser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...angularTemplatePlugin.configs.recommended.rules,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
|
const { createEsmPreset } = require('jest-preset-angular/presets')
|
||||||
|
|
||||||
|
const esmPreset = createEsmPreset({
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'jest-preset-angular',
|
...esmPreset,
|
||||||
|
transform: {
|
||||||
|
...esmPreset.transform,
|
||||||
|
'^.+\\.(ts|js|mjs|html|svg)$': [
|
||||||
|
'jest-preset-angular',
|
||||||
|
{
|
||||||
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
|
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||||
|
useESM: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
'/node_modules/',
|
'/node_modules/',
|
||||||
@@ -8,10 +26,15 @@ module.exports = {
|
|||||||
'abstract-paperless-service',
|
'abstract-paperless-service',
|
||||||
],
|
],
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
`<rootDir>/node_modules/.pnpm/(?!.*\\.mjs$|lodash-es|@angular\\+common.*locales)`,
|
'node_modules/(?!.*(\\.mjs$|tslib|lodash-es|@angular/common/locales/.*\\.js$))',
|
||||||
],
|
],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
...esmPreset.moduleNameMapper,
|
||||||
'^src/(.*)': '<rootDir>/src/$1',
|
'^src/(.*)': '<rootDir>/src/$1',
|
||||||
|
'^pdfjs-dist/legacy/build/pdf\\.mjs$':
|
||||||
|
'<rootDir>/src/test/mocks/pdfjs-legacy-build-pdf.ts',
|
||||||
|
'^pdfjs-dist/web/pdf_viewer\\.mjs$':
|
||||||
|
'<rootDir>/src/test/mocks/pdfjs-web-pdf_viewer.ts',
|
||||||
},
|
},
|
||||||
workerIdleMemoryLimit: '512MB',
|
workerIdleMemoryLimit: '512MB',
|
||||||
reporters: [
|
reporters: [
|
||||||
|
|||||||
4278
src-ui/messages.xlf
4278
src-ui/messages.xlf
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperless-ngx-ui",
|
"name": "paperless-ngx-ui",
|
||||||
"version": "2.20.3",
|
"version": "2.20.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -11,64 +11,64 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/cdk": "^20.2.13",
|
"@angular/cdk": "^21.2.0",
|
||||||
"@angular/common": "~20.3.15",
|
"@angular/common": "~21.2.0",
|
||||||
"@angular/compiler": "~20.3.15",
|
"@angular/compiler": "~21.2.0",
|
||||||
"@angular/core": "~20.3.15",
|
"@angular/core": "~21.2.0",
|
||||||
"@angular/forms": "~20.3.15",
|
"@angular/forms": "~21.2.0",
|
||||||
"@angular/localize": "~20.3.15",
|
"@angular/localize": "~21.2.0",
|
||||||
"@angular/platform-browser": "~20.3.15",
|
"@angular/platform-browser": "~21.2.0",
|
||||||
"@angular/platform-browser-dynamic": "~20.3.15",
|
"@angular/platform-browser-dynamic": "~21.2.0",
|
||||||
"@angular/router": "~20.3.15",
|
"@angular/router": "~21.2.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^19.0.1",
|
"@ng-bootstrap/ng-bootstrap": "^20.0.0",
|
||||||
"@ng-select/ng-select": "^20.7.0",
|
"@ng-select/ng-select": "^21.4.1",
|
||||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"mime-names": "^1.0.0",
|
"mime-names": "^1.0.0",
|
||||||
"ng2-pdf-viewer": "^10.4.0",
|
|
||||||
"ngx-bootstrap-icons": "^1.9.3",
|
"ngx-bootstrap-icons": "^1.9.3",
|
||||||
"ngx-color": "^10.1.0",
|
"ngx-color": "^10.1.0",
|
||||||
"ngx-cookie-service": "^20.1.1",
|
"ngx-cookie-service": "^21.1.0",
|
||||||
"ngx-device-detector": "^10.1.0",
|
"ngx-device-detector": "^11.0.0",
|
||||||
"ngx-ui-tour-ng-bootstrap": "^17.0.1",
|
"ngx-ui-tour-ng-bootstrap": "^18.0.0",
|
||||||
|
"pdfjs-dist": "^5.4.624",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"zone.js": "^0.15.1"
|
"zone.js": "^0.16.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "^20.0.0",
|
"@angular-builders/custom-webpack": "^21.0.3",
|
||||||
"@angular-builders/jest": "^20.0.0",
|
"@angular-builders/jest": "^21.0.3",
|
||||||
"@angular-devkit/core": "^20.3.13",
|
"@angular-devkit/core": "^21.2.0",
|
||||||
"@angular-devkit/schematics": "^20.3.13",
|
"@angular-devkit/schematics": "^21.2.0",
|
||||||
"@angular-eslint/builder": "20.6.0",
|
"@angular-eslint/builder": "21.3.0",
|
||||||
"@angular-eslint/eslint-plugin": "20.6.0",
|
"@angular-eslint/eslint-plugin": "21.3.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "20.6.0",
|
"@angular-eslint/eslint-plugin-template": "21.3.0",
|
||||||
"@angular-eslint/schematics": "20.6.0",
|
"@angular-eslint/schematics": "21.3.0",
|
||||||
"@angular-eslint/template-parser": "20.6.0",
|
"@angular-eslint/template-parser": "21.3.0",
|
||||||
"@angular/build": "^20.3.13",
|
"@angular/build": "^21.2.0",
|
||||||
"@angular/cli": "~20.3.13",
|
"@angular/cli": "~21.2.0",
|
||||||
"@angular/compiler-cli": "~20.3.15",
|
"@angular/compiler-cli": "~21.2.0",
|
||||||
"@codecov/webpack-plugin": "^1.9.1",
|
"@codecov/webpack-plugin": "^1.9.1",
|
||||||
"@playwright/test": "^1.57.0",
|
"@playwright/test": "^1.58.2",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^25.3.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||||
"@typescript-eslint/parser": "^8.48.1",
|
"@typescript-eslint/parser": "^8.54.0",
|
||||||
"@typescript-eslint/utils": "^8.48.1",
|
"@typescript-eslint/utils": "^8.54.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^10.0.2",
|
||||||
"jest": "30.2.0",
|
"jest": "30.2.0",
|
||||||
"jest-environment-jsdom": "^30.2.0",
|
"jest-environment-jsdom": "^30.2.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"jest-preset-angular": "^15.0.3",
|
"jest-preset-angular": "^16.1.1",
|
||||||
"jest-websocket-mock": "^2.5.0",
|
"jest-websocket-mock": "^2.5.0",
|
||||||
"prettier-plugin-organize-imports": "^4.3.0",
|
"prettier-plugin-organize-imports": "^4.3.0",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.3",
|
||||||
"webpack": "^5.103.0"
|
"webpack": "^5.105.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.17.1",
|
"packageManager": "pnpm@10.17.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
8484
src-ui/pnpm-lock.yaml
generated
8484
src-ui/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -100,10 +100,10 @@ const mock = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(window, 'open', { value: jest.fn() })
|
Object.defineProperty(globalThis, 'open', { value: jest.fn() })
|
||||||
Object.defineProperty(window, 'localStorage', { value: mock() })
|
Object.defineProperty(globalThis, 'localStorage', { value: mock() })
|
||||||
Object.defineProperty(window, 'sessionStorage', { value: mock() })
|
Object.defineProperty(globalThis, 'sessionStorage', { value: mock() })
|
||||||
Object.defineProperty(window, 'getComputedStyle', {
|
Object.defineProperty(globalThis, 'getComputedStyle', {
|
||||||
value: () => ['-webkit-appearance'],
|
value: () => ['-webkit-appearance'],
|
||||||
})
|
})
|
||||||
Object.defineProperty(navigator, 'clipboard', {
|
Object.defineProperty(navigator, 'clipboard', {
|
||||||
@@ -115,13 +115,33 @@ Object.defineProperty(navigator, 'canShare', { value: () => true })
|
|||||||
if (!navigator.share) {
|
if (!navigator.share) {
|
||||||
Object.defineProperty(navigator, 'share', { value: jest.fn() })
|
Object.defineProperty(navigator, 'share', { value: jest.fn() })
|
||||||
}
|
}
|
||||||
if (!URL.createObjectURL) {
|
if (!globalThis.URL.createObjectURL) {
|
||||||
Object.defineProperty(window.URL, 'createObjectURL', { value: jest.fn() })
|
Object.defineProperty(globalThis.URL, 'createObjectURL', { value: jest.fn() })
|
||||||
}
|
}
|
||||||
if (!URL.revokeObjectURL) {
|
if (!globalThis.URL.revokeObjectURL) {
|
||||||
Object.defineProperty(window.URL, 'revokeObjectURL', { value: jest.fn() })
|
Object.defineProperty(globalThis.URL, 'revokeObjectURL', { value: jest.fn() })
|
||||||
}
|
}
|
||||||
Object.defineProperty(window, 'ResizeObserver', { value: mock() })
|
class MockResizeObserver {
|
||||||
|
private readonly callback: ResizeObserverCallback
|
||||||
|
|
||||||
|
constructor(callback: ResizeObserverCallback) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
observe = jest.fn()
|
||||||
|
unobserve = jest.fn()
|
||||||
|
disconnect = jest.fn()
|
||||||
|
|
||||||
|
trigger = (entries: ResizeObserverEntry[] = []) => {
|
||||||
|
this.callback(entries, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(globalThis, 'ResizeObserver', {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: MockResizeObserver,
|
||||||
|
})
|
||||||
|
|
||||||
if (typeof IntersectionObserver === 'undefined') {
|
if (typeof IntersectionObserver === 'undefined') {
|
||||||
class MockIntersectionObserver {
|
class MockIntersectionObserver {
|
||||||
@@ -136,7 +156,7 @@ if (typeof IntersectionObserver === 'undefined') {
|
|||||||
takeRecords = jest.fn()
|
takeRecords = jest.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(window, 'IntersectionObserver', {
|
Object.defineProperty(globalThis, 'IntersectionObserver', {
|
||||||
writable: true,
|
writable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
value: MockIntersectionObserver,
|
value: MockIntersectionObserver,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user