├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── LICENSE ├── README.md ├── clearcache ├── __init__.py ├── apps.py ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── clearcache.py ├── templates │ ├── admin │ │ └── index.html │ └── clearcache │ │ └── admin │ │ └── clearcache_form.html ├── tests.py ├── urls.py ├── utils.py └── views.py ├── demo.gif ├── manage.py ├── poetry.lock ├── pyproject.toml ├── test_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py └── tox.ini /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [ 3.7, 3.8, 3.9 ] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | python -m pip install poetry 30 | poetry install 31 | - name: Test with tox 32 | run: | 33 | poetry run tox 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # PyCharm 132 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Kamanin and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django ClearCache 🤠🧹💰 2 | 3 | ![License](https://img.shields.io/pypi/l/django-clearcache) 4 | ![Django versions](https://img.shields.io/pypi/djversions/django-clearcache) 5 | ![Python versions](https://img.shields.io/pypi/pyversions/django-clearcache) 6 | 7 | Allows you to clear Django cache via admin UI or manage.py command. 8 | 9 | ![demo](https://raw.githubusercontent.com/timonweb/django-clearcache/master/demo.gif) 10 | 11 | ## Installation 12 | 13 | 1. Install using PIP: 14 | 15 | ``` 16 | pip install django-clearcache 17 | ``` 18 | 19 | 2. Add **clearcache** to INSTALLED_APPS, make sure it's above `django.contrib.admin`: 20 | 21 | ``` 22 | INSTALLED_APPS += [ 23 | ... 24 | 'clearcache', 25 | 'django.contrib.admin', 26 | ... 27 | ] 28 | ``` 29 | 30 | 3. Add url to the main **urls.py** right above root admin url: 31 | 32 | ``` 33 | urlpatterns = [ 34 | path('admin/clearcache/', include('clearcache.urls')), 35 | path('admin/', admin.site.urls), 36 | ] 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### Via Django admin 42 | 43 | 1. Go to `/admin/clearcache/`, you should see a form with cache selector 44 | 2. Pick a cache. Usually there's one default cache, but can be more. 45 | 3. Click the button, you're done! 46 | 47 | ### Via manage.py command 48 | 49 | 1. Run the following command to clear the default cache 50 | 51 | ``` 52 | python manage.py clearcache 53 | ``` 54 | 55 | 2. Run the command above with an additional parameter to clear non-default cache (if exists): 56 | 57 | ``` 58 | python manage.py clearcache cache_name 59 | ``` 60 | 61 | ## Follow me 62 | 63 | 1. Check my dev blog with Python and JavaScript tutorials at [https://timonweb.com](https://timonweb.com) 64 | 2. Follow me on twitter [@timonweb](https://twitter.com/timonweb) 65 | -------------------------------------------------------------------------------- /clearcache/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timonweb/django-clearcache/7b50eda45ae22a1c9d59fbf2a2342fbf8fc1bf81/clearcache/__init__.py -------------------------------------------------------------------------------- /clearcache/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ClearcacheConfig(AppConfig): 5 | name = 'clearcache' 6 | -------------------------------------------------------------------------------- /clearcache/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.conf import settings 3 | 4 | 5 | def get_cache_choices(): 6 | caches = settings.CACHES or {} 7 | return [(key, f"{key} ({cache['BACKEND']}") for key, cache in caches.items()] 8 | 9 | 10 | class ClearCacheForm(forms.Form): 11 | cache_name = forms.ChoiceField(choices=get_cache_choices) 12 | -------------------------------------------------------------------------------- /clearcache/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timonweb/django-clearcache/7b50eda45ae22a1c9d59fbf2a2342fbf8fc1bf81/clearcache/management/__init__.py -------------------------------------------------------------------------------- /clearcache/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timonweb/django-clearcache/7b50eda45ae22a1c9d59fbf2a2342fbf8fc1bf81/clearcache/management/commands/__init__.py -------------------------------------------------------------------------------- /clearcache/management/commands/clearcache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from clearcache.utils import clear_cache 4 | 5 | 6 | class Command(BaseCommand): 7 | help = 'Clears cache_id' 8 | 9 | def add_arguments(self, parser): 10 | parser.add_argument('cache_name', nargs='?', type=str) 11 | 12 | def handle(self, *args, **options): 13 | cache_name = options['cache_name'] or 'default' 14 | try: 15 | clear_cache(cache_name) 16 | self.stdout.write(self.style.SUCCESS(f'Successfully cleared "{cache_name}" cache')) 17 | except Exception as err: 18 | self.stderr.write(self.style.ERROR(err)) 19 | -------------------------------------------------------------------------------- /clearcache/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/index.html' %} 2 | 3 | {% block sidebar %} 4 | {{ block.super }} 5 |
6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
8 | Clear cache 9 |
Clear cache
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /clearcache/templates/clearcache/admin/clearcache_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/base_site.html' %} 2 | 3 | {% block content %} 4 |
5 |

