root/cpsskins/branches/paris-sprint-2006/locations/README.txt

Revision 3596, 9.3 kB (checked in by jmorliaguet, 3 years ago)

- test update: scopes with a non-zero end do not work as expected

- added a scope (all immediate sub-folders)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1
2 $Id$
3
4 =========
5 LOCATIONS
6 =========
7
8 Locations are used to store local site data in a central place.
9
10 Background
11 ----------
12
13 By design, themes are stored entirely in a Theme Management Folder.
14
15 This has the following advantages:
16
17 - the application can run on virtually all zope-based platforms since the
18   contact surface against the underlying application is limited to a single
19   folder.
20
21 - themes can be designed and tested before the actual site exists.
22
23 - theme-related security policy is kept entirely separate from the
24   application's own security policy.
25
26 - high performance since the data is directly available (no traversals, or
27   complex catalog queries are necessary)
28
29 - the import and export of data is very straightforward (see cpsskins.setup.io)
30
31
32 The downsides are:
33
34 - all "local" information must also be stored in a central location.
35
36 - all locations used by the underlying application must be identifiable, and
37   modifications must be monitored.
38
39
40 Design
41 ------
42
43 We have to maintain a mapping between locations and some data.
44
45 We assume that objects are locatable, i.e. by knowing the object we can know its
46 location reliably.
47
48
49 Containment
50 ...........
51
52 Given a location we can find the set of locations that are contained in it and
53 another set of locations that it contains.
54
55 Locations contained in a given location are called 'sublocations'.
56
57 Since we can't keep information about *all* the locations of a site we
58 must store some information about how locations and related to sublocations
59
60
61 Rules of inheritence
62 ....................
63
64 The "standard" behaviour for locations is to follow some sort of inheritence,
65 i.e. what is true of a given location is also true of its sublocations.
66
67 This behaviour can be declined into various rules:
68
69 - what is true of a location applies only to the location itself
70
71 - what is true of a location applies only to its sublocations
72
73 - what is true of a location applies only to the parent location
74
75 - ...
76
77 For each rule we have a location, a scope and some data. The scope determines
78 the locations to which the rule applies.
79
80 In order to avoid conflicting statements we must assume that some rules
81 have precedence over others. To determine the order in which rules are to be
82 applied in a given location context we look for the nearest locations first.
83
84
85 Nearest location in a context
86 .............................
87
88 All location information will be looked up from a given context which can
89 considered as the sublocation of a nearest location.
90
91 For instance in the context of folder1/folder2/folder3/, among the following
92 locations:
93
94  - /folder1/
95
96  - /folder1/folder2/
97
98 the former is the nearest location, because the location context is contained
99 in it and because it is contained in all other locations.
100
101 We can sort locations as:
102
103  /folder1/ > /folder1/folder2/ > /folder1/folder2/folder3/
104
105  (with '>' meaning 'contains')
106
107 the rules associated with the location 'folder1' will shadow the rules
108 of associated with '/folder1/folder2/'
109
110 If no nearest locations can be found in a given context then no rules will be
111 applied, and the context will contain no local information.
112
113
114 Applying rules
115 ..............
116
117 Rules are applied starting from the one that has the nearest location until
118 its scope covers the location context. The rule's data can then be associated
119 to the location context.
120
121 Implementation
122 --------------
123
124 We create 2 locations:
125
126     >>> from cpsskins.locations import Location
127
128     >>> l1 = Location(path=u'/f1/', data=u'd1')
129     >>> l1
130     <Location at /f1/>
131
132     >>> l2 = Location(path=u'/f1/f2/', data=u'd1/2')
133     >>> l2
134     <Location at /f1/f2/>
135
136 to get the location's data, we call the location:
137
138     >>> l1()
139     u'd1'
140
141     >>> l2()
142     u'd1/2'
143
144 the two locations can be compared:
145
146     >>> l1 == l2
147     False
148
149     >>> l1 > l2
150     True
151
152     >>> l2 < l1
153     True
154
155 if two locations have a same path, they are equal:
156
157     >>> lA = Location(path=u'/f1/f2/', data=u'A')
158     >>> lB = Location(path=u'/f1/f2/', data=u'B')
159
160     >>> lA == lB
161     True
162
163 We create a location storage:
164
165     >>> from cpsskins.storage.locations import LocationStorage
166     >>> locations = LocationStorage()
167
168 we add the locations to the storage:
169
170     >>> locations.add(l1)
171     >>> locations.add(l2)
172
173     >>> from pprint import pprint
174     >>> pprint(dict(locations))
175     {(u'', u'', u'', u'f1'): <Location at /f1/>,
176      (u'', u'', u'', u'f1', u'f2'): <Location at /f1/f2/>}
177
178 we can obtain the list of locations with:
179
180     >>> locations.getPaths()
181     [(u'', u'f1'), (u'', u'f1', u'f2')]
182
183
184 now we want to find the location of '/f1/f2/f3':
185
186     >>> locations.find(u'/f1/f2/f3/')
187     <Location at /f1/f2/>
188
189 or get the location of '/f1/f2/'
190
191     >>> locations.find(u'/f1/f2/')
192     <Location at /f1/f2/>
193
194 or '/f1':
195
196     >>> locations.find(u'/f1/')
197     <Location at /f1/>
198
199     >>> locations.find(u'/f2/') is None
200     True
201
202 scopes
203 ......
204
205 so far the locations that we have created did not specify any scope. By
206 default their scope was (0, 0), meaning:
207
208 - starting from the location's path to all sublocations
209
210 By specifying a scope we can restrict the paths covered by a location, for
211 instance:
212
213     >>> l3 = Location(path=u'/f1/f3/', data=u'C', scope=(0, 1))
214     >>> locations.add(l3)
215
216     >>> l3
217     <Location at /f1/f3/>
218
219 - the first element of the couple is the position starting from the location.
220 - the second element of the couple is the end position relative to the
221   location. 1 is the level of the location, 2 is the level of immediate
222   sublocation, 0 means no limit.
223
224 (0, 1) means that the 'l3' location at /f1/f3/ has a scope covering /f1/f3/
225 only.
226
227
228     >>> locations.find(u'/f1/f3/')
229     <Location at /f1/f3/>
230
231     >>> locations.find(u'/f1/f3/f4/')
232     <Location at /f1/>
233
234     >>> locations.find(u'/f1/f3/f4/f5/')
235     <Location at /f1/>
236
237
238 l4 has a scope (1, 0) covering all sublocations of '/f1/f4/'.
239
240     >>> l4 = Location(path=u'/f1/f4/', data=u'D', scope=(1, 0))
241     >>> locations.add(l4)
242
243     >>> locations.find(u'/f1/f4/')
244     <Location at /f1/>
245
246     >>> locations.find(u'/f1/f4/f5/')
247     <Location at /f1/f4/>
248
249     >>> locations.find(u'/f1/f4/f5/f6/')
250     <Location at /f1/f4/>
251
252     >>> locations.find(u'/f1/f4/f5/f6/f7/')
253     <Location at /f1/f4/>
254
255
256 l5 has a scope (1, 2) covering the immediate sublocations of '/f1/f5' only:
257
258     >>> l5 = Location(path=u'/f1/f5/', data=u'E', scope=(1, 2))
259     >>> locations.add(l5)
260
261     >>> locations.find(u'/f1/f5/')
262     <Location at /f1/>
263
264     >>> locations.find(u'/f1/f5/f6/')
265     <Location at /f1/f5/>
266
267     >>> locations.find(u'/f1/f5/f6/f7/')
268     <Location at /f1/>
269
270
271 l6 has a scope (2, 0) covering the '/f1/f6/' location's sublocations of level
272 2 or more:
273
274     >>> l6 = Location(path=u'/f1/f6/', data=u'F', scope=(2, 0))
275     >>> locations.add(l6)
276
277     >>> locations.find(u'/f1/f6/')
278     <Location at /f1/>
279
280     >>> locations.find(u'/f1/f6/f7/')
281     <Location at /f1/>
282
283     >>> locations.find(u'/f1/f6/f7/f8/')
284     <Location at /f1/f6/>
285
286     >>> locations.find(u'/f1/f6/f7/f8/f9/')
287     <Location at /f1/f6/>
288
289     >>> locations.find(u'/f1/f6/f7/f8/f9/f10/')
290     <Location at /f1/f6/>
291
292
293 namespaces
294 ..........
295
296 Namespaces can be created by specifying a location root:
297
298     >>> l7 = Location(path=u'/f1/', data=u'G', root=u'pages')
299     >>> locations.add(l7)
300
301     >>> locations.find(u'/f1/', root=u'pages')
302     <Location at /f1/ for 'pages'>
303
304     >>> l8 = Location(path=u'/f1/', data=u'H', root=u'engines')
305     >>> locations.add(l8)
306
307     >>> locations.find(u'/f1/', root=u'engines')
308     <Location at /f1/ for 'engines'>
309
310 we get the list of roots with:
311
312     >>> locations.getRoots()
313     [u'', u'pages', u'engines']
314
315 we get the list of location paths with:
316
317     >>> locations.getPaths(u'pages')
318     [(u'', u'f1')]
319
320 or with:
321
322     >>> locations.getPaths(u'engines')
323     [(u'', u'f1')]
324
325
326 to obtain all the paths we use:
327
328     >>> locations.getAllPaths() # doctest: +NORMALIZE_WHITESPACE
329     [(u'', u'f1', u'f2', u''), (u'', u'f1', u'f3', u''),
330      (u'', u'f1', u'f6', u''), (u'', u'f1', u''), (u'', u'f1', u'f4', u''),
331      (u'', u'f1', u'f5', u'')]
332
333 view names
334 ...........
335
336 View or method names can be included in paths:
337
338 if a location has a /f10/ path, all locations such as /f10/_____ will
339 match the location:
340
341     >>> l10 = Location(path=u'/f10/', data=u'any')
342     >>> locations.add(l10)
343
344     >>> locations.find(u'/f10/edit.html')
345     <Location at /f10/>
346
347     >>> locations.find(u'/f10/view.html')
348     <Location at /f10/>
349
350 but if we add a location where the method name is explicitly specified we
351 get that location instead:
352
353     >>> l10b = Location(path=u'/f10/edit.html', data=u'edit')
354     >>> locations.add(l10b)
355
356     >>> locations.find(u'/f10/edit.html')
357     <Location at /f10/edit.html>
358
359     >>> locations.find(u'/f10/view.html')
360     <Location at /f10/>
361
362
363 if only the method name is specified, all paths that have a method with that
364 name will match:
365
366     >>> l11 = Location(path=u'view.html', data=u'view')
367     >>> locations.add(l11)
368
369     >>> locations.find(u'/f10/view.html')
370     <Location at view.html>
371
372     >>> locations.find(u'/f10/f11/view.html')
373     <Location at view.html>
374
375
376 in order to register a location with only a method name as the path no other
377 location must have been registered with the same name already:
378
379     >>> l12 = Location(path=u'edit.html', data=u'edit')
380     >>> locations.add(l12) # doctest: +ELLIPSIS
381     Traceback (most recent call last):
382     ...
383     KeyError: u"The 'edit.html' method name is already registered ..."
384
385
386 we purge the entire storage:
387
388     >>> locations.purge()
389
390     >>> list(locations)
391     []
Note: See TracBrowser for help on using the browser.