{"id":4517,"library":"django-safedelete","title":"Django SafeDelete","description":"Django SafeDelete is an active library (current version 1.4.1) that provides soft deletion functionality for Django models, allowing objects to be masked from the database instead of permanently deleted. This enables recovery of 'deleted' data and aids in auditing. It offers various deletion policies and integrates with Django's ORM and Admin interface. Releases occur periodically, with significant updates often tied to Django version compatibility.","status":"active","version":"1.4.1","language":"en","source_language":"en","source_url":"https://github.com/makinacorpus/django-safedelete","tags":["django","soft-delete","ORM","database","data integrity"],"install":[{"cmd":"pip install django-safedelete","lang":"bash","label":"Install with pip"}],"dependencies":[{"reason":"Core framework dependency; version 1.4.x supports Django 4.2 and 5.0, and Python 3.8-3.11.","package":"Django","optional":false},{"reason":"Used for version parsing and compatibility checks.","package":"packaging","optional":false}],"imports":[{"note":"SafeDeleteMixin was renamed to SafeDeleteModel and deprecated in version 0.4.0. Always use SafeDeleteModel for new code.","wrong":"from safedelete.models import SafeDeleteMixin","symbol":"SafeDeleteModel","correct":"from safedelete.models import SafeDeleteModel"},{"note":"While these constants were internally moved to `safedelete.config` in version 0.4.0, they are still re-exported and commonly imported directly from `safedelete.models` as per current documentation and examples.","symbol":"SOFT_DELETE, HARD_DELETE, SOFT_DELETE_CASCADE, HARD_DELETE_NOCASCADE, NO_DELETE","correct":"from safedelete.models import SOFT_DELETE"}],"quickstart":{"code":"import os\nimport django\nfrom django.db import models\nfrom safedelete.models import SafeDeleteModel, SOFT_DELETE\n\n# Configure Django settings minimally for a runnable example\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', __name__)\ndjango.setup()\n\nclass Article(SafeDeleteModel):\n    _safedelete_policy = SOFT_DELETE\n    title = models.CharField(max_length=200)\n    content = models.TextField()\n    published_date = models.DateTimeField(auto_now_add=True)\n\n    def __str__(self):\n        return self.title\n\n# Example Usage:\n# Make sure Django is set up and models are migrated (not covered here)\n\n# Create an object\narticle = Article.objects.create(title='My First Article', content='This is some content.')\nprint(f\"Created: {article}\") # cite: 5\n\n# Soft delete the object\narticle.delete()\nprint(f\"Article soft-deleted. Is it deleted? {article.deleted is not None}\") # cite: 5\n\n# It won't appear in default queries\nprint(f\"Visible articles count: {Article.objects.count()}\") # Should be 0\n\n# Access soft-deleted objects using the all_objects manager\nall_articles = Article.all_objects.all()\nprint(f\"All articles (including deleted): {all_articles.count()}\") # Should be 1 # cite: 5\n\n# Filter for only deleted objects\ndeleted_articles = Article.deleted_objects.all()\nprint(f\"Only deleted articles: {deleted_articles.count()}\") # Should be 1\n\n# Undelete the object\ndeleted_article = Article.deleted_objects.get(pk=article.pk)\ndeleted_article.undelete()\nprint(f\"Article undeleted. Is it deleted? {deleted_article.deleted is not None}\")\nprint(f\"Visible articles count after undelete: {Article.objects.count()}\") # Should be 1\n\n# Hard delete (bypassing soft delete)\nhard_delete_article = Article.objects.create(title='To be hard deleted', content='Ephemeral content.')\nhard_delete_article.delete(force_policy=SafeDeleteModel.HARD_DELETE)\nprint(f\"Hard-deleted article count: {Article.all_objects.filter(pk=hard_delete_article.pk).count()}\") # Should be 0","lang":"python","description":"To use django-safedelete, make your model inherit from `SafeDeleteModel` and optionally set a `_safedelete_policy`. By default, `SOFT_DELETE` is used. Objects are then soft-deleted by calling `.delete()` and can be queried using `Article.all_objects` or `Article.deleted_objects`. To undelete, use the `.undelete()` method. You can force a hard delete using `force_policy=SafeDeleteModel.HARD_DELETE`."},"warnings":[{"fix":"Update your models to inherit from `safedelete.models.SafeDeleteModel` instead of `SafeDeleteMixin`.","message":"The `SafeDeleteMixin` class was renamed to `SafeDeleteModel` in version 0.4.0. Using `SafeDeleteMixin` is deprecated and will raise a warning.","severity":"breaking","affected_versions":"0.4.0+"},{"fix":"Adjust any code that checks `obj.deleted` to check `obj.deleted is not None` or `obj.deleted__isnull=False` in queries, to account for the `DateTimeField` behavior.","message":"The `deleted` field on `SafeDeleteModel` changed from a `BooleanField` to a `DateTimeField` in version 0.4.0. This can break existing queries or logic relying on a boolean check.","severity":"breaking","affected_versions":"0.4.0+"},{"fix":"Always check the `django-safedelete` changelog or PyPI page for supported Django and Python versions before upgrading, especially if your project relies on older environments.","message":"Significant Django and Python version compatibility changes can occur between minor and major versions of django-safedelete. For instance, version 1.3.0 dropped support for Django < 3.2 and Python 3.6, and version 1.4.0 dropped support for Django 3.2, 4.0, and 4.1.","severity":"breaking","affected_versions":"1.3.0, 1.4.0+"},{"fix":"Set `_safedelete_visibility = DELETED_INVISIBLE` (the default for managers) on your managers or models, and understand that direct access can still bypass this. For stricter control, you might need to override managers for related fields or use `SafeDeleteQueryset.visible()` or `SafeDeleteQueryset.undeleted()` in your queries.","message":"By default, related objects (via `ForeignKey` or `OneToOneField`) that are soft-deleted might still be accessible through direct relation traversal (e.g., `article.author` if `author` is deleted). This can lead to unexpected behavior where 'deleted' objects are still retrieved.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Be aware of this setting's effect when using `update_or_create()`. If you need to distinguish between a new creation and an undeletion, inspect the object's `deleted` field or use custom logic.","message":"If the `SAFE_DELETE_INTERPRET_UNDELETED_OBJECTS_AS_CREATED` setting is enabled, Django's `update_or_create()` method will return `(object, True)` (indicating 'created') when an object was soft-deleted and subsequently 'revived' or undeleted, rather than a truly new object being created. This can be misleading.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-12T00:00:00.000Z","next_check":"2026-07-11T00:00:00.000Z"}