[CMF-checkins] CVS: CMF/CMFCore - CachingPolicyManager.py:1.2 FSPageTemplate.py:1.7 __init__.py:1.15

Tres Seaver tseaver@zope.com
Thu, 21 Mar 2002 07:28:04 -0500


Update of /cvs-repository/CMF/CMFCore
In directory cvs.zope.org:/tmp/cvs-serv32416/CMFCore

Modified Files:
	FSPageTemplate.py __init__.py 
Added Files:
	CachingPolicyManager.py 
Log Message:


  - Added CachingPolicyManager tool, which manages caching policies
    for skin methods, and updated FSPageTemplate to use a CPM if
    found.


=== CMF/CMFCore/CachingPolicyManager.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Caching tool implementation.
+
+$Id$
+"""
+from App.Common import rfc1123_date
+
+from AccessControl import ClassSecurityInfo
+
+from DateTime.DateTime import DateTime
+
+from Globals import InitializeClass
+from Globals import DTMLFile
+from Globals import PersistentMapping
+
+from OFS.SimpleItem import SimpleItem
+
+from Products.PageTemplates.Expressions import getEngine
+from Products.PageTemplates.Expressions import SecureModuleImporter
+
+from Products.CMFCore.interfaces.CachingPolicyManager \
+        import CachingPolicyManager
+
+from Products.CMFCore.ActionProviderBase import ActionProviderBase
+from Products.CMFCore.CMFCorePermissions import View
+from Products.CMFCore.CMFCorePermissions import ManagePortal
+from Products.CMFCore.Expression import Expression
+
+from Products.CMFCore.utils import _getAuthenticatedUser
+from Products.CMFCore.utils import _checkPermission
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import _dtmldir
+
+
+def createCPContext( content, view_method, keywords ):
+    """
+        Construct an expression context for TALES expressions,
+        for use by CachingPolicy objects.
+    """
+    pm = getToolByName( content, 'portal_membership', None )
+    if not pm or pm.isAnonymousUser():
+        member = None
+    else:
+        member = pm.getAuthenticatedMember()
+
+    data = { 'content'  : content
+           , 'view'     : view_method
+           , 'keywords' : keywords
+           , 'request'  : getattr( content, 'REQUEST', {} )
+           , 'member'   : member
+           , 'modules'  : SecureModuleImporter
+           , 'nothing'  : None
+           }
+
+    return getEngine().getContext( data )
+
+class CachingPolicy:
+    """
+        Represent a single class of cachable objects:
+
+          - class membership is defined by 'predicate', a TALES expression
+            with access to the following top-level names:
+
+            'content' -- the content object itself
+
+            'view' -- the name of the view method
+           
+            'keywords' -- keywords passed to the request
+           
+            'request' -- the REQUEST object itself
+           
+            'member' -- the authenticated member, or None if anonymous
+           
+            'modules' -- usual TALES access-with-import
+           
+            'nothing' -- None
+
+          - The "Last-modified" HTTP response header will be set using
+            'mtime_func', which is another TALES expression evaluated
+            against the same namespace.  If not specified explicitly,
+            uses 'content/modified'.
+
+          - The "Expires" HTTP response header and the "max-age" token of
+            the "Cache-control" header will be set using 'max_age_secs',
+            if passed;  it should be an integer value in seconds.
+
+          - Other tokens will be added to the "Cache-control" HTTP response
+            header as follows:
+
+             'no_cache=1' argument => "no-cache" token
+
+             'no_store=1' argument => "no-store" token
+
+             'must_revalidate=1' argument => "must-revalidate" token
+    """
+
+    def __init__( self
+                , policy_id
+                , predicate=''
+                , mtime_func=''
+                , max_age_secs=None
+                , no_cache=0
+                , no_store=0
+                , must_revalidate=0
+                ):
+
+        if not predicate:
+            predicate = 'python:1'
+
+        if not mtime_func:
+            mtime_func = 'content/modified'
+
+        if max_age_secs is not None:
+            max_age_secs = int( max_age_secs )
+
+        self._policy_id = policy_id
+        self._predicate = Expression( text=predicate )
+        self._mtime_func = Expression( text=mtime_func )
+        self._max_age_secs = max_age_secs
+        self._no_cache = int( no_cache )
+        self._no_store = int( no_store )
+        self._must_revalidate = int( must_revalidate )
+
+    def getPolicyId( self ):
+        """
+        """
+        return self._policy_id
+
+    def getPredicate( self ):
+        """
+        """
+        return self._predicate.text
+
+    def getMTimeFunc( self ):
+        """
+        """
+        return self._mtime_func.text
+
+    def getMaxAgeSecs( self ):
+        """
+        """
+        return self._max_age_secs
+
+    def getNoCache( self ):
+        """
+        """
+        return self._no_cache
+
+    def getNoStore( self ):
+        """
+        """
+        return self._no_store
+
+    def getMustRevalidate( self ):
+        """
+        """
+        return self._must_revalidate
+
+    def getHeaders( self, expr_context ):
+        """
+            Does this request match our predicate?  If so, return a
+            sequence of caching headers as ( key, value ) tuples.
+            Otherwise, return an empty sequence.
+        """
+        headers = []
+
+        if self._predicate( expr_context ):
+
+            mtime = self._mtime_func( expr_context )
+
+            if type( mtime ) is type( '' ):
+                mtime = DateTime( mtime )
+
+            if mtime is not None:
+                mtime_str = mtime.rfc822()
+                headers.append( ( 'Last-modified', mtime_str ) )
+
+            control = []
+
+            if self._max_age_secs is not None:
+                max_age_days = float( self._max_age_secs ) / 86400.0
+                exp_time = mtime + max_age_days
+                exp_time_str = exp_time.rfc822()
+
+                headers.append( ( 'Expires', exp_time_str ) )
+                control.append( 'max-age=%d' % self._max_age_secs )
+
+            if self._no_cache:
+                control.append( 'no-cache' )
+
+            if self._no_store:
+                control.append( 'no-store' )
+
+            if self._must_revalidate:
+                control.append( 'must-revalidate' )
+
+            if control:
+                headers.append( ( 'Cache-control', ', '.join( control ) ) )
+
+        return headers
+
+
+class CachingPolicyManager( SimpleItem ):
+    """
+        Manage the set of CachingPolicy objects for the site;  dispatch
+        to them from skin methods.
+    """
+
+    __implements__ = CachingPolicyManager
+
+    id = 'caching_policy_manager'
+    meta_type = 'CMF Caching Policy Manager'
+
+    security = ClassSecurityInfo()
+
+    def __init__( self ):
+        self._policy_ids = ()
+        self._policies = PersistentMapping()
+
+    #
+    #   ZMI
+    #
+    manage_options = ( ( { 'label' : 'Policies'
+                         , 'action' : 'manage_cachingPolicies'
+                         }
+                       ,
+                       )
+                     + SimpleItem.manage_options
+                     )
+
+    security.declareProtected( ManagePortal, 'manage_cachingPolicies' )
+    manage_cachingPolicies = DTMLFile( 'cachingPolicies', _dtmldir )
+
+    security.declarePublic( 'listPolicies' )
+    def listPolicies( self ):
+        """
+            Return a sequence of tuples,
+            '( policy_id, ( policy, typeObjectName ) )'
+            for all policies in the registry 
+        """
+        result = []
+        for policy_id in self._policy_ids:
+            result.append( ( policy_id, self._policies[ policy_id ] ) )
+        return tuple( result )
+
+    security.declareProtected( ManagePortal, 'addPolicy' )
+    def addPolicy( self
+                 , policy_id
+                 , predicate        # TALES expr (def. 'python:1')
+                 , mtime_func       # TALES expr (def. 'content/modified')
+                 , max_age_secs     # integer, seconds (def. 0)
+                 , no_cache         # boolean (def. 0)
+                 , no_store         # boolean (def. 0)
+                 , must_revalidate  # boolean (def. 0)
+                 , REQUEST
+                 ):
+        """
+            Add a caching policy.
+        """
+        self._addPolicy( policy_id
+                       , predicate
+                       , mtime_func
+                       , max_age_secs
+                       , no_cache
+                       , no_store
+                       , must_revalidate
+                       )
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                              + '/manage_cachingPolicies'
+                              + '?manage_tabs_message=Policy+added.'
+                              )
+
+    security.declareProtected( ManagePortal, 'updatePolicy' )
+    def updatePolicy( self
+                    , policy_id
+                    , predicate         # TALES expr (def. 'python:1')
+                    , mtime_func        # TALES expr (def. 'content/modified')
+                    , max_age_secs      # integer, seconds
+                    , no_cache          # boolean (def. 0)
+                    , no_store          # boolean (def. 0)
+                    , must_revalidate   # boolean (def. 0)
+                    , REQUEST ):
+        """
+            Update a caching policy.
+        """
+        self._updatePolicy( policy_id
+                          , predicate
+                          , mtime_func
+                          , max_age_secs
+                          , no_cache
+                          , no_store
+                          , must_revalidate
+                          )
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                              + '/manage_cachingPolicies'
+                              + '?manage_tabs_message=Policy+updated.'
+                              )
+
+    security.declareProtected( ManagePortal, 'movePolicyUp' )
+    def movePolicyUp( self, policy_id, REQUEST ):
+        """
+            Move a caching policy up in the list.
+        """
+        policy_ids = list( self._policy_ids )
+        ndx = policy_ids.index( policy_id )
+        if ndx == 0:
+            msg = "Policy+already+first."
+        else:
+            self._reorderPolicy( predicate_id, ndx - 1 )
+            msg = "Policy+moved."
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                              + '/manage_cachingPolicies'
+                              + '?manage_tabs_message=%s' % msg
+                              )
+
+    security.declareProtected( ManagePortal, 'movePolicyDown' )
+    def movePolicyDown( self, policy_id, REQUEST ):
+        """
+            Move a caching policy down in the list.
+        """
+        policy_ids = list( self._policy_ids )
+        ndx = policy_ids.index( policy_id )
+        if ndx == len( policy_ids ) - 1:
+            msg = "Policy+already+last."
+        else:
+            self._reorderPolicy( policy_id, ndx + 1 )
+            msg = "Policy+moved."
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                              + '/manage_cachingPolicies'
+                              + '?manage_tabs_message=%s' % msg
+                              )
+
+    security.declareProtected( ManagePortal, 'removePolicy' )
+    def removePolicy( self, policy_id, REQUEST ):
+        """
+            Remove a caching policy.
+        """
+        self._removePolicy( policy_id )
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                              + '/manage_cachingPolicies'
+                              + '?manage_tabs_message=Policy+removed.'
+                              )
+
+    #
+    #   Policy manipulation methods.
+    #
+    security.declarePrivate( '_addPolicy' )
+    def _addPolicy( self
+                  , policy_id
+                  , predicate
+                  , mtime_func
+                  , max_age_secs
+                  , no_cache
+                  , no_store
+                  , must_revalidate
+                  ):
+        """
+            Add a policy to our registry.
+        """
+        policy_id = str( policy_id ).strip()
+
+        if not policy_id:
+            raise ValueError, "Policy ID is required!"
+
+        if policy_id in self._policy_ids:
+            raise KeyError, "Policy %s already exists!" % policy_id
+
+        self._policies[ policy_id ] = CachingPolicy( policy_id
+                                                   , predicate
+                                                   , mtime_func
+                                                   , max_age_secs
+                                                   , no_cache
+                                                   , no_store
+                                                   , must_revalidate
+                                                   )
+        idlist = list( self._policy_ids )
+        idlist.append( policy_id )
+        self._policy_ids = tuple( idlist )
+
+    security.declarePrivate( '_updatePolicy' )
+    def _updatePolicy( self
+                     , policy_id
+                     , predicate
+                     , mtime_func
+                     , max_age_secs
+                     , no_cache
+                     , no_store
+                     , must_revalidate
+                     ):
+        """
+            Update a policy in our registry.
+        """
+        if policy_id not in self._policy_ids:
+            raise KeyError, "Policy %s does not exist!" % policy_id
+
+        self._policies[ policy_id ] = CachingPolicy( policy_id
+                                                   , predicate
+                                                   , mtime_func
+                                                   , max_age_secs
+                                                   , no_cache
+                                                   , no_store
+                                                   , must_revalidate
+                                                   )
+
+    security.declarePrivate( '_reorderPolicy' )
+    def _reorderPolicy( self, policy_id, newIndex ):
+        """
+            Reorder a policy in our registry.
+        """
+        if policy_id not in self._policy_ids:
+            raise KeyError, "Policy %s does not exist!" % policy_id
+
+        idlist = list( self._policy_ids )
+        ndx = idlist.index( policy_id )
+        pred = idlist[ ndx ]
+        idlist = idlist[ :ndx ] + idlist[ ndx+1: ]
+        idlist.insert( newIndex, pred )
+        self._policy_ids = tuple( idlist )
+
+    security.declarePrivate( '_removePolicy' )
+    def _removePolicy( self, policy_id ):
+        """
+            Remove a policy from our registry.
+        """
+        if policy_id not in self._policy_ids:
+            raise KeyError, "Policy %s does not exist!" % policy_id
+
+        del self._policies[ policy_id ]
+        idlist = list( self._policy_ids )
+        ndx = idlist.index( policy_id )
+        idlist = idlist[ :ndx ] + idlist[ ndx+1: ]
+        self._policy_ids = tuple( idlist )
+
+
+    #
+    #   'portal_caching' interface methods
+    #
+    security.declareProtected( View, 'getHTTPCachingHeaders' )
+    def getHTTPCachingHeaders( self, content, view_method, keywords ):
+        """
+            Return a list of HTTP caching headers based on 'content',
+            'view_method', and 'keywords'.
+        """
+        context = createCPContext( content, view_method, keywords )
+        for policy_id, policy in self.listPolicies():
+
+            headers = policy.getHeaders( context )
+
+            if headers:
+
+                return headers
+
+        return ()
+
+
+InitializeClass( CachingPolicyManager )
+
+def manage_addCachingPolicyManager( self, REQUEST=None ):
+    """
+        Add a CPM to self.
+    """
+    id = CachingPolicyManager.id
+    mgr = CachingPolicyManager()
+    self._setObject( id, mgr )
+
+    if REQUEST is not None:
+        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
+                      + '/manage_main'
+                      + '?manage_tabs_message=Caching+Policy+Manager+added.'
+                      )


=== CMF/CMFCore/FSPageTemplate.py 1.6 => 1.7 ===
 from DateTime import DateTime
 from DocumentTemplate.DT_Util import html_quote
+from Acquisition import aq_parent
 from AccessControl import getSecurityManager, ClassSecurityInfo
 from Shared.DC.Scripts.Script import Script
 from Products.PageTemplates.PageTemplate import PageTemplate
@@ -27,6 +28,7 @@
 from DirectoryView import registerFileExtension, registerMetaType, expandpath
 from CMFCorePermissions import ViewManagementScreens, View, FTPAccess
 from FSObject import FSObject
+from utils import getToolByName
 
 class FSPageTemplate(FSObject, Script, PageTemplate):
     "Wrapper for Page Template"
@@ -95,6 +97,31 @@
     def pt_render(self, source=0, extra_context={}):
         self._updateFromFS()  # Make sure the template has been loaded.
         try:
+            if not source: # Hook up to caching policy.
+
+                REQUEST = getattr( self, 'REQUEST', None )
+
+                if REQUEST:
+
+                    content = aq_parent( self )
+
+                    mgr = getToolByName( content
+                                       , 'caching_policy_manager'
+                                       , None
+                                       )
+
+                    if mgr:
+                        view_name = self.getId()
+                        RESPONSE = REQUEST[ 'RESPONSE' ]
+                        headers = mgr.getHTTPCachingHeaders( content
+                                                           , view_name
+                                                           , extra_context
+                                                           )
+                        for key, value in headers:
+                            RESPONSE.setHeader( key, value )
+
+            return FSPageTemplate.inheritedAttribute('pt_render')( self,
+                    source, extra_context )
             return FSPageTemplate.inheritedAttribute('pt_render')(
                 self, source, extra_context )
         except RuntimeError:
@@ -107,7 +134,6 @@
                 raise RuntimeError, msg
             else:
                 raise
-            
             
     # Copy over more mothods
     security.declareProtected(FTPAccess, 'manage_FTPget')


=== CMF/CMFCore/__init__.py 1.14 => 1.15 ===
 import CookieCrumbler
 import ContentTypeRegistry
+import CachingPolicyManager
 import utils
 
 try:
@@ -91,6 +92,12 @@
     context.registerClass(
         ContentTypeRegistry.ContentTypeRegistry,
         constructors=( ContentTypeRegistry.manage_addRegistry, ),
+        icon = 'images/registry.gif'
+        )
+
+    context.registerClass(
+        CachingPolicyManager.CachingPolicyManager,
+        constructors=( CachingPolicyManager.manage_addCachingPolicyManager, ),
         icon = 'images/registry.gif'
         )