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 | ) |
---|