mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-19 01:03:18 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7547c5df48 | ||
|
|
c774dff5ed | ||
|
|
b17a512539 | ||
|
|
fda058437e | ||
|
|
737110ac97 | ||
|
|
4cf027de40 | ||
|
|
41953c7846 | ||
|
|
e5abe5cf32 | ||
|
|
643d0205bc |
@@ -11,7 +11,7 @@ concurrency:
|
||||
group: backend-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
DEFAULT_UV_VERSION: "0.11.x"
|
||||
DEFAULT_UV_VERSION: "0.12.x"
|
||||
NLTK_DATA: "/usr/share/nltk_data"
|
||||
permissions: {}
|
||||
jobs:
|
||||
|
||||
@@ -11,7 +11,7 @@ concurrency:
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
DEFAULT_UV_VERSION: "0.11.x"
|
||||
DEFAULT_UV_VERSION: "0.12.x"
|
||||
DEFAULT_PYTHON_VERSION: "3.12"
|
||||
jobs:
|
||||
changes:
|
||||
|
||||
@@ -8,7 +8,7 @@ concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
env:
|
||||
DEFAULT_UV_VERSION: "0.11.x"
|
||||
DEFAULT_UV_VERSION: "0.12.x"
|
||||
DEFAULT_PYTHON_VERSION: "3.12"
|
||||
permissions: {}
|
||||
jobs:
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
branches:
|
||||
- dev
|
||||
env:
|
||||
DEFAULT_UV_VERSION: "0.11.x"
|
||||
DEFAULT_UV_VERSION: "0.12.x"
|
||||
jobs:
|
||||
generate-translate-strings:
|
||||
name: Generate Translation Strings
|
||||
|
||||
+1
-1
@@ -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.32-python3.12-trixie-slim AS s6-overlay-base
|
||||
FROM ghcr.io/astral-sh/uv:0.12.5-python3.14-trixie-slim AS s6-overlay-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
|
||||
|
||||
@@ -68,7 +68,14 @@
|
||||
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
||||
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
||||
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
||||
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
||||
<!-- Paperless does not process SVG or ImageMagick scripting formats. -->
|
||||
<policy domain="coder" rights="none" pattern="SVG" />
|
||||
<policy domain="coder" rights="none" pattern="SVGZ" />
|
||||
<policy domain="coder" rights="none" pattern="MSVG" />
|
||||
<policy domain="coder" rights="none" pattern="RSVG" />
|
||||
<policy domain="coder" rights="none" pattern="MSL" />
|
||||
<policy domain="coder" rights="none" pattern="MVG" />
|
||||
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
|
||||
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="HTTPS" /> -->
|
||||
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
||||
@@ -78,8 +85,6 @@
|
||||
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- not needed due to the need to use explicitly by mvg: -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
||||
<!-- use curl -->
|
||||
<policy domain="delegate" rights="none" pattern="URL" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
||||
|
||||
+8
-15
@@ -416,22 +416,15 @@ to a positive number to enable polling and disable native filesystem notificatio
|
||||
You may need to change the path in the files. Example:
|
||||
`ExecStart=/opt/paperless/.local/bin/celery --app paperless worker --loglevel INFO`
|
||||
|
||||
12. Configure ImageMagick to allow processing of PDF documents. Most
|
||||
distributions have this disabled by default, since PDF documents can
|
||||
contain malware. If you don't do this, Paperless-ngx will fall back to
|
||||
Ghostscript for certain steps such as thumbnail generation.
|
||||
12. Configure ImageMagick to allow processing of PDF documents and disable
|
||||
formats that Paperless-ngx does not use. Most distributions disable PDF
|
||||
processing by default, since PDF documents can contain malware. If you
|
||||
don't enable it, Paperless-ngx will fall back to Ghostscript for certain
|
||||
steps such as thumbnail generation.
|
||||
|
||||
Edit `/etc/ImageMagick-6/policy.xml` and adjust
|
||||
|
||||
```
|
||||
<policy domain="coder" rights="none" pattern="PDF" />
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```
|
||||
<policy domain="coder" rights="read|write" pattern="PDF" />
|
||||
```
|
||||
Configure the active ImageMagick policy file (commonly
|
||||
`/etc/ImageMagick-6/policy.xml` or `/etc/ImageMagick-7/policy.xml`) and
|
||||
adjust similar to [the docker policy file](https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/main/docker/rootfs/etc/ImageMagick-6/paperless-policy.xml). You should also include restrictions as noted there.
|
||||
|
||||
**Optional: Install the [jbig2enc](https://ocrmypdf.readthedocs.io/en/latest/jbig2.html) encoder.**
|
||||
This will reduce the size of generated PDF documents. You'll most likely need to compile this yourself, because this
|
||||
|
||||
+6
-4
@@ -84,9 +84,9 @@ mariadb = [
|
||||
"mysqlclient~=2.2.7",
|
||||
]
|
||||
postgres = [
|
||||
"psycopg[c,pool]==3.3",
|
||||
"psycopg[c,pool]==3.3.4",
|
||||
# Direct dependency for proper resolution of the pre-built wheels
|
||||
"psycopg-c==3.3",
|
||||
"psycopg-c==3.3.4",
|
||||
"psycopg-pool==3.3.1",
|
||||
]
|
||||
webserver = [
|
||||
@@ -160,8 +160,10 @@ explicit = true
|
||||
[tool.uv.sources]
|
||||
# Markers are chosen to select these almost exclusively when building the Docker image
|
||||
psycopg-c = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.14'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.14'" },
|
||||
]
|
||||
torch = [
|
||||
{ index = "pytorch-cpu" },
|
||||
|
||||
@@ -195,32 +195,32 @@
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="attributes-submenu ms-2"
|
||||
class="attributes-submenu ms-3"
|
||||
[ngbCollapse]="slimSidebarEnabled || attributesSectionsCollapsed"
|
||||
>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||
<a class="nav-link py-1" routerLink="attributes/tags" routerLinkActive="active" (click)="closeMenu()">
|
||||
<a class="nav-link" routerLink="attributes/tags" routerLinkActive="active" (click)="closeMenu()">
|
||||
<i-bs class="me-2" name="tags"></i-bs><span><ng-container i18n>Tags</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
<a class="nav-link py-1" routerLink="attributes/correspondents" routerLinkActive="active" (click)="closeMenu()">
|
||||
<a class="nav-link" routerLink="attributes/correspondents" routerLinkActive="active" (click)="closeMenu()">
|
||||
<i-bs class="me-2" name="person"></i-bs><span><ng-container i18n>Correspondents</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
<a class="nav-link py-1" routerLink="attributes/documenttypes" routerLinkActive="active" (click)="closeMenu()">
|
||||
<a class="nav-link" routerLink="attributes/documenttypes" routerLinkActive="active" (click)="closeMenu()">
|
||||
<i-bs class="me-2" name="hash"></i-bs><span><ng-container i18n>Document types</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
<a class="nav-link py-1" routerLink="attributes/storagepaths" routerLinkActive="active" (click)="closeMenu()">
|
||||
<a class="nav-link" routerLink="attributes/storagepaths" routerLinkActive="active" (click)="closeMenu()">
|
||||
<i-bs class="me-2" name="folder"></i-bs><span><ng-container i18n>Storage paths</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
|
||||
<a class="nav-link py-1" routerLink="attributes/customfields" routerLinkActive="active" (click)="closeMenu()">
|
||||
<a class="nav-link" routerLink="attributes/customfields" routerLinkActive="active" (click)="closeMenu()">
|
||||
<i-bs class="me-2" name="ui-radios"></i-bs><span><ng-container i18n>Custom fields</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -216,8 +216,8 @@ main {
|
||||
|
||||
// bring sub-menu markers back out to L edge
|
||||
.attributes-submenu .nav-link {
|
||||
margin-left: -0.5rem;
|
||||
padding-left: calc(var(--bs-nav-link-padding-x) + 0.5rem);
|
||||
margin-left: -1rem;
|
||||
padding-left: calc(var(--bs-nav-link-padding-x) + 1rem);
|
||||
}
|
||||
|
||||
.attributes-row .attributes-expand-btn {
|
||||
|
||||
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+911
-1418
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+910
-1417
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+934
-1441
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+1098
-1605
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+2152
-2658
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+945
-1452
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+910
-1417
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
+908
-1415
File diff suppressed because it is too large
Load Diff
@@ -62,18 +62,17 @@ def generate_unique_filename(doc, *, archive_filename=False) -> Path:
|
||||
old_filename = Path(doc.filename) if doc.filename else None
|
||||
root = settings.ORIGINALS_DIR
|
||||
|
||||
base_filename = generate_filename(doc, archive_filename=archive_filename)
|
||||
|
||||
# If generating archive filenames, try to make a name that is similar to
|
||||
# the original filename first.
|
||||
|
||||
if archive_filename and doc.filename:
|
||||
# Generate the full path using the same logic as generate_filename
|
||||
base_generated = generate_filename(doc, archive_filename=archive_filename)
|
||||
|
||||
# Try to create a simple PDF version based on the original filename
|
||||
# but preserve any directory structure from the template
|
||||
if str(base_generated.parent) != ".":
|
||||
if str(base_filename.parent) != ".":
|
||||
# Has directory structure, preserve it
|
||||
simple_pdf_name = base_generated.parent / (Path(doc.filename).stem + ".pdf")
|
||||
simple_pdf_name = base_filename.parent / (Path(doc.filename).stem + ".pdf")
|
||||
else:
|
||||
# No directory structure
|
||||
simple_pdf_name = Path(Path(doc.filename).stem + ".pdf")
|
||||
@@ -81,14 +80,17 @@ def generate_unique_filename(doc, *, archive_filename=False) -> Path:
|
||||
if simple_pdf_name == old_filename or not (root / simple_pdf_name).exists():
|
||||
return simple_pdf_name
|
||||
|
||||
file_extension = ".pdf" if archive_filename else doc.file_type
|
||||
filename_stem = base_filename.name.removesuffix(file_extension)
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
new_filename = generate_filename(
|
||||
doc,
|
||||
counter=counter,
|
||||
archive_filename=archive_filename,
|
||||
)
|
||||
new_filename = base_filename
|
||||
if counter:
|
||||
new_filename = base_filename.with_name(
|
||||
f"{filename_stem}_{counter:02}{file_extension}",
|
||||
)
|
||||
|
||||
if new_filename == old_filename:
|
||||
# still the same as before.
|
||||
return new_filename
|
||||
|
||||
@@ -10,8 +10,10 @@ from auditlog.context import disable_auditlog
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import DatabaseError
|
||||
from django.db import connection
|
||||
from django.test import TestCase
|
||||
from django.test import override_settings
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from django.utils import timezone
|
||||
|
||||
from documents.file_handling import create_source_path_directory
|
||||
@@ -32,6 +34,36 @@ from documents.tests.utils import FileSystemAssertsMixin
|
||||
|
||||
|
||||
class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
@override_settings(FILENAME_FORMAT="{title}")
|
||||
def test_generate_unique_filename_renders_template_once(self) -> None:
|
||||
document = Document.objects.create(
|
||||
title="collision",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
Document.objects.filter(pk=document.pk).update(filename="collision_03.pdf")
|
||||
document.refresh_from_db()
|
||||
|
||||
for filename in (
|
||||
"collision.pdf",
|
||||
"collision_01.pdf",
|
||||
"collision_02.pdf",
|
||||
"collision_03.pdf",
|
||||
):
|
||||
(settings.ORIGINALS_DIR / filename).touch()
|
||||
|
||||
with CaptureQueriesContext(connection) as queries:
|
||||
generated = generate_unique_filename(document)
|
||||
|
||||
relation_queries = [
|
||||
query["sql"]
|
||||
for query in queries.captured_queries
|
||||
if "documents_tag" in query["sql"]
|
||||
or "documents_customfieldinstance" in query["sql"]
|
||||
]
|
||||
|
||||
self.assertEqual(generated, Path("collision_03.pdf"))
|
||||
self.assertEqual(len(relation_queries), 2)
|
||||
|
||||
@override_settings(FILENAME_FORMAT="")
|
||||
def test_generate_source_filename(self) -> None:
|
||||
document = Document()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1034
-1254
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user