Commit d9b010c4 authored by Hanno Schlichting's avatar Hanno Schlichting

Revert "Remove ZCacheable logic and StandardCacheManagers dependency."

This reverts part of commit dbb476e6.
parent b43972fe
......@@ -27,6 +27,8 @@ Features Added
Restructuring
+++++++++++++
- Add back ZCacheable support.
- Update to zope.testbrowser 5.0 and its WebTest based implementation.
- Use `@implementer` and `@adapter` class decorators.
......
......@@ -14,10 +14,19 @@
"""
from logging import getLogger
import sys
import time
from AccessControl.class_init import InitializeClass
from AccessControl.Permissions import view_management_screens
from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from AccessControl.unauthorized import Unauthorized
from Acquisition import aq_acquire
from Acquisition import aq_base
from Acquisition import aq_get
from Acquisition import aq_inner
from Acquisition import aq_parent
ZCM_MANAGERS = '__ZCacheManager_ids__'
......@@ -27,16 +36,18 @@ LOG = getLogger('Cache')
def isCacheable(ob):
return False
return getattr(aq_base(ob), '_isCacheable', 0)
def managersExist(ob):
# Returns 1 if any CacheManagers exist in the context of ob.
return False
if aq_get(ob, ZCM_MANAGERS, None, 1):
return 1
return 0
def filterCacheTab(ob):
return False
return managersExist(ob)
def filterCacheManagers(orig, container, name, value, extra):
......@@ -45,14 +56,22 @@ def filterCacheManagers(orig, container, name, value, extra):
It causes objects to be found only if they are
in the list of cache managers.
'''
return False
if (hasattr(aq_base(container), ZCM_MANAGERS) and
name in getattr(container, ZCM_MANAGERS)):
return 1
return 0
def getVerifiedManagerIds(container):
'''
Gets the list of cache managers in a container, verifying each one.
'''
return ()
ids = getattr(container, ZCM_MANAGERS, ())
rval = []
for id in ids:
if getattr(getattr(container, id, None), '_isCacheManager', 0):
rval.append(id)
return tuple(rval)
# Anytime a CacheManager is added or removed, all _v_ZCacheable_cache
......@@ -73,19 +92,42 @@ class Cacheable(object):
_v_ZCacheable_cache = None
_v_ZCacheable_manager_timestamp = 0
__manager_id = None
__enabled = False
_isCacheable = False
__enabled = True
_isCacheable = True
security.declarePrivate('ZCacheable_getManager')
def ZCacheable_getManager(self):
'''Returns the currently associated cache manager.'''
manager_id = self.__manager_id
if manager_id is None:
return None
try:
return aq_acquire(
self, manager_id, containment=1,
filter=filterCacheManagers, extra=None, default=None)
except AttributeError:
return None
security.declarePrivate('ZCacheable_getCache')
def ZCacheable_getCache(self):
'''Gets the cache associated with this object.
'''
if self.__manager_id is None:
return None
c = self._v_ZCacheable_cache
if c is not None:
# We have a volatile reference to the cache.
if self._v_ZCacheable_manager_timestamp == manager_timestamp:
return aq_base(c)
manager = self.ZCacheable_getManager()
if manager is not None:
c = aq_base(manager.ZCacheManager_getCache())
else:
return None
# Set a volatile reference to the cache then return it.
self._v_ZCacheable_cache = c
self._v_ZCacheable_manager_timestamp = manager_timestamp
return c
security.declarePrivate('ZCacheable_isCachingEnabled')
def ZCacheable_isCachingEnabled(self):
......@@ -93,7 +135,7 @@ class Cacheable(object):
Returns true only if associated with a cache manager and
caching of this method is enabled.
'''
return False
return self.__enabled and self.ZCacheable_getCache()
security.declarePrivate('ZCacheable_getObAndView')
def ZCacheable_getObAndView(self, view_name):
......@@ -107,6 +149,16 @@ class Cacheable(object):
conditions specified by keywords. If the value is
not yet cached, returns the default.
'''
c = self.ZCacheable_getCache()
if c is not None and self.__enabled:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
val = c.ZCache_get(ob, view_name, keywords,
mtime_func, default)
return val
except:
LOG.warn('ZCache_get() exception')
return default
return default
security.declarePrivate('ZCacheable_set')
......@@ -115,7 +167,14 @@ class Cacheable(object):
'''Cacheable views should call this method after generating
cacheable results. The data argument can be of any Python type.
'''
pass
c = self.ZCacheable_getCache()
if c is not None and self.__enabled:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
c.ZCache_set(ob, data, view_name, keywords,
mtime_func)
except:
LOG.warn('ZCache_set() exception')
security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_invalidate')
......@@ -124,23 +183,55 @@ class Cacheable(object):
cache entries that apply to the view_name to be removed.
Returns a status message.
'''
pass
c = self.ZCacheable_getCache()
if c is not None:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
message = c.ZCache_invalidate(ob)
if not message:
message = 'Invalidated.'
except:
exc = sys.exc_info()
try:
LOG.warn('ZCache_invalidate() exception')
message = 'An exception occurred: %s: %s' % exc[:2]
finally:
exc = None
else:
message = 'This object is not associated with a cache manager.'
return message
security.declarePrivate('ZCacheable_getModTime')
def ZCacheable_getModTime(self, mtime_func=None):
'''Returns the highest of the last mod times.'''
return 0
# Based on:
# mtime_func
# self.mtime
# self.__class__.mtime
mtime = 0
if mtime_func:
# Allow mtime_func to influence the mod time.
mtime = mtime_func()
base = aq_base(self)
mtime = max(getattr(base, '_p_mtime', mtime), mtime)
klass = getattr(base, '__class__', None)
if klass:
mtime = max(getattr(klass, '_p_mtime', mtime), mtime)
return mtime
security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_getManagerId')
def ZCacheable_getManagerId(self):
'''Returns the id of the current ZCacheManager.'''
return None
return self.__manager_id
security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_getManagerURL')
def ZCacheable_getManagerURL(self):
'''Returns the URL of the current ZCacheManager.'''
manager = self.ZCacheable_getManager()
if manager is not None:
return manager.absolute_url()
return None
security.declareProtected(ViewManagementScreensPermission,
......@@ -148,32 +239,105 @@ class Cacheable(object):
def ZCacheable_getManagerIds(self):
'''Returns a list of mappings containing the id and title
of the available ZCacheManagers.'''
return ()
rval = []
ob = self
used_ids = {}
while ob is not None:
if hasattr(aq_base(ob), ZCM_MANAGERS):
ids = getattr(ob, ZCM_MANAGERS)
for id in ids:
manager = getattr(ob, id, None)
if manager is not None:
id = manager.getId()
if id not in used_ids:
title = getattr(aq_base(manager), 'title', '')
rval.append({'id': id, 'title': title})
used_ids[id] = 1
ob = aq_parent(aq_inner(ob))
return tuple(rval)
security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheable_setManagerId')
def ZCacheable_setManagerId(self, manager_id, REQUEST=None):
'''Changes the manager_id for this object.'''
pass
self.ZCacheable_invalidate()
if not manager_id:
# User requested disassociation
# from the cache manager.
manager_id = None
else:
manager_id = str(manager_id)
self.__manager_id = manager_id
self._v_ZCacheable_cache = None
security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_enabled')
def ZCacheable_enabled(self):
'''Returns true if caching is enabled for this object
or method.'''
return False
return self.__enabled
security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheable_setEnabled')
def ZCacheable_setEnabled(self, enabled=0, REQUEST=None):
'''Changes the enabled flag.'''
pass
self.__enabled = enabled and 1 or 0
InitializeClass(Cacheable)
class Cache:
def findCacheables(ob, manager_id, require_assoc, subfolders,
meta_types, rval, path):
'''
Used by the CacheManager UI. Recursive. Similar to the Zope
"Find" function. Finds all Cacheable objects in a hierarchy.
'''
try:
if meta_types:
subobs = ob.objectValues(meta_types)
else:
subobs = ob.objectValues()
sm = getSecurityManager()
# Add to the list of cacheable objects.
for subob in subobs:
if not isCacheable(subob):
continue
associated = (subob.ZCacheable_getManagerId() == manager_id)
if require_assoc and not associated:
continue
if not sm.checkPermission('Change cache settings', subob):
continue
subpath = path + (subob.getId(),)
info = {
'sortkey': subpath,
'path': '/'.join(subpath),
'title': getattr(aq_base(subob), 'title', ''),
'icon': None,
'associated': associated,
}
rval.append(info)
# Visit subfolders.
if subfolders:
if meta_types:
subobs = ob.objectValues()
for subob in subobs:
subpath = path + (subob.getId(),)
if hasattr(aq_base(subob), 'objectValues'):
if sm.checkPermission(
'Access contents information', subob):
findCacheables(
subob, manager_id, require_assoc,
subfolders, meta_types, rval, subpath)
except:
# Ignore exceptions.
import traceback
traceback.print_exc()
class Cache(object):
'''
A base class (and interface description) for caches.
Note that Cache objects are not intended to be visible by
......@@ -207,7 +371,7 @@ class Cache:
raise NotImplementedError
class CacheManager:
class CacheManager(object):
'''
A base class for cache managers. Implement ZCacheManager_getCache().
'''
......@@ -225,11 +389,27 @@ class CacheManager:
def manage_afterAdd(self, item, container):
# Adds self to the list of cache managers in the container.
pass
if aq_base(self) is aq_base(item):
ids = getVerifiedManagerIds(container)
id = self.getId()
if id not in ids:
setattr(container, ZCM_MANAGERS, ids + (id,))
global manager_timestamp
manager_timestamp = time.time()
def manage_beforeDelete(self, item, container):
# Removes self from the list of cache managers.
pass
if aq_base(self) is aq_base(item):
ids = getVerifiedManagerIds(container)
id = self.getId()
if id in ids:
manager_ids = [s for s in ids if s != id]
if manager_ids:
setattr(container, ZCM_MANAGERS, manager_ids)
elif getattr(aq_base(self), ZCM_MANAGERS, None) is not None:
delattr(self, ZCM_MANAGERS)
global manager_timestamp
manager_timestamp = time.time()
security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheManager_locate')
......@@ -237,7 +417,15 @@ class CacheManager:
meta_types=[], REQUEST=None):
'''Locates cacheable objects.
'''
return []
ob = aq_parent(aq_inner(self))
rval = []
manager_id = self.getId()
if '' in meta_types:
# User selected "All".
meta_types = []
findCacheables(ob, manager_id, require_assoc, subfolders,
meta_types, rval, ())
return rval
security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheManager_setAssociations')
......@@ -245,6 +433,30 @@ class CacheManager:
'''Associates and un-associates cacheable objects with this
cache manager.
'''
pass
addcount = 0
remcount = 0
parent = aq_parent(aq_inner(self))
sm = getSecurityManager()
my_id = str(self.getId())
if props is None:
props = REQUEST.form
for key, do_associate in props.items():
if key[:10] == 'associate_':
path = key[10:]
ob = parent.restrictedTraverse(path)
if not sm.checkPermission('Change cache settings', ob):
raise Unauthorized
if not isCacheable(ob):
# Not a cacheable object.
continue
manager_id = str(ob.ZCacheable_getManagerId())
if do_associate:
if manager_id != my_id:
ob.ZCacheable_setManagerId(my_id)
addcount = addcount + 1
else:
if manager_id == my_id:
ob.ZCacheable_setManagerId(None)
remcount = remcount + 1
InitializeClass(CacheManager)
......@@ -60,6 +60,7 @@ class DTMLDocument(PropertyManager, DTMLMethod):
file = file.read()
self.munge(file)
self.ZCacheable_invalidate()
if REQUEST:
message = "Content uploaded."
return self.manage_main(self, REQUEST, manage_tabs_message=message)
......@@ -69,6 +70,12 @@ class DTMLDocument(PropertyManager, DTMLMethod):
o If supplied, use REQUEST mapping, Response, and key word arguments.
"""
if not self._cache_namespace_keys:
data = self.ZCacheable_get(default=_marker)
if data is not _marker:
# Return cached results.
return data
__traceback_supplement__ = (PathTracebackSupplement, self)
kw['document_id'] = self.getId()
kw['document_title'] = self.title
......@@ -88,11 +95,15 @@ class DTMLDocument(PropertyManager, DTMLMethod):
result = r
else:
result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result
r = HTML.__call__(self, (client, bself), REQUEST, **kw)
if RESPONSE is None or not isinstance(r, str):
if not self._cache_namespace_keys:
self.ZCacheable_set(r)
return r
finally:
......@@ -106,6 +117,8 @@ class DTMLDocument(PropertyManager, DTMLMethod):
c, e = guess_content_type(self.__name__, r)
RESPONSE.setHeader('Content-Type', c)
result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result
......
......@@ -37,6 +37,8 @@ from OFS import bbb
from OFS.Cache import Cacheable
from OFS.role import RoleManager
from OFS.SimpleItem import Item_w__name__
from ZPublisher.Iterators import IStreamIterator
if sys.version_info >= (3, ):
basestring = str
......@@ -59,6 +61,7 @@ class DTMLMethod(RestrictedDTML,
"""
meta_type = 'DTML Method'
index_html = None # Prevent accidental acquisition
_cache_namespace_keys = ()
security = ClassSecurityInfo()
security.declareObjectProtected(View)
......@@ -72,7 +75,8 @@ class DTMLMethod(RestrictedDTML,
{'label': 'Edit', 'action': 'manage_main'},
) +
RoleManager.manage_options +
Item_w__name__.manage_options
Item_w__name__.manage_options +
Cacheable.manage_options
)
# More reasonable default for content-type for http HEAD requests.
......@@ -88,6 +92,27 @@ class DTMLMethod(RestrictedDTML,
o If supplied, use the REQUEST mapping, Response, and key word
arguments.
"""
if not self._cache_namespace_keys:
data = self.ZCacheable_get(default=_marker)
if data is not _marker:
if (IStreamIterator.isImplementedBy(data) and
RESPONSE is not None):
# This is a stream iterator and we need to set some
# headers now before giving it to medusa
headers_get = RESPONSE.headers.get
if headers_get('content-length', None) is None:
RESPONSE.setHeader('content-length', len(data))
if (headers_get('content-type', None) is None and
headers_get('Content-type', None) is None):
ct = (self.__dict__.get('content_type') or
self.default_content_type)
RESPONSE.setHeader('content-type', ct)
# Return cached results.
return data
__traceback_supplement__ = (PathTracebackSupplement, self)
kw['document_id'] = self.getId()
kw['document_title'] = self.title
......@@ -108,10 +133,14 @@ class DTMLMethod(RestrictedDTML,
result = r
else:
result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result
r = HTML.__call__(self, client, REQUEST, **kw)
if RESPONSE is None or not isinstance(r, str):
if not self._cache_namespace_keys:
self.ZCacheable_set(r)
return r
finally:
......@@ -127,24 +156,54 @@ class DTMLMethod(RestrictedDTML,
c, e = guess_content_type(self.getId(), r)
RESPONSE.setHeader('Content-Type', c)
result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result
def validate(self, inst, parent, name, value, md=None):
return getSecurityManager().validate(inst, parent, name, value)
def ZDocumentTemplate_beforeRender(self, md, default):
# Tries to get a cached value.
if self._cache_namespace_keys:
# Use the specified keys from the namespace to identify a
# cache entry.
kw = {}
for key in self._cache_namespace_keys:
try:
val = md[key]
except:
val = None
kw[key] = val
return self.ZCacheable_get(keywords=kw, default=default)
return default
def ZDocumentTemplate_afterRender(self, md, result):
pass
# Tries to set a cache value.
if self._cache_namespace_keys:
kw = {}
for key in self._cache_namespace_keys:
try:
val = md[key]
except:
val = None
kw[key] = val
self.ZCacheable_set(result, keywords=kw)
security.declareProtected(change_dtml_methods, 'getCacheNamespaceKeys')
def getCacheNamespaceKeys(self):
return ()
# Return the cacheNamespaceKeys.
return self._cache_namespace_keys
security.declareProtected(change_dtml_methods, 'setCacheNamespaceKeys')
def setCacheNamespaceKeys(self, keys, REQUEST=None):
pass
# Set the list of names looked up to provide a cache key.
ks = []
for key in keys:
key = str(key).strip()
if key:
ks.append(key)
self._cache_namespace_keys = tuple(ks)
security.declareProtected(View, 'get_size')
def get_size(self):
......@@ -178,6 +237,7 @@ class DTMLMethod(RestrictedDTML,
if not isinstance(data, basestring):
data = data.read()
self.munge(data)
self.ZCacheable_invalidate()
if REQUEST:
message = "Saved changes."
return self.manage_main(self, REQUEST, manage_tabs_message=message)
......@@ -195,6 +255,7 @@ class DTMLMethod(RestrictedDTML,
file = file.read()
self.munge(file)
self.ZCacheable_invalidate()
if REQUEST:
message = "Saved changes."
return self.manage_main(self, REQUEST, manage_tabs_message=message)
......@@ -220,6 +281,7 @@ class DTMLMethod(RestrictedDTML,
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
body = REQUEST.get('BODY', '')
self.munge(body)
self.ZCacheable_invalidate()
RESPONSE.setStatus(204)
return RESPONSE
......
......@@ -112,7 +112,8 @@ class File(Persistent, Implicit, PropertyManager,
manage_options = (
({'label': 'Edit', 'action': 'manage_main'}, ) +
RoleManager.manage_options +
Item_w__name__.manage_options
Item_w__name__.manage_options +
Cacheable.manage_options
)
_properties = (
......@@ -372,6 +373,13 @@ class File(Persistent, Implicit, PropertyManager,
"""
if self._if_modified_since_request_handler(REQUEST, RESPONSE):
# we were able to handle this by returning a 304
# unfortunately, because the HTTP cache manager uses the cache
# API, and because 304 responses are required to carry the Expires
# header for HTTP/1.1, we need to call ZCacheable_set here.
# This is nonsensical for caches other than the HTTP cache manager
# unfortunately.
self.ZCacheable_set(None)
return ''
if self.precondition and hasattr(self, str(self.precondition)):
......@@ -393,6 +401,17 @@ class File(Persistent, Implicit, PropertyManager,
RESPONSE.setHeader('Content-Length', self.size)
RESPONSE.setHeader('Accept-Ranges', 'bytes')
if self.ZCacheable_isCachingEnabled():
result = self.ZCacheable_get(default=None)
if result is not None:
# We will always get None from RAMCacheManager and HTTP
# Accelerated Cache Manager but we will get
# something implementing the IStreamIterator interface
# from a "FileCacheManager"
return result
self.ZCacheable_set(None)
data = self.data
if isinstance(data, str):
RESPONSE.setBase(None)
......@@ -430,6 +449,8 @@ class File(Persistent, Implicit, PropertyManager,
size = len(data)
self.size = size
self.data = data
self.ZCacheable_invalidate()
self.ZCacheable_set(None)
self.http__refreshEtag()
security.declareProtected(change_images_and_files, 'manage_edit')
......@@ -449,6 +470,8 @@ class File(Persistent, Implicit, PropertyManager,
del self.precondition
if filedata is not None:
self.update_data(filedata, content_type, len(filedata))
else:
self.ZCacheable_invalidate()
notify(ObjectModifiedEvent(self))
......@@ -609,6 +632,18 @@ class File(Persistent, Implicit, PropertyManager,
def manage_FTPget(self):
"""Return body for ftp."""
RESPONSE = self.REQUEST.RESPONSE
if self.ZCacheable_isCachingEnabled():
result = self.ZCacheable_get(default=None)
if result is not None:
# We will always get None from RAMCacheManager but we will
# get something implementing the IStreamIterator interface
# from FileCacheManager.
# the content-length is required here by HTTPResponse,
# even though FTP doesn't use it.
RESPONSE.setHeader('Content-Length', self.size)
return result
data = self.data
if isinstance(data, str):
RESPONSE.setBase(None)
......@@ -765,7 +800,8 @@ class Image(File):
manage_options = (
({'label': 'Edit', 'action': 'manage_main'}, ) +
RoleManager.manage_options +
Item_w__name__.manage_options
Item_w__name__.manage_options +
Cacheable.manage_options
)
manage_editForm = DTMLFile('dtml/imageEdit', globals(),
......@@ -803,6 +839,8 @@ class Image(File):
if content_type is not None:
self.content_type = content_type
self.ZCacheable_invalidate()
self.ZCacheable_set(None)
self.http__refreshEtag()
def __str__(self):
......
......@@ -4,4 +4,7 @@
<five:deprecatedManageAddDelete
class="OFS.userfolder.BasicUserFolder"/>
<five:deprecatedManageAddDelete
class="OFS.Cache.CacheManager"/>
</configure>
import unittest
from OFS.Cache import CacheManager
from OFS.Folder import Folder
from OFS.SimpleItem import SimpleItem
from OFS.metaconfigure import setDeprecatedManageAddDelete
class DummyCacheManager(CacheManager, SimpleItem):
def __init__(self, id, *args, **kw):
self.id = id
setDeprecatedManageAddDelete(DummyCacheManager)
class CacheTests(unittest.TestCase):
def test_managersExist(self):
from OFS.Cache import managersExist
from OFS.DTMLMethod import DTMLMethod
root = Folder('root')
root._setObject('root_cache', DummyCacheManager('root_cache'))
root._setObject('child', Folder('child'))
root.child._setObject('child_cache', DummyCacheManager('child_cache'))
root.child._setObject('child_content', DTMLMethod('child_content'))
# To begin with, cache managers will be found correctly
# using managersExist
self.assertTrue(managersExist(root.child.child_content))
# Now we delete the cache in the child folder
root.child.manage_delObjects(['child_cache'])
# The parent_cache should still trigger managersExist
self.assertTrue(managersExist(root.child.child_content))
......@@ -10,6 +10,8 @@ from io import BytesIO
from Acquisition import aq_base
from OFS.Application import Application
from OFS.SimpleItem import SimpleItem
from OFS.Cache import ZCM_MANAGERS
from OFS.Image import Pdata
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
......@@ -50,6 +52,41 @@ def aputrequest(file, content_type):
return req
class DummyCache(object):
def __init__(self):
self.clear()
def ZCache_set(self, ob, data, view_name='', keywords=None,
mtime_func=None):
self.set = (ob, data)
def ZCache_get(self, ob, data, view_name='', keywords=None,
mtime_func=None):
self.get = ob
if self.si:
return self.si
def ZCache_invalidate(self, ob):
self.invalidated = ob
def clear(self):
self.set = None
self.get = None
self.invalidated = None
self.si = None
def setStreamIterator(self, si):
self.si = si
ADummyCache = DummyCache()
class DummyCacheManager(SimpleItem):
def ZCacheManager_getCache(self):
return ADummyCache
class EventCatcher(object):
def __init__(self):
......@@ -97,9 +134,13 @@ class FileTests(unittest.TestCase):
self.root = a
responseOut = self.responseOut = BytesIO()
self.app = makerequest(self.root, stdout=responseOut)
self.app.dcm = DummyCacheManager()
factory = getattr(self.app, self.factory)
factory('file',
file=self.data, content_type=self.content_type)
self.app.file.ZCacheable_setManagerId('dcm')
self.app.file.ZCacheable_setEnabled(enabled=1)
setattr(self.app, ZCM_MANAGERS, ('dcm',))
# Hack, we need a _p_mtime for the file, so we make sure that it
# has one.
transaction.commit()
......@@ -128,6 +169,7 @@ class FileTests(unittest.TestCase):
del self.responseOut
del self.root
del self.connection
ADummyCache.clear()
self.eventCatcher.tearDown()
def testViewImageOrFile(self):
......@@ -137,6 +179,8 @@ class FileTests(unittest.TestCase):
self.file.update_data('foo')
self.assertEqual(self.file.size, 3)
self.assertEqual(self.file.data, 'foo')
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
def testReadData(self):
s = "a" * (2 << 16)
......@@ -161,6 +205,8 @@ class FileTests(unittest.TestCase):
self.file.manage_edit('foobar', 'text/plain', filedata='ASD')
self.assertEqual(self.file.title, 'foobar')
self.assertEqual(self.file.content_type, 'text/plain')
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
self.assertEqual(1, len(self.eventCatcher.modified))
self.assertTrue(self.eventCatcher.modified[0].object is self.file)
......@@ -168,6 +214,7 @@ class FileTests(unittest.TestCase):
self.file.manage_edit('foobar', 'text/plain')
self.assertEqual(self.file.title, 'foobar')
self.assertEqual(self.file.content_type, 'text/plain')
self.assertTrue(ADummyCache.invalidated)
self.assertEqual(1, len(self.eventCatcher.modified))
self.assertTrue(self.eventCatcher.modified[0].object is self.file)
......@@ -256,6 +303,8 @@ class ImageTests(FileTests):
self.assertEqual(self.file.data, self.data)
self.assertEqual(self.file.width, 16)
self.assertEqual(self.file.height, 16)
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
def testStr(self):
self.assertEqual(
......
......@@ -88,7 +88,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
manage_options = (
{'label': 'Edit', 'action': 'pt_editForm'},
) + SimpleItem.manage_options
) + SimpleItem.manage_options + Cacheable.manage_options
_properties = (
{'id': 'title', 'type': 'ustring', 'mode': 'w'},
......@@ -164,6 +164,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
if not is_unicode:
text = unicode(text, encoding)
self.ZCacheable_invalidate()
super(ZopePageTemplate, self).pt_edit(text, content_type)
pt_editForm = PageTemplateFile('www/ptEdit', globals(),
......@@ -203,6 +204,11 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
title = unicode(title, encoding)
self._setPropValue('title', title)
def _setPropValue(self, id, value):
""" set a property and invalidate the cache """
PropertyManager._setPropValue(self, id, value)
self.ZCacheable_invalidate()
security.declareProtected(change_page_templates, 'pt_upload')
def pt_upload(self, REQUEST, file='', encoding='utf-8'):
"""Replace the document with the text in file."""
......@@ -248,6 +254,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
preferred_encodings)
self.output_encoding = encoding
self.ZCacheable_invalidate()
ZopePageTemplate.inheritedAttribute('write')(self, text)
def _exec(self, bound_names, args, kw):
......@@ -265,11 +272,25 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
security = getSecurityManager()
bound_names['user'] = security.getUser()
# Retrieve the value from the cache.
keyset = None
if self.ZCacheable_isCachingEnabled():
# Prepare a cache key.
keyset = {'here': self._getContext(),
'bound_names': bound_names}
result = self.ZCacheable_get(keywords=keyset)
if result is not None:
# Got a cached value.
return result
# Execute the template in a new security context.
security.addContext(self)
try:
result = self.pt_render(extra_context=bound_names)
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
return result
finally:
security.removeContext(self)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment