Basic start of soft delete
Currently blocked by https://github.com/san4ezy/django_softdelete/pull/30
This commit is contained in:
parent
9d4e2d4652
commit
06bb218c71
1
Pipfile
1
Pipfile
@ -17,6 +17,7 @@ django-extensions = "*"
|
|||||||
django-filter = "~=24.2"
|
django-filter = "~=24.2"
|
||||||
django-guardian = "*"
|
django-guardian = "*"
|
||||||
django-multiselectfield = "*"
|
django-multiselectfield = "*"
|
||||||
|
django-soft-delete = "*"
|
||||||
djangorestframework = "==3.14.0"
|
djangorestframework = "==3.14.0"
|
||||||
djangorestframework-guardian = "*"
|
djangorestframework-guardian = "*"
|
||||||
drf-writable-nested = "*"
|
drf-writable-nested = "*"
|
||||||
|
9
Pipfile.lock
generated
9
Pipfile.lock
generated
@ -540,6 +540,15 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.1.12"
|
"version": "==0.1.12"
|
||||||
},
|
},
|
||||||
|
"django-soft-delete": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2b28ab95d18847ea301c46f6f172ea878490fdf12261c697ec7e44b9d652f866",
|
||||||
|
"sha256:5afe70c0f5ce066c0e287c39806fb949ab66e73305a11b88d4c533247f3b96f0"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.0.12"
|
||||||
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
||||||
|
@ -1362,6 +1362,20 @@ processing. This only has an effect if
|
|||||||
|
|
||||||
Defaults to false.
|
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
|
## Binaries
|
||||||
|
|
||||||
There are a few external software packages that Paperless expects to
|
There are a few external software packages that Paperless expects to
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -23,11 +23,13 @@ from multiselectfield import MultiSelectField
|
|||||||
if settings.AUDIT_LOG_ENABLED:
|
if settings.AUDIT_LOG_ENABLED:
|
||||||
from auditlog.registry import auditlog
|
from auditlog.registry import auditlog
|
||||||
|
|
||||||
|
from django_softdelete.models import SoftDeleteModel
|
||||||
|
|
||||||
from documents.data_models import DocumentSource
|
from documents.data_models import DocumentSource
|
||||||
from documents.parsers import get_default_file_extension
|
from documents.parsers import get_default_file_extension
|
||||||
|
|
||||||
|
|
||||||
class ModelWithOwner(models.Model):
|
class ModelWithOwner(SoftDeleteModel):
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -1262,7 +1264,7 @@ class WorkflowAction(models.Model):
|
|||||||
return f"WorkflowAction {self.pk}"
|
return f"WorkflowAction {self.pk}"
|
||||||
|
|
||||||
|
|
||||||
class Workflow(models.Model):
|
class Workflow(SoftDeleteModel):
|
||||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||||
|
|
||||||
order = models.IntegerField(_("order"), default=0)
|
order = models.IntegerField(_("order"), default=0)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from celery import states
|
from celery import states
|
||||||
@ -302,7 +303,13 @@ def set_storage_path(
|
|||||||
|
|
||||||
|
|
||||||
@receiver(models.signals.post_delete, sender=Document)
|
@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):
|
with FileLock(settings.MEDIA_LOCK):
|
||||||
if settings.TRASH_DIR:
|
if settings.TRASH_DIR:
|
||||||
# Find a non-conflicting filename in case a document with the same
|
# Find a non-conflicting filename in case a document with the same
|
||||||
|
@ -207,6 +207,19 @@ def _parse_beat_schedule() -> dict:
|
|||||||
"expires": ((7.0 * 24.0) - 1.0) * 60.0 * 60.0,
|
"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:
|
for task in tasks:
|
||||||
# Either get the environment setting or use the default
|
# 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
|
if DEBUG: # pragma: no cover
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
|
||||||
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
|
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Soft Delete
|
||||||
|
###############################################################################
|
||||||
|
EMPTY_TRASH_DELAY = max(__get_int("PAPERLESS_SOFT_DELETE_DELAY", 30), 1)
|
||||||
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user