django-deletion-records


Namedjango-deletion-records JSON
Version 0.2.0 PyPI version JSON
download
home_page
SummaryA Django application to track deleted records.
upload_time2024-02-19 16:19:36
maintainer
docs_urlNone
author
requires_python>=3.10
licenseCopyright (c) 2024, Şuayip Üzülmez All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
keywords django soft-delete
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django Deletion Records

This simple Django app contains a mechanism to track deleted model records.
Unlike the soft-deletion mechanism, the deleted content is saved to a separate
table.

The deleted records are inserted via database triggers, so you don't have to
write any additional code or change your base model classes. In fact, you only
need to perform related migration operations, and you are good to go; the
deleted records will start to appear in Django admin site.

## Comparison with soft delete

### Pros

* No overly-complicated application code. To make soft-deletion work, you
  need to override model managers so that deleted records are kept out. Even
  then, the abstraction is very leaky when managers are not available such
  as doing aggregations or joins.

* Database constraints are easier since you don't need to consider deleted
  records.

* Managing relationships are easier, for example you don't need to worry
  about `ForeignKey` cascades.

* Deleted records do not affect undeleted records at all. More often than
  not, soft-delete create problems with new or existing rows because
  someone forgets that soft-deleted records are actually there.

### Cons

* Reversing deletions are pretty difficult. In soft-delete, you can just flip
  a column (most of the time). For deletion records, you'll have to manually
  re-insert the data (and its dependencies).

* Data lookup is relatively difficult especially if you're maintaining
  complex relationships spanning multiple tables. In soft-delete, you can
  just do regular SQL lookups. However, deletion records require custom
  resolution for `ForeignKey`'s and such.

* Deletion operations are *relatively* slower since soft-delete updates
  are faster and there is no trigger penalty.

Soft-deleting is convenient when you need to restore data but creates
complexity in application code. Deletion records are much easier manage,
however it is difficult to restore data.

You may also use soft-deletes alongside deletion records if you want to keep
some tables soft-deleted and others recorded (for example, as audit logs).

## Installation

> `django-deletion-records` only works for PostgreSQL, other database backends
> are not supported.

1. Install from PyPI:

    ```shell
    pip install django-deletion-records
    ```

2. Add `deletion_records` to your `INSTALLED_APPS`, such as:

    ```python
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # ...
        'deletion_records',
    ]
    ```

3. Run migrate for `deletion_records`:

    ```shell
    python manage.py migrate deletion_records

    Operations to perform:
      Apply all migrations: deletion_records
    Running migrations:
      Applying deletion_records.0001_initial... OK
    ```

### Marking a model for deletion

To start recording deletions for a given model, you'll need to perform a
`RunSQL` operation (that will create the related trigger) via Django
migrations.

To start, create an empty migration file in your related app:

```shell
python manage.py makemigrations --empty your-app
```

This will create an empty migration with no operations. To get the related
operations we'll use the `deletion_migration_operations` management command:

```shell
python manage.py deletion_migration_operations yourapp.User

# yourapp.User
operations = [
    migrations.RunSQL(
        """
        CREATE TRIGGER deleted_record_insert
            AFTER DELETE
            ON yourapp_user
            FOR EACH ROW
        EXECUTE FUNCTION deletion_record_insert();
        """,
        reverse_sql="DROP TRIGGER IF EXISTS deleted_record_insert ON yourapp_user CASCADE;",
    )
]
```

Copy the given operations to your empty migration file, and apply the
migrations. Deletion records will start to appear, that's it!

## Tips

* You can supply multiple models for `deletion_migration_operations` to get
  multiple operations at once.

* You can have multiple operations in one migration file if you want to. It
  just depends on how you want to handle the migrations.

