From 206caaffebc7009d0365f3cfddd8ce08f25908b4 Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Wed, 19 Oct 2022 20:22:19 -0400 Subject: [PATCH 1/8] Fix sponsor in docs theme --- docs/_templates/sponsor.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/_templates/sponsor.html b/docs/_templates/sponsor.html index 4c3ec03..87454fd 100644 --- a/docs/_templates/sponsor.html +++ b/docs/_templates/sponsor.html @@ -1,8 +1,7 @@ - +
- +

From c986b06e6f4ac83ab75b448d2491603f6ce250f5 Mon Sep 17 00:00:00 2001 From: Sonny Baker Date: Wed, 14 Jun 2023 17:43:08 +0100 Subject: [PATCH 2/8] Wagtail 5 compatibility (#52) This adds support for Wagtail 5 by removing deprecated paths, namely `wagtail.core.hooks` (now `wagtail.hooks`) and `wagtail.core.urls` (now `wagtail.urls`). Documentation has also been updated. Tests raised an error on `NoneType` as default for the `urls: List[str]` arg of `clear_cache` so I resolved that too. --- docs/getting_started/hooks.rst | 4 ++-- docs/getting_started/install.rst | 6 +++--- setup.py | 4 ++-- wagtailcache/__init__.py | 2 +- wagtailcache/cache.py | 17 ++++++----------- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/docs/getting_started/hooks.rst b/docs/getting_started/hooks.rst index c4199e6..1d01566 100644 --- a/docs/getting_started/hooks.rst +++ b/docs/getting_started/hooks.rst @@ -27,7 +27,7 @@ For example: .. code-block:: python - from wagtail.core import hooks + from wagtail import hooks @hooks.register("is_request_cacheable") def nocache_in_query(request, curr_cache_decision): @@ -54,7 +54,7 @@ For example: .. code-block:: python - from wagtail.core import hooks + from wagtail import hooks @hooks.register("is_response_cacheable") def nocache_secrets(response, curr_cache_decision): diff --git a/docs/getting_started/install.rst b/docs/getting_started/install.rst index 2c892ae..6455f77 100644 --- a/docs/getting_started/install.rst +++ b/docs/getting_started/install.rst @@ -200,11 +200,11 @@ To this: from django.conf.urls import url from django.contrib.auth import views as auth_views - from wagtail.core.urls import serve_pattern, WAGTAIL_FRONTEND_LOGIN_TEMPLATE - from wagtail.core import views as wagtail_views + from wagtail.urls import serve_pattern, WAGTAIL_FRONTEND_LOGIN_TEMPLATE + from wagtail import views as wagtail_views from wagtailcache.cache import cache_page - # Copied from wagtail.core.urls: + # Copied from wagtail.urls: url(r'^_util/authenticate_with_password/(\d+)/(\d+)/$', wagtail_views.authenticate_with_password, name='wagtailcore_authenticate_with_password'), url(r'^_util/login/$', auth_views.LoginView.as_view(template_name=WAGTAIL_FRONTEND_LOGIN_TEMPLATE), diff --git a/setup.py b/setup.py index af12fc7..34ee4a1 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import find_packages, setup from wagtailcache import __version__ with open("README.md", encoding="utf8") as readme_file: @@ -16,7 +16,7 @@ license="BSD license", include_package_data=True, packages=find_packages(), - install_requires=["wagtail>=3.0,<5.0"], + install_requires=["wagtail>=3.0,<6"], classifiers=[ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", diff --git a/wagtailcache/__init__.py b/wagtailcache/__init__.py index ff94b74..1dc3005 100644 --- a/wagtailcache/__init__.py +++ b/wagtailcache/__init__.py @@ -1,3 +1,3 @@ -release = ["2", "2", "0"] +release = ["2", "2", "1"] __version__ = "{0}.{1}.{2}".format(release[0], release[1], release[2]) __shortversion__ = "{0}.{1}".format(release[0], release[1]) diff --git a/wagtailcache/cache.py b/wagtailcache/cache.py index 531b586..dadc666 100644 --- a/wagtailcache/cache.py +++ b/wagtailcache/cache.py @@ -4,25 +4,20 @@ import re from enum import Enum from functools import wraps -from typing import Callable, Optional, List +from typing import Callable, List, Optional from urllib.parse import unquote + from django.conf import settings from django.core.cache import caches from django.core.cache.backends.base import BaseCache from django.core.handlers.wsgi import WSGIRequest from django.http.response import HttpResponse from django.template.response import SimpleTemplateResponse -from django.utils.cache import ( - cc_delim_re, - get_cache_key, - get_max_age, - has_vary_header, - learn_cache_key, - patch_response_headers, -) +from django.utils.cache import (cc_delim_re, get_cache_key, get_max_age, + has_vary_header, learn_cache_key, + patch_response_headers) from django.utils.deprecation import MiddlewareMixin from wagtail import hooks - from wagtailcache.settings import wagtailcache_settings @@ -334,7 +329,7 @@ def process_response( return response -def clear_cache(urls: List[str] = None) -> None: +def clear_cache(urls: List[str] = []) -> None: """ Clears the Wagtail cache backend. From 65191ed94d5e20edab75daf0c1b8605bec9074c7 Mon Sep 17 00:00:00 2001 From: jonatron Date: Wed, 14 Jun 2023 17:46:38 +0100 Subject: [PATCH 3/8] remove redis compatibility backend (#46) See also #45 . Edit: To add, the quickest way to run redis locally, temporarily would be `docker run --rm -p 6379:6379 redis` Co-authored-by: Jonathan Aylard --- docs/getting_started/install.rst | 2 +- docs/getting_started/supported_backends.rst | 13 ------------- testproject/home/tests.py | 6 ++++++ testproject/home/views.py | 7 +++++++ testproject/testproject/urls.py | 5 +++++ wagtailcache/cache.py | 8 +++++--- wagtailcache/compat_backends/__init__.py | 0 wagtailcache/compat_backends/django_redis.py | 16 ---------------- 8 files changed, 24 insertions(+), 33 deletions(-) delete mode 100644 wagtailcache/compat_backends/__init__.py delete mode 100644 wagtailcache/compat_backends/django_redis.py diff --git a/docs/getting_started/install.rst b/docs/getting_started/install.rst index 6455f77..77b80b6 100644 --- a/docs/getting_started/install.rst +++ b/docs/getting_started/install.rst @@ -40,7 +40,7 @@ adding the Wagtail Cache middleware. 2. Define a cache ----------------- -Next a cache must be configured in the settings. If you use django-redis, see :doc:`Supported Cache Backends `. Here is an example file cache, +Next a cache must be configured in the settings. Here is an example file cache, which is suitable for use on any web server: .. code-block:: python diff --git a/docs/getting_started/supported_backends.rst b/docs/getting_started/supported_backends.rst index acabaa3..5058e95 100644 --- a/docs/getting_started/supported_backends.rst +++ b/docs/getting_started/supported_backends.rst @@ -16,19 +16,6 @@ Built-in Django cache backends Wagtail Cache may or may not work correctly with 3rd party backends. If you experience an issue, please `report it on our GitHub page `_. -django-redis ------------- - -Wagtail Cache provides a compatibility backend to support ``django-redis``. Install as follows: - -#. Install wagtail-cache :doc:`following the installation guide `. - -#. `Install django-redis `_ and define - a Redis cache in your settings.py. - -#. Replace ``django_redis.cache.RedisCache`` with ``wagtailcache.compat_backends.django_redis.RedisCache`` - in your cache definition. - .. note:: If you are currently using Redis or have other code that uses a Redis cache, It is advised to use separate cache definitions for wagtail-cache and your other uses. diff --git a/testproject/home/tests.py b/testproject/home/tests.py index 841b278..d97a56d 100644 --- a/testproject/home/tests.py +++ b/testproject/home/tests.py @@ -460,6 +460,12 @@ def test_view_skip(self): # Second get should continue to skip. self.get_skip(reverse("nocached_view")) + def test_template_response_view_hit(self): + # First get should miss cache. + self.get_miss(reverse("template_response_view")) + # Second get should hit cache. + self.get_hit(reverse("template_response_view")) + # ---- ADMIN VIEWS --------------------------------------------------------- def test_admin(self): diff --git a/testproject/home/views.py b/testproject/home/views.py index 1327ea2..22e7918 100644 --- a/testproject/home/views.py +++ b/testproject/home/views.py @@ -1,4 +1,5 @@ from django.http import HttpResponse +from django.template.response import TemplateResponse from wagtailcache.cache import cache_page, nocache_page @@ -16,3 +17,9 @@ def vary_view(request): r = HttpResponse("Variety is the spice of life.") r.headers["Vary"] = "A, B, Cookie, C" return r + + +@cache_page +def template_response_view(request): + response = TemplateResponse(request, "home/page.html", {}) + return response diff --git a/testproject/testproject/urls.py b/testproject/testproject/urls.py index 250e3b0..52f9487 100644 --- a/testproject/testproject/urls.py +++ b/testproject/testproject/urls.py @@ -15,6 +15,11 @@ path("views/cached/", views.cached_view, name="cached_view"), path("views/nocache/", views.nocached_view, name="nocached_view"), path("views/vary/", views.vary_view, name="vary_view"), + path( + "views/template-response-view/", + views.template_response_view, + name="template_response_view", + ), # For anything not caught by a more specific rule above, hand over to # Wagtail's page serving mechanism. This should be the last pattern in # the list: diff --git a/wagtailcache/cache.py b/wagtailcache/cache.py index dadc666..4495020 100644 --- a/wagtailcache/cache.py +++ b/wagtailcache/cache.py @@ -317,9 +317,11 @@ def process_response( self._wagcache.set("keyring", keyring) if isinstance(response, SimpleTemplateResponse): - response.add_post_render_callback( - lambda r: self._wagcache.set(cache_key, r, timeout) - ) + + def callback(r): + self._wagcache.set(cache_key, r, timeout) + + response.add_post_render_callback(callback) else: self._wagcache.set(cache_key, response, timeout) diff --git a/wagtailcache/compat_backends/__init__.py b/wagtailcache/compat_backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/wagtailcache/compat_backends/django_redis.py b/wagtailcache/compat_backends/django_redis.py deleted file mode 100644 index 85f51f0..0000000 --- a/wagtailcache/compat_backends/django_redis.py +++ /dev/null @@ -1,16 +0,0 @@ -from django_redis.cache import omit_exception, RedisCache as BaseBackend - - -class RedisCache(BaseBackend): - """ - Extends django_redis.cache.RedisCache for compatibility - with the Django cache middleware. - """ - - @omit_exception - def set(self, *args, **kwargs): - """ - Return the value instead of a boolean. - """ - super().set(*args, **kwargs) - return args[1] From 7b84aa68eca85bd4a2c60746c1ffbe0f4690986e Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Wed, 14 Jun 2023 13:04:54 -0400 Subject: [PATCH 4/8] Bump version to 2.3; fix linter errors --- setup.cfg | 1 + setup.py | 2 ++ wagtailcache/__init__.py | 2 +- wagtailcache/cache.py | 17 +++++++++++------ wagtailcache/settings.py | 1 - 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8320bc6..92c4eee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ exclude = .*,migrations [mypy] ignore_missing_imports = True +check_untyped_defs = True [tool:pytest] DJANGO_SETTINGS_MODULE = testproject.settings diff --git a/setup.py b/setup.py index 34ee4a1..a83cf10 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,7 @@ "Framework :: Django", "Framework :: Wagtail", "Framework :: Wagtail :: 3", + "Framework :: Wagtail :: 4", + "Framework :: Wagtail :: 5", ], ) diff --git a/wagtailcache/__init__.py b/wagtailcache/__init__.py index 1dc3005..a97e3c7 100644 --- a/wagtailcache/__init__.py +++ b/wagtailcache/__init__.py @@ -1,3 +1,3 @@ -release = ["2", "2", "1"] +release = ["2", "3", "0"] __version__ = "{0}.{1}.{2}".format(release[0], release[1], release[2]) __shortversion__ = "{0}.{1}".format(release[0], release[1]) diff --git a/wagtailcache/cache.py b/wagtailcache/cache.py index 4495020..2d8f0f5 100644 --- a/wagtailcache/cache.py +++ b/wagtailcache/cache.py @@ -13,9 +13,14 @@ from django.core.handlers.wsgi import WSGIRequest from django.http.response import HttpResponse from django.template.response import SimpleTemplateResponse -from django.utils.cache import (cc_delim_re, get_cache_key, get_max_age, - has_vary_header, learn_cache_key, - patch_response_headers) +from django.utils.cache import ( + cc_delim_re, + get_cache_key, + get_max_age, + has_vary_header, + learn_cache_key, + patch_response_headers, +) from django.utils.deprecation import MiddlewareMixin from wagtail import hooks from wagtailcache.settings import wagtailcache_settings @@ -421,7 +426,7 @@ def serve_password_required_response(self, request, form, action_url): """ Add a cache-control header if the page requires a password. """ - response = super().serve_password_required_response( + response = super().serve_password_required_response( # type: ignore request, form, action_url ) response["Cache-Control"] = CacheControl.PRIVATE.value @@ -432,8 +437,8 @@ def serve(self, request, *args, **kwargs): Add a custom cache-control header, or set to private if the page is being served behind a view restriction. """ - response = super().serve(request, *args, **kwargs) - if self.get_view_restrictions(): + response = super().serve(request, *args, **kwargs) # type: ignore + if self.get_view_restrictions(): # type: ignore response["Cache-Control"] = CacheControl.PRIVATE.value elif hasattr(self, "cache_control"): if callable(self.cache_control): diff --git a/wagtailcache/settings.py b/wagtailcache/settings.py index 48f9cd9..0f909ea 100644 --- a/wagtailcache/settings.py +++ b/wagtailcache/settings.py @@ -7,7 +7,6 @@ class _DefaultSettings: - WAGTAIL_CACHE = True WAGTAIL_CACHE_BACKEND = "default" WAGTAIL_CACHE_HEADER = "X-Wagtail-Cache" From e7f6c6787686baa13888b5befb0c807af35f062b Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Wed, 14 Jun 2023 13:59:49 -0400 Subject: [PATCH 5/8] Sort imports with isort --- azure-pipelines.yml | 13 +++-- docs/conf.py | 40 +++++++-------- docs/contributing.rst | 4 +- pyproject.toml | 16 +++++- requirements-dev.txt | 1 + setup.py | 5 +- testproject/home/models.py | 1 + testproject/home/tests.py | 50 ++++++------------- testproject/home/views.py | 4 +- testproject/manage.py | 2 +- testproject/testproject/settings.py | 23 +-------- testproject/testproject/urls.py | 7 +-- testproject/testproject/wsgi.py | 2 +- wagtailcache/cache.py | 34 ++++--------- .../commands/clear_wagtail_cache.py | 2 +- wagtailcache/settings.py | 2 +- .../templatetags/wagtailcache_tags.py | 2 + wagtailcache/urls.py | 5 +- wagtailcache/views.py | 3 +- wagtailcache/wagtail_hooks.py | 7 +-- 20 files changed, 97 insertions(+), 126 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e0e7590..ff79fed 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,14 +30,14 @@ stages: vmImage: 'ubuntu-latest' strategy: matrix: - py3.7: - PYTHON_VERSION: '3.7' py3.8: PYTHON_VERSION: '3.8' py3.9: PYTHON_VERSION: '3.9' py3.10: PYTHON_VERSION: '3.10' + py3.11: + PYTHON_VERSION: '3.11' steps: - task: UsePythonVersion@0 @@ -86,7 +86,7 @@ stages: - task: UsePythonVersion@0 displayName: 'Use Python version' inputs: - versionSpec: '3.10' + versionSpec: '3.11' architecture: 'x64' - script: python -m pip install -r requirements-dev.txt @@ -98,8 +98,11 @@ stages: - script: flake8 . displayName: 'CR-QC: Static analysis (flake8)' + - script: isort --check . + displayName: 'CR-QC: Format check (isort)' + - script: black --check . - displayName: 'CR-QC: Format check' + displayName: 'CR-QC: Format check (black)' - script: mypy ./wagtailcache/ displayName: 'CR-QC: Type check (mypy)' @@ -134,7 +137,7 @@ stages: - task: UsePythonVersion@0 displayName: 'Use Python version' inputs: - versionSpec: '3.10' + versionSpec: '3.11' architecture: 'x64' - script: python -m pip install -r requirements-dev.txt diff --git a/docs/conf.py b/docs/conf.py index f47a56f..bed38de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,20 +1,22 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +""" +Configuration file for the Sphinx documentation builder. +This file does only contain a selection of the most common options. For a +full list see the documentation: +http://www.sphinx-doc.org/en/master/config + +-- Path setup -------------------------------------------------------------- + +If extensions (or modules to document with autodoc) are in another directory, +add these directories to sys.path here. If the directory is relative to the +documentation root, use os.path.abspath to make it absolute, like shown here. + +import os +import sys +sys.path.insert(0, os.path.abspath('.')) +""" import datetime + from wagtailcache import __shortversion__ @@ -23,15 +25,11 @@ project = "wagtail-cache" author = "CodeRed LLC" copyright = f"2018–{str(datetime.datetime.now().year)}, {author}" - # The short X.Y version version = __shortversion__ # The full version, including alpha/beta/rc tags release = __shortversion__ - - # -- General configuration --------------------------------------------------- - source_suffix = ".rst" master_doc = "index" @@ -39,15 +37,11 @@ language = "en" exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] extensions = ["sphinx_wagtail_theme"] - - # -- Options for HTML output ------------------------------------------------- - html_show_sourcelink = False html_theme = "sphinx_wagtail_theme" diff --git a/docs/contributing.rst b/docs/contributing.rst index 10b841f..15b36e4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -29,10 +29,12 @@ development tools: Write some code. -Next, run the static analysis tools (``flake8`` and ``mypy``) +Next, run the formatters and static analysis tools: .. code-block:: console + $ isort . + $ black . $ flake8 ./wagtailcache/ $ mypy ./wagtailcache/ diff --git a/pyproject.toml b/pyproject.toml index 78f7ad4..e4e2566 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.black] line-length = 80 -target-version = ['py37', 'py38', 'py39', 'py310'] +target-version = ['py38', 'py39', 'py310', 'py311'] # Regular expression of files to exclude. exclude = ''' /( @@ -9,3 +9,17 @@ exclude = ''' | migrations )/ ''' + +[tool.isort] +force_alphabetical_sort_within_sections = true +force_single_line = true +lines_after_imports = 2 +lines_before_imports = 0 +lines_between_sections = 1 +lines_between_types = 0 +line_length = 80 +profile = "black" +skip_gitignore = true +extend_skip = ["migrations"] +# Specific to this project +known_first_party = ["home", "wagtailcache"] diff --git a/requirements-dev.txt b/requirements-dev.txt index 6b7262a..4920eb5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ black codespell flake8 +isort mypy pytest pytest-cov diff --git a/setup.py b/setup.py index a83cf10..8e1b2ce 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,9 @@ -from setuptools import find_packages, setup +from setuptools import find_packages +from setuptools import setup + from wagtailcache import __version__ + with open("README.md", encoding="utf8") as readme_file: readme = readme_file.read() diff --git a/testproject/home/models.py b/testproject/home/models.py index 185b64d..29c0877 100644 --- a/testproject/home/models.py +++ b/testproject/home/models.py @@ -1,4 +1,5 @@ from wagtail.models import Page + from wagtailcache.cache import WagtailCacheMixin diff --git a/testproject/home/tests.py b/testproject/home/tests.py index d97a56d..650a505 100644 --- a/testproject/home/tests.py +++ b/testproject/home/tests.py @@ -1,21 +1,23 @@ -from django.contrib.contenttypes.models import ContentType -from django.test import TestCase, override_settings, modify_settings -from django.urls import reverse from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType from django.core.cache import caches -from wagtailcache.settings import wagtailcache_settings -from wagtailcache.cache import CacheControl, Status, clear_cache +from django.test import modify_settings +from django.test import override_settings +from django.test import TestCase +from django.urls import reverse from wagtail import hooks from wagtail.models import PageViewRestriction -from home.models import ( - CachedPage, - CacheControlPage, - CallableCacheControlPage, - CookiePage, - CsrfPage, - WagtailPage, -) +from home.models import CacheControlPage +from home.models import CachedPage +from home.models import CallableCacheControlPage +from home.models import CookiePage +from home.models import CsrfPage +from home.models import WagtailPage +from wagtailcache.cache import CacheControl +from wagtailcache.cache import clear_cache +from wagtailcache.cache import Status +from wagtailcache.settings import wagtailcache_settings def hook_true(obj, is_cacheable: bool) -> bool: @@ -87,14 +89,12 @@ def setUpClass(cls): ) cls.page_wagtailpage.add_child(instance=cls.page_cookiepage) cls.page_wagtailpage.add_child(instance=cls.page_csrfpage) - # Create the view restriction. cls.view_restriction = PageViewRestriction.objects.create( page=cls.page_cachedpage_restricted, restriction_type=PageViewRestriction.PASSWORD, password="the cybers", ) - # List of pages to test. cls.should_cache_pages = [ cls.page_wagtailpage, @@ -134,7 +134,6 @@ def tearDown(self): pass # --- UTILITIES ------------------------------------------------------------ - def head_hit(self, url: str): """ HEAD a page and test that it was served from the cache. @@ -215,7 +214,6 @@ def post_skip(self, url: str): return response # ---- TEST PAGES ---------------------------------------------------------- - def test_page_miss(self): for page in self.should_cache_pages: self.head_miss(page.get_url()) @@ -279,7 +277,6 @@ def test_cookie_page_ignore(self): # With the setting turned on, it will strip all cookies (other than CSRF # or session id). Therefore, each response is going to SKIP due to # setting a cooking in response to a cookie-less request. - # First request should skip, since the cookie is not yet set. self.get_skip(self.page_cookiepage.get_url()) # Second request should continue to skip. @@ -298,7 +295,6 @@ def test_csrf_page(self): @override_settings(WAGTAIL_CACHE_IGNORE_COOKIES=True) def test_csrf_page_ignore(self): # This should behave exactly the same with or without the setting. - # First request should skip, since the CSRF token is being set. self.get_skip(self.page_csrfpage.get_url()) # Second request should also miss, since this request now contains a @@ -313,7 +309,6 @@ def test_client_tracking_cookies(self): # the cache due to presence of the ``Vary: Cookie`` header. This means # trackers that pollute the cookies effectively bust the cache. Under # normal behavior, these should in fact be cached separately. - # A get should miss cache. self.get_miss(self.page_cachedpage.get_url()) # A get with different cookies should also miss the cache. @@ -329,10 +324,8 @@ def test_client_tracking_cookies_ignore(self): # the cache due to presence of the ``Vary: Cookie`` header. This means # trackers that pollute the cookies effectively bust the cache. Under # normal behavior, these should in fact be cached separately. - # With this setting ``True``, all cookies other than Django session and # CSRF token should be ignored and continue to hit the cache. - # A get should miss cache. self.get_miss(self.page_cachedpage.get_url()) # A get with different cookies should hit. @@ -425,7 +418,6 @@ def test_page_404_without_auth(self): # ---- TEST VIEWS ---------------------------------------------------------- # Views use the decorators and should work without the middleware. - @modify_settings( MIDDLEWARE={ "remove": "wagtailcache.cache.UpdateCacheMiddleware", # noqa @@ -467,7 +459,6 @@ def test_template_response_view_hit(self): self.get_hit(reverse("template_response_view")) # ---- ADMIN VIEWS --------------------------------------------------------- - def test_admin(self): self.client.force_login(self.user) response = self.client.get(reverse("wagtailcache:index")) @@ -488,7 +479,6 @@ def test_admin_clearcache(self): self.get_miss(self.page_cachedpage.get_url()) # ---- PURGE SPECIFIC URLS & CLEAR ALL-------------------------------------- - def test_cache_keyring(self): # Check if keyring is not present self.assertEqual(self.cache.get("keyring"), None) @@ -505,38 +495,31 @@ def test_clear_cache(self): self.get_miss(self.page_cachedpage.get_url()) # Second get should hit cache. self.get_hit(self.page_cachedpage.get_url()) - # clear all from Cache clear_cache() - # Now the page should miss cache. self.get_miss(self.page_cachedpage.get_url()) def test_clear_cache_url(self): u1 = self.page_cachedpage.get_url() u2 = self.page_cachedpage.get_url() + "?action=pytest" - # First get should miss cache. self.get_miss(u1) self.get_miss(u2) - # Second get should hit cache. self.get_hit(u1) self.get_hit(u2) - # Clear only the second URL, using a regex. clear_cache( [ r".*" + u1 + r"[\?\&]action=", ] ) - # url1 should still hit, but url2 should miss. self.get_hit(u1) self.get_miss(u2) # ---- ALTERNATE SETTINGS -------------------------------------------------- - @override_settings(WAGTAIL_CACHE=True) def test_enable_wagtailcache(self): # Intentionally enable wagtail-cache, make sure it works. @@ -561,7 +544,6 @@ def test_zero_timeout(self): self.test_admin() # ---- HOOKS --------------------------------------------------------------- - def test_request_hook_true(self): # A POST should never be cached. response = self.client.post(reverse("cached_view")) @@ -572,12 +554,10 @@ def test_request_hook_true(self): self.assertEqual( response.get(self.header_name, None), Status.SKIP.value ) - # Register hook and assert it was actually registered. hooks.register("is_request_cacheable", hook_true) hook_fns = hooks.get_hooks("is_request_cacheable") self.assertEqual(hook_fns, [hook_true]) - # Setting `is_request_cachebale=True` does not really do much, because # the response still has the final say in whether or not the response is # cached. However a simple POST request where the response does not diff --git a/testproject/home/views.py b/testproject/home/views.py index 22e7918..0a1dfaf 100644 --- a/testproject/home/views.py +++ b/testproject/home/views.py @@ -1,6 +1,8 @@ from django.http import HttpResponse from django.template.response import TemplateResponse -from wagtailcache.cache import cache_page, nocache_page + +from wagtailcache.cache import cache_page +from wagtailcache.cache import nocache_page @cache_page diff --git a/testproject/manage.py b/testproject/manage.py index 97ed576..22adcd8 100644 --- a/testproject/manage.py +++ b/testproject/manage.py @@ -2,9 +2,9 @@ import os import sys + if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings") - from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) diff --git a/testproject/testproject/settings.py b/testproject/testproject/settings.py index a29b3b6..53f3cce 100644 --- a/testproject/testproject/settings.py +++ b/testproject/testproject/settings.py @@ -9,19 +9,14 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ - - # Application definition - INSTALLED_APPS = [ "home", "wagtailcache", @@ -80,22 +75,16 @@ ] WSGI_APPLICATION = "testproject.wsgi.application" - - # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases - DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } - - # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ - LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" @@ -103,11 +92,8 @@ USE_I18N = True USE_TZ = True - - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ - STATICFILES_FINDERS = [ "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", @@ -120,22 +106,15 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_URL = "/media/" - - # Wagtail settings - WAGTAIL_SITE_NAME = "testproject" - # Base URL to use when referring to full URLs within the Wagtail admin backend - # e.g. in notification emails. Don't include '/admin' or a trailing slash BASE_URL = "http://example.com" - # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True - # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "4n5y9*ny(o(i5e^f!9=5yc8ie(z&0#^=@3*4pohw$8iv!tkcmr" - # SECURITY WARNING: define the correct hosts in production! ALLOWED_HOSTS = ["*"] diff --git a/testproject/testproject/urls.py b/testproject/testproject/urls.py index 52f9487..b2a35ae 100644 --- a/testproject/testproject/urls.py +++ b/testproject/testproject/urls.py @@ -1,13 +1,14 @@ from django.conf import settings -from django.urls import include, path from django.contrib import admin - -from wagtail.admin import urls as wagtailadmin_urls +from django.urls import include +from django.urls import path from wagtail import urls as wagtail_urls +from wagtail.admin import urls as wagtailadmin_urls from wagtail.documents import urls as wagtaildocs_urls from home import views + urlpatterns = [ path("django-admin/", admin.site.urls), path("admin/", include(wagtailadmin_urls)), diff --git a/testproject/testproject/wsgi.py b/testproject/testproject/wsgi.py index 2b24860..6a80e1a 100644 --- a/testproject/testproject/wsgi.py +++ b/testproject/testproject/wsgi.py @@ -6,11 +6,11 @@ For more information on this file, see https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ """ - import os from django.core.wsgi import get_wsgi_application + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings") application = get_wsgi_application() diff --git a/wagtailcache/cache.py b/wagtailcache/cache.py index 2d8f0f5..73f2e2c 100644 --- a/wagtailcache/cache.py +++ b/wagtailcache/cache.py @@ -4,7 +4,9 @@ import re from enum import Enum from functools import wraps -from typing import Callable, List, Optional +from typing import Callable +from typing import List +from typing import Optional from urllib.parse import unquote from django.conf import settings @@ -13,16 +15,15 @@ from django.core.handlers.wsgi import WSGIRequest from django.http.response import HttpResponse from django.template.response import SimpleTemplateResponse -from django.utils.cache import ( - cc_delim_re, - get_cache_key, - get_max_age, - has_vary_header, - learn_cache_key, - patch_response_headers, -) +from django.utils.cache import cc_delim_re +from django.utils.cache import get_cache_key +from django.utils.cache import get_max_age +from django.utils.cache import has_vary_header +from django.utils.cache import learn_cache_key +from django.utils.cache import patch_response_headers from django.utils.deprecation import MiddlewareMixin from wagtail import hooks + from wagtailcache.settings import wagtailcache_settings @@ -184,7 +185,6 @@ def __init__(self, get_response=None): def process_request(self, request: WSGIRequest) -> Optional[HttpResponse]: if not wagtailcache_settings.WAGTAIL_CACHE: return None - # Check if request is cacheable # Only cache GET and HEAD requests. # Don't cache requests that are previews. @@ -196,7 +196,6 @@ def process_request(self, request: WSGIRequest) -> Optional[HttpResponse]: and not getattr(request, "is_preview", False) and not (hasattr(request, "user") and request.user.is_authenticated) ) - # Allow the user to override our caching decision. for fn in hooks.get_hooks("is_request_cacheable"): result = fn(request, is_cacheable) @@ -207,7 +206,6 @@ def process_request(self, request: WSGIRequest) -> Optional[HttpResponse]: setattr(request, "_wagtailcache_update", False) setattr(request, "_wagtailcache_skip", True) return None # Don't bother checking the cache. - # Try and get the cached response. cache_key = _get_cache_key(request, self._wagcache) if cache_key is None: @@ -219,7 +217,6 @@ def process_request(self, request: WSGIRequest) -> Optional[HttpResponse]: if response is None: setattr(request, "_wagtailcache_update", True) return None # No cache information available, need to rebuild. - # Hit. Return cached response. setattr(request, "_wagtailcache_update", False) return response @@ -260,7 +257,6 @@ def process_response( _chop_response_vary(request, response) # We don't need to update the cache, just return. return response - # Check if the response is cacheable # Don't cache private or no-cache responses. # Do cache 200, 301, 302, 304, and 404 codes so that wagtail doesn't @@ -280,22 +276,18 @@ def process_response( and has_vary_header(response, "Cookie") ) ) - # Allow the user to override our caching decision. for fn in hooks.get_hooks("is_response_cacheable"): result = fn(response, is_cacheable) if isinstance(result, bool): is_cacheable = result - # If we are not allowed to cache the response, just return. if not is_cacheable: # Add response header to indicate this was intentionally not cached. _patch_header(response, Status.SKIP) return response - # Potentially remove the ``Vary: Cookie`` header. _chop_response_vary(request, response) - # Try to get the timeout from the ``max-age`` section of the # ``Cache-Control`` header before reverting to using the cache's # default. @@ -307,7 +299,6 @@ def process_response( cache_key = _learn_cache_key( request, response, timeout, self._wagcache ) - # Track cache keys based on URI. # (of the chopped request, not the real one). cr = _chop_querystring(request) @@ -329,7 +320,6 @@ def callback(r): response.add_post_render_callback(callback) else: self._wagcache.set(cache_key, response, timeout) - # Add a response header to indicate this was a cache miss. _patch_header(response, Status.MISS) @@ -351,14 +341,12 @@ def clear_cache(urls: List[str] = []) -> None: _wagcache = caches[wagtailcache_settings.WAGTAIL_CACHE_BACKEND] if urls and "keyring" in _wagcache: keyring = _wagcache.get("keyring") - # Check the provided URL matches a key in our keyring. matched_urls = [] for regex in urls: for key in keyring: if re.match(regex, key): matched_urls.append(key) - # If it matches, delete each entry from the cache, # and delete the URL from the keyring. for url in matched_urls: @@ -366,10 +354,8 @@ def clear_cache(urls: List[str] = []) -> None: for cache_key in entries: _wagcache.delete(cache_key) del keyring[url] - # Save the keyring. _wagcache.set("keyring", keyring) - # Clears the entire cache backend used by wagtail-cache. else: _wagcache.clear() diff --git a/wagtailcache/management/commands/clear_wagtail_cache.py b/wagtailcache/management/commands/clear_wagtail_cache.py index 160a30c..bec0f85 100644 --- a/wagtailcache/management/commands/clear_wagtail_cache.py +++ b/wagtailcache/management/commands/clear_wagtail_cache.py @@ -1,6 +1,6 @@ """CLI tool to clear wagtailcache.""" - from django.core.management.base import BaseCommand + from wagtailcache.cache import clear_cache diff --git a/wagtailcache/settings.py b/wagtailcache/settings.py index 0f909ea..c599746 100644 --- a/wagtailcache/settings.py +++ b/wagtailcache/settings.py @@ -1,8 +1,8 @@ """ Default django settings for wagtail-cache. """ - from typing import Text + from django.conf import settings diff --git a/wagtailcache/templatetags/wagtailcache_tags.py b/wagtailcache/templatetags/wagtailcache_tags.py index d045f02..c1e75b9 100644 --- a/wagtailcache/templatetags/wagtailcache_tags.py +++ b/wagtailcache/templatetags/wagtailcache_tags.py @@ -1,7 +1,9 @@ from typing import Optional + from django import template from django.core.cache import caches from django.utils.translation import gettext_lazy as _ + from wagtailcache.settings import wagtailcache_settings diff --git a/wagtailcache/urls.py b/wagtailcache/urls.py index 890b74d..5e1fcae 100644 --- a/wagtailcache/urls.py +++ b/wagtailcache/urls.py @@ -1,9 +1,10 @@ """ URLs for the wagtail admin dashboard. """ - from django.urls import path -from wagtailcache.views import index, clear + +from wagtailcache.views import clear +from wagtailcache.views import index urlpatterns = [ diff --git a/wagtailcache/views.py b/wagtailcache/views.py index 2ce0786..2511864 100644 --- a/wagtailcache/views.py +++ b/wagtailcache/views.py @@ -1,7 +1,8 @@ """ Views for the wagtail admin dashboard. """ -from typing import Dict, List +from typing import Dict +from typing import List from django.core.cache import caches from django.http import HttpResponseRedirect diff --git a/wagtailcache/wagtail_hooks.py b/wagtailcache/wagtail_hooks.py index 1b5df7c..26b3bda 100644 --- a/wagtailcache/wagtail_hooks.py +++ b/wagtailcache/wagtail_hooks.py @@ -1,11 +1,12 @@ """ Registers wagtail-cache in the wagtail admin dashboard. """ - -from django.urls import include, path, reverse +from django.urls import include +from django.urls import path +from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from wagtail.admin.menu import MenuItem from wagtail import hooks +from wagtail.admin.menu import MenuItem from wagtailcache import urls From a4c27eaddfdfa28887e74dd69e49a4f888fe2c02 Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Wed, 14 Jun 2023 14:28:03 -0400 Subject: [PATCH 6/8] Test versions of wagtail in pipeline (#54) --- azure-pipelines.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ff79fed..857ac26 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,14 +30,18 @@ stages: vmImage: 'ubuntu-latest' strategy: matrix: - py3.8: + py3.8_wag3: PYTHON_VERSION: '3.8' - py3.9: + WAGTAIL: '3.*' + py3.9_wag4: PYTHON_VERSION: '3.9' - py3.10: + WAGTAIL: '4.*' + py3.10_wag5: PYTHON_VERSION: '3.10' - py3.11: + WAGTAIL: '5.*' + py3.11_wag5: PYTHON_VERSION: '3.11' + WAGTAIL: '5.*' steps: - task: UsePythonVersion@0 @@ -46,7 +50,7 @@ stages: versionSpec: '$(PYTHON_VERSION)' architecture: 'x64' - - script: python -m pip install -r requirements-dev.txt + - script: python -m pip install -r requirements-dev.txt wagtail==$(WAGTAIL) displayName: 'CR-QC: Install from local repo' - script: | From cb2b5f6d58c9caa3ecf7e19994601b26c3cefac1 Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Thu, 27 Jul 2023 16:53:58 -0400 Subject: [PATCH 7/8] Add release notes for 2.3 --- docs/releases.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/releases.rst b/docs/releases.rst index 1e2d3dd..fd1dd04 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -3,10 +3,18 @@ Release Notes ============= +2.3.0 +===== + +* Support Wagtail 5 and Django 4.2. + +* Remove ``django-redis`` compatibility backend. Django 4.0+ has a native redis backend. + + 2.2.0 ===== -* Support Wagtail 4 and Django 4.1 +* Support Wagtail 4 and Django 4.1. 2.1.1 From 9b3e7f920834cac3ef6920bd37bf368c0eb5364f Mon Sep 17 00:00:00 2001 From: Vince Salvino Date: Thu, 27 Jul 2023 18:47:09 -0400 Subject: [PATCH 8/8] Update sponsorship banner in docs --- docs/_templates/sponsor.html | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/_templates/sponsor.html b/docs/_templates/sponsor.html index 87454fd..e2efdff 100644 --- a/docs/_templates/sponsor.html +++ b/docs/_templates/sponsor.html @@ -1,13 +1,3 @@ - -

+ + Wagtail Hosting by CodeRed