""" CacheSetup ~~~~~~~~~~~~~~~~~~~~~~~~~~~ $Id: $ """ __authors__ = 'Geoff Davis ' __docformat__ = 'restructuredtext' from AccessControl import ClassSecurityInfo from Products.Archetypes.interfaces import IBaseObject from Products.Archetypes.atapi import BaseContent from Products.Archetypes.atapi import DisplayList from Products.Archetypes.atapi import listTypes from Products.Archetypes.atapi import registerType from Products.Archetypes.atapi import Schema from Products.Archetypes.atapi import BooleanField from Products.Archetypes.atapi import LinesField from Products.Archetypes.atapi import StringField from Products.Archetypes.atapi import TextField from Products.Archetypes.atapi import BooleanWidget from Products.Archetypes.atapi import LinesWidget from Products.Archetypes.atapi import MultiSelectionWidget from Products.Archetypes.atapi import StringWidget from Products.Archetypes.atapi import TextAreaWidget from Products.CMFCore import permissions from Products.CMFCore import Expression from Products.CMFCore.utils import getToolByName from Products.CacheSetup.config import PROJECT_NAME from Products.CacheSetup.utils import base_hasattr import base_cache_rule as BaseCacheRule schema = BaseContent.schema.copy() + Schema(( TextField( 'description', required=0, allowable_content_types = ('text/plain',), default='A cache rule for CMF content types', write_permission = permissions.ManagePortal, widget=TextAreaWidget( label='Description', cols=60, rows=5, description='Basic documentation for this cache rule')), LinesField( 'contentTypes', required=1, default=(), multiValued = 1, vocabulary='getContentTypesVocabulary', write_permission = permissions.ManagePortal, widget=MultiSelectionWidget( label='Content Types', size=10, description='Please indicate the content types to which this rule applies')), BooleanField( 'defaultView', default=1, write_permission = permissions.ManagePortal, widget=BooleanWidget( label='Default View', description='Should this rule apply to the default view of this content object? ' 'The "default view" is the template that is shown when the user ' 'navigates to a content object without appending a template ' 'or view id to the end of the URL.')), LinesField( 'templates', write_permission = permissions.ManagePortal, widget=LinesWidget( label='Templates', description='IDs for additional templates to which this rule should apply, one ' 'per line. If the template is a Zope 3-style view, enter its name ' 'without the @@ prefix. For example, to cache the @@types-controlpanel ' 'view (probably not a good idea), enter "types-controlpanel"')), )) + BaseCacheRule.header_set_schema + BaseCacheRule.etag_schema + Schema(( StringField( 'purgeExpression', required=0, write_permission = permissions.ManagePortal, widget=StringWidget( label='Purge Expression', size=80, description='A TALES expression that generates a list of additional URLs to purge ' '(URLs should be relative to the portal root) when an object is reindexed. ' ' Available values: request, object.')), )) schema['id'].widget.ignore_visible_ids=True schema['id'].widget.description="Should not contain spaces, underscores or mixed case. An 'X-Caching-Rule-Id' header with this id will be added." class ContentCacheRule(BaseCacheRule.BaseCacheRule): """ """ security = ClassSecurityInfo() archetype_name = 'Content Cache Rule' portal_type = meta_type = 'ContentCacheRule' __implements__ = BaseCacheRule.BaseCacheRule.__implements__ schema = schema _at_rename_after_creation = True actions = ( {'action': 'string:$object_url', 'category': 'object', 'id': 'view', 'name': 'Cache Setup', 'permissions': (permissions.ManagePortal,), 'visible': False}, ) aliases = { '(Default)': 'cache_policy_item_config', 'view' : 'cache_policy_item_config', 'edit' : 'cache_policy_item_config' } def getPurgeExpression(self): expression = self.getField('purgeExpression').get(self) if expression: return expression.text def setPurgeExpression(self, expression): return self.getField('purgeExpression').set(self, Expression.Expression(expression)) def validate_purgeExpression(self, expression): return self._validate_expression(expression) def getPurgeExpressionValue(self, expr_context): expression = self.getField('purgeExpression').get(self) if expression: return expression(expr_context) security.declarePublic('getHeaderSet') def getHeaderSet(self, request, object, view, member): # see if this rule applies if not base_hasattr(object, 'portal_type'): return if object.portal_type not in self.getContentTypes(): return None if view not in self.getTemplates() and \ not (self.getDefaultView() and view == self.getObjectDefaultView(object)): return None header_set = self._getHeaderSet(request, object, view, member) # associate template with PageCacheManager if header_set and header_set.getPageCache(): self._associateTemplate(object, view) return header_set def _getViewsUrlsForObject(self, object): """return a list of relative URLs to the possible views for an object""" suffixes = [] if self.getDefaultView(): suffixes.extend(['', '/', '/view', '/'+self.getObjectDefaultView(object)]) templates = self.getTemplates() if templates: for t in templates: if t: if t.startswith('/'): suffixes.append(t) else: suffixes.append('/'+t) return suffixes def _getObjectFieldUrls(self, object): """return a list of relative URLs to fields for an object""" if not IBaseObject.providedBy(object): return [] schema = object.Schema() def InterestingField(field): # Argh. Evil Z2 interfaces alert. return field.getType() in ["Products.Archetypes.Field.ImageField", "Products.Archetypes.Field.FieldField"] urls=[] for field in schema.filterFields(InterestingField): baseurl="/"+field.getName() urls.append(baseurl) if field.getType()=="Products.Archetypes.Field.ImageField": for size in field.getAvailableSizes(object).keys(): urls.append("%s_%s" % (baseurl, size)) return urls security.declarePublic('getRelativeUrlsToPurge') def getRelativeUrlsToPurge(self, object, urls): if object.portal_type == "Discussion Item": object=self.plone_utils.getDiscussionThread(object)[0] # Abort if this is not a known content if object.portal_type not in self.getContentTypes(): return suffixes=self._getViewsUrlsForObject(object) suffixes.extend(self._getObjectFieldUrls(object)) if suffixes: url_tool = getToolByName(self, 'portal_url') obj_url = url_tool.getRelativeUrl(object) urls.union_update([obj_url + s for s in suffixes]) portal = url_tool.getPortalObject() if object.aq_base is not portal: parent = object.getParentNode() parent_default_view = self.getObjectDefaultView(parent) if object.getId() == parent_default_view: parent_url = url_tool.getRelativeUrl(parent) urls.union_update([parent_url + s for s in ('','/','/view', '/'+parent_default_view)]) purge_expression = self.getPurgeExpression() if purge_expression: expr_context = self._getExpressionContext(self.REQUEST, object, None, None) urls.union_update(self.getPurgeExpressionValue(expr_context)) def getContentTypesVocabulary(self): tt = getToolByName(self, 'portal_types') types_list = [] from Products.Archetypes.public import listTypes cachefu_types = [t['portal_type'] for t in listTypes(PROJECT_NAME)] atct_criteria = [] try: from Products.ATContentTypes.config import PROJECTNAME as ATCTNAME from Products.ATContentTypes.interfaces import IATTopicCriterion for t in listTypes(ATCTNAME): if IATTopicCriterion.isImplementedByInstancesOf(t['klass']): atct_criteria.append(t['portal_type']) except: pass for t in tt.listTypeInfo(): # filter out a few types id = t.getId() if id == 'TempFolder': continue if id in cachefu_types: continue if id in atct_criteria: continue title = t.getProperty('title') if not title: title = id else: if title != id: title = '%s (%s)' % (title, id) types_list.append((id, title)) types_list.sort(lambda x, y: cmp(x[1], y[1])) return DisplayList(tuple(types_list)) registerType(ContentCacheRule, PROJECT_NAME) __all__ = ( 'ContentCacheRule', )