* If you want to enable deletion records for a third party model, you can
  still employ the steps above. You just need to figure out a proper
  application (that'll hold the migrations), people generally use "core" apps
  to do that kind of stuff.

* Reversing the migration created above will drop the related trigger, thus
  stopping the deletion recording. The formerly deleted records will be still
  available.

If you want to hard-delete some records (or manage deleted records for any
other reason), you can use the provided model as such:

```python
from deletion_records.models import DeletedRecord

# Deletes User objects with provided ids.
DeletedRecord.objects.filter(
    table_name="yourapp_user", object_id__in=[2, 3, 4]
).delete()
```

```python
from deletion_records.models import DeletedRecord

# Fetch a deleted record and view the data.
user = DeletedRecord.objects.get(table_name="account_user", object_id=276833)

assert user.data == {
    "id": 276833,
    "email": "lauren62@example.com",
    "is_staff": False,
    "password": "pbkdf2_sha256$600000$4X48PbRIeemb2ECW1pIlO4$jFuePmugUuTE4D6nIbP9TxGKcYxLBut81bR4JbshU8I=",
    "username": "qwSyeamDqT",
    "is_active": True,
    "last_name": "Jackson",
    "first_name": "Cynthia",
    "last_login": None,
    "date_joined": "2024-01-16T19:35:32.331011+00:00",
    "is_superuser": False,
}
```

You can also browse deleted records via Django admin.

## Credits

I primarily read the following posts to implement this in Django:

https://brandur.org/fragments/deleted-record-insert

https://brandur.org/soft-deletion

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "django-deletion-records",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "",
    "keywords": "django,soft-delete",
    "author": "",
    "author_email": "suayip uzulmez <suayip.541@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/17/c2/6a44d82d1251c8d92f7ef46ad9f6fcb0ab09fb5ae351693394871fa0b477/django_deletion_records-0.2.0.tar.gz",
    "platform": null,
    "description": "# Django Deletion Records\n\nThis simple Django app contains a mechanism to track deleted model records.\nUnlike the soft-deletion mechanism, the deleted content is saved to a separate\ntable.\n\nThe deleted records are inserted via database triggers, so you don't have to\nwrite any additional code or change your base model classes. In fact, you only\nneed to perform related migration operations, and you are good to go; the\ndeleted records will start to appear in Django admin site.\n\n## Comparison with soft delete\n\n### Pros\n\n* No overly-complicated application code. To make soft-deletion work, you\n  need to override model managers so that deleted records are kept out. Even\n  then, the abstraction is very leaky when managers are not available such\n  as doing aggregations or joins.\n\n* Database constraints are easier since you don't need to consider deleted\n  records.\n\n* Managing relationships are easier, for example you don't need to worry\n  about `ForeignKey` cascades.\n\n* Deleted records do not affect undeleted records at all. More often than\n  not, soft-delete create problems with new or existing rows because\n  someone forgets that soft-deleted records are actually there.\n\n### Cons\n\n* Reversing deletions are pretty difficult. In soft-delete, you can just flip\n  a column (most of the time). For deletion records, you'll have to manually\n  re-insert the data (and its dependencies).\n\n* Data lookup is relatively difficult especially if you're maintaining\n  complex relationships spanning multiple tables. In soft-delete, you can\n  just do regular SQL lookups. However, deletion records require custom\n  resolution for `ForeignKey`'s and such.\n\n* Deletion operations are *relatively* slower since soft-delete updates\n  are faster and there is no trigger penalty.\n\nSoft-deleting is convenient when you need to restore data but creates\ncomplexity in application code. Deletion records are much easier manage,\nhowever it is difficult to restore data.\n\nYou may also use soft-deletes alongside deletion records if you want to keep\nsome tables soft-deleted and others recorded (for example, as audit logs).\n\n## Installation\n\n> `django-deletion-records` only works for PostgreSQL, other database backends\n> are not supported.\n\n1. Install from PyPI:\n\n    ```shell\n    pip install django-deletion-records\n    ```\n\n2. Add `deletion_records` to your `INSTALLED_APPS`, such as:\n\n    ```python\n    INSTALLED_APPS = [\n        'django.contrib.admin',\n        'django.contrib.auth',\n        'django.contrib.contenttypes',\n        'django.contrib.sessions',\n        'django.contrib.messages',\n        'django.contrib.staticfiles',\n        # ...\n        'deletion_records',\n    ]\n    ```\n\n3. Run migrate for `deletion_records`:\n\n    ```shell\n    python manage.py migrate deletion_records\n\n    Operations to perform:\n      Apply all migrations: deletion_records\n    Running migrations:\n      Applying deletion_records.0001_initial... OK\n    ```\n\n### Marking a model for deletion\n\nTo start recording deletions for a given model, you'll need to perform a\n`RunSQL` operation (that will create the related trigger) via Django\nmigrations.\n\nTo start, create an empty migration file in your related app:\n\n```shell\npython manage.py makemigrations --empty your-app\n```\n\nThis will create an empty migration with no operations. To get the related\noperations we'll use the `deletion_migration_operations` management command:\n\n```shell\npython manage.py deletion_migration_operations yourapp.User\n\n# yourapp.User\noperations = [\n    migrations.RunSQL(\n        \"\"\"\n        CREATE TRIGGER deleted_record_insert\n            AFTER DELETE\n            ON yourapp_user\n            FOR EACH ROW\n        EXECUTE FUNCTION deletion_record_insert();\n        \"\"\",\n        reverse_sql=\"DROP TRIGGER IF EXISTS deleted_record_insert ON yourapp_user CASCADE;\",\n    )\n]\n```\n\nCopy the given operations to your empty migration file, and apply the\nmigrations. Deletion records will start to appear, that's it!\n\n## Tips\n\n* You can supply multiple models for `deletion_migration_operations` to get\n  multiple operations at once.\n\n* You can have multiple operations in one migration file if you want to. It\n  just depends on how you want to handle the migrations.\n\n* If you want to enable deletion records for a third party model, you can\n  still employ the steps above. You just need to figure out a proper\n  application (that'll hold the migrations), people generally use \"core\" apps\n  to do that kind of stuff.\n\n* Reversing the migration created above will drop the related trigger, thus\n  stopping the deletion recording. The formerly deleted records will be still\n  available.\n\nIf you want to hard-delete some records (or manage deleted records for any\nother reason), you can use the provided model as such:\n\n```python\nfrom deletion_records.models import DeletedRecord\n\n# Deletes User objects with provided ids.\nDeletedRecord.objects.filter(\n    table_name=\"yourapp_user\", object_id__in=[2, 3, 4]\n).delete()\n```\n\n```python\nfrom deletion_records.models import DeletedRecord\n\n# Fetch a deleted record and view the data.\nuser = DeletedRecord.objects.get(table_name=\"account_user\", object_id=276833)\n\nassert user.data == {\n    \"id\": 276833,\n    \"email\": \"lauren62@example.com\",\n    \"is_staff\": False,\n    \"password\": \"pbkdf2_sha256$600000$4X48PbRIeemb2ECW1pIlO4$jFuePmugUuTE4D6nIbP9TxGKcYxLBut81bR4JbshU8I=\",\n    \"username\": \"qwSyeamDqT\",\n    \"is_active\": True,\n    \"last_name\": \"Jackson\",\n    \"first_name\": \"Cynthia\",\n    \"last_login\": None,\n    \"date_joined\": \"2024-01-16T19:35:32.331011+00:00\",\n    \"is_superuser\": False,\n}\n```\n\nYou can also browse deleted records via Django admin.\n\n## Credits\n\nI primarily read the following posts to implement this in Django:\n\nhttps://brandur.org/fragments/deleted-record-insert\n\nhttps://brandur.org/soft-deletion\n",
    "bugtrack_url": null,
    "license": "Copyright (c) 2024, \u015euayip \u00dcz\u00fclmez All rights reserved.  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.",
    "summary": "A Django application to track deleted records.",
    "version": "0.2.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/realsuayip/django-deletion-records/issues",
        "Homepage": "https://github.com/realsuayip/django-deletion-records"
    },
    "split_keywords": [
        "django",
        "soft-delete"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "df897e8e7772c14bbe96c0c0da68ab92b5376fb21444b5df669b2d3489a2c70f",
                "md5": "5f882b38c58e8565697eaa05a7b4c549",
                "sha256": "9cf22c4b8a2ebaed88758763363714ea8eb2497a1a73d1015a4cab49a30e7d29"
            },
            "downloads": -1,
            "filename": "django_deletion_records-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5f882b38c58e8565697eaa05a7b4c549",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 9120,
            "upload_time": "2024-02-19T16:19:34",
            "upload_time_iso_8601": "2024-02-19T16:19:34.303204Z",
            "url": "https://files.pythonhosted.org/packages/df/89/7e8e7772c14bbe96c0c0da68ab92b5376fb21444b5df669b2d3489a2c70f/django_deletion_records-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "17c26a44d82d1251c8d92f7ef46ad9f6fcb0ab09fb5ae351693394871fa0b477",
                "md5": "f99c21e2e6fab72efa7c513fdd0e9a4b",
                "sha256": "5028dbbf03b6e69fb0da057956bd947901b476fcc0f14e927ec7278981e8a1bb"
            },
            "downloads": -1,
            "filename": "django_deletion_records-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "f99c21e2e6fab72efa7c513fdd0e9a4b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 6350,
            "upload_time": "2024-02-19T16:19:36",
            "upload_time_iso_8601": "2024-02-19T16:19:36.102278Z",
            "url": "https://files.pythonhosted.org/packages/17/c2/6a44d82d1251c8d92f7ef46ad9f6fcb0ab09fb5ae351693394871fa0b477/django_deletion_records-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-19 16:19:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "realsuayip",
    "github_project": "django-deletion-records",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "django-deletion-records"
}
        
Elapsed time: 0.21311s