From 64c5163e7aead7eb60951e040998ade343c079c5 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 15 Sep 2014 11:29:40 +0200 Subject: [PATCH] v. 0.2. * Nicer cache choosing: use settings.SSIFY_CACHE_ALIASES if set, otherwise use 'ssify' cache if configure, otherwise fall back to 'default'. * Renamed `csrf_token` template tag to `ssi_csrf_token` to avoid simple mistakes. * Cache control: `ssi_variable` now takes `patch_response` instead of `vary` parameter, `ssi_include` takes `timeout`, `version` and also `patch_reponse` parameters. Also added some helper functions in ssify.utils. * Added `flush_ssi_includes` function. * Debug rendering: renamed SSIFY_DEBUG to SSIFY_RENDER, added support for including streaming responses when SSIFY_RENDER=True. * Dropped Django 1.4 support. --- CHANGELOG.md | 27 +++++++ README.md | 4 +- runtests.py | 1 - setup.py | 8 +-- ssify/__init__.py | 15 +--- ssify/cache.py | 83 ++++++++++------------ ssify/cache_backends.py | 51 +++++++++++++ ssify/conf.py | 22 ++++++ ssify/decorators.py | 14 ++-- ssify/middleware.py | 69 ++++++++++++++---- ssify/middleware_debug.py | 59 +++++++++------ ssify/store.py | 15 ---- ssify/templatetags/ssify.py | 25 +++---- ssify/utils.py | 13 ++++ ssify/variables.py | 8 +-- tests/templates/tests_args/args.html | 7 +- tests/templates/tests_csrf/csrf_token.html | 4 +- tests/tests/test_args.py | 7 +- tests/tests/test_basic.py | 4 +- tox.ini | 13 +--- 20 files changed, 283 insertions(+), 166 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 ssify/cache_backends.py create mode 100644 ssify/conf.py delete mode 100644 ssify/store.py create mode 100644 ssify/utils.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f45d006 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +## 0.2 (2014-09-15) + +* Nicer cache choosing: use settings.SSIFY_CACHE_ALIASES if set, + otherwise use 'ssify' cache if configure, otherwise + fall back to 'default'. + +* Renamed `csrf_token` template tag to `ssi_csrf_token` to avoid + simple mistakes. + +* Cache control: `ssi_variable` now takes `patch_response` instead + of `vary` parameter, `ssi_include` takes `timeout`, `version` and + also `patch_reponse` parameters. Also added some helper functions + in ssify.utils. + +* Added `flush_ssi_includes` function. + +* Debug rendering: renamed SSIFY_DEBUG to SSIFY_RENDER, added support + for including streaming responses when SSIFY_RENDER=True. + +* Dropped Django 1.4 support. + + +## 0.1 (2014-09-02) + +* Initial release. diff --git a/README.md b/README.md index 0838661..042d91d 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Dependencies ============ * Python >= 2.6 - * Django >= 1.4 + * Django >= 1.5 Installation @@ -39,8 +39,6 @@ Installation * ssify.middleware.LocaleMiddleware instead of stock LocaleMiddleware. 3. Make sure you have 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS. -3. Define your caches in CACHES and SSIFY_INCLUDES_CACHES - for storing ssi includes. 4. Configure your webserver to use SSI ('ssi=on' with Nginx). Usage diff --git a/runtests.py b/runtests.py index 3d066dc..4fd6a9d 100644 --- a/runtests.py +++ b/runtests.py @@ -53,7 +53,6 @@ if not settings.configured and not os.environ.get('DJANGO_SETTINGS_MODULE'): STATIC_URL='/static/', ROOT_URLCONF='tests.urls', SITE_ID=1, - SSIFY_DEBUG_VERBOSE=False, TEMPLATE_CONTEXT_PROCESSORS=( "django.core.context_processors.debug", "django.core.context_processors.i18n", diff --git a/setup.py b/setup.py index 6237343..b3a40da 100755 --- a/setup.py +++ b/setup.py @@ -7,20 +7,20 @@ from setuptools import setup, find_packages setup( name='django-ssify', - version='0.1', + version='0.2', author='Radek Czajka', author_email='radekczajka@nowoczesnapolska.org.pl', - url='http://git.nowoczesnapolska.org.pl/?p=django-ssify.git', + url='http://git.mdrn.pl/django-ssify.git', packages=find_packages(exclude=['tests*']), license='LICENSE', description='Two-phased rendering using SSI.', long_description=open('README.md').read(), install_requires=[ - 'Django>=1.4', + 'Django>=1.5', ], test_suite="runtests.runtests", classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", diff --git a/ssify/__init__.py b/ssify/__init__.py index 51322bc..2c1681d 100644 --- a/ssify/__init__.py +++ b/ssify/__init__.py @@ -16,19 +16,8 @@ from __future__ import unicode_literals __version__ = '1.0' __date__ = '2014-08-26' -__all__ = ('ssi_expect', 'SsiVariable', 'ssi_included', 'ssi_variable') - -from django.conf import settings -from django.utils.functional import lazy - -SETTING = lazy( - lambda name, default: getattr(settings, name, default), - bool, int, list, tuple, str) - -INCLUDES_CACHES = SETTING('SSIFY_INCLUDES_CACHES', ('ssify',)) -DEBUG = SETTING('SSIFY_DEBUG', False) -DEBUG_VERBOSE = SETTING('SSIFY_DEBUG_VERBOSE', True) - +__all__ = ('flush_ssi_includes', 'ssi_expect', 'SsiVariable', 'ssi_included', 'ssi_variable') from .variables import ssi_expect, SsiVariable from .decorators import ssi_included, ssi_variable +from .cache import flush_ssi_includes diff --git a/ssify/cache.py b/ssify/cache.py index 2fa846c..699f45f 100644 --- a/ssify/cache.py +++ b/ssify/cache.py @@ -3,49 +3,42 @@ # Copyright © Fundacja Nowoczesna Polska. See README.md for more information. # from __future__ import unicode_literals -import os -from django.core.cache.backends.filebased import FileBasedCache - - -class StaticFileBasedCache(FileBasedCache): - def __init__(self, *args, **kwargs): - super(StaticFileBasedCache, self).__init__(*args, **kwargs) - self._dir = os.path.abspath(self._dir) - - def make_key(self, key, version=None): - assert version is None, \ - 'StaticFileBasedCache does not support versioning.' - return key - - def _key_to_file(self, key): - key = os.path.abspath(os.path.join(self._dir, key.lstrip('/'))) - assert key.startswith(self._dir), 'Trying to save path outside root.' - if key.endswith('/'): - key += 'index.html' - return key - - def get(self, key, default=None, version=None): - key = self.make_key(key, version=version) - self.validate_key(key) - fname = self._key_to_file(key) - try: - with open(fname, 'rb') as inf: - return inf.read() - except (IOError, OSError): - pass - return default - - def set(self, key, value, timeout=None, version=None): - assert timeout is None, \ - 'StaticFileBasedCache does not support timeouts.' - key = self.make_key(key, version=version) - self.validate_key(key) - fname = self._key_to_file(key) - dirname = os.path.dirname(fname) +from django.core.cache import InvalidCacheBackendError +from .conf import conf + + +DEFAULT_TIMEOUT = object() + + +try: + from django.core.cache import caches +except ImportError: + from django.core.cache import get_cache +else: + get_cache = lambda alias: caches[alias] + + +def get_caches(): + try: + return [get_cache(c) for c in conf.CACHE_ALIASES] + except: try: - if not os.path.exists(dirname): - os.makedirs(dirname) - with open(fname, 'wb') as outf: - outf.write(value) - except (IOError, OSError): - pass + return [get_cache('ssify')] + except InvalidCacheBackendError: + return [get_cache('default')] + + +def cache_include(path, content, timeout=DEFAULT_TIMEOUT, version=None): + for cache in get_caches(): + if timeout is DEFAULT_TIMEOUT: + cache.set(path, content, version=version) + else: + cache.set(path, content, timeout=timeout, version=version) + + +def flush_ssi_includes(paths=None): + for cache in get_caches(): + if paths is None: + cache.clear() + else: + cache.delete_many(paths) diff --git a/ssify/cache_backends.py b/ssify/cache_backends.py new file mode 100644 index 0000000..2fa846c --- /dev/null +++ b/ssify/cache_backends.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# This file is part of django-ssify, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See README.md for more information. +# +from __future__ import unicode_literals +import os +from django.core.cache.backends.filebased import FileBasedCache + + +class StaticFileBasedCache(FileBasedCache): + def __init__(self, *args, **kwargs): + super(StaticFileBasedCache, self).__init__(*args, **kwargs) + self._dir = os.path.abspath(self._dir) + + def make_key(self, key, version=None): + assert version is None, \ + 'StaticFileBasedCache does not support versioning.' + return key + + def _key_to_file(self, key): + key = os.path.abspath(os.path.join(self._dir, key.lstrip('/'))) + assert key.startswith(self._dir), 'Trying to save path outside root.' + if key.endswith('/'): + key += 'index.html' + return key + + def get(self, key, default=None, version=None): + key = self.make_key(key, version=version) + self.validate_key(key) + fname = self._key_to_file(key) + try: + with open(fname, 'rb') as inf: + return inf.read() + except (IOError, OSError): + pass + return default + + def set(self, key, value, timeout=None, version=None): + assert timeout is None, \ + 'StaticFileBasedCache does not support timeouts.' + key = self.make_key(key, version=version) + self.validate_key(key) + fname = self._key_to_file(key) + dirname = os.path.dirname(fname) + try: + if not os.path.exists(dirname): + os.makedirs(dirname) + with open(fname, 'wb') as outf: + outf.write(value) + except (IOError, OSError): + pass diff --git a/ssify/conf.py b/ssify/conf.py new file mode 100644 index 0000000..c06eaa8 --- /dev/null +++ b/ssify/conf.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# This file is part of django-ssify, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See README.md for more information. +# +from django.conf import settings + + +class AppSettings(object): + prefix = 'SSIFY_' + + @classmethod + def add(cls, name, default): + setattr(cls, name, property(lambda self: + getattr(settings, self.prefix + name, default))) + + +AppSettings.add('CACHE_ALIASES', None) +AppSettings.add('RENDER', False) +AppSettings.add('RENDER_VERBOSE', False) + + +conf = AppSettings() diff --git a/ssify/decorators.py b/ssify/decorators.py index 80dc2f5..9caddd0 100644 --- a/ssify/decorators.py +++ b/ssify/decorators.py @@ -11,12 +11,14 @@ from inspect import getargspec import warnings from django.template.base import parse_bits from django.utils.translation import get_language, activate -from .store import cache_include +from .cache import cache_include, DEFAULT_TIMEOUT from . import exceptions from .variables import SsiVariable -def ssi_included(view=None, use_lang=True, get_ssi_vars=None): +def ssi_included(view=None, use_lang=True, + timeout=DEFAULT_TIMEOUT, version=None, + get_ssi_vars=None, patch_response=None): """ Marks a view to be used as a snippet to be included with SSI. @@ -72,7 +74,8 @@ def ssi_included(view=None, use_lang=True, get_ssi_vars=None): # Don't use default django response caching for this view, # just save the contents instead. - cache_include(request.path, response.content) + cache_include(request.path, response.content, + timeout=timeout, version=version) if hasattr(response, 'render') and callable(response.render): response.add_post_render_callback(_check_included_vars) @@ -84,11 +87,12 @@ def ssi_included(view=None, use_lang=True, get_ssi_vars=None): # Remember get_ssi_vars so that in can be computed from args/kwargs # by including view. new_view.get_ssi_vars = get_ssi_vars + new_view.ssi_patch_response = patch_response return new_view return dec(view) if view else dec -def ssi_variable(register, vary=None, name=None): +def ssi_variable(register, name=None, patch_response=None): """ Creates a template tag representing an SSI variable from a function. @@ -142,7 +146,7 @@ def ssi_variable(register, vary=None, name=None): ['context'] + params[1:], varargs, varkw, defaults, takes_context=True, name=function_name) - return SsiVariableNode(tagpath, args, kwargs, vary, asvar) + return SsiVariableNode(tagpath, args, kwargs, patch_response, asvar) _ssi_var_tag.get_value = func #return _ssi_var_tag return func diff --git a/ssify/middleware.py b/ssify/middleware.py index a1f8444..6b8b2ed 100644 --- a/ssify/middleware.py +++ b/ssify/middleware.py @@ -37,11 +37,15 @@ So, you should end up with something like this: """ from __future__ import unicode_literals from django.conf import settings -from django.utils.cache import patch_vary_headers from django.middleware import locale +from django.utils.cache import patch_vary_headers +from .conf import conf from .serializers import json_decode, json_encode +from .utils import ssi_vary_on_cookie from .variables import SsiVariable, provide_vars -from . import DEBUG + + +CACHE_HEADERS = ('Pragma', 'Cache-Control', 'Vary') class PrepareForCacheMiddleware(object): @@ -53,12 +57,41 @@ class PrepareForCacheMiddleware(object): @staticmethod def process_response(request, response): """Adds a 'X-Ssi-Vars-Needed' header to the response.""" - if getattr(request, 'ssi_vars_needed', None): + if ('X-Ssi-Vars-Needed' not in response and + getattr(request, 'ssi_vars_needed', None)): vars_needed = {} for (k, v) in request.ssi_vars_needed.items(): vars_needed[k] = v.definition response['X-Ssi-Vars-Needed'] = json_encode( vars_needed, sort_keys=True) + + if ('X-ssi-restore' not in response and + getattr(request, 'ssi_patch_response', None)): + # We have some response modifiers set by ssi_includes and + # ssi_variables. Those are used, because unrendered SSI + # templates Django cache receives should have different + # caching headers, than pages rendered with request-specific + # information. + # What we do here is apply the modifiers, but restore + # previous values of any cache-relevant headers and set + # a custom header with modified values to set them + # after-cache. + original_fields = {} + for field in CACHE_HEADERS: + original_fields[field] = response.get(field, None) + for modifier in request.ssi_patch_response: + modifier(response) + restore_fields = {} + for field in CACHE_HEADERS: + new_value = response.get(field, None) + if new_value != original_fields[field]: + restore_fields[field] = new_value + if original_fields[field] is None: + del response[field] + else: + response[field] = original_fields[field] + response['X-ssi-restore'] = json_encode(restore_fields) + return response @@ -72,15 +105,14 @@ class SsiMiddleware(object): It also patches the Vary header with the values given by the SSI variables. - If SSIFY_DEBUG is set, it also passes the response through - DebugUnSsiMiddleware, which interprets and renders the SSI + If SSIFY_RENDER is set, it also passes the response through + SsiRenderMiddleware, which interprets and renders the SSI statements, so you can see the output without an actual SSI-enabled webserver. """ def process_request(self, request): - request.ssi_vary = set() - #request.ssi_cache_control_after = set() + request.ssi_patch_response = [] def process_view(self, request, view_func, view_args, view_kwargs): request.ssi_vars_needed = {} @@ -98,9 +130,18 @@ class SsiMiddleware(object): response.content = provide_vars(request, vars_needed) + \ response.content - # Add the Vary headers declared by all the SSI vars. - patch_vary_headers(response, sorted(request.ssi_vary)) - # TODO: cache control? + if 'X-ssi-restore' in response: + # The modifiers have already been applied to the response + # by the PrepareForCacheMiddleware. + # All we need to do is restore cache-relevant headers. + for header, content in json_decode(response['X-ssi-restore']).items(): + if content is None: + del response[header] + else: + response[header] = content + else: + for response_modifier in getattr(request, 'ssi_patch_response', []): + response_modifier(response) def process_response(self, request, response): if hasattr(response, 'render') and callable(response.render): @@ -110,9 +151,9 @@ class SsiMiddleware(object): else: self._process_rendered_response(request, response) - if DEBUG: - from .middleware_debug import DebugUnSsiMiddleware - response = DebugUnSsiMiddleware().process_response( + if conf.RENDER: + from .middleware_debug import SsiRenderMiddleware + response = SsiRenderMiddleware().process_response( request, response) return response @@ -147,4 +188,4 @@ class LocaleMiddleware(locale.LocaleMiddleware): if (request.session.accessed and (settings.USE_I18N or settings.USE_L10N)): request.session.accessed = False - request.ssi_vary.add('Cookie') + request.ssi_patch_response.append(ssi_vary_on_cookie) diff --git a/ssify/middleware_debug.py b/ssify/middleware_debug.py index 5eb1cbd..863d8fe 100644 --- a/ssify/middleware_debug.py +++ b/ssify/middleware_debug.py @@ -5,7 +5,7 @@ """ This module should only be used for debugging SSI statements. -Using DebugUnSsiMiddleware in production defeats the purpose of using SSI +Using SsiRenderMiddleware in production defeats the purpose of using SSI in the first place, and is unsafe. You should use a proper webserver with SSI support as a proxy (i.e. Nginx with ssi=on). @@ -17,7 +17,9 @@ try: except ImportError: from urlparse import urlparse from django.core.urlresolvers import resolve -from ssify import DEBUG_VERBOSE +from .cache import get_caches + +from .conf import conf SSI_SET = re.compile(r")" SSI_VAR = re.compile(r"\$\{(?P.+)\}") # TODO: escaped? -class DebugUnSsiMiddleware(object): +class SsiRenderMiddleware(object): """ Emulates a webserver with SSI support. This middleware should only be used for debugging purposes. - SsiMiddleware will enable it automatically, if SSIFY_DEBUG setting + SsiMiddleware will enable it automatically, if SSIFY_RENDER setting is set to True, so you don't normally need to include it in MIDDLEWARE_CLASSES. - If SSIFY_DEBUG_VERBOSE setting is True, it will also leave some + If SSIFY_RENDER_VERBOSE setting is True, it will also leave some information in HTML comments. """ @@ -50,18 +52,29 @@ class DebugUnSsiMiddleware(object): def ssi_include(match): """Replaces SSI include with contents rendered by relevant view.""" path = process_value(match.group('path')) - func, args, kwargs = resolve(path) - parsed = urlparse(path) - - # Reuse the original request, but reset some attributes. - request.META['PATH_INFO'] = request.path_info = \ - request.path = parsed.path - request.META['QUERY_STRING'] = parsed.query - request.ssi_vars_needed = {} - - content = func(request, *args, **kwargs).content.decode('ascii') - content = process_content(content) - if DEBUG_VERBOSE: + content = None + for cache in get_caches(): + content = cache.get(path) + if content is not None: + break + if content is None: + func, args, kwargs = resolve(path) + parsed = urlparse(path) + + # Reuse the original request, but reset some attributes. + request.META['PATH_INFO'] = request.path_info = \ + request.path = parsed.path + request.META['QUERY_STRING'] = parsed.query + request.ssi_vars_needed = {} + + subresponse = func(request, *args, **kwargs) + # FIXME: we should deal directly with bytes here. + if subresponse.streaming: + content = b"".join(subresponse.streaming_content) + else: + content = subresponse.content + content = process_content(content.decode('utf-8')) + if conf.RENDER_VERBOSE: return "".join(( match.group(0), content, @@ -73,7 +86,7 @@ class DebugUnSsiMiddleware(object): def ssi_set(match): """Interprets SSI set statement.""" variables[match.group('var')] = match.group('value') - if DEBUG_VERBOSE: + if conf.RENDER_VERBOSE: return match.group(0) else: return "" @@ -81,7 +94,7 @@ class DebugUnSsiMiddleware(object): def ssi_echo(match): """Interprets SSI echo, outputting the value of the variable.""" content = variables[match.group('var')] - if DEBUG_VERBOSE: + if conf.RENDER_VERBOSE: return "".join(( match.group(0), content, @@ -96,8 +109,8 @@ class DebugUnSsiMiddleware(object): if expr: content = match.group('value') else: - content = match.group('else') - if DEBUG_VERBOSE: + content = match.group('else') or '' + if conf.RENDER_VERBOSE: return "".join(( match.group('header'), content, @@ -124,11 +137,13 @@ class DebugUnSsiMiddleware(object): variables = {} response.content = process_content( - response.content.decode('ascii')).encode('ascii') + response.content.decode('utf-8')).encode('utf-8') response['Content-Length'] = len(response.content) def process_response(self, request, response): """Support for unrendered responses.""" + if response.streaming: + return response if hasattr(response, 'render') and callable(response.render): response.add_post_render_callback( lambda r: self._process_rendered_response(request, r) diff --git a/ssify/store.py b/ssify/store.py deleted file mode 100644 index ec40bb0..0000000 --- a/ssify/store.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of django-ssify, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See README.md for more information. -# -from __future__ import unicode_literals -from django.core.cache import get_cache -from ssify import INCLUDES_CACHES - - -includes_caches = [get_cache(c) for c in INCLUDES_CACHES] - - -def cache_include(path, content): - for cache in includes_caches: - cache.set(path, content) diff --git a/ssify/templatetags/ssify.py b/ssify/templatetags/ssify.py index 06be39e..b0ecd04 100644 --- a/ssify/templatetags/ssify.py +++ b/ssify/templatetags/ssify.py @@ -5,25 +5,13 @@ from __future__ import absolute_import, unicode_literals from django.conf import settings from django.core.urlresolvers import NoReverseMatch, reverse, resolve -from django.middleware.csrf import get_token, _sanitize_token +from django.middleware.csrf import get_token, _sanitize_token, rotate_token from django import template from django.utils.translation import get_language from ssify.decorators import ssi_variable +from ssify.utils import ssi_vary_on_cookie from ssify.variables import SsiVariable -try: - from django.middleware.csrf import rotate_token -except ImportError: - from django.middleware.csrf import _get_new_csrf_key - - # Missing in Django 1.4 - def rotate_token(request): - request.META.update({ - "CSRF_COOKIE_USED": True, - "CSRF_COOKIE": _get_new_csrf_key(), - - }) - register = template.Library() @@ -72,11 +60,16 @@ def ssi_include(context, name_, **kwargs): var = SsiVariable(*var) request.ssi_vars_needed[var.name] = var + # Remember the decorators to use on the including view. + patch_response = getattr(view, 'ssi_patch_response', None) + if patch_response: + request.ssi_patch_response.extend(patch_response) + # Output the SSI include. return "" % url -@ssi_variable(register, vary=('Cookie',)) +@ssi_variable(register, patch_response=[ssi_vary_on_cookie]) def get_csrf_token(request): """ CsrfViewMiddleware.process_view is never called for cached @@ -103,5 +96,5 @@ def get_csrf_token(request): @register.inclusion_tag('ssify/csrf_token.html', takes_context=True) -def csrf_token(context): +def ssi_csrf_token(context): return {'request': context['request']} diff --git a/ssify/utils.py b/ssify/utils.py new file mode 100644 index 0000000..d672016 --- /dev/null +++ b/ssify/utils.py @@ -0,0 +1,13 @@ +from functools import partial +from django.utils.cache import patch_cache_control, patch_vary_headers + + +def ssi_cache_control(**kwargs): + return partial(patch_cache_control, **kwargs) + + +def ssi_vary(newheaders): + return partial(patch_vary_headers, newheaders=newheaders) + + +ssi_vary_on_cookie = ssi_vary(('Cookie',)) diff --git a/ssify/variables.py b/ssify/variables.py index e969450..668bb51 100644 --- a/ssify/variables.py +++ b/ssify/variables.py @@ -119,11 +119,11 @@ def ssi_expect(var, type_): class SsiVariableNode(template.Node): """ Node for the SsiVariable tags. """ - def __init__(self, tagpath, args, kwargs, vary=None, asvar=None): + def __init__(self, tagpath, args, kwargs, patch_response=None, asvar=None): self.tagpath = tagpath self.args = args self.kwargs = kwargs - self.vary = vary + self.patch_response = patch_response self.asvar = asvar def __repr__(self): @@ -138,8 +138,8 @@ class SsiVariableNode(template.Node): request = context['request'] request.ssi_vars_needed[var.name] = var - if self.vary: - request.ssi_vary.update(self.vary) + if self.patch_response: + request.ssi_patch_response.extend(self.patch_response) if self.asvar: context.dicts[0][self.asvar] = var diff --git a/tests/templates/tests_args/args.html b/tests/templates/tests_args/args.html index 253fcde..1f8b0a1 100644 --- a/tests/templates/tests_args/args.html +++ b/tests/templates/tests_args/args.html @@ -1,10 +1,5 @@ {% load ssify test_tags %} -{# Django 1.4 compatibility: TemplateView sets `params` from the URL. #} -{% if params.limit %} - {% random_number limit=params.limit as a %} -{% else %} - {% random_number limit=limit as a %} -{% endif %} +{% random_number limit=limit as a %} {% random_number a as b %} {% random_number limit=b %} \ No newline at end of file diff --git a/tests/templates/tests_csrf/csrf_token.html b/tests/templates/tests_csrf/csrf_token.html index 9b0845f..fe01e65 100644 --- a/tests/templates/tests_csrf/csrf_token.html +++ b/tests/templates/tests_csrf/csrf_token.html @@ -1,3 +1,3 @@ -{% load ssify %} +{% load ssi_csrf_token from ssify %} -{% csrf_token %} \ No newline at end of file +{% ssi_csrf_token %} \ No newline at end of file diff --git a/tests/tests/test_args.py b/tests/tests/test_args.py index a45f47c..7339cdb 100644 --- a/tests/tests/test_args.py +++ b/tests/tests/test_args.py @@ -49,10 +49,9 @@ class ArgsTestCase(TestCase): ]), ) - @override_settings(SSIFY_DEBUG=True) - def test_debug_render_include_args(self): - pass - """Renders the complete view using the DebugSsiMiddleware.""" + @override_settings(SSIFY_RENDER=True) + def test_render_include_args(self): + """Renders the complete view using the SsiRenderMiddleware.""" response = self.client.get('/include_args') if hasattr(response, 'render') and callable(response.render): response.render() diff --git a/tests/tests/test_basic.py b/tests/tests/test_basic.py index 0c32349..544d35b 100644 --- a/tests/tests/test_basic.py +++ b/tests/tests/test_basic.py @@ -70,9 +70,9 @@ Line 3 of b"file='/quote/${v3e7f638af74c9f420b6d2c5fe4dda51d}'-->" ) - @override_settings(SSIFY_DEBUG=True) + @override_settings(SSIFY_RENDER=True) def test_debug_render_random_quote(self): - """Renders the complete view using the DebugSsiMiddleware.""" + """Renders the complete view using the SsiRenderMiddleware.""" response = self.client.get('/') if hasattr(response, 'render') and callable(response.render): response.render() diff --git a/tox.ini b/tox.ini index acef65a..421e095 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ # [tox] envlist=clear, - d14-py26, d15-py26, d15-py27, d15-py32, d15-py33, d16-py26, d16-py27, d16-py32, d16-py33, d17-py27, d17-py32, d17-py33, d17-py34, @@ -23,12 +22,6 @@ commands=coverage html [base] -[testenv:d14-py26] -basepython=python2.6 -deps= - Django>=1.4,<1.5 - {[testenv]deps} - [testenv:d15-py26] basepython=python2.6 deps= @@ -86,19 +79,19 @@ deps= [testenv:d17-py32] basepython=python3.2 deps= - https://www.djangoproject.com/download/1.7c3/tarball/ + Django>=1.7,<1.8 {[testenv]deps} [testenv:d17-py33] basepython=python3.3 deps= - https://www.djangoproject.com/download/1.7c3/tarball/ + Django>=1.7,<1.8 {[testenv]deps} [testenv:d17-py34] basepython=python3.4 deps= - https://www.djangoproject.com/download/1.7c3/tarball/ + Django>=1.7,<1.8 {[testenv]deps} [testenv:dd-py27] -- 2.20.1