Skip to content

Commit

Permalink
feat: add cache ttl extension point
Browse files Browse the repository at this point in the history
Adds the setting `CMS_CACHE_LIMIT_TTL_CLASS` that should have a
`limit_page_cache_ttl` method that would be called to limit the cache
ttl of a page using business logic.
Closes #7296
  • Loading branch information
igobranco committed Jun 22, 2022
1 parent 7941d33 commit bda41f8
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 12 deletions.
32 changes: 20 additions & 12 deletions cms/cache/page.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import hashlib
from datetime import timedelta
from importlib import import_module

from django.conf import settings
from django.utils.cache import (
Expand Down Expand Up @@ -45,30 +46,37 @@ def set_page_cache(response):

placeholders = toolbar.content_renderer.get_rendered_placeholders()
# Checks if there's a plugin using the legacy "cache = False"
placeholder_ttl_list = []
ttl_list = []
vary_cache_on_set = set()
for ph in placeholders:
# get_cache_expiration() always returns:
# EXPIRE_NOW <= int <= MAX_EXPIRATION_IN_SECONDS
ttl = ph.get_cache_expiration(request, timestamp)
vary_cache_on = ph.get_vary_cache_on(request)

placeholder_ttl_list.append(ttl)
ttl_list.append(ttl)
if ttl and vary_cache_on:
# We're only interested in vary headers if they come from
# a cache-able placeholder.
vary_cache_on_set |= set(vary_cache_on)

if EXPIRE_NOW not in placeholder_ttl_list:
if placeholder_ttl_list:
min_placeholder_ttl = min(x for x in placeholder_ttl_list)
else:
# Should only happen when there are no placeholders at all
min_placeholder_ttl = MAX_EXPIRATION_TTL
ttl = min(
get_cms_setting('CACHE_DURATIONS')['content'],
min_placeholder_ttl
)
if EXPIRE_NOW not in ttl_list:
ttl_list.append(get_cms_setting('CACHE_DURATIONS')['content'])
ttl_list.append(MAX_EXPIRATION_TTL)

if hasattr(settings, 'CMS_LIMIT_TTL_CACHE_FUNCTION'):
extension_point = settings.CMS_LIMIT_TTL_CACHE_FUNCTION

module, func_name = extension_point.rsplit('.', 1)
module = import_module(module)
limit_ttl_cache_function = getattr(module, func_name)
limit_ttl = limit_ttl_cache_function(response)

# if the extension point returns an integer as ttl
if isinstance(limit_ttl, int):
ttl_list.append(limit_ttl)

ttl = min(ttl_list)

if ttl > 0:
# Adds expiration, etc. to headers
Expand Down
41 changes: 41 additions & 0 deletions cms/tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,3 +860,44 @@ def test_set_get_placeholder_cache_with_long_prefix(self):
# Prove it still works as expected
cached_en_crazy_content = get_placeholder_cache(self.placeholder, 'en', 1, en_crazy_request)
self.assertEqual(en_crazy_content, cached_en_crazy_content)

def test_cache_limit_ttl(self):
""""
Test the `CMS_LIMIT_TTL_CACHE_FUNCTION` setting that allows to change the default 40 seconds
default ttl cache value with a business logic function.
"""
page1 = create_page('test page 1', 'nav_playground.html', 'en',
published=True)
page1_url = page1.get_absolute_url()

limit_page_cache_ttl_function = ".".join([PlaceholderCacheTestCase.__module__, limit_page_cache_ttl_test_5.__name__])
with self.settings(CMS_LIMIT_TTL_CACHE_FUNCTION=limit_page_cache_ttl_function):
page1.publish('en')
request = self.get_request(page1_url)
request.current_page = Page.objects.get(pk=page1.pk)
response = self.client.get(page1_url)
self.assertTrue('max-age=5' in response['Cache-Control'], response['Cache-Control']) # noqa

def test_cache_limit_ttl_greater_than_default_cache_ttl(self):
"""
Test the `CMS_LIMIT_TTL_CACHE_FUNCTION` setting with a class that returns a value much
greater thant the default value of 40 seconds.
"""
page1 = create_page('test page 1', 'nav_playground.html', 'en',
published=True)
page1_url = page1.get_absolute_url()

limit_page_cache_ttl_function = ".".join([PlaceholderCacheTestCase.__module__, limit_page_cache_ttl_test_500.__name__])
with self.settings(CMS_LIMIT_TTL_CACHE_FUNCTION=limit_page_cache_ttl_function):
page1.publish('en')
request = self.get_request(page1_url)
request.current_page = Page.objects.get(pk=page1.pk)
response = self.client.get(page1_url)
self.assertTrue('max-age=40' in response['Cache-Control'], response['Cache-Control']) # noqa


def limit_page_cache_ttl_test_5(response):
return 5

def limit_page_cache_ttl_test_500(response):
return 40
1 change: 1 addition & 0 deletions docs/how_to/caching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Have a look at the following settings to enable/disable various caching behaviou
- :setting:`CMS_PAGE_CACHE`
- :setting:`CMS_PLACEHOLDER_CACHE`
- :setting:`CMS_PLUGIN_CACHE`
- :setting:`CMS_LIMIT_TTL_CACHE_FUNCTION`



Expand Down
12 changes: 12 additions & 0 deletions docs/reference/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,18 @@ Default value of the ``cache`` attribute of plugins. Should plugins be cached by
If you disable the plugin cache be sure to restart the server and clear the cache afterwards.


.. setting:: CMS_LIMIT_TTL_CACHE_FUNCTION

CMS_LIMIT_TTL_CACHE_FUNCTION
============================

default
``None``

If defined, specifies the function to be called that allows to limit the page cache ttl value
using a business logic. The function receives one argument, the `response`, and returns an `int`
the max business value of the page cache ttl.

.. setting:: CMS_MAX_PAGE_PUBLISH_REVERSIONS


Expand Down

0 comments on commit bda41f8

Please sign in to comment.