Clear cache

6 |
7 | {% csrf_token %} 8 | {{ form.as_p }} 9 |
10 | 11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /clearcache/tests.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | from django.core.cache import cache 5 | from django.core.management import call_command 6 | from django.urls import reverse 7 | 8 | CACHE_KEY = 'example_cache_key' 9 | CACHE_VALUE = 'example_cache_value' 10 | 11 | 12 | def test_cache_works(): 13 | CACHE_EXPIRE_IN_SEC = 1 14 | 15 | assert cache.get(CACHE_KEY) is None, "The value isn't cached yet" 16 | 17 | cache.set(CACHE_KEY, CACHE_VALUE, CACHE_EXPIRE_IN_SEC) 18 | assert cache.get(CACHE_KEY) == CACHE_VALUE, "The value came from a cache" 19 | 20 | time.sleep(1) 21 | assert cache.get(CACHE_KEY) is None, "The value isn't cached because it's expired" 22 | 23 | 24 | def test_clears_cache_via_command_line(): 25 | cache.set(CACHE_KEY, CACHE_VALUE, 100) 26 | assert cache.get(CACHE_KEY) == CACHE_VALUE, "Cache populated" 27 | 28 | call_command('clearcache') 29 | 30 | assert cache.get(CACHE_KEY) is None, "Cache cleared" 31 | 32 | 33 | @pytest.mark.django_db 34 | def test_clear_cache_is_in_admin_index(admin_client): 35 | response = admin_client.get('/admin/') 36 | assert "Clear cache" in str(response.content) 37 | 38 | 39 | @pytest.mark.django_db 40 | def test_clear_cache_form_is_rendered(admin_client): 41 | response = admin_client.get(reverse('clearcache_admin')) 42 | assert "Clear cache now" in str(response.content), "Clear cache now button is visible" 43 | 44 | 45 | @pytest.mark.django_db 46 | def test_non_superuser_cant_access_clearcache(client): 47 | response = client.get(reverse('clearcache_admin')) 48 | assert response.status_code == 302 49 | 50 | 51 | @pytest.mark.django_db 52 | def test_clears_cache_via_admin_ui(admin_client): 53 | cache.set(CACHE_KEY, CACHE_VALUE, 100) 54 | assert cache.get(CACHE_KEY) == CACHE_VALUE, "Cache populated" 55 | 56 | admin_client.post(reverse('clearcache_admin'), { 57 | 'cache_name': 'default' 58 | }) 59 | 60 | assert cache.get(CACHE_KEY) is None, "Cache cleared" 61 | -------------------------------------------------------------------------------- /clearcache/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ClearCacheAdminView 3 | 4 | urlpatterns = [ 5 | path('', ClearCacheAdminView.as_view(), name="clearcache_admin"), 6 | ] 7 | -------------------------------------------------------------------------------- /clearcache/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.cache import caches 3 | 4 | 5 | def clear_cache(cache_name): 6 | assert settings.CACHES 7 | caches[cache_name].clear() 8 | -------------------------------------------------------------------------------- /clearcache/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib import messages 2 | from django.contrib.auth.mixins import UserPassesTestMixin 3 | from django.http import HttpResponseRedirect 4 | from django.urls import reverse_lazy 5 | from django.views.generic import FormView 6 | 7 | from clearcache.forms import ClearCacheForm 8 | from clearcache.utils import clear_cache 9 | 10 | 11 | class ClearCacheAdminView(UserPassesTestMixin, FormView): 12 | form_class = ClearCacheForm 13 | template_name = "clearcache/admin/clearcache_form.html" 14 | 15 | success_url = reverse_lazy('clearcache_admin') 16 | 17 | def test_func(self): 18 | # Only super user can clear caches via admin. 19 | return self.request.user.is_superuser 20 | 21 | def dispatch(self, request, *args, **kwargs): 22 | response = super().dispatch(request, args, kwargs) 23 | return response 24 | 25 | def form_valid(self, form): 26 | try: 27 | cache_name = form.cleaned_data['cache_name'] 28 | clear_cache(cache_name) 29 | messages.success(self.request, f"Successfully cleared '{form.cleaned_data['cache_name']}' cache") 30 | except Exception as err: 31 | messages.error(self.request, f"Couldn't clear cache, something went wrong. Received error: {err}") 32 | return HttpResponseRedirect(self.success_url) 33 | 34 | def get_context_data(self, **kwargs): 35 | context = super().get_context_data(**kwargs) 36 | context['title'] = 'Clear cache' 37 | return context 38 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timonweb/django-clearcache/7b50eda45ae22a1c9d59fbf2a2342fbf8fc1bf81/demo.gif -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "Atomic file writes." 4 | marker = "sys_platform == \"win32\"" 5 | name = "atomicwrites" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | version = "1.3.0" 9 | 10 | [[package]] 11 | category = "dev" 12 | description = "Classes Without Boilerplate" 13 | name = "attrs" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | version = "19.3.0" 17 | 18 | [package.extras] 19 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 20 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 21 | docs = ["sphinx", "zope.interface"] 22 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 23 | 24 | [[package]] 25 | category = "dev" 26 | description = "Cross-platform colored terminal text." 27 | marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" 28 | name = "colorama" 29 | optional = false 30 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 31 | version = "0.4.3" 32 | 33 | [[package]] 34 | category = "main" 35 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." 36 | name = "django" 37 | optional = false 38 | python-versions = ">=3.5" 39 | version = "2.2.9" 40 | 41 | [package.dependencies] 42 | pytz = "*" 43 | sqlparse = "*" 44 | 45 | [package.extras] 46 | argon2 = ["argon2-cffi (>=16.1.0)"] 47 | bcrypt = ["bcrypt"] 48 | 49 | [[package]] 50 | category = "dev" 51 | description = "A platform independent file lock." 52 | name = "filelock" 53 | optional = false 54 | python-versions = "*" 55 | version = "3.0.12" 56 | 57 | [[package]] 58 | category = "dev" 59 | description = "Read metadata from Python packages" 60 | marker = "python_version < \"3.8\"" 61 | name = "importlib-metadata" 62 | optional = false 63 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 64 | version = "1.4.0" 65 | 66 | [package.dependencies] 67 | zipp = ">=0.5" 68 | 69 | [package.extras] 70 | docs = ["sphinx", "rst.linker"] 71 | testing = ["packaging", "importlib-resources"] 72 | 73 | [[package]] 74 | category = "dev" 75 | description = "More routines for operating on iterables, beyond itertools" 76 | name = "more-itertools" 77 | optional = false 78 | python-versions = ">=3.5" 79 | version = "8.1.0" 80 | 81 | [[package]] 82 | category = "dev" 83 | description = "Core utilities for Python packages" 84 | name = "packaging" 85 | optional = false 86 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 87 | version = "20.0" 88 | 89 | [package.dependencies] 90 | pyparsing = ">=2.0.2" 91 | six = "*" 92 | 93 | [[package]] 94 | category = "dev" 95 | description = "plugin and hook calling mechanisms for python" 96 | name = "pluggy" 97 | optional = false 98 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 99 | version = "0.13.1" 100 | 101 | [package.dependencies] 102 | [package.dependencies.importlib-metadata] 103 | python = "<3.8" 104 | version = ">=0.12" 105 | 106 | [package.extras] 107 | dev = ["pre-commit", "tox"] 108 | 109 | [[package]] 110 | category = "dev" 111 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 112 | name = "py" 113 | optional = false 114 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 115 | version = "1.8.1" 116 | 117 | [[package]] 118 | category = "dev" 119 | description = "Python parsing module" 120 | name = "pyparsing" 121 | optional = false 122 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 123 | version = "2.4.6" 124 | 125 | [[package]] 126 | category = "dev" 127 | description = "pytest: simple powerful testing with Python" 128 | name = "pytest" 129 | optional = false 130 | python-versions = ">=3.5" 131 | version = "5.3.4" 132 | 133 | [package.dependencies] 134 | atomicwrites = ">=1.0" 135 | attrs = ">=17.4.0" 136 | colorama = "*" 137 | more-itertools = ">=4.0.0" 138 | packaging = "*" 139 | pluggy = ">=0.12,<1.0" 140 | py = ">=1.5.0" 141 | wcwidth = "*" 142 | 143 | [package.dependencies.importlib-metadata] 144 | python = "<3.8" 145 | version = ">=0.12" 146 | 147 | [package.extras] 148 | checkqa-mypy = ["mypy (v0.761)"] 149 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 150 | 151 | [[package]] 152 | category = "dev" 153 | description = "A Django plugin for pytest." 154 | name = "pytest-django" 155 | optional = false 156 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 157 | version = "3.8.0" 158 | 159 | [package.dependencies] 160 | pytest = ">=3.6" 161 | 162 | [package.extras] 163 | docs = ["sphinx", "sphinx-rtd-theme"] 164 | testing = ["django", "django-configurations (>=2.0)", "six"] 165 | 166 | [[package]] 167 | category = "main" 168 | description = "World timezone definitions, modern and historical" 169 | name = "pytz" 170 | optional = false 171 | python-versions = "*" 172 | version = "2019.3" 173 | 174 | [[package]] 175 | category = "dev" 176 | description = "Python 2 and 3 compatibility utilities" 177 | name = "six" 178 | optional = false 179 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 180 | version = "1.14.0" 181 | 182 | [[package]] 183 | category = "main" 184 | description = "Non-validating SQL parser" 185 | name = "sqlparse" 186 | optional = false 187 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 188 | version = "0.3.0" 189 | 190 | [[package]] 191 | category = "dev" 192 | description = "Python Library for Tom's Obvious, Minimal Language" 193 | name = "toml" 194 | optional = false 195 | python-versions = "*" 196 | version = "0.10.0" 197 | 198 | [[package]] 199 | category = "dev" 200 | description = "tox is a generic virtualenv management and test command line tool" 201 | name = "tox" 202 | optional = false 203 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 204 | version = "3.14.3" 205 | 206 | [package.dependencies] 207 | colorama = ">=0.4.1" 208 | filelock = ">=3.0.0,<4" 209 | packaging = ">=14" 210 | pluggy = ">=0.12.0,<1" 211 | py = ">=1.4.17,<2" 212 | six = ">=1.0.0,<2" 213 | toml = ">=0.9.4" 214 | virtualenv = ">=16.0.0" 215 | 216 | [package.dependencies.importlib-metadata] 217 | python = "<3.8" 218 | version = ">=0.12,<2" 219 | 220 | [package.extras] 221 | docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] 222 | testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] 223 | 224 | [[package]] 225 | category = "dev" 226 | description = "Virtual Python Environment builder" 227 | name = "virtualenv" 228 | optional = false 229 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 230 | version = "16.7.9" 231 | 232 | [package.extras] 233 | docs = ["sphinx (>=1.8.0,<2)", "towncrier (>=18.5.0)", "sphinx-rtd-theme (>=0.4.2,<1)"] 234 | testing = ["pytest (>=4.0.0,<5)", "coverage (>=4.5.0,<5)", "pytest-timeout (>=1.3.0,<2)", "six (>=1.10.0,<2)", "pytest-xdist", "pytest-localserver", "pypiserver", "mock", "xonsh"] 235 | 236 | [[package]] 237 | category = "dev" 238 | description = "Measures number of Terminal column cells of wide-character codes" 239 | name = "wcwidth" 240 | optional = false 241 | python-versions = "*" 242 | version = "0.1.8" 243 | 244 | [[package]] 245 | category = "dev" 246 | description = "Backport of pathlib-compatible object wrapper for zip files" 247 | marker = "python_version < \"3.8\"" 248 | name = "zipp" 249 | optional = false 250 | python-versions = ">=3.6" 251 | version = "2.0.0" 252 | 253 | [package.dependencies] 254 | more-itertools = "*" 255 | 256 | [package.extras] 257 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 258 | testing = ["pathlib2", "contextlib2", "unittest2"] 259 | 260 | [metadata] 261 | content-hash = "7cc141332e001353d73c436d87c5dbf57ea5a36849e88f91a0b47fccaca46ab0" 262 | python-versions = ">=3.6,<3.9" 263 | 264 | [metadata.files] 265 | atomicwrites = [ 266 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, 267 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, 268 | ] 269 | attrs = [ 270 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 271 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 272 | ] 273 | colorama = [ 274 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 275 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 276 | ] 277 | django = [ 278 | {file = "Django-2.2.9-py3-none-any.whl", hash = "sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"}, 279 | {file = "Django-2.2.9.tar.gz", hash = "sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5"}, 280 | ] 281 | filelock = [ 282 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 283 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 284 | ] 285 | importlib-metadata = [ 286 | {file = "importlib_metadata-1.4.0-py2.py3-none-any.whl", hash = "sha256:bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359"}, 287 | {file = "importlib_metadata-1.4.0.tar.gz", hash = "sha256:f17c015735e1a88296994c0697ecea7e11db24290941983b08c9feb30921e6d8"}, 288 | ] 289 | more-itertools = [ 290 | {file = "more-itertools-8.1.0.tar.gz", hash = "sha256:c468adec578380b6281a114cb8a5db34eb1116277da92d7c46f904f0b52d3288"}, 291 | {file = "more_itertools-8.1.0-py3-none-any.whl", hash = "sha256:1a2a32c72400d365000412fe08eb4a24ebee89997c18d3d147544f70f5403b39"}, 292 | ] 293 | packaging = [ 294 | {file = "packaging-20.0-py2.py3-none-any.whl", hash = "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb"}, 295 | {file = "packaging-20.0.tar.gz", hash = "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"}, 296 | ] 297 | pluggy = [ 298 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 299 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 300 | ] 301 | py = [ 302 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 303 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 304 | ] 305 | pyparsing = [ 306 | {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, 307 | {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, 308 | ] 309 | pytest = [ 310 | {file = "pytest-5.3.4-py3-none-any.whl", hash = "sha256:c13d1943c63e599b98cf118fcb9703e4d7bde7caa9a432567bcdcae4bf512d20"}, 311 | {file = "pytest-5.3.4.tar.gz", hash = "sha256:1d122e8be54d1a709e56f82e2d85dcba3018313d64647f38a91aec88c239b600"}, 312 | ] 313 | pytest-django = [ 314 | {file = "pytest-django-3.8.0.tar.gz", hash = "sha256:489b904f695f9fb880ce591cf5a4979880afb467763b1f180c07574554bdfd26"}, 315 | {file = "pytest_django-3.8.0-py2.py3-none-any.whl", hash = "sha256:456fa6854d04ee625d6bbb8b38ca2259e7040a6f93333bfe8bc8159b7e987203"}, 316 | ] 317 | pytz = [ 318 | {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, 319 | {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, 320 | ] 321 | six = [ 322 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 323 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 324 | ] 325 | sqlparse = [ 326 | {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"}, 327 | {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"}, 328 | ] 329 | toml = [ 330 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 331 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 332 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 333 | ] 334 | tox = [ 335 | {file = "tox-3.14.3-py2.py3-none-any.whl", hash = "sha256:806d0a9217584558cc93747a945a9d9bff10b141a5287f0c8429a08828a22192"}, 336 | {file = "tox-3.14.3.tar.gz", hash = "sha256:06ba73b149bf838d5cd25dc30c2dd2671ae5b2757cf98e5c41a35fe449f131b3"}, 337 | ] 338 | virtualenv = [ 339 | {file = "virtualenv-16.7.9-py2.py3-none-any.whl", hash = "sha256:55059a7a676e4e19498f1aad09b8313a38fcc0cdbe4fdddc0e9b06946d21b4bb"}, 340 | {file = "virtualenv-16.7.9.tar.gz", hash = "sha256:0d62c70883c0342d59c11d0ddac0d954d0431321a41ab20851facf2b222598f3"}, 341 | ] 342 | wcwidth = [ 343 | {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"}, 344 | {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"}, 345 | ] 346 | zipp = [ 347 | {file = "zipp-2.0.0-py3-none-any.whl", hash = "sha256:57147f6b0403b59f33fd357f169f860e031303415aeb7d04ede4839d23905ab8"}, 348 | {file = "zipp-2.0.0.tar.gz", hash = "sha256:7ae5ccaca427bafa9760ac3cd8f8c244bfc259794b5b6bb9db4dda2241575d09"}, 349 | ] 350 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "django-clearcache" 3 | version = "1.2.1" 4 | description = "Allows you to clear Django cache via admin UI or manage.py command" 5 | authors = ["Tim Kamanin "] 6 | homepage = "https://timonweb.com" 7 | repository = "https://github.com/timonweb/django-clearcache" 8 | license = "MIT" 9 | readme = "README.md" 10 | keywords = ["django", "cache"] 11 | classifiers = [ 12 | "Development Status :: 5 - Production/Stable", 13 | "Intended Audience :: Developers", 14 | "License :: OSI Approved :: MIT License", 15 | "Programming Language :: Python :: 3.7", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Operating System :: OS Independent", 19 | "Topic :: Software Development :: Libraries", 20 | "Topic :: Utilities", 21 | "Environment :: Web Environment", 22 | "Framework :: Django", 23 | "Framework :: Django :: 2.2", 24 | "Framework :: Django :: 3.0", 25 | "Framework :: Django :: 3.1", 26 | "Framework :: Django :: 3.2" 27 | ] 28 | packages = [ 29 | { include = "clearcache" } 30 | ] 31 | 32 | 33 | [tool.poetry.dependencies] 34 | python = ">=3.7" 35 | django = ">=2.2" 36 | 37 | [tool.poetry.dev-dependencies] 38 | pytest-django = "^3.8.0" 39 | pytest = "^5.3.4" 40 | tox = "^3.14.3" 41 | 42 | [build-system] 43 | requires = ["poetry>=0.12"] 44 | build-backend = "poetry.masonry.api" 45 | -------------------------------------------------------------------------------- /test_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timonweb/django-clearcache/7b50eda45ae22a1c9d59fbf2a2342fbf8fc1bf81/test_project/__init__.py -------------------------------------------------------------------------------- /test_project/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 | 5 | SECRET_KEY = 'secretkey' 6 | 7 | DEBUG = True 8 | 9 | ALLOWED_HOSTS = [] 10 | 11 | INSTALLED_APPS = [ 12 | 'clearcache', 13 | 'django.contrib.admin', 14 | 'django.contrib.auth', 15 | 'django.contrib.contenttypes', 16 | 'django.contrib.sessions', 17 | 'django.contrib.messages', 18 | 'django.contrib.staticfiles', 19 | 20 | ] 21 | 22 | MIDDLEWARE = [ 23 | 'django.middleware.security.SecurityMiddleware', 24 | 'django.contrib.sessions.middleware.SessionMiddleware', 25 | 'django.middleware.common.CommonMiddleware', 26 | 'django.middleware.csrf.CsrfViewMiddleware', 27 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 28 | 'django.contrib.messages.middleware.MessageMiddleware', 29 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 30 | ] 31 | 32 | ROOT_URLCONF = 'test_project.urls' 33 | 34 | TEMPLATES = [ 35 | { 36 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 37 | 'DIRS': [], 38 | 'APP_DIRS': True, 39 | 'OPTIONS': { 40 | 'context_processors': [ 41 | 'django.template.context_processors.debug', 42 | 'django.template.context_processors.request', 43 | 'django.contrib.auth.context_processors.auth', 44 | 'django.contrib.messages.context_processors.messages', 45 | ], 46 | }, 47 | }, 48 | ] 49 | 50 | WSGI_APPLICATION = 'test_project.wsgi.application' 51 | 52 | DATABASES = { 53 | 'default': { 54 | 'ENGINE': 'django.db.backends.sqlite3', 55 | 'NAME': ':memory:', 56 | } 57 | } 58 | 59 | AUTH_PASSWORD_VALIDATORS = [ 60 | { 61 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 62 | }, 63 | { 64 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 65 | }, 66 | { 67 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 68 | }, 69 | { 70 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 71 | }, 72 | ] 73 | 74 | LANGUAGE_CODE = 'en-us' 75 | 76 | TIME_ZONE = 'UTC' 77 | 78 | USE_I18N = True 79 | 80 | USE_L10N = True 81 | 82 | USE_TZ = True 83 | 84 | STATIC_URL = '/static/' 85 | 86 | CACHES = { 87 | 'default': { 88 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 89 | 'LOCATION': '' 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /test_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('admin/clearcache/', include('clearcache.urls')), 6 | path('admin/', admin.site.urls), 7 | ] 8 | -------------------------------------------------------------------------------- /test_project/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.core.wsgi import get_wsgi_application 3 | 4 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings") 5 | application = get_wsgi_application() 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = test_project.settings 3 | python_files = tests.py test_*.py *_tests.py 4 | 5 | [tox] 6 | isolated_build = True 7 | envlist = django{22,30,31,32} 8 | 9 | [tox:.package] 10 | # note tox will use the same python version as under what tox is installed to package 11 | # so unless this is python 3 you can require a given python version for the packaging 12 | # environment via the basepython key 13 | basepython = python3 14 | 15 | [testenv] 16 | commands = pytest {posargs} 17 | deps = 18 | django22: Django>=2.2.16,<3.0 19 | django30: Django>=3.0.10,<3.1 20 | django31: Django>=3.1.2,<3.2 21 | django32: Django>=3.2,<3.3 22 | setenv = 23 | DJANGO_SETTINGS_MODULE = test_project.settings 24 | PYTHONPATH = {toxinidir} 25 | --------------------------------------------------------------------------------