Skip to content

Commit

Permalink
Merge pull request #359 from CTPUG/feature/cached_pages
Browse files Browse the repository at this point in the history
Hacky approach to caching the page rendering for dynamic page content
  • Loading branch information
drnlm committed Jul 31, 2017
2 parents 4243af6 + 4f58475 commit 66073b1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 4 deletions.
3 changes: 2 additions & 1 deletion wafer/pages/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
class PageAdmin(CompareVersionAdmin, admin.ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
list_display = ('name', 'slug', 'get_absolute_url',
'get_people_display_names', 'get_in_schedule')
'cache_time', 'get_people_display_names',
'get_in_schedule')

list_filter = (DateModifiedFilter,)

Expand Down
20 changes: 20 additions & 0 deletions wafer/pages/migrations/0005_page_cache_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2017-07-28 12:53
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('pages', '0004_allow_blank_files_description'),
]

operations = [
migrations.AddField(
model_name='page',
name='cache_time',
field=models.IntegerField(default=-1, help_text='Length of time (in seconds) to cache the page for dynamic page content. A negative value means this page is not dynamic and it will be not be regenerated until it is next edited.'),
),
]
37 changes: 36 additions & 1 deletion wafer/pages/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
from django.conf import settings
from django.db import models
from django.core.cache import caches
from django.db.models.signals import post_save
from django.utils.encoding import python_2_unicode_compatible


from markitup.fields import MarkupField
from markitup.fields import MarkupField, render_func
from wafer.menu import MenuError, refresh_menu_cache


Expand Down Expand Up @@ -51,9 +52,18 @@ class Page(models.Model):
help_text=_("People associated with this page for display in the"
" schedule (Session chairs, panelists, etc.)"))

cache_time = models.IntegerField(
default=-1,
help_text=_("Length of time (in seconds) to cache the page for "
"dynamic page content. A negative value means this page "
"is not dynamic and it will be not be regenerated "
"until it is next edited."))

def __str__(self):
return u'%s' % (self.name,)

cache_name = settings.WAFER_CACHE

def get_path(self):
path, parent = [self.slug], self.parent
while parent is not None:
Expand All @@ -68,6 +78,26 @@ def get_absolute_url(self):
url = "/".join(self.get_path())
return reverse('wafer_page', args=(url,))

def _cache_key(self):
return "wafer.pages:rendered:%s" % self.get_absolute_url()

def cached_render(self):
if self.cache_time < 0:
return self.content.rendered
cache = caches[self.cache_name]
cache_key = self._cache_key()
rendered = cache.get(cache_key)
if rendered is None:
rendered = render_func(self.content.raw)
# Should reset the database copy, but this is enough for
# now
cache.set(cache_key, rendered, self.cache_time)
return rendered

def invalidate_cache(self):
cache = caches[self.cache_name]
cache.delete(self._cache_key())

get_absolute_url.short_description = 'page url'

def get_in_schedule(self):
Expand Down Expand Up @@ -121,6 +151,11 @@ def validate_unique(self, exclude=None):
})
return super(Page, self).validate_unique(exclude)

def save(self, *args, **kwargs):
"""Ensure we invalidate the cache after saving"""
super(Page, self).save(*args, **kwargs)
self.invalidate_cache()


def page_menus(root_menu):
"""Add page menus."""
Expand Down
2 changes: 1 addition & 1 deletion wafer/pages/templates/wafer.pages/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block title %}{{ page.name }} - {{ WAFER_CONFERENCE_NAME }}{% endblock %}
{% block content %}
<div>
{{ page.content.rendered|safe }}
{{ page.cached_render|safe }}
{% if perms.pages.change_page %}
<div class="clearfix">
<a href="{{ page.get_absolute_url }}?compare"
Expand Down
26 changes: 25 additions & 1 deletion wafer/pages/tests/test_pages.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Simple test of the edit logic around pages

from django.test import Client, TestCase
from django.test import Client, TestCase, override_settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.cache import caches

from wafer.pages.models import Page

Expand Down Expand Up @@ -88,3 +89,26 @@ def test_root_page(self):
templates = [x.name for x in response.templates]
self.assertTrue('wafer.pages/page_form.html' in templates)
self.assertEqual(response.status_code, 200)

def test_cache_time(self):
"""Test the behaviour of cache_time"""
uncached_page = Page.objects.create(name="not cached", slug="no_cache",
content="*aa*")
cached_page = Page.objects.create(name="Cached Page", slug="cached",
content="*bb*",
cache_time=100)
# We use a memory cache here to actually check caching behaviour
with override_settings(CACHES={"default": {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
"wafer_cache": {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'test_cache'}}):
cache = caches['wafer_cache']
result = uncached_page.cached_render()
self.assertEqual(result, uncached_page.content.rendered)
self.assertEqual(cache.get(uncached_page._cache_key()), None)
result = cached_page.cached_render()
self.assertEqual(result, cached_page.content.rendered)
self.assertEqual(cache.get(cached_page._cache_key()), result)
# Check that updating the page content invalidates the cache
cached_page.content = '*cc*'
cached_page.save()
self.assertEqual(cache.get(cached_page._cache_key()), None)

0 comments on commit 66073b1

Please sign in to comment.