From 97ff2e126c718ab20e62733d65d8891535b01eac Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Sat, 11 Mar 2023 17:43:58 -0800 Subject: [PATCH] Adds owner and original name to the possible naming schemes --- docs/advanced_usage.md | 2 + src/documents/file_handling.py | 31 ++++++-- src/documents/serialisers.py | 2 + src/documents/tests/test_api.py | 2 - src/documents/tests/test_file_handling.py | 91 +++++++++++++++++++++++ 5 files changed, 121 insertions(+), 7 deletions(-) diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 55ca8ee74..9b1816bef 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -309,6 +309,8 @@ Paperless provides the following placeholders within filenames: - `{added_month_name_short}`: Month added abbreviated name, as per locale - `{added_day}`: Day added only (number 01-31). +- `{owner_username}`: Username of document owner, if any, or "none" +- `{original_name}`: Document original filename, minus the extension, if any, or "none" Paperless will try to conserve the information from your database as much as possible. However, some characters that you can use in document diff --git a/src/documents/file_handling.py b/src/documents/file_handling.py index 5947fe1b0..c046ae15a 100644 --- a/src/documents/file_handling.py +++ b/src/documents/file_handling.py @@ -1,12 +1,13 @@ import logging import os from collections import defaultdict +from pathlib import PurePath import pathvalidate from django.conf import settings from django.template.defaultfilters import slugify from django.utils import timezone - +from documents.models import Document logger = logging.getLogger("paperless.filehandling") @@ -125,7 +126,12 @@ def generate_unique_filename(doc, archive_filename=False): return new_filename -def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): +def generate_filename( + doc: Document, + counter=0, + append_gpg=True, + archive_filename=False, +): path = "" filename_format = settings.FILENAME_FORMAT @@ -150,13 +156,15 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): replacement_text="-", ) + no_value_default = "-none-" + if doc.correspondent: correspondent = pathvalidate.sanitize_filename( doc.correspondent.name, replacement_text="-", ) else: - correspondent = "-none-" + correspondent = no_value_default if doc.document_type: document_type = pathvalidate.sanitize_filename( @@ -164,12 +172,23 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): replacement_text="-", ) else: - document_type = "-none-" + document_type = no_value_default if doc.archive_serial_number: asn = str(doc.archive_serial_number) else: - asn = "-none-" + asn = no_value_default + + if doc.owner is not None: + owner_username_str = str(doc.owner.username) + else: + owner_username_str = no_value_default + + if doc.original_filename is not None: + # No extension + original_name = PurePath(doc.original_filename).with_suffix("").name + else: + original_name = no_value_default # Convert UTC database datetime to localized date local_added = timezone.localdate(doc.added) @@ -196,6 +215,8 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False): asn=asn, tags=tags, tag_list=tag_list, + owner_username=owner_username_str, + original_name=original_name, ).strip() if settings.FILENAME_FORMAT_REMOVE_NONE: diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index d5fc0f079..1f74a43c0 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -818,6 +818,8 @@ class StoragePathSerializer(MatchingModelSerializer, OwnedObjectSerializer): asn="asn", tags="tags", tag_list="tag_list", + owner_username="someone", + original_name="testfile", ) except (KeyError): diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 96f82dea3..f56d0344c 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -20,8 +20,6 @@ except ImportError: import backports.zoneinfo as zoneinfo import pytest -from django.db import transaction -from django.db.utils import IntegrityError from django.conf import settings from django.contrib.auth.models import Group from django.contrib.auth.models import Permission diff --git a/src/documents/tests/test_file_handling.py b/src/documents/tests/test_file_handling.py index 0d548264c..04ef0a79f 100644 --- a/src/documents/tests/test_file_handling.py +++ b/src/documents/tests/test_file_handling.py @@ -5,6 +5,7 @@ from pathlib import Path from unittest import mock from django.conf import settings +from django.contrib.auth.models import User from django.db import DatabaseError from django.test import override_settings from django.test import TestCase @@ -1059,3 +1060,93 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase): checksum="2", ) self.assertEqual(generate_filename(doc), "84/August/Aug/The Title.pdf") + + @override_settings( + FILENAME_FORMAT="{owner_username}/{title}", + ) + def test_document_owner_string(self): + """ + GIVEN: + - Document with an other + - Document without an owner + - Filename format string includes owner + WHEN: + - Filename is generated for each document + THEN: + - Owned document includes username + - Document without owner returns "none" + """ + + u1 = User.objects.create_user("user1") + + owned_doc = Document.objects.create( + title="The Title", + mime_type="application/pdf", + checksum="2", + owner=u1, + ) + + no_owner_doc = Document.objects.create( + title="does matter", + mime_type="application/pdf", + checksum="3", + ) + + self.assertEqual(generate_filename(owned_doc), "user1/The Title.pdf") + self.assertEqual(generate_filename(no_owner_doc), "none/does matter.pdf") + + @override_settings( + FILENAME_FORMAT="{original_name}", + ) + def test_document_original_filename(self): + """ + GIVEN: + - Document with an original filename + - Document without an original filename + - Document which was plain text document + - Filename format string includes original filename + WHEN: + - Filename is generated for each document + THEN: + - Document with original name uses it, dropping suffix + - Document without original name returns "none" + - Text document returns extension of .txt + - Text document archive returns extension of .pdf + - No extensions are doubled + """ + doc_with_original = Document.objects.create( + title="does matter", + mime_type="application/pdf", + checksum="3", + original_filename="someepdf.pdf", + ) + tricky_with_original = Document.objects.create( + title="does matter", + mime_type="application/pdf", + checksum="1", + original_filename="some pdf with spaces and stuff.pdf", + ) + no_original = Document.objects.create( + title="does matter", + mime_type="application/pdf", + checksum="2", + ) + + text_doc = Document.objects.create( + title="does matter", + mime_type="text/plain", + checksum="4", + original_filename="logs.txt", + ) + + self.assertEqual(generate_filename(doc_with_original), "someepdf.pdf") + + self.assertEqual( + generate_filename(tricky_with_original), + "some pdf with spaces and stuff.pdf", + ) + + self.assertEqual(generate_filename(no_original), "none.pdf") + + self.assertEqual(generate_filename(text_doc), "logs.txt") + self.assertEqual(generate_filename(text_doc, archive_filename=True), "logs.pdf")