mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-17 09:24:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5446b7c97a | ||
|
|
b7b332af3a |
@@ -38,7 +38,7 @@ repos:
|
||||
- json
|
||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.9.4'
|
||||
rev: 'v3.8.4'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
@@ -46,16 +46,16 @@ repos:
|
||||
- ts
|
||||
- markdown
|
||||
additional_dependencies:
|
||||
- prettier@3.9.4
|
||||
- prettier@3.8.3
|
||||
- 'prettier-plugin-organize-imports@4.3.0'
|
||||
# Python hooks
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.20
|
||||
rev: v0.15.17
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "v2.25.1"
|
||||
rev: "v2.24.1"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
additional_dependencies: [tomli]
|
||||
|
||||
+1
-5
@@ -30,7 +30,7 @@ RUN set -eux \
|
||||
# Purpose: Installs s6-overlay and rootfs
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here either
|
||||
FROM ghcr.io/astral-sh/uv:0.11.28-python3.12-trixie-slim AS s6-overlay-base
|
||||
FROM ghcr.io/astral-sh/uv:0.11.19-python3.12-trixie-slim AS s6-overlay-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
|
||||
@@ -238,10 +238,6 @@ RUN set -eux \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
|
||||
&& echo "Making fontconfig cache writable for arbitrary container UIDs" \
|
||||
&& chmod 1777 /var/cache/fontconfig \
|
||||
&& echo "Making /run world-writable for rootless operation" \
|
||||
&& chmod 1777 /run \
|
||||
&& echo "Removing setuid from s6-overlay-suexec for rootless compat" \
|
||||
&& chmod u-s /command/s6-overlay-suexec \
|
||||
&& echo "Collecting static files" \
|
||||
&& PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py collectstatic --clear --no-input --link \
|
||||
&& PAPERLESS_SECRET_KEY=build-time-dummy s6-setuidgid paperless python3 manage.py compilemessages \
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# correct networking for the tests
|
||||
services:
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
hostname: gotenberg
|
||||
container_name: gotenberg
|
||||
network_mode: host
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
greenmail:
|
||||
image: docker.io/greenmail/standalone:2.1.9
|
||||
image: docker.io/greenmail/standalone:2.1.8
|
||||
hostname: greenmail
|
||||
container_name: greenmail
|
||||
environment:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
- "3143:3143" # IMAP
|
||||
restart: unless-stopped
|
||||
nginx:
|
||||
image: docker.io/nginx:1.31.2-alpine
|
||||
image: docker.io/nginx:1.31.1-alpine
|
||||
hostname: nginx
|
||||
container_name: nginx
|
||||
ports:
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
||||
@@ -67,7 +67,7 @@ services:
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:8.34
|
||||
image: docker.io/gotenberg/gotenberg:8.33
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -142,7 +142,7 @@ a [superuser](usage.md#superusers) account.
|
||||
|
||||
It is not possible to run the container rootless if additional languages are specified via `PAPERLESS_OCR_LANGUAGES`.
|
||||
|
||||
If you want to run Paperless as a rootless container, set `user:` in `docker-compose.yml` to the UID and GID of your host user (use `id -u` and `id -g` to find these values). The container process starts directly as that user with no internal privilege remapping. Any UID and GID is supported:
|
||||
If you want to run Paperless as a rootless container, set `user:` in `docker-compose.yml` to the UID and GID of your host user (use `id -u` and `id -g` to find these values). The container process starts directly as that user with no internal privilege remapping:
|
||||
|
||||
```yaml
|
||||
webserver:
|
||||
|
||||
+2
-2
@@ -101,11 +101,11 @@ dev = [
|
||||
{ include-group = "testing" },
|
||||
]
|
||||
docs = [
|
||||
"zensical>=0.0.47",
|
||||
"zensical>=0.0.43",
|
||||
]
|
||||
lint = [
|
||||
"prek~=0.3.10",
|
||||
"ruff~=0.15.20",
|
||||
"ruff~=0.15.15",
|
||||
]
|
||||
testing = [
|
||||
"daphne",
|
||||
|
||||
+83
-112
@@ -332,7 +332,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
|
||||
@@ -379,11 +379,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">273</context>
|
||||
<context context-type="linenumber">272</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">275</context>
|
||||
<context context-type="linenumber">274</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5890330709052835856" datatype="html">
|
||||
@@ -724,11 +724,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">308</context>
|
||||
<context context-type="linenumber">307</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">311</context>
|
||||
<context context-type="linenumber">310</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2272120016352772836" datatype="html">
|
||||
@@ -757,14 +757,14 @@
|
||||
<source>lines</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8838884664569764142" datatype="html">
|
||||
<source>Auto refresh</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">22</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
@@ -775,11 +775,11 @@
|
||||
<source>Loading...</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
<context context-type="linenumber">48</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
@@ -886,7 +886,7 @@
|
||||
<source>Jump to bottom</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1255048712725285892" datatype="html">
|
||||
@@ -1135,11 +1135,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">233</context>
|
||||
<context context-type="linenumber">232</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">235</context>
|
||||
<context context-type="linenumber">234</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
|
||||
@@ -1696,7 +1696,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
<context context-type="linenumber">203</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context>
|
||||
@@ -1801,11 +1801,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">296</context>
|
||||
<context context-type="linenumber">295</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">298</context>
|
||||
<context context-type="linenumber">297</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8492095365580052820" datatype="html">
|
||||
@@ -1905,18 +1905,11 @@
|
||||
<context context-type="linenumber">227</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3570013039254960445" datatype="html">
|
||||
<source>Search tasks</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6849725902312323996" datatype="html">
|
||||
<source>Reset filters</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
<context context-type="linenumber">104</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
@@ -1931,14 +1924,14 @@
|
||||
<source>{VAR_PLURAL, plural, =1 {1 task} other {<x id="INTERPOLATION"/> tasks}}</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
<context context-type="linenumber">125</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8953033926734869941" datatype="html">
|
||||
<source>Name</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">148</context>
|
||||
<context context-type="linenumber">147</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
@@ -2049,7 +2042,7 @@
|
||||
<source>Created</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">149</context>
|
||||
<context context-type="linenumber">148</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||
@@ -2080,21 +2073,21 @@
|
||||
<source>Results</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">151</context>
|
||||
<context context-type="linenumber">150</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="314315645942131479" datatype="html">
|
||||
<source>Info</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
<context context-type="linenumber">152</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3193976279273491157" datatype="html">
|
||||
<source>Actions</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">154</context>
|
||||
<context context-type="linenumber">153</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/trash/trash.component.html</context>
|
||||
@@ -2165,14 +2158,14 @@
|
||||
<source>click for full output</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">205</context>
|
||||
<context context-type="linenumber">204</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1536087519743707362" datatype="html">
|
||||
<source>Dismiss</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
<context context-type="linenumber">217</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
@@ -2188,35 +2181,35 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2134950584701094962" datatype="html">
|
||||
<source>Open Document</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">223</context>
|
||||
<context context-type="linenumber">222</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5404759957685833020" datatype="html">
|
||||
<source>Result message</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">236</context>
|
||||
<context context-type="linenumber">235</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6621329748219109148" datatype="html">
|
||||
<source>Duplicate</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">243</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7593555694782789615" datatype="html">
|
||||
<source>Open</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">251</context>
|
||||
<context context-type="linenumber">250</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/global-search/global-search.component.html</context>
|
||||
@@ -2247,21 +2240,21 @@
|
||||
<source>Input data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">261</context>
|
||||
<context context-type="linenumber">260</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1585185618099050920" datatype="html">
|
||||
<source>Result data</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">267</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7976920528153858271" datatype="html">
|
||||
<source>No tasks match the current filters.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.html</context>
|
||||
<context context-type="linenumber">289</context>
|
||||
<context context-type="linenumber">288</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2525230676386818985" datatype="html">
|
||||
@@ -2520,11 +2513,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">256</context>
|
||||
<context context-type="linenumber">255</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">259</context>
|
||||
<context context-type="linenumber">258</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3818027200170621545" datatype="html">
|
||||
@@ -2877,11 +2870,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">287</context>
|
||||
<context context-type="linenumber">286</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">289</context>
|
||||
<context context-type="linenumber">288</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4569276013106377105" datatype="html">
|
||||
@@ -3218,11 +3211,11 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">317</context>
|
||||
<context context-type="linenumber">316</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">320</context>
|
||||
<context context-type="linenumber">319</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="472206565520537964" datatype="html">
|
||||
@@ -3247,36 +3240,36 @@
|
||||
<source>Close all</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">163</context>
|
||||
<context context-type="linenumber">162</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">165</context>
|
||||
<context context-type="linenumber">164</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3897348120591552265" datatype="html">
|
||||
<source>Manage</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">174</context>
|
||||
<context context-type="linenumber">173</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8008131619909556709" datatype="html">
|
||||
<source>Attributes</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">181</context>
|
||||
<context context-type="linenumber">180</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7437910965833684826" datatype="html">
|
||||
<source>Correspondents</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">209</context>
|
||||
<context context-type="linenumber">208</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.html</context>
|
||||
@@ -3295,7 +3288,7 @@
|
||||
<source>Document types</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">214</context>
|
||||
<context context-type="linenumber">213</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
|
||||
@@ -3306,7 +3299,7 @@
|
||||
<source>Storage paths</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">219</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.ts</context>
|
||||
@@ -3317,7 +3310,7 @@
|
||||
<source>Custom fields</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">224</context>
|
||||
<context context-type="linenumber">223</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
@@ -3336,11 +3329,11 @@
|
||||
<source>Workflows</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
<context context-type="linenumber">241</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">244</context>
|
||||
<context context-type="linenumber">243</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
|
||||
@@ -3351,71 +3344,71 @@
|
||||
<source>Mail</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">249</context>
|
||||
<context context-type="linenumber">248</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">252</context>
|
||||
<context context-type="linenumber">251</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7844706011418789951" datatype="html">
|
||||
<source>Administration</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">267</context>
|
||||
<context context-type="linenumber">266</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3008420115644088420" datatype="html">
|
||||
<source>Configuration</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">280</context>
|
||||
<context context-type="linenumber">279</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">282</context>
|
||||
<context context-type="linenumber">281</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1534029177398918729" datatype="html">
|
||||
<source>GitHub</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">327</context>
|
||||
<context context-type="linenumber">326</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4112664765954374539" datatype="html">
|
||||
<source>is available.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">336,337</context>
|
||||
<context context-type="linenumber">335,336</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1175891574282637937" datatype="html">
|
||||
<source>Click to view.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">337</context>
|
||||
<context context-type="linenumber">336</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9811291095862612" datatype="html">
|
||||
<source>Paperless-ngx can automatically check for updates</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">341</context>
|
||||
<context context-type="linenumber">340</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="894819944961861800" datatype="html">
|
||||
<source> How does this work? </source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">348,350</context>
|
||||
<context context-type="linenumber">347,349</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="509090351011426949" datatype="html">
|
||||
<source>Update available</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">361</context>
|
||||
<context context-type="linenumber">360</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1542489069631984294" datatype="html">
|
||||
@@ -3611,17 +3604,6 @@
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8066608938393600549" datatype="html">
|
||||
<source>Message</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/chat/chat/chat.component.html</context>
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3167745892412525370" datatype="html">
|
||||
<source>Ask a question about this document...</source>
|
||||
<context-group purpose="location">
|
||||
@@ -5846,6 +5828,13 @@
|
||||
<context context-type="linenumber">32</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8066608938393600549" datatype="html">
|
||||
<source>Message</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/email-document-dialog/email-document-dialog.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5867799091834207531" datatype="html">
|
||||
<source>Use archive version</source>
|
||||
<context-group purpose="location">
|
||||
@@ -6705,7 +6694,7 @@
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">88</context>
|
||||
<context context-type="linenumber">87</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
|
||||
@@ -6782,25 +6771,18 @@
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4306169234658044987" datatype="html">
|
||||
<source>Share link</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2618101617221155143" datatype="html">
|
||||
<source>Copy link</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">82</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8372007266188249803" datatype="html">
|
||||
<source>Never</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">94</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
|
||||
@@ -6815,7 +6797,7 @@
|
||||
<source>File version</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">97</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
|
||||
@@ -6826,7 +6808,7 @@
|
||||
<source>Size</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">100</context>
|
||||
<context context-type="linenumber">99</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component.html</context>
|
||||
@@ -6837,14 +6819,14 @@
|
||||
<source>A zip file containing the selected documents will be created for this share link bundle. This process happens in the background and may take some time, especially for large bundles.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">110</context>
|
||||
<context context-type="linenumber">109</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7274497464693086242" datatype="html">
|
||||
<source>Manage share link bundles</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/common/share-link-bundle-dialog/share-link-bundle-dialog.component.html</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
<context context-type="linenumber">113</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
@@ -8255,67 +8237,63 @@
|
||||
<source>Optional</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
<context context-type="linenumber">16</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="787787001194154763" datatype="html">
|
||||
<source>Add new version</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">32</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3119565892291077820" datatype="html">
|
||||
<source>Uploading version...</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8538204518127762311" datatype="html">
|
||||
<source>Processing version...</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">45</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6587183611950398104" datatype="html">
|
||||
<source>Version upload failed.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6328986243040954523" datatype="html">
|
||||
<source>Version label</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">75</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2724055831234181057" datatype="html">
|
||||
<source>Version</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">96</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2491751767883178298" datatype="html">
|
||||
<source>Delete this version?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
<context context-type="linenumber">122</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="894108684749537200" datatype="html">
|
||||
<source>Delete version</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||
<context context-type="linenumber">132</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="9111284310237278846" datatype="html">
|
||||
@@ -10218,13 +10196,6 @@
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8669280833944376006" datatype="html">
|
||||
<source>Select all processed email messages</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8691920320483720007" datatype="html">
|
||||
<source>Received</source>
|
||||
<context-group purpose="location">
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
i18n-info>
|
||||
<div class="input-group input-group-sm align-items-center">
|
||||
<div class="input-group input-group-sm me-3">
|
||||
<label class="input-group-text text-muted" for="logLineLimit" i18n>Show</label>
|
||||
<span class="input-group-text text-muted" i18n>Show</span>
|
||||
<input
|
||||
id="logLineLimit"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="100"
|
||||
@@ -18,7 +17,7 @@
|
||||
<span class="input-group-text text-muted" i18n>lines</span>
|
||||
</div>
|
||||
<div class="form-check form-switch mt-1">
|
||||
<input id="autoRefreshSwitch" class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)">
|
||||
<input class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)">
|
||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,9 @@ export class LogsComponent
|
||||
parsed.some((log, idx) => {
|
||||
const current = this.logs()[idx]
|
||||
return (
|
||||
current?.message !== log.message || current?.level !== log.level
|
||||
!current ||
|
||||
current.message !== log.message ||
|
||||
current.level !== log.level
|
||||
)
|
||||
})
|
||||
if (hasChanges) {
|
||||
|
||||
@@ -150,10 +150,10 @@
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<label for="searchLink" i18n>Full search links to</label>
|
||||
<span i18n>Full search links to</span>
|
||||
</div>
|
||||
<div class="col mb-3">
|
||||
<select id="searchLink" class="form-select" formControlName="searchLink">
|
||||
<select class="form-select" formControlName="searchLink">
|
||||
<option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option>
|
||||
<option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option>
|
||||
</select>
|
||||
@@ -199,10 +199,10 @@
|
||||
<h5 i18n>Documents</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<label for="documentListItemPerPage" i18n>Items per page</label>
|
||||
<span i18n>Items per page</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select id="documentListItemPerPage" class="form-select" formControlName="documentListItemPerPage">
|
||||
<select class="form-select" formControlName="documentListItemPerPage">
|
||||
<option [ngValue]="10">10</option>
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
@@ -274,10 +274,10 @@
|
||||
<h5 class="mt-3" i18n>PDF Editor</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-form-label pt-0">
|
||||
<label for="pdfEditorDefaultEditMode" i18n>Default editing mode</label>
|
||||
<span i18n>Default editing mode</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<select id="pdfEditorDefaultEditMode" class="form-select" formControlName="pdfEditorDefaultEditMode">
|
||||
<select class="form-select" formControlName="pdfEditorDefaultEditMode">
|
||||
<option [ngValue]="PdfEditorEditMode.Create" i18n>Create new document(s)</option>
|
||||
<option [ngValue]="PdfEditorEditMode.Update" i18n>Add document version</option>
|
||||
</select>
|
||||
|
||||
@@ -399,17 +399,17 @@ describe('SettingsComponent', () => {
|
||||
completeSetup()
|
||||
const field = 'storage_path'
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(0)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(0)
|
||||
component.toggleDocumentDetailField(field, false)
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(1)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(1)
|
||||
expect(component.isDocumentDetailFieldShown(field)).toBeFalsy()
|
||||
component.toggleDocumentDetailField(field, true)
|
||||
expect(
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value
|
||||
).toHaveLength(0)
|
||||
component.settingsForm.get('documentDetailsHiddenFields').value.length
|
||||
).toEqual(0)
|
||||
expect(component.isDocumentDetailFieldShown(field)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<i-bs name="check2-all" class="me-1"></i-bs><ng-container i18n>Dismiss all</ng-container>
|
||||
</button>
|
||||
<div class="form-check form-switch mb-0 ms-2">
|
||||
<input id="autoRefreshSwitch" class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
|
||||
<input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
|
||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,8 +93,7 @@
|
||||
<i-bs width="1em" height="1em" name="x"></i-bs>
|
||||
</button>
|
||||
}
|
||||
<label class="visually-hidden" for="taskSearch" i18n>Search tasks</label>
|
||||
<input #filterInput id="taskSearch" class="form-control form-control-sm" type="text"
|
||||
<input #filterInput class="form-control form-control-sm" type="text"
|
||||
(keyup)="filterInputKeyup($event)"
|
||||
[(ngModel)]="filterText">
|
||||
</div>
|
||||
@@ -212,7 +211,7 @@
|
||||
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
|
||||
</button>
|
||||
</td>
|
||||
<td class="actions-col">
|
||||
<td scope="row" class="actions-col">
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
|
||||
<i-bs name="check" class="me-1"></i-bs><ng-container i18n>Dismiss</ng-container>
|
||||
|
||||
@@ -29,11 +29,9 @@
|
||||
</div>
|
||||
|
||||
<form class="chat-input">
|
||||
<label class="visually-hidden" for="chatInput" i18n>Message</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
#chatInput
|
||||
id="chatInput"
|
||||
class="form-control form-control-sm" name="chatInput" type="text"
|
||||
[placeholder]="placeholder"
|
||||
[disabled]="loading"
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('ChatComponent', () => {
|
||||
component.input = 'Hello'
|
||||
component.sendMessage()
|
||||
|
||||
expect(component.messages).toHaveLength(2)
|
||||
expect(component.messages.length).toBe(2)
|
||||
expect(component.messages[0].content).toBe('Hello')
|
||||
expect(component.loading).toBe(true)
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('ChatComponent', () => {
|
||||
const message = { content: '', role: 'assistant', isStreaming: true }
|
||||
component.enqueueTypewriter(null, message as any) // coverage for null
|
||||
component.enqueueTypewriter('Hello', message as any)
|
||||
expect(component['typewriterBuffer']).toHaveLength(4)
|
||||
expect(component['typewriterBuffer'].length).toBe(4)
|
||||
})
|
||||
|
||||
it('should scroll to bottom after sending a message', () => {
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@
|
||||
<div class="col">
|
||||
<div class="trigger-filters mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="form-label mb-0" i18n>Advanced Filters</span>
|
||||
<label class="form-label mb-0" i18n>Advanced Filters</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-primary ms-auto"
|
||||
|
||||
+5
-5
@@ -401,7 +401,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.addFilter(triggerGroup as FormGroup)
|
||||
|
||||
const filters = component.getFiltersFormArray(triggerGroup as FormGroup)
|
||||
expect(filters).toHaveLength(3)
|
||||
expect(filters.length).toBe(3)
|
||||
|
||||
filters.at(0).get('values').setValue([1])
|
||||
filters.at(1).get('values').setValue([2, 3])
|
||||
@@ -672,7 +672,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
} as any
|
||||
|
||||
const filters = component['buildFiltersFormArray'](trigger)
|
||||
expect(filters).toHaveLength(0)
|
||||
expect(filters.length).toBe(0)
|
||||
|
||||
component.filterDefinitions = originalDefinitions
|
||||
})
|
||||
@@ -739,7 +739,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.ngOnInit()
|
||||
const triggerGroup = component.triggerFields.at(0) as FormGroup
|
||||
const filters = component.getFiltersFormArray(triggerGroup)
|
||||
expect(filters).toHaveLength(13)
|
||||
expect(filters.length).toBe(13)
|
||||
const customFieldFilter = filters.at(12) as FormGroup
|
||||
expect(customFieldFilter.get('type').value).toBe(
|
||||
TriggerFilterType.CustomFieldQuery
|
||||
@@ -969,12 +969,12 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
component.addFilter(triggerGroup)
|
||||
|
||||
component.removeFilter(triggerGroup, 0)
|
||||
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(0)
|
||||
expect(component.getFiltersFormArray(triggerGroup).length).toBe(0)
|
||||
|
||||
component.addFilter(triggerGroup)
|
||||
const filterArrayAfterAdd = component.getFiltersFormArray(triggerGroup)
|
||||
filterArrayAfterAdd.at(0).get('type').setValue(TriggerFilterType.TagsAll)
|
||||
expect(component.getFiltersFormArray(triggerGroup)).toHaveLength(1)
|
||||
expect(component.getFiltersFormArray(triggerGroup).length).toBe(1)
|
||||
})
|
||||
|
||||
it('should remove selected custom field from the form group', () => {
|
||||
|
||||
+1
-1
@@ -933,7 +933,7 @@ export class WorkflowEditDialogComponent
|
||||
|
||||
getFilterSelectItems(type: TriggerFilterType) {
|
||||
const definition = this.getFilterDefinition(type)
|
||||
if (definition?.inputType !== 'select') {
|
||||
if (!definition || definition.inputType !== 'select') {
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -157,8 +157,8 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
private removeChildren(tagIDs: number[], tag: Tag) {
|
||||
if (tag.children?.length) {
|
||||
const childIDs = new Set(tag.children.map((child) => child.id))
|
||||
tagIDs = tagIDs.filter((id) => !childIDs.has(id))
|
||||
const childIDs = tag.children.map((child) => child.id)
|
||||
tagIDs = tagIDs.filter((id) => !childIDs.includes(id))
|
||||
for (const child of tag.children) {
|
||||
tagIDs = this.removeChildren(tagIDs, child)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { environment } from 'src/environments/environment'
|
||||
})
|
||||
export class PageHeaderComponent {
|
||||
private titleService = inject(Title)
|
||||
private readonly clipboard = inject(Clipboard)
|
||||
private clipboard = inject(Clipboard)
|
||||
|
||||
readonly id = input<number>(undefined)
|
||||
readonly subTitle = input('')
|
||||
|
||||
@@ -49,7 +49,8 @@ describe('PngxPdfViewerComponent', () => {
|
||||
new URL('assets/js/pdf.worker.min.mjs', document.baseURI).toString()
|
||||
)
|
||||
const isVisible = (component as any).findController.onIsPageVisible as
|
||||
(() => boolean) | undefined
|
||||
| (() => boolean)
|
||||
| undefined
|
||||
expect(isVisible?.()).toBe(true)
|
||||
expect(loadSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ numPages: 1 })
|
||||
|
||||
+1
-2
@@ -65,9 +65,8 @@
|
||||
<dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd>
|
||||
<dt class="col-sm-4" i18n>Link</dt>
|
||||
<dd class="col-sm-8">
|
||||
<label class="visually-hidden" for="shareBundleLink" i18n>Share link</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input id="shareBundleLink" class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
|
||||
<input class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
type="button"
|
||||
|
||||
@@ -510,7 +510,7 @@ describe('DocumentDetailComponent', () => {
|
||||
const deleteButtons = fixture.debugElement.queryAll(
|
||||
By.css('pngx-confirm-button')
|
||||
)
|
||||
expect(deleteButtons).toHaveLength(1)
|
||||
expect(deleteButtons.length).toEqual(1)
|
||||
})
|
||||
|
||||
it('should fall back to details tab when duplicates tab is active but no duplicates', () => {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if (loading()) {
|
||||
<div class="d-flex">
|
||||
<output class="spinner-border spinner-border-sm fw-normal"></output>
|
||||
<output class="spinner-border spinner-border-sm fw-normal" role="status"></output>
|
||||
</div>
|
||||
} @else {
|
||||
<ul class="list-group">
|
||||
|
||||
+6
-6
@@ -26,11 +26,11 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
],
|
||||
})
|
||||
export class DocumentHistoryComponent implements OnInit {
|
||||
private readonly documentService = inject(DocumentService)
|
||||
private readonly correspondentService = inject(CorrespondentService)
|
||||
private readonly storagePathService = inject(StoragePathService)
|
||||
private readonly documentTypeService = inject(DocumentTypeService)
|
||||
private readonly userService = inject(UserService)
|
||||
private documentService = inject(DocumentService)
|
||||
private correspondentService = inject(CorrespondentService)
|
||||
private storagePathService = inject(StoragePathService)
|
||||
private documentTypeService = inject(DocumentTypeService)
|
||||
private userService = inject(UserService)
|
||||
|
||||
public AuditLogAction = AuditLogAction
|
||||
|
||||
@@ -70,7 +70,7 @@ export class DocumentHistoryComponent implements OnInit {
|
||||
return cached
|
||||
}
|
||||
|
||||
const idInt = Number.parseInt(id, 10)
|
||||
const idInt = parseInt(id, 10)
|
||||
const fallback$ = of(id)
|
||||
|
||||
let result$: Observable<string>
|
||||
|
||||
+1
-6
@@ -7,9 +7,8 @@
|
||||
<div class="px-3 py-2 mb-2">
|
||||
@if (versionUploadState === UploadState.Idle) {
|
||||
<div class="input-group input-group-sm mb-2">
|
||||
<label class="input-group-text" for="newVersionLabel" i18n>Label</label>
|
||||
<span class="input-group-text" i18n>Label</span>
|
||||
<input
|
||||
id="newVersionLabel"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="newVersionLabel"
|
||||
@@ -71,13 +70,9 @@
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
@if (isEditingVersion(version.id)) {
|
||||
<label class="visually-hidden" for="versionLabel{{ version.id }}" i18n>Version label</label>
|
||||
}
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
@if (isEditingVersion(version.id)) {
|
||||
<input
|
||||
id="versionLabel{{ version.id }}"
|
||||
class="form-control"
|
||||
type="text"
|
||||
[(ngModel)]="versionLabelDraft"
|
||||
|
||||
+1
-1
@@ -328,7 +328,7 @@ describe('FilterEditorComponent', () => {
|
||||
})
|
||||
|
||||
it('should ingest legacy text filter rules for doc title + content', () => {
|
||||
expect(component.textFilter).toBeNull()
|
||||
expect(component.textFilter).toEqual(null)
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_TITLE_CONTENT,
|
||||
|
||||
+4
-4
@@ -1,18 +1,18 @@
|
||||
<div class="row mb-3">
|
||||
<div class="col mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<label class="text-muted me-2 mb-0" for="managementNameFilter" i18n>Filter by:</label>
|
||||
<input id="managementNameFilter" class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
|
||||
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
|
||||
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto mb-2 mb-xl-0">
|
||||
<div class="form-inline d-flex align-items-center">
|
||||
<div class="input-group input-group-sm w-auto d-none d-md-flex">
|
||||
<label class="input-group-text border-0" for="managementPageSize" i18n>Show:</label>
|
||||
<span class="input-group-text border-0" i18n>Show:</span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm w-auto me-3">
|
||||
<select id="managementPageSize" class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||
<select class="form-select form-select-sm small" [(ngModel)]="pageSize">
|
||||
<option [ngValue]="25">25</option>
|
||||
<option [ngValue]="50">50</option>
|
||||
<option [ngValue]="100">100</option>
|
||||
|
||||
+2
-2
@@ -83,12 +83,12 @@ describe('TagListComponent', () => {
|
||||
]
|
||||
component['_nameFilter'] = null // Simulate empty name filter
|
||||
const filtered = component.filterData(tags as any)
|
||||
expect(filtered).toHaveLength(2)
|
||||
expect(filtered.length).toBe(2)
|
||||
expect(filtered.find((t) => t.id === 2)).toBeUndefined()
|
||||
|
||||
component['_nameFilter'] = 'Tag2' // Simulate non-empty name filter
|
||||
const filteredWithName = component.filterData(tags as any)
|
||||
expect(filteredWithName).toHaveLength(2)
|
||||
expect(filteredWithName.length).toBe(2)
|
||||
expect(filteredWithName.find((t) => t.id === 2)).toBeUndefined()
|
||||
expect(
|
||||
filteredWithName
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<th scope="col" style="width: 40px;">
|
||||
<div class="form-check m-0 ms-2 me-n2">
|
||||
<input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="toggleAllEnabled()" (ngModelChange)="toggleAllEnabled.set($event)" [disabled]="processedMails().length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||
<label class="form-check-label" for="all-objects"><span class="visually-hidden" i18n>Select all processed email messages</span></label>
|
||||
<label class="form-check-label" for="all-objects"></label>
|
||||
</div>
|
||||
</th>
|
||||
<th scope="col" i18n>Subject</th>
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { normalizeSync } from 'normalize-diacritics'
|
||||
|
||||
export type SearchTextValue =
|
||||
string | number | boolean | bigint | null | undefined
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| bigint
|
||||
| null
|
||||
| undefined
|
||||
|
||||
export function normalizeSearchText(value: SearchTextValue): string {
|
||||
return normalizeSync(String(value ?? '')).toLocaleLowerCase()
|
||||
|
||||
@@ -771,14 +771,7 @@ class CustomFieldQueryFilter(Filter):
|
||||
)
|
||||
q, annotations = parser.parse(value)
|
||||
|
||||
# The Count(...) annotations above require a GROUP BY/HAVING to evaluate.
|
||||
# Applying them directly to `qs` mixes that HAVING with `qs`'s existing
|
||||
# joins (e.g. repeated tag joins from tags__id__all, the object-permission
|
||||
# OR-filter), which some backends (e.g. MariaDB) fail to plan correctly,
|
||||
# raising "Unknown column ... in 'HAVING'". Evaluating the annotation on
|
||||
# an isolated queryset keeps the GROUP BY/HAVING self-contained.
|
||||
matching_ids = Document.objects.annotate(**annotations).filter(q).values("pk")
|
||||
return qs.filter(pk__in=matching_ids)
|
||||
return qs.annotate(**annotations).filter(q)
|
||||
|
||||
|
||||
class DocumentFilterSet(FilterSet):
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-04 15:31
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("documents", "0003_remove_document_storage_type"),
|
||||
("documents", "0004_workflowtrigger_filter_has_any_correspondents_and_more"),
|
||||
("documents", "0005_alter_document_checksum_unique"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("documents", "0002_squashed"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="document",
|
||||
name="storage_type",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowtrigger",
|
||||
name="filter_has_any_correspondents",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="workflowtriggers_has_any_correspondent",
|
||||
to="documents.correspondent",
|
||||
verbose_name="has one of these correspondents",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowtrigger",
|
||||
name="filter_has_any_document_types",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="workflowtriggers_has_any_document_type",
|
||||
to="documents.documenttype",
|
||||
verbose_name="has one of these document types",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="workflowtrigger",
|
||||
name="filter_has_any_storage_paths",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="workflowtriggers_has_any_storage_path",
|
||||
to="documents.storagepath",
|
||||
verbose_name="has one of these storage paths",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="document",
|
||||
name="checksum",
|
||||
field=models.CharField(
|
||||
editable=False,
|
||||
help_text="The checksum of the original document.",
|
||||
max_length=32,
|
||||
verbose_name="checksum",
|
||||
),
|
||||
),
|
||||
]
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-04 15:31
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.functions.text
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("documents", "0008_workflowaction_passwords_alter_workflowaction_type"),
|
||||
("documents", "0009_alter_document_content_length"),
|
||||
("documents", "0010_optimize_integer_field_sizes"),
|
||||
("documents", "0011_alter_workflowaction_type"),
|
||||
("documents", "0012_document_root_document"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("documents", "0007_sharelinkbundle"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="workflowaction",
|
||||
name="passwords",
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
help_text="Passwords to try when removing PDF protection. Separate with commas or new lines.",
|
||||
null=True,
|
||||
verbose_name="passwords",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="document",
|
||||
name="content_length",
|
||||
field=models.GeneratedField(
|
||||
db_persist=True,
|
||||
expression=django.db.models.functions.text.Length("content"),
|
||||
help_text="Length of the content field in characters. Automatically maintained by the database for faster statistics computation.",
|
||||
output_field=models.PositiveIntegerField(default=0),
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="correspondent",
|
||||
name="matching_algorithm",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
(6, "Automatic"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="documenttype",
|
||||
name="matching_algorithm",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
(6, "Automatic"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="savedviewfilterrule",
|
||||
name="rule_type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "title contains"),
|
||||
(1, "content contains"),
|
||||
(2, "ASN is"),
|
||||
(3, "correspondent is"),
|
||||
(4, "document type is"),
|
||||
(5, "is in inbox"),
|
||||
(6, "has tag"),
|
||||
(7, "has any tag"),
|
||||
(8, "created before"),
|
||||
(9, "created after"),
|
||||
(10, "created year is"),
|
||||
(11, "created month is"),
|
||||
(12, "created day is"),
|
||||
(13, "added before"),
|
||||
(14, "added after"),
|
||||
(15, "modified before"),
|
||||
(16, "modified after"),
|
||||
(17, "does not have tag"),
|
||||
(18, "does not have ASN"),
|
||||
(19, "title or content contains"),
|
||||
(20, "fulltext query"),
|
||||
(21, "more like this"),
|
||||
(22, "has tags in"),
|
||||
(23, "ASN greater than"),
|
||||
(24, "ASN less than"),
|
||||
(25, "storage path is"),
|
||||
(26, "has correspondent in"),
|
||||
(27, "does not have correspondent in"),
|
||||
(28, "has document type in"),
|
||||
(29, "does not have document type in"),
|
||||
(30, "has storage path in"),
|
||||
(31, "does not have storage path in"),
|
||||
(32, "owner is"),
|
||||
(33, "has owner in"),
|
||||
(34, "does not have owner"),
|
||||
(35, "does not have owner in"),
|
||||
(36, "has custom field value"),
|
||||
(37, "is shared by me"),
|
||||
(38, "has custom fields"),
|
||||
(39, "has custom field in"),
|
||||
(40, "does not have custom field in"),
|
||||
(41, "does not have custom field"),
|
||||
(42, "custom fields query"),
|
||||
(43, "created to"),
|
||||
(44, "created from"),
|
||||
(45, "added to"),
|
||||
(46, "added from"),
|
||||
(47, "mime type is"),
|
||||
],
|
||||
verbose_name="rule type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="storagepath",
|
||||
name="matching_algorithm",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
(6, "Automatic"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="tag",
|
||||
name="matching_algorithm",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
(6, "Automatic"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowrun",
|
||||
name="type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Consumption Started"),
|
||||
(2, "Document Added"),
|
||||
(3, "Document Updated"),
|
||||
(4, "Scheduled"),
|
||||
],
|
||||
null=True,
|
||||
verbose_name="workflow trigger type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowtrigger",
|
||||
name="matching_algorithm",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "None"),
|
||||
(1, "Any word"),
|
||||
(2, "All words"),
|
||||
(3, "Exact match"),
|
||||
(4, "Regular expression"),
|
||||
(5, "Fuzzy word"),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="matching algorithm",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowtrigger",
|
||||
name="type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Consumption Started"),
|
||||
(2, "Document Added"),
|
||||
(3, "Document Updated"),
|
||||
(4, "Scheduled"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="Workflow Trigger Type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowaction",
|
||||
name="type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Assignment"),
|
||||
(2, "Removal"),
|
||||
(3, "Email"),
|
||||
(4, "Webhook"),
|
||||
(5, "Password removal"),
|
||||
(6, "Move to trash"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="Workflow Action Type",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="document",
|
||||
name="root_document",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="versions",
|
||||
to="documents.document",
|
||||
verbose_name="root document for this version",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="document",
|
||||
name="version_label",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Optional short label for a document version.",
|
||||
max_length=64,
|
||||
null=True,
|
||||
verbose_name="version label",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -138,7 +138,7 @@ class TestCustomFieldsSearch(DirectoriesMixin, APITestCase):
|
||||
title = str(kwargs)
|
||||
document = Document.objects.create(
|
||||
title=title,
|
||||
checksum=title[:64],
|
||||
checksum=title,
|
||||
archive_serial_number=len(self.documents) + 1,
|
||||
)
|
||||
data = {
|
||||
|
||||
@@ -2,7 +2,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-15 20:44+0000\n"
|
||||
"POT-Creation-Date: 2026-07-11 23:04+0000\n"
|
||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
@@ -2433,25 +2433,21 @@ msgid "uid"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:342
|
||||
msgid "uid validity"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:350
|
||||
msgid "subject"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:358
|
||||
#: paperless_mail/models.py:350
|
||||
msgid "received"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:365
|
||||
#: paperless_mail/models.py:357
|
||||
msgid "processed"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:371
|
||||
#: paperless_mail/models.py:363
|
||||
msgid "status"
|
||||
msgstr ""
|
||||
|
||||
#: paperless_mail/models.py:379
|
||||
#: paperless_mail/models.py:371
|
||||
msgid "error"
|
||||
msgstr ""
|
||||
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-04 15:30
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
def _create_singleton(apps, schema_editor):
|
||||
settings_model = apps.get_model("paperless", "ApplicationConfiguration")
|
||||
settings_model.objects.create()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("paperless", "0001_initial"),
|
||||
("paperless", "0002_applicationconfiguration_app_logo_and_more"),
|
||||
("paperless", "0003_alter_applicationconfiguration_max_image_pixels"),
|
||||
("paperless", "0004_applicationconfiguration_barcode_asn_prefix_and_more"),
|
||||
("paperless", "0005_applicationconfiguration_ai_enabled_and_more"),
|
||||
("paperless", "0006_applicationconfiguration_barcode_tag_split"),
|
||||
]
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ApplicationConfiguration",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"output_type",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("pdf", "pdf"),
|
||||
("pdfa", "pdfa"),
|
||||
("pdfa-1", "pdfa-1"),
|
||||
("pdfa-2", "pdfa-2"),
|
||||
("pdfa-3", "pdfa-3"),
|
||||
],
|
||||
max_length=8,
|
||||
null=True,
|
||||
verbose_name="Sets the output PDF type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"pages",
|
||||
models.PositiveIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Do OCR from page 1 to this value",
|
||||
),
|
||||
),
|
||||
(
|
||||
"language",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="Do OCR using these languages",
|
||||
),
|
||||
),
|
||||
(
|
||||
"mode",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("skip", "skip"),
|
||||
("redo", "redo"),
|
||||
("force", "force"),
|
||||
("skip_noarchive", "skip_noarchive"),
|
||||
],
|
||||
max_length=16,
|
||||
null=True,
|
||||
verbose_name="Sets the OCR mode",
|
||||
),
|
||||
),
|
||||
(
|
||||
"skip_archive_file",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("never", "never"),
|
||||
("with_text", "with_text"),
|
||||
("always", "always"),
|
||||
],
|
||||
max_length=16,
|
||||
null=True,
|
||||
verbose_name="Controls the generation of an archive file",
|
||||
),
|
||||
),
|
||||
(
|
||||
"image_dpi",
|
||||
models.PositiveIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets image DPI fallback value",
|
||||
),
|
||||
),
|
||||
(
|
||||
"unpaper_clean",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("clean", "clean"),
|
||||
("clean-final", "clean-final"),
|
||||
("none", "none"),
|
||||
],
|
||||
max_length=16,
|
||||
null=True,
|
||||
verbose_name="Controls the unpaper cleaning",
|
||||
),
|
||||
),
|
||||
(
|
||||
"deskew",
|
||||
models.BooleanField(null=True, verbose_name="Enables deskew"),
|
||||
),
|
||||
(
|
||||
"rotate_pages",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables page rotation",
|
||||
),
|
||||
),
|
||||
(
|
||||
"rotate_pages_threshold",
|
||||
models.FloatField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(0.0)],
|
||||
verbose_name="Sets the threshold for rotation of pages",
|
||||
),
|
||||
),
|
||||
(
|
||||
"max_image_pixels",
|
||||
models.FloatField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(0.0)],
|
||||
verbose_name="Sets the maximum image size for decompression",
|
||||
),
|
||||
),
|
||||
(
|
||||
"color_conversion_strategy",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("LeaveColorUnchanged", "LeaveColorUnchanged"),
|
||||
("RGB", "RGB"),
|
||||
("UseDeviceIndependentColor", "UseDeviceIndependentColor"),
|
||||
("Gray", "Gray"),
|
||||
("CMYK", "CMYK"),
|
||||
],
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="Sets the Ghostscript color conversion strategy",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user_args",
|
||||
models.JSONField(
|
||||
null=True,
|
||||
verbose_name="Adds additional user arguments for OCRMyPDF",
|
||||
),
|
||||
),
|
||||
(
|
||||
"app_logo",
|
||||
models.FileField(
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to="logo/",
|
||||
validators=[
|
||||
django.core.validators.FileExtensionValidator(
|
||||
allowed_extensions=["jpg", "png", "gif", "svg"],
|
||||
),
|
||||
],
|
||||
verbose_name="Application logo",
|
||||
),
|
||||
),
|
||||
(
|
||||
"app_title",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=48,
|
||||
null=True,
|
||||
verbose_name="Application title",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_asn_prefix",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="Sets the ASN barcode prefix",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_dpi",
|
||||
models.PositiveIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets the barcode DPI",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_enable_asn",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables ASN barcode",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_enable_tag",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables tag barcode",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_enable_tiff_support",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables barcode TIFF support",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_max_pages",
|
||||
models.PositiveIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets the maximum pages for barcode",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_retain_split_pages",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Retains split pages",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_string",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="Sets the barcode string",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_tag_mapping",
|
||||
models.JSONField(
|
||||
null=True,
|
||||
verbose_name="Sets the tag barcode mapping",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_upscale",
|
||||
models.FloatField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1.0)],
|
||||
verbose_name="Sets the barcode upscale factor",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcodes_enabled",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables barcode scanning",
|
||||
),
|
||||
),
|
||||
(
|
||||
"ai_enabled",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
null=True,
|
||||
verbose_name="Enables AI features",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_api_key",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=1024,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM API key",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_backend",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("openai-like", "OpenAI-compatible"),
|
||||
("ollama", "Ollama"),
|
||||
],
|
||||
max_length=128,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM backend",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_embedding_backend",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("openai-like", "OpenAI-compatible"),
|
||||
("huggingface", "Huggingface"),
|
||||
],
|
||||
max_length=128,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM embedding backend",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_embedding_model",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=128,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM embedding model",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_endpoint",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM endpoint, optional",
|
||||
),
|
||||
),
|
||||
(
|
||||
"llm_model",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=128,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM model",
|
||||
),
|
||||
),
|
||||
(
|
||||
"barcode_tag_split",
|
||||
models.BooleanField(
|
||||
null=True,
|
||||
verbose_name="Enables splitting on tag barcodes",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "paperless application settings",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=_create_singleton,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
]
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-04 15:19
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("paperless", "0009_alter_applicationconfiguration_options"),
|
||||
("paperless", "0010_alter_applicationconfiguration_llm_embedding_backend"),
|
||||
("paperless", "0011_applicationconfiguration_llm_embedding_chunk_size"),
|
||||
("paperless", "0012_applicationconfiguration_llm_output_language"),
|
||||
("paperless", "0013_applicationconfiguration_llm_request_timeout"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("paperless", "0008_replace_skip_archive_file"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="applicationconfiguration",
|
||||
options={
|
||||
"permissions": [
|
||||
("view_global_statistics", "Can view global object counts"),
|
||||
("view_system_monitoring", "Can view system status information"),
|
||||
],
|
||||
"verbose_name": "paperless application settings",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_embedding_backend",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("openai-like", "OpenAI-compatible"),
|
||||
("huggingface", "Huggingface"),
|
||||
("ollama", "Ollama"),
|
||||
],
|
||||
max_length=128,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM embedding backend",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_embedding_endpoint",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM embedding endpoint, optional",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_embedding_chunk_size",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets the LLM embedding chunk size",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_context_size",
|
||||
field=models.PositiveIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets the LLM context size",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_output_language",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
max_length=32,
|
||||
null=True,
|
||||
verbose_name="Sets the LLM output language",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="applicationconfiguration",
|
||||
name="llm_request_timeout",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
null=True,
|
||||
validators=[django.core.validators.MinValueValidator(1)],
|
||||
verbose_name="Sets the LLM request timeout in seconds",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -18,7 +18,6 @@ from celery import shared_task
|
||||
from celery.canvas import Signature
|
||||
from django.conf import settings
|
||||
from django.db import DatabaseError
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import is_naive
|
||||
from django.utils.timezone import make_aware
|
||||
@@ -246,7 +245,6 @@ def apply_mail_action(
|
||||
message_uid: str,
|
||||
message_subject: str,
|
||||
message_date: datetime.datetime,
|
||||
uid_validity: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
This shared task applies the mail action of a particular mail rule to the
|
||||
@@ -288,7 +286,6 @@ def apply_mail_action(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message_uid,
|
||||
uid_validity=uid_validity,
|
||||
subject=message_subject,
|
||||
received=message_date,
|
||||
status="SUCCESS",
|
||||
@@ -300,7 +297,6 @@ def apply_mail_action(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message_uid,
|
||||
uid_validity=uid_validity,
|
||||
subject=message_subject,
|
||||
received=message_date,
|
||||
status="FAILED",
|
||||
@@ -318,7 +314,6 @@ def error_callback(
|
||||
message_uid: str,
|
||||
message_subject: str,
|
||||
message_date: datetime.datetime,
|
||||
uid_validity: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
A shared task that is called whenever something goes wrong during
|
||||
@@ -330,7 +325,6 @@ def error_callback(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message_uid,
|
||||
uid_validity=uid_validity,
|
||||
subject=message_subject,
|
||||
received=make_aware(message_date) if is_naive(message_date) else message_date,
|
||||
status="FAILED",
|
||||
@@ -343,7 +337,6 @@ def queue_consumption_tasks(
|
||||
consume_tasks: list[Signature],
|
||||
rule: MailRule,
|
||||
message: MailMessage,
|
||||
uid_validity: str | None,
|
||||
) -> None:
|
||||
"""
|
||||
Queue a list of consumption tasks (Signatures for the consume_file shared
|
||||
@@ -355,7 +348,6 @@ def queue_consumption_tasks(
|
||||
message_uid=message.uid,
|
||||
message_subject=message.subject,
|
||||
message_date=message.date,
|
||||
uid_validity=uid_validity,
|
||||
)
|
||||
chord(header=consume_tasks, body=mail_action_task).on_error(
|
||||
error_callback.s(
|
||||
@@ -363,7 +355,6 @@ def queue_consumption_tasks(
|
||||
message_uid=message.uid,
|
||||
message_subject=message.subject,
|
||||
message_date=message.date,
|
||||
uid_validity=uid_validity,
|
||||
),
|
||||
).delay()
|
||||
|
||||
@@ -469,7 +460,6 @@ class MailAccountHandler(LoggingMixin):
|
||||
super().__init__()
|
||||
self.renew_logging_group()
|
||||
self._init_preprocessors()
|
||||
self._current_uid_validity: str | None = None
|
||||
|
||||
def _init_preprocessors(self) -> None:
|
||||
self._message_preprocessors: list[MailMessagePreprocessor] = []
|
||||
@@ -520,21 +510,6 @@ class MailAccountHandler(LoggingMixin):
|
||||
"Unknown title selector.",
|
||||
) # pragma: no cover
|
||||
|
||||
def _get_uid_validity(self, M: MailBox, folder: str) -> str | None:
|
||||
try:
|
||||
uid_validity = M.folder.status(folder, ["UIDVALIDITY"]).get("UIDVALIDITY")
|
||||
if uid_validity is not None:
|
||||
return str(uid_validity)
|
||||
except errors.MailboxFolderStatusError as e:
|
||||
self.log.warning(
|
||||
f"Server does not support retrieving UIDVALIDITY for folder {folder}: {e}",
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.warning(
|
||||
f"Unable to retrieve UIDVALIDITY for folder {folder}: {e}",
|
||||
)
|
||||
return None
|
||||
|
||||
def _get_correspondent(
|
||||
self,
|
||||
message: MailMessage,
|
||||
@@ -672,8 +647,6 @@ class MailAccountHandler(LoggingMixin):
|
||||
f"does not exist in account {rule.account}",
|
||||
) from err
|
||||
|
||||
self._current_uid_validity = self._get_uid_validity(M, rule.folder)
|
||||
|
||||
criterias = make_criterias(rule, supports_gmail_labels=supports_gmail_labels)
|
||||
|
||||
self.log.debug(
|
||||
@@ -714,17 +687,11 @@ class MailAccountHandler(LoggingMixin):
|
||||
)
|
||||
continue
|
||||
|
||||
already_processed = ProcessedMail.objects.filter(
|
||||
if ProcessedMail.objects.filter(
|
||||
rule=rule,
|
||||
uid=message.uid,
|
||||
folder=rule.folder,
|
||||
)
|
||||
if self._current_uid_validity is not None:
|
||||
already_processed = already_processed.filter(
|
||||
Q(uid_validity=self._current_uid_validity)
|
||||
| Q(uid_validity__isnull=True),
|
||||
)
|
||||
if already_processed.exists():
|
||||
).exists():
|
||||
self.log.debug(
|
||||
f"Skipping mail '{message.uid}' subject '{message.subject}' from '{message.from_}', already processed.",
|
||||
)
|
||||
@@ -954,7 +921,6 @@ class MailAccountHandler(LoggingMixin):
|
||||
consume_tasks=consume_tasks,
|
||||
rule=rule,
|
||||
message=message,
|
||||
uid_validity=self._current_uid_validity,
|
||||
)
|
||||
else:
|
||||
# No files to consume, just mark as processed if it wasn't by .eml processing
|
||||
@@ -962,13 +928,11 @@ class MailAccountHandler(LoggingMixin):
|
||||
rule=rule,
|
||||
uid=message.uid,
|
||||
folder=rule.folder,
|
||||
uid_validity=self._current_uid_validity,
|
||||
).exists():
|
||||
ProcessedMail.objects.create(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message.uid,
|
||||
uid_validity=self._current_uid_validity,
|
||||
subject=message.subject,
|
||||
received=make_aware(message.date)
|
||||
if is_naive(message.date)
|
||||
@@ -1045,7 +1009,6 @@ class MailAccountHandler(LoggingMixin):
|
||||
consume_tasks=[consume_task],
|
||||
rule=rule,
|
||||
message=message,
|
||||
uid_validity=self._current_uid_validity,
|
||||
)
|
||||
|
||||
processed_elements = 1
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-04 15:10
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
replaces = [
|
||||
("paperless_mail", "0002_optimize_integer_field_sizes"),
|
||||
("paperless_mail", "0003_mailrule_stop_processing"),
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
("paperless_mail", "0001_squashed"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="mailaccount",
|
||||
name="account_type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "IMAP"), (2, "Gmail OAuth"), (3, "Outlook OAuth")],
|
||||
default=1,
|
||||
verbose_name="account type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailaccount",
|
||||
name="imap_port",
|
||||
field=models.PositiveIntegerField(
|
||||
blank=True,
|
||||
help_text="This is usually 143 for unencrypted and STARTTLS connections, and 993 for SSL connections.",
|
||||
null=True,
|
||||
verbose_name="IMAP port",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailaccount",
|
||||
name="imap_security",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[(1, "No encryption"), (2, "Use SSL"), (3, "Use STARTTLS")],
|
||||
default=2,
|
||||
verbose_name="IMAP security",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="action",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Delete"),
|
||||
(2, "Move to specified folder"),
|
||||
(3, "Mark as read, don't process read mails"),
|
||||
(4, "Flag the mail, don't process flagged mails"),
|
||||
(5, "Tag the mail with specified tag, don't process tagged mails"),
|
||||
],
|
||||
default=3,
|
||||
verbose_name="action",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="assign_correspondent_from",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Do not assign a correspondent"),
|
||||
(2, "Use mail address"),
|
||||
(3, "Use name (or mail address if not available)"),
|
||||
(4, "Use correspondent selected below"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="assign correspondent from",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="assign_title_from",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Use subject as title"),
|
||||
(2, "Use attachment filename as title"),
|
||||
(3, "Do not assign title from rule"),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="assign title from",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="attachment_type",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Only process attachments."),
|
||||
(2, "Process all files, including 'inline' attachments."),
|
||||
],
|
||||
default=1,
|
||||
help_text="Inline attachments include embedded images, so it's best to combine this option with a filename filter.",
|
||||
verbose_name="attachment type",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="consumption_scope",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(1, "Only process attachments."),
|
||||
(
|
||||
2,
|
||||
"Process full Mail (with embedded attachments in file) as .eml",
|
||||
),
|
||||
(
|
||||
3,
|
||||
"Process full Mail (with embedded attachments in file) as .eml + process attachments as separate documents",
|
||||
),
|
||||
],
|
||||
default=1,
|
||||
verbose_name="consumption scope",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="maximum_age",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
default=30,
|
||||
help_text="Specified in days.",
|
||||
verbose_name="maximum age",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="order",
|
||||
field=models.SmallIntegerField(default=0, verbose_name="order"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="pdf_layout",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
choices=[
|
||||
(0, "System default"),
|
||||
(1, "Text, then HTML"),
|
||||
(2, "HTML, then text"),
|
||||
(3, "HTML only"),
|
||||
(4, "Text only"),
|
||||
],
|
||||
default=0,
|
||||
verbose_name="pdf layout",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="mailrule",
|
||||
name="stop_processing",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="If True, no further rules will be processed after this one if any document is queued.",
|
||||
verbose_name="Stop processing further rules",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("paperless_mail", "0003_mailrule_stop_processing"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="processedmail",
|
||||
name="uid_validity",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
editable=False,
|
||||
max_length=64,
|
||||
null=True,
|
||||
verbose_name="uid validity",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -338,14 +338,6 @@ class ProcessedMail(document_models.ModelWithOwner):
|
||||
editable=False,
|
||||
)
|
||||
|
||||
uid_validity = models.CharField(
|
||||
_("uid validity"),
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=64,
|
||||
editable=False,
|
||||
)
|
||||
|
||||
subject = models.CharField(
|
||||
_("subject"),
|
||||
null=False,
|
||||
|
||||
@@ -54,16 +54,12 @@ class _AttachmentDef:
|
||||
|
||||
class BogusFolderManager:
|
||||
current_folder = "INBOX"
|
||||
uidvalidity = "1"
|
||||
|
||||
def set(self, new_folder) -> None:
|
||||
if new_folder not in ["INBOX", "spam"]:
|
||||
raise MailboxFolderSelectError(None, "uhm")
|
||||
self.current_folder = new_folder
|
||||
|
||||
def status(self, folder, options):
|
||||
return {"UIDVALIDITY": self.uidvalidity}
|
||||
|
||||
|
||||
class BogusClient:
|
||||
def __init__(self, messages) -> None:
|
||||
@@ -948,223 +944,6 @@ class TestMail(
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 1)
|
||||
|
||||
def test_handle_mail_account_skips_mail_already_processed_in_same_uidvalidity(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A ProcessedMail row recorded under the mailbox's current UIDVALIDITY
|
||||
WHEN:
|
||||
- A mail with the same UID is fetched from the same UIDVALIDITY epoch
|
||||
THEN:
|
||||
- The mail is skipped as a duplicate.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.DELETE,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message()
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
self.mailMocker.bogus_mailbox.folder.uidvalidity = "SAME"
|
||||
ProcessedMail.objects.create(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message.uid,
|
||||
uid_validity="SAME",
|
||||
subject="Previously processed mail",
|
||||
status="SUCCESS",
|
||||
received=timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)),
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0)
|
||||
|
||||
def test_handle_mail_account_processes_mail_after_uidvalidity_change(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A ProcessedMail row recorded under a previous UIDVALIDITY epoch
|
||||
WHEN:
|
||||
- A mail with the same UID is fetched after UIDVALIDITY has changed
|
||||
THEN:
|
||||
- The mail is processed, not skipped as a duplicate.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.DELETE,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message()
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
self.mailMocker.bogus_mailbox.folder.uidvalidity = "NEW"
|
||||
ProcessedMail.objects.create(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message.uid,
|
||||
uid_validity="OLD",
|
||||
subject="Previously processed mail",
|
||||
status="SUCCESS",
|
||||
received=timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)),
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 1)
|
||||
|
||||
def test_handle_mail_account_skips_mail_processed_before_uidvalidity_tracking(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A ProcessedMail row recorded before UIDVALIDITY tracking existed
|
||||
(uid_validity is NULL)
|
||||
WHEN:
|
||||
- A mail with the same UID is fetched
|
||||
THEN:
|
||||
- The mail is skipped as a duplicate, to avoid re-ingesting all
|
||||
previously processed mail after upgrading.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.DELETE,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message()
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
ProcessedMail.objects.create(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message.uid,
|
||||
uid_validity=None,
|
||||
subject="Previously processed mail",
|
||||
status="SUCCESS",
|
||||
received=timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)),
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0)
|
||||
|
||||
def test_handle_mail_account_processes_mail_when_uidvalidity_unavailable(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- The mail server fails to report a UIDVALIDITY for the folder
|
||||
WHEN:
|
||||
- A mail account is processed
|
||||
THEN:
|
||||
- The failure is logged and the rule still processes the mail,
|
||||
instead of the whole rule being disabled.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
_ = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.DELETE,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message()
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
self.mailMocker.bogus_mailbox.folder.status = mock.MagicMock(
|
||||
side_effect=errors.MailboxFolderStatusError(("NO", [b"unsupported"]), "OK"),
|
||||
)
|
||||
|
||||
with self.assertLogs("paperless_mail", level="WARNING") as cm:
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 1)
|
||||
self.assertEqual(len(cm.output), 1)
|
||||
self.assertIn(
|
||||
"Server does not support retrieving UIDVALIDITY",
|
||||
cm.output[0],
|
||||
)
|
||||
|
||||
def test_handle_mail_account_skips_mail_when_uidvalidity_unavailable_but_prior_record_exists(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A ProcessedMail row recorded with a real uid_validity value
|
||||
- The mail server fails to report UIDVALIDITY (MailboxFolderStatusError),
|
||||
so _get_uid_validity returns None
|
||||
WHEN:
|
||||
- A mail with the same UID is fetched
|
||||
THEN:
|
||||
- The mail is skipped as already-processed rather than re-ingested,
|
||||
falling back to (rule, uid, folder) matching.
|
||||
"""
|
||||
account = MailAccount.objects.create(
|
||||
name="test",
|
||||
imap_server="",
|
||||
username="admin",
|
||||
password="secret",
|
||||
)
|
||||
rule = MailRule.objects.create(
|
||||
name="testrule",
|
||||
account=account,
|
||||
action=MailRule.MailAction.DELETE,
|
||||
)
|
||||
|
||||
message = self.mailMocker.messageBuilder.create_message()
|
||||
self.mailMocker.bogus_mailbox.messages = [message]
|
||||
self.mailMocker.bogus_mailbox.updateClient()
|
||||
|
||||
ProcessedMail.objects.create(
|
||||
rule=rule,
|
||||
folder=rule.folder,
|
||||
uid=message.uid,
|
||||
uid_validity="REAL_VALIDITY",
|
||||
subject="Previously processed mail",
|
||||
status="SUCCESS",
|
||||
received=timezone.make_aware(timezone.datetime(2023, 1, 1, 12, 0, 0)),
|
||||
)
|
||||
|
||||
self.mailMocker.bogus_mailbox.folder.status = mock.MagicMock(
|
||||
side_effect=errors.MailboxFolderStatusError(("NO", [b"unsupported"]), "OK"),
|
||||
)
|
||||
|
||||
self.mail_account_handler.handle_mail_account(account)
|
||||
|
||||
self.assertEqual(self.mailMocker._queue_consumption_tasks_mock.call_count, 0)
|
||||
|
||||
@pytest.mark.flaky(reruns=4)
|
||||
def test_handle_mail_account_flag(self) -> None:
|
||||
account = MailAccount.objects.create(
|
||||
|
||||
@@ -3102,14 +3102,14 @@ dev = [
|
||||
{ name = "pytest-rerunfailures", specifier = "~=16.1" },
|
||||
{ name = "pytest-sugar" },
|
||||
{ name = "pytest-xdist", specifier = "~=3.8.0" },
|
||||
{ name = "ruff", specifier = "~=0.15.20" },
|
||||
{ name = "ruff", specifier = "~=0.15.15" },
|
||||
{ name = "time-machine", specifier = ">=2.13" },
|
||||
{ name = "zensical", specifier = ">=0.0.47" },
|
||||
{ name = "zensical", specifier = ">=0.0.43" },
|
||||
]
|
||||
docs = [{ name = "zensical", specifier = ">=0.0.47" }]
|
||||
docs = [{ name = "zensical", specifier = ">=0.0.43" }]
|
||||
lint = [
|
||||
{ name = "prek", specifier = "~=0.3.10" },
|
||||
{ name = "ruff", specifier = "~=0.15.20" },
|
||||
{ name = "ruff", specifier = "~=0.15.15" },
|
||||
]
|
||||
testing = [
|
||||
{ name = "daphne" },
|
||||
@@ -4363,24 +4363,24 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.20"
|
||||
version = "0.15.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5743,7 +5743,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "zensical"
|
||||
version = "0.0.47"
|
||||
version = "0.0.43"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
@@ -5755,18 +5755,18 @@ dependencies = [
|
||||
{ name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
{ name = "tomli", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/99/53171a5211359a95b0d6caad9a1c960b002a4bf82eacdd3cdbde225db25f/zensical-0.0.47.tar.gz", hash = "sha256:324f783b22cd0deed0d0f3b69e28d5380b47238a1ef0f25913b88014af2bade2", size = 3976587, upload-time = "2026-07-05T15:51:25.488Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/85/ec45162e7824a8f879d887ef0774ee65926bf7d1064e2eebccc7eaee3378/zensical-0.0.43.tar.gz", hash = "sha256:dc2d3804ff562795c1024130e0c3ce79736467930729dda314f096d0e35b98c8", size = 3932396, upload-time = "2026-05-19T09:44:07.418Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/7c/5848aad6a5b566f5a3cebc77ab787793daec622b79295de243da7b8efae6/zensical-0.0.47-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4162fb8b62f38e6d9b75688c1fb87e18a1cffef5eee51d2c18b79334b548e973", size = 12789282, upload-time = "2026-07-05T15:50:45.118Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/08/c2a056ae22eba7063985cc89554ab8e54efb11af8e26f6d606e5bd300ae8/zensical-0.0.47-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:cdc2d84f38da809a28402eda5f2b6dbb150e14427f296c5b521e0dfc6a2e8a39", size = 12661193, upload-time = "2026-07-05T15:50:49.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/2b/30dc3262e99bc72e9a6529487fbfca58cf4f81cebfac59ef8e8d757d90eb/zensical-0.0.47-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97ed2b21aba5f788fc39d1597d00938d602b4d2724ed599a1dab7548fe4f0025", size = 13044347, upload-time = "2026-07-05T15:50:52.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/80/296ff21b686cda47c4a3f94c35bf62472291501c34e4f06dfd107cb237da/zensical-0.0.47-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f6b2de477c45284201e92301f997415f45f78493bd20f479ea9a1c86bbbbcca", size = 13006494, upload-time = "2026-07-05T15:50:55.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/4e/5aeba02363db89c625cd41c76d2a6e40b07d6a75d869d970ace7accd2bfa/zensical-0.0.47-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ee29ff819372eaab02ca0f14ac82e804332d898c57c56ffd4d89674f3e5ff71", size = 13379253, upload-time = "2026-07-05T15:50:58.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/dc/e72a1449544aeffa3dbe878c6340bcec98ba4eebabe7a592414064b0ae8d/zensical-0.0.47-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd94937c48a2e42b5b65b32c5075849937f23cebccaf250d249efa27266e0be", size = 13103356, upload-time = "2026-07-05T15:51:02.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/eb/7fb956915cc59ee9f2ddf36ec5b16e8e858823217611e1d766816a661651/zensical-0.0.47-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4702605b991bece11494a9bb318d5ba7229f00e5adcc9e6010acd58e9719686b", size = 13220897, upload-time = "2026-07-05T15:51:05.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/6b/944954d60bf1d61de2a7fa2732d0fb2d5deee64567db6088cdd2b5f76973/zensical-0.0.47-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:77f08ffcc3da9ca2f972330e501927aa7e8e445bfa7b758107edc645acda266e", size = 13280476, upload-time = "2026-07-05T15:51:09.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/80/4aece9b99eb5809b04fc6577fc50e877c998917293dfd58b5b229616b92b/zensical-0.0.47-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:319cf370ecc6d87da69c935c5acd6e1959dd48954766e9f954319bdb0bec5d36", size = 13439392, upload-time = "2026-07-05T15:51:12.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/10/5cbffe680605b4fa7c0d6326f07a6adc86854f8b9d61bb1b29bb482d40eb/zensical-0.0.47-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:59656bf604a8b03eede4ce1a847640bab1129ab86dec2e39e5bd4b808b1802d2", size = 13386534, upload-time = "2026-07-05T15:51:15.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/c2/55e0709607ae41c266987c3b91a1a9702b37fbbef0d07eddfe5e25c2d823/zensical-0.0.43-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:17c335362b6bac3a50178181694a964f6d9f0c516fc532129ba5a0a5c4103fb6", size = 12706531, upload-time = "2026-05-19T09:43:32.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/64/ce8627bc5ea30556162b29b041fe97d6a6aef2a87b51f12def628e4fa608/zensical-0.0.43-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8fe97f185194215f6193af45a17d2b30ebd72c8113e3650f2d7d6767b9c2206", size = 12563012, upload-time = "2026-05-19T09:43:35.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/d1/533bc9454f0e06b3d9d8bd2e7ac405308c3d4dee6572acab98f0ed6d1c07/zensical-0.0.43-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c4c85978c765b3e7f347e8102dfe1373d4bbe4229d7008b6bdbf352f1fbcd7f", size = 12947599, upload-time = "2026-05-19T09:43:38.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/a0/94f47d6fb592997be7ab9526938c929f0199adf2637c3c2b2b9b2101b28e/zensical-0.0.43-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90d7c06ffd07b2bdf78bef041d541baba8a3ea51fd2dd84dbdbc5b0229076524", size = 12904911, upload-time = "2026-05-19T09:43:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/fb/1db3ad9a86ff772f74a8bc60ad5b447aa02a158e70f94adacf50bdd5c40f/zensical-0.0.43-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60022f4a6b95e46ec0023f51052fcd491743b3ebd08c0066b22a5cf1e741fecd", size = 13269386, upload-time = "2026-05-19T09:43:45.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/ee/b24fd0f94885519d851c35615b086d069a1077b0198021a56755395a4633/zensical-0.0.43-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e278eb948a0b7545d50609d713c7c27e366dade4523ff73a311a5d5f136518a", size = 12999364, upload-time = "2026-05-19T09:43:48.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/78/401ccd7afd9d2690f81b5319b7f1eed05108154ce20e4207053914518c1c/zensical-0.0.43-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b85e5ab99fbda13823e67c43a4be6e5ebda6600602969c6575e143f20ac203fd", size = 13124392, upload-time = "2026-05-19T09:43:50.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/b3/9af6eba5826b0ef143fc8308bd1e219e221441e307a958e39f824ba9ab53/zensical-0.0.43-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:751385accc92cccfd4560dabed7c423870686ef6ede244a67e5c96286af25e8f", size = 13177538, upload-time = "2026-05-19T09:43:53.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/6b/cd090bd6659d32692487206469988ee84d41aa6de4cdf9e380f847da90e2/zensical-0.0.43-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:dd3ff5bfa6e65cf3d2550dc639c3da2a3bfa11087b83d57e06623c4c1607d583", size = 13327086, upload-time = "2026-05-19T09:43:56.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/5b/ac2555354b5a53cb9c2c942811905c47be0b9f5603d3c1328ee8564333eb/zensical-0.0.43-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85055a115b12f49c6ab194dcf04f966fc06b690ed6a8ddddd819929fc5f340e6", size = 13284645, upload-time = "2026-05-19T09:43:59.329Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user