Skip to main content

Yet another approach to provide soft (logical) delete or masking (thrashing) django models instead of deleting them physically from db.

Project description

Django Permanent

Yet another approach to provide soft (logical) delete or masking (thrashing) django models instead of deleting them physically from db.

Tests

Installation

Install using pip:

pip install django-permanent

Or install from source:

git clone https://github.com/meteozond/django-permanent.git
cd django-permanent
python setup.py install

Requirements:

  • Python 3.10+
  • Django 4.2+

Quick Start

To create a non-deletable model just inherit it from PermanentModel:

class MyModel(PermanentModel):
    pass

It automatically changes delete behaviour to hide objects instead of deleting them:

>>> a = MyModel.objects.create(pk=1)
>>> b = MyModel.objects.create(pk=2)
>>> MyModel.objects.count()
2
>>> a.delete()
>>> MyModel.objects.count()
1

To recover a deleted object just call its restore method:

>>> a.restore()
>>> MyModel.objects.count()
2

Use the force kwarg to enforce physical deletion:

>>> a.delete(force=True) # Will act as the default django delete
>>> MyModel._base_manager.count()
0

Restore on Create

If you want create() to restore deleted objects instead of raising an integrity error on unique constraints, use the restore_on_create option:

class Article(PermanentModel):
    title = models.CharField(max_length=100, unique=True)

    class Permanent:
        restore_on_create = True

How it works:

When restore_on_create = True, calling Model.objects.create(**kwargs) will:

  1. First try to find a matching object (including soft-deleted ones)
  2. If found and deleted: restore it and update with new kwargs
  3. If found and not deleted: return the existing object
  4. If not found: create a new object

Example:

>>> article = Article.objects.create(title="Django Tips")
>>> article.delete()  # Soft delete
>>> Article.objects.count()
0

# Without restore_on_create: would raise IntegrityError
# With restore_on_create: restores the deleted article
>>> article2 = Article.objects.create(title="Django Tips")
>>> article2.pk == article.pk  # Same object!
True
>>> Article.objects.count()
1

Note: This feature is most useful for models with unique constraints where you want to "resurrect" deleted objects rather than creating duplicates.

Managers

It changes the default model manager to ignore deleted objects, adding a deleted_objects manager to see them instead:

>>> MyModel.objects.count()
2
>>> a.delete()
>>> MyModel.objects.count()
1
>>> MyModel.deleted_objects.count()
1
>>> MyModel.all_objects.count()
2
>>> MyModel._base_manager.count()
2

Accessing Deleted Related Objects

By default, accessing a foreign key to a deleted object will raise DoesNotExist. Use show_all_context() to access deleted related objects:

from django_permanent.related import show_all_context

# Create models with relationship
parent = ParentModel.objects.create(name="parent")
child = ChildModel.objects.create(parent=parent)

# Soft delete parent
parent.delete()

# Without show_all_context: raises DoesNotExist
child.parent  # Raises ParentModel.DoesNotExist

# With show_all_context: can access deleted parent
with show_all_context():
    print(child.parent.name)  # Works! Returns "parent"

Note: This is useful when you need to access relationships to soft-deleted objects, for example in admin interfaces or audit logs.

QuerySet

The QuerySet.delete method will act as the default django delete, with one exception - objects of models subclassing PermanentModel will be marked as deleted; the rest will be deleted physically:

>>> MyModel.objects.all().delete()

You can still force django query set physical deletion:

>>> MyModel.objects.all().delete(force=True)

Using custom querysets

  1. Inherit your query set from PermanentQuerySet:

    class ServerFileQuerySet(PermanentQuerySet)
        pass
    
  2. Wrap PermanentQuerySet or DeletedQuerySet in you model manager declaration:

    class MyModel(PermanentModel)
        objects = MultiPassThroughManager(ServerFileQuerySet, NonDeletedQuerySet)
        deleted_objects = MultiPassThroughManager(ServerFileQuerySet, DeletedQuerySet)
        all_objects = MultiPassThroughManager(ServerFileQuerySet, PermanentQuerySet)
    

Method get_restore_or_create

  1. Check for existence of the object.
  2. Restore it if it was deleted.
  3. Create a new one, if it was never created.

Field name

The default field named is 'removed', but you can override it with the PERMANENT_FIELD variable in settings.py:

PERMANENT_FIELD = 'deleted'

Requirements

  • Django 4.2+
  • Python 3.10, 3.11, 3.12+

Testing

The project uses GitHub Actions for continuous integration.

Run tests locally using act (GitHub Actions locally):

# Install act (macOS)
brew install act

# Run all tests in parallel
act

# Run specific Python version
act --matrix python-version:3.11

# Run specific Python/Django combination
act --matrix python-version:3.11 --matrix django-version:"Django>=4.2,<5.0"

Run tests directly:

# Install dependencies
pip install "Django>=4.2,<5.0" coverage flake8

# Run linter
flake8 django_permanent

# Run tests with coverage
coverage run runtests.py
coverage report

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_permanent-2.0.0.tar.gz (20.4 kB view details)

Uploaded Source

File details

Details for the file django_permanent-2.0.0.tar.gz.

File metadata

  • Download URL: django_permanent-2.0.0.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for django_permanent-2.0.0.tar.gz
Algorithm Hash digest
SHA256 671fc0b30b07fb0d781014f8173e8191bf26a8bbfb8990b2685e5610c54e899a
MD5 61d90a42de1c4cea7f1e7092de42cb88
BLAKE2b-256 5ad252794af389feb0e01cb9f5008c2b7f71d310937073200629727d67978d52

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_permanent-2.0.0.tar.gz:

Publisher: publish.yml on meteozond/django-permanent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page