Make sure get_site_root_paths returns a list of SiteRootPath objects (#8819)

Fixes #8062
pull/10060/head
Jaap Roes 2022-07-07 16:20:56 +02:00 zatwierdzone przez Matt Westcott
rodzic 11b9448dc0
commit f9872fae03
7 zmienionych plików z 95 dodań i 20 usunięć

Wyświetl plik

@ -9,6 +9,7 @@ Changelog
* Migrate `.button-longrunning` behaviour to a Stimulus controller with support for custom label element & duration (Loveth Omokaro) * Migrate `.button-longrunning` behaviour to a Stimulus controller with support for custom label element & duration (Loveth Omokaro)
* Fix: Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal) * Fix: Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal)
* Fix: Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro) * Fix: Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro)
* Fix: Ensure `Site.get_site_root_paths` works on cache backends that do not preserve Python objects (Jaap Roes)
* Docs: Add code block to make it easier to understand contribution docs (Suyash Singh) * Docs: Add code block to make it easier to understand contribution docs (Suyash Singh)
* Docs: Add new "Icons" page for icons customisation and reuse across the admin interface (Coen van der Kamp) * Docs: Add new "Icons" page for icons customisation and reuse across the admin interface (Coen van der Kamp)
* Maintenance: Removed features deprecated in Wagtail 3.0 and 4.0 (Matt Westcott) * Maintenance: Removed features deprecated in Wagtail 3.0 and 4.0 (Matt Westcott)

Wyświetl plik

@ -23,6 +23,7 @@ depth: 1
* Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal) * Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal)
* Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro) * Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro)
* Ensure `Site.get_site_root_paths` works on cache backends that do not preserve Python objects (Jaap Roes)
### Documentation ### Documentation

Wyświetl plik

