Basic start of soft delete

Currently blocked by https://github.com/san4ezy/django_softdelete/pull/30
This commit is contained in:
shamoon 2024-04-22 15:19:05 -07:00
parent 9d4e2d4652
commit 06bb218c71
8 changed files with 183 additions and 3 deletions

View File

@ -17,6 +17,7 @@ django-extensions = "*"
django-filter = "~=24.2"
django-guardian = "*"
django-multiselectfield = "*"
django-soft-delete = "*"
djangorestframework = "==3.14.0"
djangorestframework-guardian = "*"
drf-writable-nested = "*"

9
Pipfile.lock generated
View File

@ -540,6 +540,15 @@
"index": "pypi",
"version": "==0.1.12"
},
"django-soft-delete": {
"hashes": [
"sha256:2b28ab95d18847ea301c46f6f172ea878490fdf12261c697ec7e44b9d652f866",
"sha256:5afe70c0f5ce066c0e287c39806fb949ab66e73305a11b88d4c533247f3b96f0"
],
"index": "pypi",
"markers": "python_version >= '3.6'",
"version": "==1.0.12"
},
"djangorestframework": {
"hashes": [
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",

View File

@ -1362,6 +1362,20 @@ processing. This only has an effect if
Defaults to false.
## Trash
#### [`EMPTY_TRASH_DELAY=<num>`](#EMPTY_TRASH_DELAY) {#EMPTY_TRASH_DELAY}
: Sets how long in days objects remain in the 'trash' before they are permanently deleted.
Defaults to 30 days, minimum of 1 day.
#### [`PAPERLESS_EMPTY_TRASH_TASK_CRON=<cron expression>`](#PAPERLESS_EMPTY_TRASH_TASK_CRON) {#PAPERLESS_EMPTY_TRASH_TASK_CRON}
: Configures the schedule to empty the trash of expired deleted objects.
Defaults to `0 1 * * *`, once per day.
## Binaries
There are a few external software packages that Paperless expects to

View File

@ -0,0 +1,84 @@
# Generated by Django 4.2.11 on 2024-04-22 20:44
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("documents", "1046_workflowaction_remove_all_correspondents_and_more"),
]
operations = [
migrations.AddField(
model_name="correspondent",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="correspondent",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="document",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="document",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="documenttype",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="documenttype",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="savedview",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="savedview",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="storagepath",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="storagepath",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="tag",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="tag",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="workflow",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="workflow",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -23,11 +23,13 @@ from multiselectfield import MultiSelectField
if settings.AUDIT_LOG_ENABLED:
from auditlog.registry import auditlog
from django_softdelete.models import SoftDeleteModel
from documents.data_models import DocumentSource
from documents.parsers import get_default_file_extension
class ModelWithOwner(models.Model):
class ModelWithOwner(SoftDeleteModel):
owner = models.ForeignKey(
User,
blank=True,
@ -1262,7 +1264,7 @@ class WorkflowAction(models.Model):
return f"WorkflowAction {self.pk}"
class Workflow(models.Model):
class Workflow(SoftDeleteModel):
name = models.CharField(_("name"), max_length=256, unique=True)
order = models.IntegerField(_("order"), default=0)

View File

@ -1,6 +1,7 @@
import logging
import os
import shutil
from datetime import timedelta
from typing import Optional
from celery import states
@ -302,7 +303,13 @@ def set_storage_path(
@receiver(models.signals.post_delete, sender=Document)
def cleanup_document_deletion(sender, instance, using, **kwargs):
def cleanup_document_deletion(sender, instance, **kwargs):
now = timezone.localtime(timezone.now())
if now - instance.deleted_at < timedelta(days=settings.EMPTY_TRASH_DELAY):
logger.info(
f"Detected soft delete of {instance!s}. Deferring cleanup.",
)
return
with FileLock(settings.MEDIA_LOCK):
if settings.TRASH_DIR:
# Find a non-conflicting filename in case a document with the same

View File

@ -207,6 +207,19 @@ def _parse_beat_schedule() -> dict:
"expires": ((7.0 * 24.0) - 1.0) * 60.0 * 60.0,
},
},
{
"name": "Empty trash",
"env_key": "PAPERLESS_EMPTY_TRASH_TASK_CRON",
# Default daily at 01:00
"env_default": "0 1 * * *",
"task": "documents.tasks.sanity_check",
"options": {
# 1 hour before default schedule sends again
"expires": 23.0
* 60.0
* 60.0,
},
},
]
for task in tasks:
# Either get the environment setting or use the default
@ -1148,3 +1161,9 @@ EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] "
if DEBUG: # pragma: no cover
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
###############################################################################
# Soft Delete
###############################################################################
EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_SOFT_DELETE_DELAY", 30), 1)

View File

@ -0,0 +1,44 @@
# Generated by Django 4.2.11 on 2024-04-22 20:44
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
dependencies = [
("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"),
]
operations = [
migrations.AddField(
model_name="mailaccount",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="mailaccount",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="mailrule",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="mailrule",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="processedmail",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="processedmail",
name="restored_at",
field=models.DateTimeField(blank=True, null=True),
),
]