diff --git a/README.rst b/README.rst index 6ac8d94..0370f61 100644 --- a/README.rst +++ b/README.rst @@ -157,6 +157,21 @@ mark a model for cleanup: class MyModel(models.Model): image = models.FileField() +Only cleanup selected fields +---------------------------- +If you prefer to explicitly configure which fields django-cleanup will handle, you can use the :code:`CLEANUP` setting in your settings.py: + +.. code-block:: py + + CLEANUP = { + 'model.name': {'field1', 'field2'}, # Only clean these fields for this model + 'other.model': {'field3'} # Only clean field3 for other.model + } + +The setting maps model names (in the format "app_label.model_name") to sets of field names that should be cleaned up. + +Note that if :code:`CLEANUP` is set and a model or field is not included in the :code:`CLEANUP` setting, its files will not be cleaned up. + How to run tests ================ Install, setup and use pyenv_ to install all the required versions of cPython diff --git a/pyproject.toml b/pyproject.toml index 57ab455..9baa06b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,5 +49,6 @@ pythonpath = [".", "src"] addopts = ["-v", "--cov-report=term-missing", "--cov=django_cleanup"] markers = [ "cleanup_selected_config: marks test as using the CleanupSelectedConfig app config", + "cleanup_settings: marks test as using the CLEANUP django setting", "django_storage: change django storage backends" ] diff --git a/src/django_cleanup/cache.py b/src/django_cleanup/cache.py index 8800c0a..f1751a9 100644 --- a/src/django_cleanup/cache.py +++ b/src/django_cleanup/cache.py @@ -2,6 +2,7 @@ from collections import defaultdict from django.apps import apps +from django.conf import settings from django.db import models from django.utils.module_loading import import_string @@ -28,6 +29,7 @@ def prepare(select_mode): if FIELDS: # pragma: no cover return + config = getattr(settings, "CLEANUP", {}) for model in apps.get_models(): if ignore_model(model, select_mode): continue @@ -36,8 +38,11 @@ def prepare(select_mode): continue opts = model._meta for field in opts.get_fields(): - if isinstance(field, models.FileField): - add_field_for_model(name, field.name, field) + if not isinstance(field, models.FileField): + continue + if config and field.name not in config.get(name, []): + continue + add_field_for_model(name, field.name, field) def add_field_for_model(model_name, field_name, field): diff --git a/test/conftest.py b/test/conftest.py index 88015e2..0229892 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,6 +21,9 @@ def pytest_collection_modifyitems(items): @pytest.fixture(autouse=True) def setup_django_cleanup_state(request, settings): + settings_marker = request.node.get_closest_marker("cleanup_settings") + settings.CLEANUP = settings_marker.args[0] if settings_marker else None + for model in cache.cleanup_models(): suffix = f'_django_cleanup_{cache.get_model_name(model)}' post_init.disconnect(None, sender=model, diff --git a/test/test_all.py b/test/test_all.py index e5d64d0..a6743ec 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -432,3 +432,47 @@ def test__select_config__replace_file_with_file_ignore(picture): new_image_path = os.path.join(django_settings.MEDIA_ROOT, random_pic_name) assert product.image.path == new_image_path #endregion + + +#region cleanup settings +@pytest.mark.cleanup_settings({'test.product': {'image'}}) +def test_cleanup_settings_model_included(picture): + product = Product.objects.create(image=picture['filename']) + assert os.path.exists(picture['path']) + random_pic_name = get_random_pic_name() + product.image = random_pic_name + with transaction.atomic(get_using(product)): + product.save() + assert not os.path.exists(picture['path']) + assert product.image + new_image_path = os.path.join(django_settings.MEDIA_ROOT, random_pic_name) + assert product.image.path == new_image_path + + +@pytest.mark.cleanup_settings({'test.other_model': {'image'}}) +def test_cleanup_settings_model_excluded(picture): + product = Product.objects.create(image=picture['filename']) + assert os.path.exists(picture['path']) + random_pic_name = get_random_pic_name() + product.image = random_pic_name + with transaction.atomic(get_using(product)): + product.save() + assert os.path.exists(picture['path']) # File should not be cleaned up + assert product.image + new_image_path = os.path.join(django_settings.MEDIA_ROOT, random_pic_name) + assert product.image.path == new_image_path + + +@pytest.mark.cleanup_settings({'test.product': {'other_field'}}) +def test_cleanup_settings_field_excluded(picture): + product = Product.objects.create(image=picture['filename']) + assert os.path.exists(picture['path']) + random_pic_name = get_random_pic_name() + product.image = random_pic_name + with transaction.atomic(get_using(product)): + product.save() + assert os.path.exists(picture['path']) # File should not be cleaned up + assert product.image + new_image_path = os.path.join(django_settings.MEDIA_ROOT, random_pic_name) + assert product.image.path == new_image_path +#endregion