mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	Adds owner and original name to the possible naming schemes
This commit is contained in:
		
							parent
							
								
									ef6c4e6789
								
							
						
					
					
						commit
						97ff2e126c
					
				@ -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
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
@ -818,6 +818,8 @@ class StoragePathSerializer(MatchingModelSerializer, OwnedObjectSerializer):
 | 
			
		||||
                asn="asn",
 | 
			
		||||
                tags="tags",
 | 
			
		||||
                tag_list="tag_list",
 | 
			
		||||
                owner_username="someone",
 | 
			
		||||
                original_name="testfile",
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        except (KeyError):
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user