[3296] | 1 | """ |
---|
| 2 | CacheSetup |
---|
| 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
---|
| 4 | |
---|
| 5 | $Id: $ |
---|
| 6 | """ |
---|
| 7 | |
---|
| 8 | __authors__ = 'Geoff Davis <geoff@geoffdavis.net>' |
---|
| 9 | __docformat__ = 'restructuredtext' |
---|
| 10 | |
---|
| 11 | from AccessControl import ClassSecurityInfo |
---|
| 12 | |
---|
| 13 | from Products.Archetypes.interfaces import IBaseObject |
---|
| 14 | from Products.Archetypes.atapi import BaseContent |
---|
| 15 | from Products.Archetypes.atapi import DisplayList |
---|
| 16 | from Products.Archetypes.atapi import listTypes |
---|
| 17 | from Products.Archetypes.atapi import registerType |
---|
| 18 | from Products.Archetypes.atapi import Schema |
---|
| 19 | |
---|
| 20 | from Products.Archetypes.atapi import BooleanField |
---|
| 21 | from Products.Archetypes.atapi import LinesField |
---|
| 22 | from Products.Archetypes.atapi import StringField |
---|
| 23 | from Products.Archetypes.atapi import TextField |
---|
| 24 | |
---|
| 25 | from Products.Archetypes.atapi import BooleanWidget |
---|
| 26 | from Products.Archetypes.atapi import LinesWidget |
---|
| 27 | from Products.Archetypes.atapi import MultiSelectionWidget |
---|
| 28 | from Products.Archetypes.atapi import StringWidget |
---|
| 29 | from Products.Archetypes.atapi import TextAreaWidget |
---|
| 30 | |
---|
| 31 | from Products.CMFCore import permissions |
---|
| 32 | from Products.CMFCore import Expression |
---|
| 33 | from Products.CMFCore.utils import getToolByName |
---|
| 34 | |
---|
| 35 | from Products.CacheSetup.config import PROJECT_NAME |
---|
| 36 | from Products.CacheSetup.utils import base_hasattr |
---|
| 37 | import base_cache_rule as BaseCacheRule |
---|
| 38 | |
---|
| 39 | schema = BaseContent.schema.copy() + Schema(( |
---|
| 40 | |
---|
| 41 | TextField( |
---|
| 42 | 'description', |
---|
| 43 | required=0, |
---|
| 44 | allowable_content_types = ('text/plain',), |
---|
| 45 | default='A cache rule for CMF content types', |
---|
| 46 | write_permission = permissions.ManagePortal, |
---|
| 47 | widget=TextAreaWidget( |
---|
| 48 | label='Description', |
---|
| 49 | cols=60, |
---|
| 50 | rows=5, |
---|
| 51 | description='Basic documentation for this cache rule')), |
---|
| 52 | |
---|
| 53 | LinesField( |
---|
| 54 | 'contentTypes', |
---|
| 55 | required=1, |
---|
| 56 | default=(), |
---|
| 57 | multiValued = 1, |
---|
| 58 | vocabulary='getContentTypesVocabulary', |
---|
| 59 | write_permission = permissions.ManagePortal, |
---|
| 60 | widget=MultiSelectionWidget( |
---|
| 61 | label='Content Types', |
---|
| 62 | size=10, |
---|
| 63 | description='Please indicate the content types to which this rule applies')), |
---|
| 64 | |
---|
| 65 | BooleanField( |
---|
| 66 | 'defaultView', |
---|
| 67 | default=1, |
---|
| 68 | write_permission = permissions.ManagePortal, |
---|
| 69 | widget=BooleanWidget( |
---|
| 70 | label='Default View', |
---|
| 71 | description='Should this rule apply to the default view of this content object? ' |
---|
| 72 | 'The "default view" is the template that is shown when the user ' |
---|
| 73 | 'navigates to a content object without appending a template ' |
---|
| 74 | 'or view id to the end of the URL.')), |
---|
| 75 | |
---|
| 76 | LinesField( |
---|
| 77 | 'templates', |
---|
| 78 | write_permission = permissions.ManagePortal, |
---|
| 79 | widget=LinesWidget( |
---|
| 80 | label='Templates', |
---|
| 81 | description='IDs for additional templates to which this rule should apply, one ' |
---|
| 82 | 'per line. If the template is a Zope 3-style view, enter its name ' |
---|
| 83 | 'without the @@ prefix. For example, to cache the @@types-controlpanel ' |
---|
| 84 | 'view (probably not a good idea), enter "types-controlpanel"')), |
---|
| 85 | |
---|
| 86 | )) + BaseCacheRule.header_set_schema + BaseCacheRule.etag_schema + Schema(( |
---|
| 87 | |
---|
| 88 | StringField( |
---|
| 89 | 'purgeExpression', |
---|
| 90 | required=0, |
---|
| 91 | write_permission = permissions.ManagePortal, |
---|
| 92 | widget=StringWidget( |
---|
| 93 | label='Purge Expression', |
---|
| 94 | size=80, |
---|
| 95 | description='A TALES expression that generates a list of additional URLs to purge ' |
---|
| 96 | '(URLs should be relative to the portal root) when an object is reindexed. ' |
---|
| 97 | ' Available values: request, object.')), |
---|
| 98 | )) |
---|
| 99 | |
---|
| 100 | schema['id'].widget.ignore_visible_ids=True |
---|
| 101 | schema['id'].widget.description="Should not contain spaces, underscores or mixed case. An 'X-Caching-Rule-Id' header with this id will be added." |
---|
| 102 | |
---|
| 103 | class ContentCacheRule(BaseCacheRule.BaseCacheRule): |
---|
| 104 | """ |
---|
| 105 | """ |
---|
| 106 | security = ClassSecurityInfo() |
---|
| 107 | archetype_name = 'Content Cache Rule' |
---|
| 108 | portal_type = meta_type = 'ContentCacheRule' |
---|
| 109 | __implements__ = BaseCacheRule.BaseCacheRule.__implements__ |
---|
| 110 | schema = schema |
---|
| 111 | _at_rename_after_creation = True |
---|
| 112 | |
---|
| 113 | actions = ( |
---|
| 114 | {'action': 'string:$object_url', |
---|
| 115 | 'category': 'object', |
---|
| 116 | 'id': 'view', |
---|
| 117 | 'name': 'Cache Setup', |
---|
| 118 | 'permissions': (permissions.ManagePortal,), |
---|
| 119 | 'visible': False}, |
---|
| 120 | ) |
---|
| 121 | |
---|
| 122 | aliases = { |
---|
| 123 | '(Default)': 'cache_policy_item_config', |
---|
| 124 | 'view' : 'cache_policy_item_config', |
---|
| 125 | 'edit' : 'cache_policy_item_config' |
---|
| 126 | } |
---|
| 127 | |
---|
| 128 | def getPurgeExpression(self): |
---|
| 129 | expression = self.getField('purgeExpression').get(self) |
---|
| 130 | if expression: |
---|
| 131 | return expression.text |
---|
| 132 | |
---|
| 133 | def setPurgeExpression(self, expression): |
---|
| 134 | return self.getField('purgeExpression').set(self, Expression.Expression(expression)) |
---|
| 135 | |
---|
| 136 | def validate_purgeExpression(self, expression): |
---|
| 137 | return self._validate_expression(expression) |
---|
| 138 | |
---|
| 139 | def getPurgeExpressionValue(self, expr_context): |
---|
| 140 | expression = self.getField('purgeExpression').get(self) |
---|
| 141 | if expression: |
---|
| 142 | return expression(expr_context) |
---|
| 143 | |
---|
| 144 | security.declarePublic('getHeaderSet') |
---|
| 145 | def getHeaderSet(self, request, object, view, member): |
---|
| 146 | # see if this rule applies |
---|
| 147 | if not base_hasattr(object, 'portal_type'): |
---|
| 148 | return |
---|
| 149 | if object.portal_type not in self.getContentTypes(): |
---|
| 150 | return None |
---|
| 151 | if view not in self.getTemplates() and \ |
---|
| 152 | not (self.getDefaultView() and view == self.getObjectDefaultView(object)): |
---|
| 153 | return None |
---|
| 154 | |
---|
| 155 | header_set = self._getHeaderSet(request, object, view, member) |
---|
| 156 | |
---|
| 157 | # associate template with PageCacheManager |
---|
| 158 | if header_set and header_set.getPageCache(): |
---|
| 159 | self._associateTemplate(object, view) |
---|
| 160 | |
---|
| 161 | return header_set |
---|
| 162 | |
---|
| 163 | |
---|
| 164 | def _getViewsUrlsForObject(self, object): |
---|
| 165 | """return a list of relative URLs to the possible views for an object""" |
---|
| 166 | suffixes = [] |
---|
| 167 | if self.getDefaultView(): |
---|
| 168 | suffixes.extend(['', '/', '/view', '/'+self.getObjectDefaultView(object)]) |
---|
| 169 | templates = self.getTemplates() |
---|
| 170 | if templates: |
---|
| 171 | for t in templates: |
---|
| 172 | if t: |
---|
| 173 | if t.startswith('/'): |
---|
| 174 | suffixes.append(t) |
---|
| 175 | else: |
---|
| 176 | suffixes.append('/'+t) |
---|
| 177 | |
---|
| 178 | return suffixes |
---|
| 179 | |
---|
| 180 | |
---|
| 181 | def _getObjectFieldUrls(self, object): |
---|
| 182 | """return a list of relative URLs to fields for an object""" |
---|
| 183 | if not IBaseObject.providedBy(object): |
---|
| 184 | return [] |
---|
| 185 | |
---|
| 186 | schema = object.Schema() |
---|
| 187 | |
---|
| 188 | def InterestingField(field): |
---|
| 189 | # Argh. Evil Z2 interfaces alert. |
---|
| 190 | return field.getType() in ["Products.Archetypes.Field.ImageField", |
---|
| 191 | "Products.Archetypes.Field.FieldField"] |
---|
| 192 | |
---|
| 193 | urls=[] |
---|
| 194 | for field in schema.filterFields(InterestingField): |
---|
| 195 | baseurl="/"+field.getName() |
---|
| 196 | urls.append(baseurl) |
---|
| 197 | |
---|
| 198 | if field.getType()=="Products.Archetypes.Field.ImageField": |
---|
| 199 | for size in field.getAvailableSizes(object).keys(): |
---|
| 200 | urls.append("%s_%s" % (baseurl, size)) |
---|
| 201 | |
---|
| 202 | return urls |
---|
| 203 | |
---|
| 204 | |
---|
| 205 | |
---|
| 206 | security.declarePublic('getRelativeUrlsToPurge') |
---|
| 207 | def getRelativeUrlsToPurge(self, object, urls): |
---|
| 208 | if object.portal_type == "Discussion Item": |
---|
| 209 | object=self.plone_utils.getDiscussionThread(object)[0] |
---|
| 210 | |
---|
| 211 | # Abort if this is not a known content |
---|
| 212 | if object.portal_type not in self.getContentTypes(): |
---|
| 213 | return |
---|
| 214 | |
---|
| 215 | suffixes=self._getViewsUrlsForObject(object) |
---|
| 216 | suffixes.extend(self._getObjectFieldUrls(object)) |
---|
| 217 | |
---|
| 218 | if suffixes: |
---|
| 219 | url_tool = getToolByName(self, 'portal_url') |
---|
| 220 | obj_url = url_tool.getRelativeUrl(object) |
---|
| 221 | urls.union_update([obj_url + s for s in suffixes]) |
---|
| 222 | portal = url_tool.getPortalObject() |
---|
| 223 | if object.aq_base is not portal: |
---|
| 224 | parent = object.getParentNode() |
---|
| 225 | parent_default_view = self.getObjectDefaultView(parent) |
---|
| 226 | if object.getId() == parent_default_view: |
---|
| 227 | parent_url = url_tool.getRelativeUrl(parent) |
---|
| 228 | urls.union_update([parent_url + s for s in ('','/','/view', '/'+parent_default_view)]) |
---|
| 229 | |
---|
| 230 | |
---|
| 231 | purge_expression = self.getPurgeExpression() |
---|
| 232 | if purge_expression: |
---|
| 233 | expr_context = self._getExpressionContext(self.REQUEST, object, None, None) |
---|
| 234 | urls.union_update(self.getPurgeExpressionValue(expr_context)) |
---|
| 235 | |
---|
| 236 | def getContentTypesVocabulary(self): |
---|
| 237 | tt = getToolByName(self, 'portal_types') |
---|
| 238 | types_list = [] |
---|
| 239 | |
---|
| 240 | from Products.Archetypes.public import listTypes |
---|
| 241 | cachefu_types = [t['portal_type'] for t in listTypes(PROJECT_NAME)] |
---|
| 242 | |
---|
| 243 | atct_criteria = [] |
---|
| 244 | try: |
---|
| 245 | from Products.ATContentTypes.config import PROJECTNAME as ATCTNAME |
---|
| 246 | from Products.ATContentTypes.interfaces import IATTopicCriterion |
---|
| 247 | for t in listTypes(ATCTNAME): |
---|
| 248 | if IATTopicCriterion.isImplementedByInstancesOf(t['klass']): |
---|
| 249 | atct_criteria.append(t['portal_type']) |
---|
| 250 | except: |
---|
| 251 | pass |
---|
| 252 | |
---|
| 253 | for t in tt.listTypeInfo(): |
---|
| 254 | # filter out a few types |
---|
| 255 | id = t.getId() |
---|
| 256 | if id == 'TempFolder': |
---|
| 257 | continue |
---|
| 258 | if id in cachefu_types: |
---|
| 259 | continue |
---|
| 260 | if id in atct_criteria: |
---|
| 261 | continue |
---|
| 262 | |
---|
| 263 | title = t.getProperty('title') |
---|
| 264 | if not title: |
---|
| 265 | title = id |
---|
| 266 | else: |
---|
| 267 | if title != id: |
---|
| 268 | title = '%s (%s)' % (title, id) |
---|
| 269 | types_list.append((id, title)) |
---|
| 270 | types_list.sort(lambda x, y: cmp(x[1], y[1])) |
---|
| 271 | return DisplayList(tuple(types_list)) |
---|
| 272 | |
---|
| 273 | registerType(ContentCacheRule, PROJECT_NAME) |
---|
| 274 | |
---|
| 275 | __all__ = ( |
---|
| 276 | 'ContentCacheRule', |
---|
| 277 | ) |
---|