Files
old-saburly-wagtail-web/env/lib/python3.10/site-packages/wagtail/contrib/frontend_cache/utils.py

203 lines
6.5 KiB
Python
Raw Normal View History

2024-08-27 20:33:44 +02:00
import logging
import re
from collections import defaultdict
from urllib.parse import urlsplit, urlunsplit
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_string
from wagtail.coreutils import get_content_languages
logger = logging.getLogger("wagtail.frontendcache")
class InvalidFrontendCacheBackendError(ImproperlyConfigured):
pass
def get_backends(backend_settings=None, backends=None):
# Get backend settings from WAGTAILFRONTENDCACHE setting
if backend_settings is None:
backend_settings = getattr(settings, "WAGTAILFRONTENDCACHE", None)
# Fallback to using WAGTAILFRONTENDCACHE_LOCATION setting (backwards compatibility)
if backend_settings is None:
cache_location = getattr(settings, "WAGTAILFRONTENDCACHE_LOCATION", None)
if cache_location is not None:
backend_settings = {
"default": {
"BACKEND": "wagtail.contrib.frontend_cache.backends.HTTPBackend",
"LOCATION": cache_location,
},
}
# No settings found, return empty list
if backend_settings is None:
return {}
backend_objects = {}
for backend_name, _backend_config in backend_settings.items():
if backends is not None and backend_name not in backends:
continue
backend_config = _backend_config.copy()
backend = backend_config.pop("BACKEND")
# Try to import the backend
try:
backend_cls = import_string(backend)
except ImportError as e:
raise InvalidFrontendCacheBackendError(
f"Could not find backend '{backend}': {e}"
)
backend_objects[backend_name] = backend_cls(backend_config)
return backend_objects
def purge_url_from_cache(url, backend_settings=None, backends=None):
purge_urls_from_cache([url], backend_settings=backend_settings, backends=backends)
def purge_urls_from_cache(urls, backend_settings=None, backends=None):
# Convert each url to urls one for each managed language (WAGTAILFRONTENDCACHE_LANGUAGES setting).
# The managed languages are common to all the defined backends.
# This depends on settings.USE_I18N
# If WAGTAIL_I18N_ENABLED is True, this defaults to WAGTAIL_CONTENT_LANGUAGES
wagtail_i18n_enabled = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
content_languages = get_content_languages() if wagtail_i18n_enabled else {}
languages = getattr(
settings, "WAGTAILFRONTENDCACHE_LANGUAGES", list(content_languages.keys())
)
if settings.USE_I18N and languages:
langs_regex = "^/(%s)/" % "|".join(languages)
new_urls = []
# Purge the given url for each managed language
for isocode in languages:
for url in urls:
up = urlsplit(url)
new_url = urlunsplit(
(
up.scheme,
up.netloc,
re.sub(langs_regex, "/%s/" % isocode, up.path),
up.query,
up.fragment,
)
)
# Check for best performance. True if re.sub found no match
# It happens when i18n_patterns was not used in urls.py to serve content for different languages from different URLs
if new_url in new_urls:
continue
new_urls.append(new_url)
urls = new_urls
urls_by_hostname = defaultdict(list)
for url in urls:
urls_by_hostname[urlsplit(url).netloc].append(url)
backends = get_backends(backend_settings, backends)
for hostname, urls in urls_by_hostname.items():
backends_for_hostname = {
backend_name: backend
for backend_name, backend in backends.items()
if backend.invalidates_hostname(hostname)
}
if not backends_for_hostname:
logger.info("Unable to find purge backend for %s", hostname)
continue
for backend_name, backend in backends_for_hostname.items():
for url in urls:
logger.info("[%s] Purging URL: %s", backend_name, url)
backend.purge_batch(urls)
def _get_page_cached_urls(page):
page_url = page.full_url
if page_url is None: # nothing to be done if the page has no routable URL
return []
return [page_url + path.lstrip("/") for path in page.specific.get_cached_paths()]
def purge_page_from_cache(page, backend_settings=None, backends=None):
purge_pages_from_cache([page], backend_settings=backend_settings, backends=backends)
def purge_pages_from_cache(pages, backend_settings=None, backends=None):
urls = []
for page in pages:
urls.extend(_get_page_cached_urls(page))
if urls:
purge_urls_from_cache(urls, backend_settings, backends)
class PurgeBatch:
"""Represents a list of URLs to be purged in a single request"""
def __init__(self, urls=None):
self.urls = []
if urls is not None:
self.add_urls(urls)
def add_url(self, url):
"""Adds a single URL"""
self.urls.append(url)
def add_urls(self, urls):
"""
Adds multiple URLs from an iterable
This is equivalent to running ``.add_url(url)`` on each URL
individually
"""
self.urls.extend(urls)
def add_page(self, page):
"""
Adds all URLs for the specified page
This combines the page's full URL with each path that is returned by
the page's `.get_cached_paths` method
"""
self.add_urls(_get_page_cached_urls(page))
def add_pages(self, pages):
"""
Adds multiple pages from a QuerySet or an iterable
This is equivalent to running ``.add_page(page)`` on each page
individually
"""
for page in pages:
self.add_page(page)
def purge(self, backend_settings=None, backends=None):
"""
Performs the purge of all the URLs in this batch
This method takes two optional keyword arguments: backend_settings and backends
- backend_settings can be used to override the WAGTAILFRONTENDCACHE setting for
just this call
- backends can be set to a list of backend names. When set, the invalidation request
will only be sent to these backends
"""
purge_urls_from_cache(self.urls, backend_settings, backends)