@ -22,7 +22,6 @@ from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import checks from django.core import checks
from django.core.cache import cache
from django.core.exceptions import ( from django.core.exceptions import (
ImproperlyConfigured, ImproperlyConfigured,
PermissionDenied, PermissionDenied,
@ -1414,7 +1413,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
# Note: New translations of existing site roots are considered site roots as well, so we must # Note: New translations of existing site roots are considered site roots as well, so we must
# always check if this page is a site root, even if it's new. # always check if this page is a site root, even if it's new.
if self.is_site_root(): if self.is_site_root():
cache.delete("wagtail_site_root_paths") Site.clear_site_root_paths_cache()
# Log # Log
if is_new: if is_new:

Wyświetl plik

@ -71,6 +71,10 @@ class SiteManager(models.Manager):
SiteRootPath = namedtuple("SiteRootPath", "site_id root_path root_url language_code") SiteRootPath = namedtuple("SiteRootPath", "site_id root_path root_url language_code")
SITE_ROOT_PATHS_CACHE_KEY = "wagtail_site_root_paths"
# Increase the cache version whenever the structure SiteRootPath tuple changes
SITE_ROOT_PATHS_CACHE_VERSION = 2
class Site(models.Model): class Site(models.Model):
hostname = models.CharField( hostname = models.CharField(
@ -209,13 +213,11 @@ class Site(models.Model):
- `root_url` - The scheme/domain name of the site (for example 'https://www.example.com/') - `root_url` - The scheme/domain name of the site (for example 'https://www.example.com/')
- `language_code` - The language code of the site (for example 'en') - `language_code` - The language code of the site (for example 'en')
""" """
result = cache.get("wagtail_site_root_paths") result = cache.get(
SITE_ROOT_PATHS_CACHE_KEY, version=SITE_ROOT_PATHS_CACHE_VERSION
)
# Wagtail 2.11 changed the way site root paths were stored. This can cause an upgraded 2.11 if result is None:
# site to break when loading cached site root paths that were cached with 2.10.2 or older
# versions of Wagtail. The line below checks if the any of the cached site urls is consistent
# with an older version of Wagtail and invalidates the cache.
if result is None or any(len(site_record) == 3 for site_record in result):
result = [] result = []
for site in Site.objects.select_related( for site in Site.objects.select_related(
@ -245,6 +247,20 @@ class Site(models.Model):
) )
) )
cache.set("wagtail_site_root_paths", result, 3600) cache.set(
SITE_ROOT_PATHS_CACHE_KEY,
result,
3600,
version=SITE_ROOT_PATHS_CACHE_VERSION,
)
else:
# Convert the cache result to a list of SiteRootPath tuples, as some
# cache backends (e.g. Redis) don't support named tuples.
result = [SiteRootPath(*result) for result in result]
return result return result
@staticmethod
def clear_site_root_paths_cache():
cache.delete(SITE_ROOT_PATHS_CACHE_KEY, version=SITE_ROOT_PATHS_CACHE_VERSION)

Wyświetl plik

@ -2,7 +2,6 @@ import logging
from contextlib import contextmanager from contextlib import contextmanager
from asgiref.local import Local from asgiref.local import Local
from django.core.cache import cache
from django.db import transaction from django.db import transaction
from django.db.models.signals import ( from django.db.models.signals import (
post_delete, post_delete,
@ -21,11 +20,11 @@ logger = logging.getLogger("wagtail")
# Clear the wagtail_site_root_paths from the cache whenever Site records are updated. # Clear the wagtail_site_root_paths from the cache whenever Site records are updated.
def post_save_site_signal_handler(instance, update_fields=None, **kwargs): def post_save_site_signal_handler(instance, update_fields=None, **kwargs):
cache.delete("wagtail_site_root_paths") Site.clear_site_root_paths_cache()
def post_delete_site_signal_handler(instance, **kwargs): def post_delete_site_signal_handler(instance, **kwargs):
cache.delete("wagtail_site_root_paths") Site.clear_site_root_paths_cache()
def pre_delete_page_unpublish(sender, instance, **kwargs): def pre_delete_page_unpublish(sender, instance, **kwargs):

Wyświetl plik

@ -738,9 +738,7 @@ class TestServeView(TestCase):
# Explicitly clear the cache of site root paths. Normally this would be kept # Explicitly clear the cache of site root paths. Normally this would be kept
# in sync by the Site.save logic, but this is bypassed when the database is # in sync by the Site.save logic, but this is bypassed when the database is
# rolled back between tests using transactions. # rolled back between tests using transactions.
from django.core.cache import cache Site.clear_site_root_paths_cache()
cache.delete("wagtail_site_root_paths")
# also need to clear urlresolver caches before/after tests, because we override # also need to clear urlresolver caches before/after tests, because we override
# ROOT_URLCONF in some tests here # ROOT_URLCONF in some tests here

Wyświetl plik

@ -1,3 +1,5 @@
import json
from django import template from django import template
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpRequest from django.http import HttpRequest
@ -8,6 +10,10 @@ from django.utils.safestring import SafeString
from wagtail.coreutils import get_dummy_request, resolve_model_string from wagtail.coreutils import get_dummy_request, resolve_model_string
from wagtail.models import Locale, Page, Site, SiteRootPath from wagtail.models import Locale, Page, Site, SiteRootPath
from wagtail.models.sites import (
SITE_ROOT_PATHS_CACHE_KEY,
SITE_ROOT_PATHS_CACHE_VERSION,
)
from wagtail.templatetags.wagtailcore_tags import richtext, slugurl from wagtail.templatetags.wagtailcore_tags import richtext, slugurl
from wagtail.test.testapp.models import SimplePage from wagtail.test.testapp.models import SimplePage
@ -268,6 +274,11 @@ class TestWagtailSiteTag(TestCase):
class TestSiteRootPathsCache(TestCase): class TestSiteRootPathsCache(TestCase):
fixtures = ["test.json"] fixtures = ["test.json"]
def get_cached_site_root_paths(self):
return cache.get(
SITE_ROOT_PATHS_CACHE_KEY, version=SITE_ROOT_PATHS_CACHE_VERSION
)
def test_cache(self): def test_cache(self):
""" """
This tests that the cache is populated when building URLs This tests that the cache is populated when building URLs
@ -280,7 +291,7 @@ class TestSiteRootPathsCache(TestCase):
# Check that the cache has been set correctly # Check that the cache has been set correctly
self.assertEqual( self.assertEqual(
cache.get("wagtail_site_root_paths"), self.get_cached_site_root_paths(),
[ [
SiteRootPath( SiteRootPath(
site_id=1, site_id=1,
@ -291,6 +302,36 @@ class TestSiteRootPathsCache(TestCase):
], ],
) )
def test_cache_backend_uses_json_serialization(self):
"""
This tests that, even if the cache backend uses JSON serialization,
get_site_root_paths() returns a list of SiteRootPath objects.
"""
result = Site.get_site_root_paths()
self.assertEqual(
result,
[
SiteRootPath(
site_id=1,
root_path="/home/",
root_url="http://localhost",
language_code="en",
)
],
)
# Go through JSON (de)serialisation to check that the result is
# still a list of named tuples.
cache.set(
SITE_ROOT_PATHS_CACHE_KEY,
json.loads(json.dumps(result)),
version=SITE_ROOT_PATHS_CACHE_VERSION,
)
result = Site.get_site_root_paths()
self.assertIsInstance(result[0], SiteRootPath)
def test_cache_clears_when_site_saved(self): def test_cache_clears_when_site_saved(self):
""" """
This tests that the cache is cleared whenever a site is saved This tests that the cache is cleared whenever a site is saved
@ -302,13 +343,23 @@ class TestSiteRootPathsCache(TestCase):
_ = homepage.url # noqa _ = homepage.url # noqa
# Check that the cache has been set # Check that the cache has been set
self.assertTrue(cache.get("wagtail_site_root_paths")) self.assertEqual(
self.get_cached_site_root_paths(),
[
SiteRootPath(
site_id=1,
root_path="/home/",
root_url="http://localhost",
language_code="en",
)
],
)
# Save the site # Save the site
Site.objects.get(is_default_site=True).save() Site.objects.get(is_default_site=True).save()
# Check that the cache has been cleared # Check that the cache has been cleared
self.assertFalse(cache.get("wagtail_site_root_paths")) self.assertIsNone(self.get_cached_site_root_paths())
def test_cache_clears_when_site_deleted(self): def test_cache_clears_when_site_deleted(self):
""" """
@ -321,13 +372,23 @@ class TestSiteRootPathsCache(TestCase):
_ = homepage.url # noqa _ = homepage.url # noqa
# Check that the cache has been set # Check that the cache has been set
self.assertTrue(cache.get("wagtail_site_root_paths")) self.assertEqual(
self.get_cached_site_root_paths(),
[
SiteRootPath(
site_id=1,
root_path="/home/",
root_url="http://localhost",
language_code="en",
)
],
)
# Delete the site # Delete the site
Site.objects.get(is_default_site=True).delete() Site.objects.get(is_default_site=True).delete()
# Check that the cache has been cleared # Check that the cache has been cleared
self.assertFalse(cache.get("wagtail_site_root_paths")) self.assertIsNone(self.get_cached_site_root_paths())
def test_cache_clears_when_site_root_moves(self): def test_cache_clears_when_site_root_moves(self):
""" """