Skip to content

Commit

Permalink
Merge f0cca73 into 45a295f
Browse files Browse the repository at this point in the history
  • Loading branch information
renskiy committed Oct 17, 2017
2 parents 45a295f + f0cca73 commit 178a161
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 43 deletions.
9 changes: 7 additions & 2 deletions README.rst
Expand Up @@ -47,15 +47,20 @@ If you planning to use :code:`cache_page` among with :code:`last_modified` and/o
from django.views.decorators.http import last_modified, etag
def etag_generator(request):
def etag_generator(request, *args, **kwargs):
return 'ETag!!'
@cache_page(cache_timeout=600)
@etag(etag_generator)
def view(request):
def view(request, *args, **kwargs):
pass
Django Settings
---------------

``DJANGOCACHE_CACHE_MIN_AGE`` - used to set minimal age of cache. Default is 0, meaning that client can ask server to skip cache by providing header ``Cache-Control: max-age=0``.

Installation
------------

Expand Down
27 changes: 15 additions & 12 deletions djangocache.py
@@ -1,6 +1,7 @@
import contextlib
import time

from django.conf import settings
from django.core.cache.backends.dummy import DummyCache
from django.middleware import cache as cache_middleware
from django.utils import http, cache, decorators
Expand All @@ -10,7 +11,7 @@
dummy_cache = DummyCache('dummy_host', {})

# https://tools.ietf.org/html/rfc7232#section-4.1
rfc7232_headers = ['ETag', 'Vary', 'Cache-Control', 'Expires', 'Content-Location']
rfc7232_headers = ['ETag', 'Vary', 'Cache-Control', 'Expires', 'Content-Location', 'Date', 'Last-Modified']


def cache_page(**kwargs):
Expand Down Expand Up @@ -53,6 +54,7 @@ def get_cache_max_age(cache_control):

def get_conditional_response(request, response=None):
if not (response and hasattr(cache, 'get_conditional_response')):
# Django 1.8 does not have such method, can't do anything
return response
last_modified = response.get('Last-Modified')
conditional_response = cache.get_conditional_response(
Expand All @@ -69,8 +71,6 @@ def get_conditional_response(request, response=None):
}
for header, value in headers.items():
conditional_response[header] = value
if last_modified:
conditional_response['Last-Modified'] = last_modified
return conditional_response


Expand Down Expand Up @@ -130,14 +130,6 @@ def get_key_prefix(self, request, *args, **kwargs):
return self.key_prefix

def process_request(self, request):
if request.method not in ('GET', 'HEAD'):
return None

cache_max_age = get_cache_max_age(request.META.get('HTTP_CACHE_CONTROL'))
if cache_max_age == 0:
request._cache_update_cache = True
return None

request._cache_key_prefix = key_prefix = self.get_key_prefix(
request,
*request.resolver_match.args,
Expand All @@ -156,7 +148,18 @@ def process_request(self, request):
if max_age:
expires = http.parse_http_date(response['Expires'])
timeout = expires - int(time.time())
response['Age'] = max_age - timeout
response['Age'] = age = max_age - timeout

# check cache age limit provided by client
age_limit = get_cache_max_age(request.META.get('HTTP_CACHE_CONTROL'))
if age_limit is None and request.META.get('HTTP_PRAGMA') == 'no-cache':
age_limit = 0
if age_limit is not None:
min_age = getattr(settings, 'DJANGOCACHE_CACHE_MIN_AGE', 0)
age_limit = max(min_age, age_limit)
if age > age_limit:
request._cache_update_cache = True
return None

return response

Expand Down
28 changes: 0 additions & 28 deletions settings.py
@@ -1,30 +1,2 @@
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'secret_key'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

CACHE_MIDDLEWARE_SECONDS = 600

USE_ETAGS = True

INSTALLED_APPS = [
'djangocache',
]

MIDDLEWARE_CLASSES = []


# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/

TIME_ZONE = 'UTC'

USE_TZ = True
67 changes: 66 additions & 1 deletion tests.py
Expand Up @@ -12,7 +12,7 @@
from django.core.urlresolvers import reverse
from django.views.decorators.http import last_modified, etag

from djangocache import cache_page
from djangocache import cache_page, get_cache_max_age

mocked_response = mock.Mock(side_effect=lambda: http.HttpResponse())

Expand Down Expand Up @@ -355,6 +355,67 @@ def test_static(self):
self.assertIn('Cache-Control', response)
self.assertEqual('Mon, 18 Jul 2016 10:05:00 GMT', response['Expires'])
self.assertEqual('max-age=86400', response['Cache-Control'])
mocked_response.reset_mock()

# Sun, 17 Jul 2016 10:10:00 GMT
with mock.patch.object(time, 'time', return_value=1468750200):
response = client.get(
reverse('static'),
HTTP_PRAGMA='no-cache',
)
mocked_response.assert_called_once()
self.assertNotIn('ETag', response)
self.assertNotIn('Last-Modified', response)
self.assertIn('Expires', response)
self.assertIn('Cache-Control', response)
self.assertEqual('Mon, 18 Jul 2016 10:10:00 GMT', response['Expires'])
self.assertEqual('max-age=86400', response['Cache-Control'])
mocked_response.reset_mock()

# Sun, 17 Jul 2016 10:15:00 GMT
with mock.patch.object(time, 'time', return_value=1468750500):
response = client.get(
reverse('static'),
HTTP_CACHE_CONTROL='max-age=600',
)
mocked_response.assert_not_called()
self.assertNotIn('ETag', response)
self.assertNotIn('Last-Modified', response)
self.assertIn('Expires', response)
self.assertIn('Cache-Control', response)
self.assertIn('Age', response)
self.assertEqual('Mon, 18 Jul 2016 10:10:00 GMT', response['Expires'])
self.assertEqual('max-age=86400', response['Cache-Control'])
self.assertEqual('300', response['Age'])
mocked_response.reset_mock()

with test.utils.override_settings(DJANGOCACHE_CACHE_MIN_AGE=600):
response = client.get(
reverse('static'),
HTTP_CACHE_CONTROL='max-age=200',
)
mocked_response.assert_not_called()
self.assertNotIn('ETag', response)
self.assertNotIn('Last-Modified', response)
self.assertIn('Expires', response)
self.assertIn('Cache-Control', response)
self.assertIn('Age', response)
self.assertEqual('Mon, 18 Jul 2016 10:10:00 GMT', response['Expires'])
self.assertEqual('max-age=86400', response['Cache-Control'])
self.assertEqual('300', response['Age'])
mocked_response.reset_mock()

response = client.get(
reverse('static'),
HTTP_CACHE_CONTROL='max-age=200',
)
mocked_response.assert_called_once()
self.assertNotIn('ETag', response)
self.assertNotIn('Last-Modified', response)
self.assertIn('Expires', response)
self.assertIn('Cache-Control', response)
self.assertEqual('Mon, 18 Jul 2016 10:15:00 GMT', response['Expires'])
self.assertEqual('max-age=86400', response['Cache-Control'])

def test_with_last_modified(self):
client = test.Client()
Expand Down Expand Up @@ -545,3 +606,7 @@ def test_no_cache(self):
self.assertNotIn('Last-Modified', response)
self.assertNotIn('Expires', response)
self.assertNotIn('Cache-Control', response)

def test_get_cache_max_age_returns_none_on_wrong_or_empty_result(self):
self.assertIsNone(get_cache_max_age('max-age=a'))
self.assertIsNone(get_cache_max_age('max-age='))

0 comments on commit 178a161

Please sign in to comment.