From 1ce6c6e2c50d949ab8b05d915ae23d8b2b42032c Mon Sep 17 00:00:00 2001 From: Wolf-Bastian Poettner Date: Fri, 27 Dec 2019 14:13:28 +0000 Subject: [PATCH] Add unit tests for filename feature --- src/documents/models.py | 84 +++++++----- src/documents/tests/test_file_handling.py | 154 ++++++++++++++++++++++ 2 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 src/documents/tests/test_file_handling.py diff --git a/src/documents/models.py b/src/documents/models.py index 25e5621b3..741b7079e 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -256,7 +256,7 @@ class Document(models.Model): added = models.DateTimeField( default=timezone.now, editable=False, db_index=True) - filename = models.CharField( + filename = models.FilePathField( max_length=256, editable=False, default=None, @@ -402,48 +402,70 @@ class Document(models.Model): self.filename = filename +def delete_empty_directory(directory): + if len(os.listdir(directory)) == 0: + try: + os.rmdir(directory) + except os.error: + # Directory not empty + pass + @receiver(models.signals.m2m_changed, sender=Document.tags.through) @receiver(models.signals.post_save, sender=Document) def update_filename(sender, instance, **kwargs): - if instance.filename is None: - return + if instance.filename is None: + return - # Build the new filename - new_filename = instance.source_filename_new() + # Build the new filename + new_filename = instance.source_filename_new() - # If the filename is the same, then nothing needs to be done - if instance.filename is None or \ - instance.filename == new_filename: - return + # If the filename is the same, then nothing needs to be done + if instance.filename is None or \ + instance.filename == new_filename: + return - # Check if filename needs changing - if new_filename != instance.filename: - # Determine the full "target" path - path_new = instance.filename_to_path(new_filename) - dir_new = instance.filename_to_path(os.path.dirname(new_filename)) + # Check if filename needs changing + if new_filename != instance.filename: + # Determine the full "target" path + path_new = instance.filename_to_path(new_filename) + dir_new = instance.filename_to_path(os.path.dirname(new_filename)) - # Determine the full "current" path - path_current = instance.filename_to_path(instance.filename) + # Determine the full "current" path + path_current = instance.filename_to_path(instance.filename) - # Move file + # Move file + try: os.rename(path_current, path_new) + except PermissionError: + # Do not update filename in object + return - # Delete empty directory - old_dir = os.path.dirname(instance.filename) - old_path = instance.filename_to_path(old_dir) - if len(os.listdir(old_path)) == 0: - try: - os.rmdir(old_path) - except os.error: - # Directory not empty - pass + # Delete empty directory + old_dir = os.path.dirname(instance.filename) + old_path = instance.filename_to_path(old_dir) + delete_empty_directory(old_path) - instance.filename = new_filename + instance.filename = new_filename - # Save instance - # This will not cause a cascade of post_save signals, as next time - # nothing needs to be renamed - instance.save() + # Save instance + # This will not cause a cascade of post_save signals, as next time + # nothing needs to be renamed + instance.save() + + +@receiver(models.signals.post_delete, sender=Document) +def delete_files(sender, instance, **kwargs): + if instance.filename is None: + return + + # Remove the document + old_file = instance.filename_to_path(instance.filename) + os.remove(old_file) + + # And remove the directory (if applicable) + old_dir = os.path.dirname(instance.filename) + old_path = instance.filename_to_path(old_dir) + delete_empty_directory(old_path) class Log(models.Model): diff --git a/src/documents/tests/test_file_handling.py b/src/documents/tests/test_file_handling.py new file mode 100644 index 000000000..edeb1ab5e --- /dev/null +++ b/src/documents/tests/test_file_handling.py @@ -0,0 +1,154 @@ +import datetime +import os +import shutil +from unittest import mock +from uuid import uuid4 +from pathlib import Path + +from dateutil import tz +from django.test import TestCase, override_settings + +from django.utils.text import slugify +from ..models import Document, Correspondent +from django.conf import settings + + +class TestDate(TestCase): + @override_settings(PAPERLESS_DIRECTORY_FORMAT="") + @override_settings(PAPERLESS_FILENAME_FORMAT="") + def test_source_filename(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + self.assertEqual(document.source_filename, "0000001.pdf") + + document.filename = "test.pdf" + self.assertEqual(document.source_filename, "test.pdf") + + @override_settings(PAPERLESS_DIRECTORY_FORMAT="") + @override_settings(PAPERLESS_FILENAME_FORMAT="") + def test_source_filename_new(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + self.assertEqual(document.source_filename_new(), "0000001.pdf") + + document.storage_type = Document.STORAGE_TYPE_GPG + self.assertEqual(document.source_filename_new(), "0000001.pdf.gpg") + + @override_settings(MEDIA_ROOT="/tmp/paperless-tests-{}". + format(str(uuid4())[:8])) + @override_settings(PAPERLESS_DIRECTORY_FORMAT="{correspondent}") + @override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}") + def test_file_renaming(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + # Ensure that filename is properly generated + tmp = document.source_filename + self.assertEqual(document.source_filename_new(), + "none/none-0000001.pdf") + Path(document.source_path).touch() + + # Test source_path + self.assertEqual(document.source_path, settings.MEDIA_ROOT + + "/documents/originals/none/none-0000001.pdf") + + # Enable encryption and check again + document.storage_type = Document.STORAGE_TYPE_GPG + tmp = document.source_filename + self.assertEqual(document.source_filename_new(), + "none/none-0000001.pdf.gpg") + document.save() + + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/none"), True) + + # Set a correspondent and save the document + document.correspondent = Correspondent.objects.get_or_create( + name="test")[0] + document.save() + + # Check proper handling of files + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/test"), True) + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/none"), False) + self.assertEqual(os.path.isfile(settings.MEDIA_ROOT + "/documents/" + + "originals/test/test-0000001.pdf.gpg"), True) + self.assertEqual(document.source_filename_new(), + "test/test-0000001.pdf.gpg") + + @override_settings(MEDIA_ROOT="/tmp/paperless-tests-{}". + format(str(uuid4())[:8])) + @override_settings(PAPERLESS_DIRECTORY_FORMAT="{correspondent}") + @override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}") + def test_document_delete(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + # Ensure that filename is properly generated + tmp = document.source_filename + self.assertEqual(document.source_filename_new(), + "none/none-0000001.pdf") + Path(document.source_path).touch() + + # Ensure file deletion after delete + document.delete() + self.assertEqual(os.path.isfile(settings.MEDIA_ROOT + + "/documents/originals/none/none-0000001.pdf"), False) + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/none"), False) + + @override_settings(MEDIA_ROOT="/tmp/paperless-tests-{}". + format(str(uuid4())[:8])) + @override_settings(PAPERLESS_DIRECTORY_FORMAT="{correspondent}") + @override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}") + def test_directory_not_empty(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + # Ensure that filename is properly generated + tmp = document.source_filename + self.assertEqual(document.source_filename_new(), + "none/none-0000001.pdf") + Path(document.source_path).touch() + Path(document.source_path + "test").touch() + + # Set a correspondent and save the document + document.correspondent = Correspondent.objects.get_or_create( + name="test")[0] + document.save() + + # Check proper handling of files + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/test"), True) + self.assertEqual(os.path.isdir(settings.MEDIA_ROOT + + "/documents/originals/none"), True) + + # Cleanup + os.remove(settings.MEDIA_ROOT + + "/documents/originals/none/none-0000001.pdftest") + os.rmdir(settings.MEDIA_ROOT + "/documents/originals/none") + + @override_settings(MEDIA_ROOT="/tmp/paperless-tests-{}". + format(str(uuid4())[:8])) + @override_settings(PAPERLESS_DIRECTORY_FORMAT=None) + @override_settings(PAPERLESS_FILENAME_FORMAT=None) + def test_format_none(self): + document = Document() + document.file_type = "pdf" + document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED + document.save() + + self.assertEqual(document.source_filename_new(), "0000001.pdf")