Skip to content

Commit

Permalink
Merge pull request #95 from un1t/feature/7.0
Browse files Browse the repository at this point in the history
Feature/7.0
  • Loading branch information
vinnyrose committed Feb 11, 2023
2 parents 47e3fd8 + f40c07c commit 2bc1a08
Show file tree
Hide file tree
Showing 27 changed files with 348 additions and 124 deletions.
27 changes: 13 additions & 14 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,13 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
strategy:
matrix:
include:
- python: "3.5"
toxenv: py35-django22
- python: "3.6"
toxenv: py36-django22
- python: "3.7"
toxenv: py37-django22
- python: "3.8"
toxenv: py38-django22
- python: "3.9"
toxenv: py39-django22
- python: "pypy-3.8"
toxenv: pypy3-django22

- python: "3.6"
toxenv: py36-django32
os: ubuntu-20.04
- python: "3.7"
toxenv: py37-django32
- python: "3.8"
Expand All @@ -46,6 +34,17 @@ jobs:
toxenv: py310-django40
- python: "pypy-3.8"
toxenv: pypy3-django40

- python: "3.8"
toxenv: py38-django41
- python: "3.9"
toxenv: py39-django41
- python: "3.10"
toxenv: py310-django41
- python: "3.11"
toxenv: py311-django41
- python: "pypy-3.8"
toxenv: pypy3-django41
steps:
- uses: actions/checkout@v2
- name: Setup Python
Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [7.0.0] - 2023-02-11
### Added
- Run tests for django 4.1.
- Run tests on python 3.11 with django 4.1.
- Select mode, with the ability to only cleanup selected models using a `select` decorator. Resolves issue [#75] for [@daviddavis](https://github.com/daviddavis).
- Documentation on the known limitations of referencing a file by multiple model instances. Resolves issue [#98] for [@Grosskopf](https://github.com/Grosskopf)

## Changed
- Pass more data to the cleanup_pre_delete and cleanup_post_delete signals. Resolves issue [#96] for [@NadavK](https://github.com/NadavK).

### Removed
- Dropped support for django 2.2 and python 3.5.

## [6.0.0] - 2022-01-24
### Added
- Update to run tests for python 3.10. PR [#88] from [@johnthagen](https://github.com/johnthagen).
Expand Down Expand Up @@ -69,7 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.1.4] - 2012-08-16
## [0.1.0] - 2012-08-14

[Unreleased]: https://github.com/un1t/django-cleanup/compare/6.0.0...HEAD
[Unreleased]: https://github.com/un1t/django-cleanup/compare/7.0.0...HEAD
[7.0.0]: https://github.com/un1t/django-cleanup/compare/6.0.0...7.0.0
[6.0.0]: https://github.com/un1t/django-cleanup/compare/5.2.0...6.0.0
[5.2.0]: https://github.com/un1t/django-cleanup/compare/5.1.0...5.2.0
[5.1.0]: https://github.com/un1t/django-cleanup/compare/5.0.0...5.1.0
Expand Down Expand Up @@ -104,11 +118,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[0.1.4]: https://github.com/un1t/django-cleanup/compare/0.1.0...0.1.4
[0.1.0]: https://github.com/un1t/django-cleanup/releases/tag/0.1.0

[#98]: https://github.com/un1t/django-cleanup/issues/98
[#96]: https://github.com/un1t/django-cleanup/issues/96
[#89]: https://github.com/un1t/django-cleanup/issues/89
[#88]: https://github.com/un1t/django-cleanup/pull/88
[#86]: https://github.com/un1t/django-cleanup/pull/86
[#81]: https://github.com/un1t/django-cleanup/pull/81
[#80]: https://github.com/un1t/django-cleanup/pull/80
[#76]: https://github.com/un1t/django-cleanup/pull/76
[#75]: https://github.com/un1t/django-cleanup/issues/75
[#74]: https://github.com/un1t/django-cleanup/pull/74
[#73]: https://github.com/un1t/django-cleanup/issues/73
53 changes: 43 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ is set as the :code:`FileField`'s default value will not be deleted.

Compatibility
-------------
- Django 2.2, 3.2, 4.0 (`See Django Supported Versions <https://www.djangoproject.com/download/#supported-versions>`_)
- Python 3.5+
- Django 3.2, 4.0, 4.1 (`See Django Supported Versions <https://www.djangoproject.com/download/#supported-versions>`_)
- Python 3.6+
- Compatible with `sorl-thumbnail <https://github.com/jazzband/sorl-thumbnail>`_
- Compatible with `easy-thumbnail <https://github.com/SmileyChris/easy-thumbnails>`_

Expand All @@ -25,13 +25,7 @@ whether or not a :code:`FileField`'s value has changed a local cache of original
the model instance. If a condition is detected that should result in a file deletion, a function to
delete the file is setup and inserted into the commit phase of the current transaction.

**Warning! If you are using a database that does not support transactions you may lose files if a
transaction will rollback at the right instance. This outcome is mitigated by our use of
post_save and post_delete signals, and by following the recommended configuration below. This
outcome will still occur if there are signals registered after app initialization and there are
exceptions when those signals are handled. In this case, the old file will be lost and the new file
will not be referenced in a model, though the new file will likely still exist on disk. If you are
concerned about this behavior you will need another solution for old file deletion in your project.**
**Warning! Please be aware of the known limitations documented below!**

Installation
============
Expand Down Expand Up @@ -77,6 +71,28 @@ You can check if your ``Model`` is loaded by using
from django.apps import apps
apps.get_models()
Known limitations
=================

Database should support transactions
------------------------------------
If you are using a database that does not support transactions you may lose files if a
transaction will rollback at the right instance. This outcome is mitigated by our use of
post_save and post_delete signals, and by following the recommended configuration in this README.
This outcome will still occur if there are signals registered after app initialization and there are
exceptions when those signals are handled. In this case, the old file will be lost and the new file
will not be referenced in a model, though the new file will likely still exist on disk. If you are
concerned about this behavior you will need another solution for old file deletion in your project.

File referenced by multiple model instances
-------------------------------------------
This app is designed with the assumption that each file is referenced only once. If you are sharing
a file over two or more model instances you will not have the desired functionality. Be cautious of
copying model instances, as this will cause a file to be shared by more than one instance. If you
want to reference a file from multiple models add a level of indirection. That is, use a separate
file model that is referenced from other models through a foreign key. There are many file
management apps already available in the django ecosystem that fulfill this behavior.

Advanced
========
This section contains additional functionality that can be used to interact with django-cleanup for
Expand Down Expand Up @@ -115,7 +131,8 @@ There have been rare cases where the cache would need to be refreshed. To do so
Ignore cleanup for a specific model
-----------------------------------
Ignore a model and do not perform cleanup when the model is deleted or its files change.
To ignore a model and not have cleanup performed when the model is deleted or its files change, use
the :code:`ignore` decorator to mark that model:

.. code-block:: py
Expand All @@ -125,6 +142,22 @@ Ignore a model and do not perform cleanup when the model is deleted or its files
class MyModel(models.Model):
image = models.FileField()
Only cleanup selected models
----------------------------
If you have many models to ignore, or if you prefer to be explicit about what models are selected,
you can change the mode of django-cleanup to "select mode" by using the select mode app config. In
your ``INSTALLED_APPS`` setting you will replace ':code:`django_cleanup.apps.CleanupConfig`'
with ':code:`django_cleanup.apps.CleanupSelectedConfig`'. Then use the :code:`select` decorator to
mark a model for cleanup:

.. code-block:: py
from django_cleanup import cleanup
@cleanup.select
class MyModel(models.Model):
image = models.FileField()
How to run tests
================
Install, setup and use pyenv_ to install all the required versions of cPython
Expand Down
18 changes: 0 additions & 18 deletions django_cleanup/cleanup.py

This file was deleted.

34 changes: 0 additions & 34 deletions django_cleanup/testapp/testing_helpers.py

This file was deleted.

8 changes: 5 additions & 3 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[pytest]
DJANGO_SETTINGS_MODULE=django_cleanup.testapp.settings
python_paths=.
addopts = -n auto -v --cov-report=term-missing --cov=django_cleanup django_cleanup
DJANGO_SETTINGS_MODULE = test.settings
pythonpath = . src
addopts = -n auto -v --cov-report=term-missing --cov=django_cleanup --forked
markers =
CleanupSelectedConfig: marks test as using the CleanupSelectedConfig app config
9 changes: 5 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ def find_version(*parts):

setup(
name='django-cleanup',
version=find_version('django_cleanup', '__init__.py'),
version=find_version('src/django_cleanup', '__init__.py'),
packages=['django_cleanup'],
package_dir={'': 'src'},
include_package_data=True,
requires=['python (>=3.5)', 'django (>=2.2)'],
requires=['python (>=3.6)', 'django (>=3.2)'],
description='Deletes old files.',
long_description=read('README.rst'),
long_description_content_type='text/x-rst',
Expand All @@ -46,20 +47,20 @@ def find_version(*parts):
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Utilities',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@
subclasses. It will delete old files when a new file is being save and it
will delete files on model instance deletion.
'''
import django

__version__ = '6.0.0'
if django.VERSION < (3, 2):
default_app_config = 'django_cleanup.apps.CleanupConfig'
__version__ = '7.0.0'
11 changes: 10 additions & 1 deletion django_cleanup/apps.py → src/django_cleanup/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@
class CleanupConfig(AppConfig):
name = 'django_cleanup'
verbose_name = 'Django Cleanup'
default = True

def ready(self):
cache.prepare()
cache.prepare(False)
handlers.connect()

class CleanupSelectedConfig(AppConfig):
name = 'django_cleanup'
verbose_name = 'Django Cleanup'

def ready(self):
cache.prepare(True)
handlers.connect()
15 changes: 10 additions & 5 deletions django_cleanup/cache.py → src/django_cleanup/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ def fields_dict_default():
# cache init ##


def prepare():
def prepare(select_mode):
'''Prepare the cache for all models, non-reentrant'''
if FIELDS: # pragma: no cover
return

for model in apps.get_models():
if ignore_model(model):
if ignore_model(model, select_mode):
continue
name = get_model_name(model)
if model_has_filefields(name): # pragma: no cover
Expand Down Expand Up @@ -113,10 +113,15 @@ def get_model_name(model):


def get_mangled_ignore(model):
'''returns a mangled attribute name specific to the model'''
'''returns a mangled attribute name specific to the model for ignore functionality'''
return '_{opt.model_name}__{opt.app_label}_cleanup_ignore'.format(opt=model._meta)


def get_mangled_select(model):
'''returns a mangled attribute name specific to the model for select functionality'''
return '_{opt.model_name}__{opt.app_label}_cleanup_select'.format(opt=model._meta)


# booleans ##


Expand All @@ -125,9 +130,9 @@ def model_has_filefields(model_name):
return model_name in FIELDS


def ignore_model(model):
def ignore_model(model, select_mode):
'''Check if a model should be ignored'''
return hasattr(model, get_mangled_ignore(model))
return (not hasattr(model, get_mangled_select(model))) if select_mode else hasattr(model, get_mangled_ignore(model))


# instance functions ##
Expand Down
26 changes: 26 additions & 0 deletions src/django_cleanup/cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'''Public utilities'''
from .cache import (
get_mangled_ignore as _get_mangled_ignore, get_mangled_select as _get_mangled_select,
make_cleanup_cache as _make_cleanup_cache)


__all__ = ['refresh', 'cleanup_ignore', 'cleanup_select']


def refresh(instance):
'''Refresh the cache for an instance'''
return _make_cleanup_cache(instance)


def ignore(cls):
'''Mark a model to ignore for cleanup'''
setattr(cls, _get_mangled_ignore(cls), None)
return cls
cleanup_ignore = ignore


def select(cls):
'''Mark a model to select for cleanup'''
setattr(cls, _get_mangled_select(cls), None)
return cls
cleanup_select = select
Loading

0 comments on commit 2bc1a08

Please sign in to comment.