├── .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 | 
4 | 
5 | 
6 |
7 | Allows you to clear Django cache via admin UI or manage.py command.
8 |
9 | 
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 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/clearcache/templates/clearcache/admin/clearcache_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/base_site.html' %}
2 |
3 | {% block content %}
4 |
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 |
--------------------------------------------------------------------------------