Skip to content

Commit

Permalink
Support Truncate with Cascade
Browse files Browse the repository at this point in the history
  • Loading branch information
marcostvz committed Jan 8, 2024
1 parent 8868201 commit 9e31d79
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 11 deletions.
5 changes: 4 additions & 1 deletion dj_anonymizer/anonymizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def clean(self, only=None):
for queryset in clean_list:
print(f'Cleaning {self.key(queryset.model)}')
if getattr(queryset, 'truncate') is True:
truncate_table(queryset.model)
if getattr(queryset, 'cascade') is True:
truncate_table(queryset.model, True)
else:
truncate_table(queryset.model, False)
else:
queryset.delete()
5 changes: 4 additions & 1 deletion dj_anonymizer/register_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@

class AnonymBase:
truncate = False
cascade = False

def __init__(self, truncate=False):
def __init__(self, truncate=False, cascade=False):
self.truncate = truncate
self.cascade = cascade

@classmethod
def get_fields_names(cls):
Expand Down Expand Up @@ -122,6 +124,7 @@ def register_clean(models):

queryset = model.objects.all()
queryset.truncate = cls_anonym.truncate
queryset.cascade = cls_anonym.cascade
if Anonymizer.key(model) in Anonymizer.clean_models.keys():
raise ValueError(
f'Model {Anonymizer.key(model)} '
Expand Down
2 changes: 1 addition & 1 deletion dj_anonymizer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def truncate_table(model, cascade=False):
cascade_op = VENDOR_TO_CASCADE[vendor]
except KeyError:
raise NotImplementedError(
"Database vendor %s does not support TRUNCATE with CASCADE" % vendor
"DB vendor %s does not support TRUNCATE with CASCADE" % vendor
)

dbtable = '"{}"'.format(model._meta.db_table)
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
=============

0.6.0
----------
* Feature: adds possibility to truncate with cascade option (`#73 <https://github.com/preply/dj_anonymizer/pull/73>`__)

0.5.1
----------
* Bugfix: fix issue when model manager is overridden (`#71 <https://github.com/preply/dj_anonymizer/pull/71>`__)
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.5.1'
release = '0.6.0'


# -- General configuration ---------------------------------------------------
Expand Down
16 changes: 14 additions & 2 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Model registration

* `models` - list of tuples `(model, cls_anonym)`, where `model` is a model class and `cls_anonym` - anonymization class, inherited form `AnonymBase` with specified queryset for deletion or just `AnonymBase`.

If `AnonymBase` class have `truncate=True`, parameter table will be truncated instead of performing an SQL delete query.
If `AnonymBase` class have `truncate=True`, parameter table will be truncated instead of performing an SQL delete query. Additionally, `cascade=True` may be set to truncate foreign-key related tables (supported for postgresql and oracle).

.. function:: register_skip(models)

Expand Down Expand Up @@ -186,7 +186,19 @@ Example 2 - truncate all data from model `User`::
(User, AnonymBase(truncate=True)),
])

Example 3 - delete all data from model `User`, except user with id=1::
Example 3 - truncate all data from model `User` with cascade option::

from django.contrib.auth.models import User

from dj_anonymizer.register_models import AnonymBase
from dj_anonymizer.register_models import register_clean


register_clean([
(User, AnonymBase(truncate=True, cascade=True)),
])

Example 4 - delete all data from model `User`, except user with id=1::

from django.contrib.auth.models import User

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def read(*rnames):
name='dj_anonymizer',
packages=['dj_anonymizer'],
include_package_data=True,
version='0.5.1',
version='0.6.0',
description='This project helps anonymize production database '
+ 'with fake data of any kind.',
long_description=(read('README.md')),
Expand Down
27 changes: 25 additions & 2 deletions tests/test_register_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pytest
from django.contrib.auth.models import Group, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet

from dj_anonymizer import fields, register_models
Expand Down Expand Up @@ -216,10 +217,11 @@ def test_register_clean():
register_models.register_clean([
(User, register_models.AnonymBase),
(Permission, register_models.AnonymBase(truncate=True)),
(Group, register_models.AnonymBase())
(Group, register_models.AnonymBase()),
(ContentType, register_models.AnonymBase(truncate=True, cascade=True)),
])

assert len(Anonymizer.clean_models) == 3
assert len(Anonymizer.clean_models) == 4
assert len(Anonymizer.skip_models) == 0
assert len(Anonymizer.anonym_models) == 0

Expand All @@ -229,6 +231,8 @@ def test_register_clean():
Anonymizer.clean_models.keys()
assert 'django.contrib.auth.models.Group' in \
Anonymizer.clean_models.keys()
assert 'django.contrib.contenttypes.models.ContentType' in \
Anonymizer.clean_models.keys()

assert isinstance(
Anonymizer.clean_models['django.contrib.auth.models.User'],
Expand All @@ -242,16 +246,35 @@ def test_register_clean():
Anonymizer.clean_models['django.contrib.auth.models.Group'],
QuerySet
)
assert isinstance(
Anonymizer.clean_models['django.contrib.contenttypes.models.ContentType'],
QuerySet
)

assert Anonymizer.clean_models[
"django.contrib.auth.models.User"
].model is User
assert Anonymizer.clean_models[
"django.contrib.auth.models.Permission"
].model is Permission
assert Anonymizer.clean_models[
"django.contrib.auth.models.Permission"
].truncate is True
assert Anonymizer.clean_models[
"django.contrib.auth.models.Permission"
].cascade is False
assert Anonymizer.clean_models[
"django.contrib.auth.models.Group"
].model is Group
assert Anonymizer.clean_models[
"django.contrib.contenttypes.models.ContentType"
].model is ContentType
assert Anonymizer.clean_models[
"django.contrib.contenttypes.models.ContentType"
].truncate is True
assert Anonymizer.clean_models[
"django.contrib.contenttypes.models.ContentType"
].cascade is True


@pytest.mark.django_db
Expand Down
5 changes: 3 additions & 2 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ def test_truncate_table_with_cascade(mock_connections):
mock_connections.__getitem__(DEFAULT_DB_ALIAS).vendor = 'postgresql'

truncate_table(User, True)
mock_cursor.execute.assert_called_once_with('TRUNCATE TABLE "auth_user" CASCADE')
mock_cursor.execute.assert_called_once_with(
'TRUNCATE TABLE "auth_user" CASCADE'
)

mock_connections.__getitem__(DEFAULT_DB_ALIAS).vendor = 'sqlite'

with pytest.raises(NotImplementedError):
truncate_table(User, True)

0 comments on commit 9e31d79

Please sign in to comment.