mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge pull request #24 from tribut/feature-trash
Add temporary "delete to trash" functionality
This commit is contained in:
commit
1688af7a0d
@ -17,6 +17,7 @@ paperlessng_db_sslmode: prefer
|
||||
paperlessng_directory: /opt/paperless-ng
|
||||
paperlessng_consumption_dir: "{{ paperlessng_directory }}/consumption"
|
||||
paperlessng_data_dir: "{{ paperlessng_directory }}/data"
|
||||
paperlessng_trash_dir:
|
||||
paperlessng_media_root: "{{ paperlessng_directory }}/media"
|
||||
paperlessng_staticdir: "{{ paperlessng_directory }}/static"
|
||||
paperlessng_filename_format:
|
||||
|
@ -252,9 +252,11 @@
|
||||
owner: "{{ paperlessng_system_user }}"
|
||||
group: "{{ paperlessng_system_group }}"
|
||||
mode: "750"
|
||||
when: item
|
||||
with_items:
|
||||
- "{{ paperlessng_consumption_dir }}"
|
||||
- "{{ paperlessng_data_dir }}"
|
||||
- "{{ paperlessng_trash_dir }}"
|
||||
- "{{ paperlessng_media_root }}"
|
||||
- "{{ paperlessng_staticdir }}"
|
||||
|
||||
@ -277,6 +279,8 @@
|
||||
line: "PAPERLESS_CONSUMPTION_DIR={{ paperlessng_consumption_dir }}"
|
||||
- regexp: PAPERLESS_DATA_DIR
|
||||
line: "PAPERLESS_DATA_DIR={{ paperlessng_data_dir }}"
|
||||
- regexp: PAPERLESS_TRASH_DIR
|
||||
line: "PAPERLESS_TRASH_DIR={{ paperlessng_trash_dir }}"
|
||||
- regexp: PAPERLESS_MEDIA_ROOT
|
||||
line: "PAPERLESS_MEDIA_ROOT={{ paperlessng_media_root }}"
|
||||
- regexp: PAPERLESS_STATICDIR
|
||||
|
@ -80,6 +80,15 @@ PAPERLESS_DATA_DIR=<path>
|
||||
|
||||
Defaults to "../data/", relative to the "src" directory.
|
||||
|
||||
PAPERLESS_TRASH_DIR=<path>
|
||||
Instead of removing deleted documents, they are moved to this directory.
|
||||
|
||||
This must be writeable by the user running paperless. When running inside
|
||||
docker, ensure that this path is within a permanent volume (such as
|
||||
"../media/trash") so it won't get lost on upgrades.
|
||||
|
||||
Defaults to empty (i.e. really delete documents).
|
||||
|
||||
PAPERLESS_MEDIA_ROOT=<path>
|
||||
This is where your documents and thumbnails are stored.
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#PAPERLESS_CONSUMPTION_DIR=../consume
|
||||
#PAPERLESS_DATA_DIR=../data
|
||||
#PAPERLESS_TRASH_DIR=
|
||||
#PAPERLESS_MEDIA_ROOT=../media
|
||||
#PAPERLESS_STATICDIR=../static
|
||||
#PAPERLESS_FILENAME_FORMAT=
|
||||
|
@ -225,6 +225,37 @@ def set_tags(sender,
|
||||
@receiver(models.signals.post_delete, sender=Document)
|
||||
def cleanup_document_deletion(sender, instance, using, **kwargs):
|
||||
with FileLock(settings.MEDIA_LOCK):
|
||||
if settings.TRASH_DIR:
|
||||
# Find a non-conflicting filename in case a document with the same
|
||||
# name was moved to trash earlier
|
||||
counter = 0
|
||||
old_filename = os.path.split(instance.source_path)[1]
|
||||
(old_filebase, old_fileext) = os.path.splitext(old_filename)
|
||||
|
||||
while True:
|
||||
new_file_path = os.path.join(
|
||||
settings.TRASH_DIR,
|
||||
old_filebase +
|
||||
(f"_{counter:02}" if counter else "") +
|
||||
old_fileext
|
||||
)
|
||||
|
||||
if os.path.exists(new_file_path):
|
||||
counter += 1
|
||||
else:
|
||||
break
|
||||
|
||||
logger.debug(
|
||||
f"Moving {instance.source_path} to trash at {new_file_path}")
|
||||
try:
|
||||
os.rename(instance.source_path, new_file_path)
|
||||
except OSError as e:
|
||||
logger.error(
|
||||
f"Failed to move {instance.source_path} to trash at "
|
||||
f"{new_file_path}: {e}. Skipping cleanup!"
|
||||
)
|
||||
return
|
||||
|
||||
for filename in (instance.source_path,
|
||||
instance.archive_path,
|
||||
instance.thumbnail_path):
|
||||
|
@ -2,6 +2,7 @@ import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import tempfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
@ -154,6 +155,40 @@ class TestFileHandling(DirectoriesMixin, TestCase):
|
||||
self.assertEqual(os.path.isfile(settings.ORIGINALS_DIR + "/none/none.pdf"), False)
|
||||
self.assertEqual(os.path.isdir(settings.ORIGINALS_DIR + "/none"), False)
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{correspondent}", TRASH_DIR=tempfile.mkdtemp())
|
||||
def test_document_delete_trash(self):
|
||||
document = Document()
|
||||
document.mime_type = "application/pdf"
|
||||
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
|
||||
document.save()
|
||||
|
||||
# Ensure that filename is properly generated
|
||||
document.filename = generate_filename(document)
|
||||
self.assertEqual(document.filename,
|
||||
"none/none.pdf")
|
||||
|
||||
create_source_path_directory(document.source_path)
|
||||
Path(document.source_path).touch()
|
||||
|
||||
# Ensure file was moved to trash after delete
|
||||
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none/none.pdf"), False)
|
||||
document.delete()
|
||||
self.assertEqual(os.path.isfile(settings.ORIGINALS_DIR + "/none/none.pdf"), False)
|
||||
self.assertEqual(os.path.isdir(settings.ORIGINALS_DIR + "/none"), False)
|
||||
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none.pdf"), True)
|
||||
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none_01.pdf"), False)
|
||||
|
||||
# Create an identical document and ensure it is trashed under a new name
|
||||
document = Document()
|
||||
document.mime_type = "application/pdf"
|
||||
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
|
||||
document.save()
|
||||
document.filename = generate_filename(document)
|
||||
create_source_path_directory(document.source_path)
|
||||
Path(document.source_path).touch()
|
||||
document.delete()
|
||||
self.assertEqual(os.path.isfile(settings.TRASH_DIR + "/none_01.pdf"), True)
|
||||
|
||||
@override_settings(PAPERLESS_FILENAME_FORMAT="{correspondent}/{correspondent}")
|
||||
def test_document_delete_nofile(self):
|
||||
document = Document()
|
||||
|
@ -50,6 +50,7 @@ def paths_check(app_configs, **kwargs):
|
||||
"""
|
||||
|
||||
return path_check("PAPERLESS_DATA_DIR", settings.DATA_DIR) + \
|
||||
path_check("PAPERLESS_TRASH_DIR", settings.TRASH_DIR) + \
|
||||
path_check("PAPERLESS_MEDIA_ROOT", settings.MEDIA_ROOT) + \
|
||||
path_check("PAPERLESS_CONSUMPTION_DIR", settings.CONSUMPTION_DIR)
|
||||
|
||||
|
@ -57,6 +57,8 @@ THUMBNAIL_DIR = os.path.join(MEDIA_ROOT, "documents", "thumbnails")
|
||||
|
||||
DATA_DIR = os.getenv('PAPERLESS_DATA_DIR', os.path.join(BASE_DIR, "..", "data"))
|
||||
|
||||
TRASH_DIR = os.getenv('PAPERLESS_TRASH_DIR')
|
||||
|
||||
# Lock file for synchronizing changes to the MEDIA directory across multiple
|
||||
# threads.
|
||||
MEDIA_LOCK = os.path.join(MEDIA_ROOT, "media.lock")
|
||||
|
Loading…
x
Reference in New Issue
Block a user