1
# -*- test-case-name: twisted.web.test.test_woven -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
__version__ = "$Revision: 1.53 $"[11:-2]
12
from zope.interface import implements
14
from twisted.python import components, reflect
15
from twisted.internet import defer
17
from twisted.web.woven import interfaces
21
def adaptToIModel(m, parent=None, submodel=None):
22
adapted = interfaces.IModel(m, None)
25
adapted.parent = parent
26
adapted.name = submodel
32
A Model which keeps track of views which are looking at it in order
33
to notify them when the model changes.
35
implements(interfaces.IModel)
37
def __init__(self, *args, **kwargs):
39
self.original = args[0]
47
self._getter = kwargs.get('getter')
48
self._setter = kwargs.get('setter')
50
self.initialize(*args, **kwargs)
52
def __getstate__(self):
58
def invalidateCache(self):
59
"""Invalidate the cache for this object, so the next time
60
getData is called, it's getter method is called again.
64
def initialize(self, *args, **kwargs):
66
Hook for subclasses to initialize themselves without having to
67
mess with the __init__ chain.
71
def addView(self, view):
73
Add a view for the model to keep track of.
75
if view not in [ref() for ref in self.views]:
76
self.views.append(weakref.ref(view))
78
def addSubview(self, name, subview):
79
subviewList = self.subviews.get(name, [])
80
subviewList.append(weakref.ref(subview))
81
self.subviews[name] = subviewList
83
def removeView(self, view):
85
Remove a view that the model no longer should keep track of.
87
# AM: loop on a _copy_ of the list, since we're changing it!!!
88
for weakref in list(self.views):
90
if ref is view or ref is None:
91
self.views.remove(weakref)
93
def setGetter(self, getter):
96
def setSetter(self, setter):
99
def notify(self, changed=None):
101
Notify all views that something was changed on me.
102
Passing a dictionary of {'attribute': 'new value'} in changed
103
will pass this dictionary to the view for increased performance.
104
If you don't want to do this, don't, and just use the traditional
105
MVC paradigm of querying the model for things you're interested
108
self.cachedFor = None
109
if changed is None: changed = {}
111
# AM: loop on a _copy_ of the list, since we're changing it!!!
112
for view in list(self.views):
115
retVal.append((ref, ref.modelChanged(changed)))
117
self.views.remove(view)
118
for key, value in self.subviews.items():
119
if value.wantsAllNotifications or changed.has_key(key):
120
for item in list(value):
123
retVal.append((ref, ref.modelChanged(changed)))
128
protected_names = ['initialize', 'addView', 'addSubview', 'removeView', 'notify', 'getSubmodel', 'setSubmodel', 'getData', 'setData']
131
def lookupSubmodel(self, request, submodelName):
133
Look up a full submodel name. I will split on `/' and call
134
L{getSubmodel} on each element in the 'path'.
136
Override me if you don't want 'traversing'-style lookup, but
137
would rather like to look up a model based on the entire model
140
If you override me to return Deferreds, make sure I look up
141
values in a cache (created by L{setSubmodel}) before doing a
142
regular Deferred lookup.
144
XXX: Move bits of this docstring to interfaces.py
149
# Special case: If the first character is /
150
# Start at the bottom of the model stack
152
if submodelName[0] == '/':
153
while currentModel.parent is not None:
154
currentModel = currentModel.parent
155
submodelName = submodelName[1:]
157
submodelList = submodelName.split('/') #[:-1]
158
# print "submodelList", submodelList
159
for element in submodelList:
160
if element == '.' or element == '':
162
elif element == '..':
163
currentModel = currentModel.parent
165
currentModel = currentModel.getSubmodel(request, element)
166
if currentModel is None:
170
def submodelCheck(self, request, name):
171
"""Check if a submodel name is allowed. Subclass me to implement a
172
name security policy.
174
if self.allowed_names:
175
return (name in self.allowed_names)
177
return (name and name[0] != '_' and name not in self.protected_names)
180
def submodelFactory(self, request, name):
181
warnings.warn("Warning: default Model lookup strategy is changing:"
182
"use either AttributeModel or MethodModel for now.",
184
if hasattr(self, name):
185
return getattr(self, name)
189
def getSubmodel(self, request, name):
191
Get the submodel `name' of this model. If I ever return a
192
Deferred, then I ought to check for cached values (created by
193
L{setSubmodel}) before doing a regular Deferred lookup.
195
if self.submodels.has_key(name):
196
return self.submodels[name]
197
if not self.submodelCheck(request, name):
199
m = self.submodelFactory(request, name)
202
sm = adaptToIModel(m, self, name)
203
self.submodels[name] = sm
206
def setSubmodel(self, request=None, name=None, value=None):
208
Set a submodel on this model. If getSubmodel or lookupSubmodel
209
ever return a Deferred, I ought to set this in a place that
210
lookupSubmodel/getSubmodel know about, so they can use it as a
213
if self.submodelCheck(request, name):
214
if self.submodels.has_key(name):
215
del self.submodels[name]
216
setattr(self, name, value)
218
def dataWillChange(self):
221
def getData(self, request):
222
if self.cachedFor != id(request) and self._getter is not None:
223
self.cachedFor = id(request)
224
self.dataWillChange()
225
self.orig = self.original = self._getter(request)
228
def setData(self, request, data):
229
if self._setter is not None:
230
self.cachedFor = None
231
return self._setter(request, data)
233
if hasattr(self, 'parent') and self.parent:
234
self.parent.setSubmodel(request, self.name, data)
235
self.orig = self.original = data
238
class MethodModel(Model):
239
"""Look up submodels with wmfactory_* methods.
242
def submodelCheck(self, request, name):
243
"""Allow any submodel for which I have a submodel.
245
return hasattr(self, "wmfactory_"+name)
247
def submodelFactory(self, request, name):
248
"""Call a wmfactory_name method on this model.
250
meth = getattr(self, "wmfactory_"+name)
253
def getSubmodel(self, request=None, name=None):
255
warnings.warn("Warning! getSubmodel should now take the request as the first argument")
259
cached = self.submodels.has_key(name)
260
sm = Model.getSubmodel(self, request, name)
263
sm.cachedFor = id(request)
264
sm._getter = getattr(self, "wmfactory_"+name)
268
class AttributeModel(Model):
269
"""Look up submodels as attributes with hosts.allow/deny-style security.
271
def submodelFactory(self, request, name):
272
if hasattr(self, name):
273
return getattr(self, name)
278
#backwards compatibility
282
class Wrapper(Model):
284
I'm a generic wrapper to provide limited interaction with the
285
Woven models and submodels.
289
def __init__(self, orig):
291
self.orig = self.original = orig
293
def dataWillChange(self):
297
myLongName = reflect.qual(self.__class__)
298
return "<%s instance at 0x%x: wrapped data: %s>" % (myLongName,
299
id(self), self.original)
302
class ListModel(Wrapper):
304
I wrap a Python list and allow it to interact with the Woven
305
models and submodels.
307
def dataWillChange(self):
310
def getSubmodel(self, request=None, name=None):
311
if name is None and type(request) is type(""):
312
warnings.warn("Warning!")
315
if self.submodels.has_key(name):
316
return self.submodels[name]
324
sm = adaptToIModel(orig[i], self, name)
325
self.submodels[name] = sm
328
def setSubmodel(self, request=None, name=None, value=None):
330
warnings.warn("Warning!")
334
self.original[int(name)] = value
337
return len(self.original)
339
def __getitem__(self, name):
340
return self.getSubmodel(None, str(name))
342
def __setitem__(self, name, value):
343
self.setSubmodel(None, str(name), value)
346
myLongName = reflect.qual(self.__class__)
347
return "<%s instance at 0x%x: wrapped data: %s>" % (myLongName,
348
id(self), self.original)
351
class StringModel(ListModel):
353
""" I wrap a Python string and allow it to interact with the Woven models
356
def setSubmodel(self, request=None, name=None, value=None):
357
raise ValueError("Strings are immutable.")
360
# pyPgSQL returns "PgResultSet" instances instead of lists, which look, act
361
# and breathe just like lists. pyPgSQL really shouldn't do this, but this works
363
from pyPgSQL import PgSQL
364
components.registerAdapter(ListModel, PgSQL.PgResultSet, interfaces.IModel)
368
class DictionaryModel(Wrapper):
370
I wrap a Python dictionary and allow it to interact with the Woven
371
models and submodels.
373
def dataWillChange(self):
376
def getSubmodel(self, request=None, name=None):
377
if name is None and type(request) is type(""):
378
warnings.warn("getSubmodel must get a request argument now")
381
if self.submodels.has_key(name):
382
return self.submodels[name]
386
sm = adaptToIModel(orig[name], self, name)
387
self.submodels[name] = sm
390
def setSubmodel(self, request=None, name=None, value=None):
392
warnings.warn("Warning!")
396
self.original[name] = value
399
class AttributeWrapper(Wrapper):
401
I wrap an attribute named "name" of the given parent object.
403
def __init__(self, parent, name):
405
parent = ObjectWrapper(parent)
406
Wrapper.__init__(self, parent.getSubmodel(None, name))
411
class ObjectWrapper(Wrapper):
413
I may wrap an object and allow it to interact with the Woven models
414
and submodels. By default, I am not registered for use with anything.
416
def getSubmodel(self, request=None, name=None):
417
if name is None and type(request) is type(""):
418
warnings.warn("Warning!")
421
if self.submodels.has_key(name):
422
return self.submodels[name]
423
sm = adaptToIModel(getattr(self.original, name), self, name)
424
self.submodels[name] = sm
427
def setSubmodel(self, request=None, name=None, value=None):
429
warnings.warn("Warning!")
433
setattr(self.original, name, value)
435
class UnsafeObjectWrapper(ObjectWrapper):
437
I may wrap an object and allow it to interact with the Woven models
438
and submodels. By default, I am not registered for use with anything.
439
I am unsafe because I allow methods to be called. In fact, I am
440
dangerously unsafe. Be wary or I will kill your security model!
442
def getSubmodel(self, request=None, name=None):
443
if name is None and type(request) is type(""):
444
warnings.warn("Warning!")
447
if self.submodels.has_key(name):
448
return self.submodels[name]
449
value = getattr(self.original, name)
452
sm = adaptToIModel(value, self, name)
457
class DeferredWrapper(Wrapper):
458
def setData(self, request=None, data=_Nothing):
460
warnings.warn("setData should be called with request as first arg")
463
if isinstance(data, defer.Deferred):
466
views, subviews = self.views, self.subviews
467
new = adaptToIModel(data, self.parent, self.name)
468
self.__class__ = new.__class__
469
self.__dict__ = new.__dict__
470
self.views, self.subviews = views, subviews
472
class Link(AttributeModel):
473
def __init__(self, href, text):
474
AttributeModel.__init__(self)
479
components.registerAdapter(StringModel, types.StringType, interfaces.IModel)
480
components.registerAdapter(ListModel, types.ListType, interfaces.IModel)
481
components.registerAdapter(ListModel, types.TupleType, interfaces.IModel)
482
components.registerAdapter(DictionaryModel, types.DictionaryType, interfaces.IModel)
483
components.registerAdapter(DeferredWrapper, defer.Deferred, interfaces.IModel)
484
components.registerAdapter(DeferredWrapper, defer.DeferredList, interfaces.IModel)
486
# The adapters were already registered