~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web/woven/model.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_woven -*-
 
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
 
 
6
__version__ = "$Revision: 1.53 $"[11:-2]
 
7
 
 
8
import types
 
9
import weakref
 
10
import warnings
 
11
 
 
12
from zope.interface import implements
 
13
 
 
14
from twisted.python import components, reflect
 
15
from twisted.internet import defer
 
16
 
 
17
from twisted.web.woven import interfaces
 
18
 
 
19
class _Nothing: pass
 
20
 
 
21
def adaptToIModel(m, parent=None, submodel=None):
 
22
    adapted = interfaces.IModel(m, None)
 
23
    if adapted is None:
 
24
        adapted = Wrapper(m)
 
25
    adapted.parent = parent
 
26
    adapted.name = submodel
 
27
    return adapted
 
28
 
 
29
 
 
30
class Model:
 
31
    """
 
32
    A Model which keeps track of views which are looking at it in order
 
33
    to notify them when the model changes.
 
34
    """
 
35
    implements(interfaces.IModel)
 
36
 
 
37
    def __init__(self, *args, **kwargs):
 
38
        if len(args):
 
39
            self.original = args[0]
 
40
        else:
 
41
            self.original = self
 
42
        self.name = ''
 
43
        self.parent = None
 
44
        self.views = []
 
45
        self.subviews = {}
 
46
        self.submodels = {}
 
47
        self._getter = kwargs.get('getter')
 
48
        self._setter = kwargs.get('setter')
 
49
        self.cachedFor = None
 
50
        self.initialize(*args, **kwargs)
 
51
 
 
52
    def __getstate__(self):
 
53
        self.views = []
 
54
        self.subviews = {}
 
55
        self.submodels = {}
 
56
        return self.__dict__
 
57
 
 
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.
 
61
        """
 
62
        self.cachedFor = None
 
63
 
 
64
    def initialize(self, *args, **kwargs):
 
65
        """
 
66
        Hook for subclasses to initialize themselves without having to
 
67
        mess with the __init__ chain.
 
68
        """
 
69
        pass
 
70
 
 
71
    def addView(self, view):
 
72
        """
 
73
        Add a view for the model to keep track of.
 
74
        """
 
75
        if view not in [ref() for ref in self.views]:
 
76
            self.views.append(weakref.ref(view))
 
77
 
 
78
    def addSubview(self, name, subview):
 
79
        subviewList = self.subviews.get(name, [])
 
80
        subviewList.append(weakref.ref(subview))
 
81
        self.subviews[name] = subviewList
 
82
 
 
83
    def removeView(self, view):
 
84
        """
 
85
        Remove a view that the model no longer should keep track of.
 
86
        """
 
87
        # AM: loop on a _copy_ of the list, since we're changing it!!!
 
88
        for weakref in list(self.views):
 
89
            ref = weakref()
 
90
            if ref is view or ref is None:
 
91
                self.views.remove(weakref)
 
92
 
 
93
    def setGetter(self, getter):
 
94
        self._getter = getter
 
95
 
 
96
    def setSetter(self, setter):
 
97
        self._setter = setter
 
98
 
 
99
    def notify(self, changed=None):
 
100
        """
 
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
 
106
        in.
 
107
        """
 
108
        self.cachedFor = None
 
109
        if changed is None: changed = {}
 
110
        retVal = []
 
111
        # AM: loop on a _copy_ of the list, since we're changing it!!!
 
112
        for view in list(self.views):
 
113
            ref = view()
 
114
            if ref is not None:
 
115
                retVal.append((ref, ref.modelChanged(changed)))
 
116
            else:
 
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):
 
121
                    ref = item()
 
122
                    if ref is not None:
 
123
                        retVal.append((ref, ref.modelChanged(changed)))
 
124
                    else:
 
125
                        value.remove(item)
 
126
        return retVal
 
127
 
 
128
    protected_names = ['initialize', 'addView', 'addSubview', 'removeView', 'notify', 'getSubmodel', 'setSubmodel', 'getData', 'setData']
 
129
    allowed_names = []
 
130
 
 
131
    def lookupSubmodel(self, request, submodelName):
 
132
        """
 
133
        Look up a full submodel name. I will split on `/' and call
 
134
        L{getSubmodel} on each element in the 'path'.
 
135
 
 
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
 
138
        name specified.
 
139
 
 
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.
 
143
 
 
144
        XXX: Move bits of this docstring to interfaces.py
 
145
        """
 
146
        if not submodelName:
 
147
            return None
 
148
 
 
149
        # Special case: If the first character is /
 
150
        # Start at the bottom of the model stack
 
151
        currentModel = self
 
152
        if submodelName[0] == '/':
 
153
            while currentModel.parent is not None:
 
154
                currentModel = currentModel.parent
 
155
            submodelName = submodelName[1:]
 
156
 
 
157
        submodelList = submodelName.split('/')  #[:-1]
 
158
#         print "submodelList", submodelList
 
159
        for element in submodelList:
 
160
            if element == '.' or element == '':
 
161
                continue
 
162
            elif element == '..':
 
163
                currentModel = currentModel.parent
 
164
            else:
 
165
                currentModel = currentModel.getSubmodel(request, element)
 
166
                if currentModel is None:
 
167
                    return None
 
168
        return currentModel
 
169
 
 
170
    def submodelCheck(self, request, name):
 
171
        """Check if a submodel name is allowed.  Subclass me to implement a
 
172
        name security policy.
 
173
        """
 
174
        if self.allowed_names:
 
175
            return (name in self.allowed_names)
 
176
        else:
 
177
            return (name and name[0] != '_' and name not in self.protected_names)
 
178
 
 
179
 
 
180
    def submodelFactory(self, request, name):
 
181
        warnings.warn("Warning: default Model lookup strategy is changing:"
 
182
                      "use either AttributeModel or MethodModel for now.",
 
183
                      DeprecationWarning)
 
184
        if hasattr(self, name):
 
185
            return getattr(self, name)
 
186
        else:
 
187
            return None
 
188
 
 
189
    def getSubmodel(self, request, name):
 
190
        """
 
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.
 
194
        """
 
195
        if self.submodels.has_key(name):
 
196
            return self.submodels[name]
 
197
        if not self.submodelCheck(request, name):
 
198
            return None
 
199
        m = self.submodelFactory(request, name)
 
200
        if m is None:
 
201
            return None
 
202
        sm = adaptToIModel(m, self, name)
 
203
        self.submodels[name] = sm
 
204
        return sm
 
205
 
 
206
    def setSubmodel(self, request=None, name=None, value=None):
 
207
        """
 
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
 
211
        cache.
 
212
        """
 
213
        if self.submodelCheck(request, name):
 
214
            if self.submodels.has_key(name):
 
215
                del self.submodels[name]
 
216
            setattr(self, name, value)
 
217
 
 
218
    def dataWillChange(self):
 
219
        pass
 
220
 
 
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)
 
226
        return self.original
 
227
 
 
228
    def setData(self, request, data):
 
229
        if self._setter is not None:
 
230
            self.cachedFor = None
 
231
            return self._setter(request, data)
 
232
        else:
 
233
            if hasattr(self, 'parent') and self.parent:
 
234
                self.parent.setSubmodel(request, self.name, data)
 
235
            self.orig = self.original = data
 
236
 
 
237
 
 
238
class MethodModel(Model):
 
239
    """Look up submodels with wmfactory_* methods.
 
240
    """
 
241
 
 
242
    def submodelCheck(self, request, name):
 
243
        """Allow any submodel for which I have a submodel.
 
244
        """
 
245
        return hasattr(self, "wmfactory_"+name)
 
246
 
 
247
    def submodelFactory(self, request, name):
 
248
        """Call a wmfactory_name method on this model.
 
249
        """
 
250
        meth = getattr(self, "wmfactory_"+name)
 
251
        return meth(request)
 
252
    
 
253
    def getSubmodel(self, request=None, name=None):
 
254
        if name is None:
 
255
            warnings.warn("Warning! getSubmodel should now take the request as the first argument")
 
256
            name = request
 
257
            request = None
 
258
 
 
259
        cached = self.submodels.has_key(name)
 
260
        sm = Model.getSubmodel(self, request, name)
 
261
        if sm is not None:
 
262
            if not cached:
 
263
                sm.cachedFor = id(request)
 
264
            sm._getter = getattr(self, "wmfactory_"+name)
 
265
        return sm
 
266
 
 
267
 
 
268
class AttributeModel(Model):
 
269
    """Look up submodels as attributes with hosts.allow/deny-style security.
 
270
    """
 
271
    def submodelFactory(self, request, name):
 
272
        if hasattr(self, name):
 
273
            return getattr(self, name)
 
274
        else:
 
275
            return None
 
276
 
 
277
 
 
278
#backwards compatibility
 
279
WModel = Model
 
280
 
 
281
 
 
282
class Wrapper(Model):
 
283
    """
 
284
    I'm a generic wrapper to provide limited interaction with the
 
285
    Woven models and submodels.
 
286
    """
 
287
    parent = None
 
288
    name = None
 
289
    def __init__(self, orig):
 
290
        Model.__init__(self)
 
291
        self.orig = self.original = orig
 
292
 
 
293
    def dataWillChange(self):
 
294
        pass
 
295
 
 
296
    def __repr__(self):
 
297
        myLongName = reflect.qual(self.__class__)
 
298
        return "<%s instance at 0x%x: wrapped data: %s>" % (myLongName,
 
299
                                                            id(self), self.original)
 
300
 
 
301
 
 
302
class ListModel(Wrapper):
 
303
    """
 
304
    I wrap a Python list and allow it to interact with the Woven
 
305
    models and submodels.
 
306
    """
 
307
    def dataWillChange(self):
 
308
        self.submodels = {}
 
309
 
 
310
    def getSubmodel(self, request=None, name=None):
 
311
        if name is None and type(request) is type(""):
 
312
            warnings.warn("Warning!")
 
313
            name = request
 
314
            request = None
 
315
        if self.submodels.has_key(name):
 
316
            return self.submodels[name]
 
317
        orig = self.original
 
318
        try:
 
319
            i = int(name)
 
320
        except:
 
321
            return None
 
322
        if i > len(orig):
 
323
            return None
 
324
        sm = adaptToIModel(orig[i], self, name)
 
325
        self.submodels[name] = sm
 
326
        return sm
 
327
 
 
328
    def setSubmodel(self, request=None, name=None, value=None):
 
329
        if value is None:
 
330
            warnings.warn("Warning!")
 
331
            value = name
 
332
            name = request
 
333
            request = None
 
334
        self.original[int(name)] = value
 
335
 
 
336
    def __len__(self):
 
337
        return len(self.original)
 
338
 
 
339
    def __getitem__(self, name):
 
340
        return self.getSubmodel(None, str(name))
 
341
 
 
342
    def __setitem__(self, name, value):
 
343
        self.setSubmodel(None, str(name), value)
 
344
 
 
345
    def __repr__(self):
 
346
        myLongName = reflect.qual(self.__class__)
 
347
        return "<%s instance at 0x%x: wrapped data: %s>" % (myLongName,
 
348
                                                            id(self), self.original)
 
349
 
 
350
 
 
351
class StringModel(ListModel):
 
352
 
 
353
    """ I wrap a Python string and allow it to interact with the Woven models
 
354
    and submodels.  """
 
355
 
 
356
    def setSubmodel(self, request=None, name=None, value=None):
 
357
        raise ValueError("Strings are immutable.")
 
358
 
 
359
 
 
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
 
362
try:
 
363
    from pyPgSQL import PgSQL
 
364
    components.registerAdapter(ListModel, PgSQL.PgResultSet, interfaces.IModel)
 
365
except:
 
366
    pass
 
367
 
 
368
class DictionaryModel(Wrapper):
 
369
    """
 
370
    I wrap a Python dictionary and allow it to interact with the Woven
 
371
    models and submodels.
 
372
    """
 
373
    def dataWillChange(self):
 
374
        self.submodels = {}
 
375
 
 
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")
 
379
            name = request
 
380
            request = None
 
381
        if self.submodels.has_key(name):
 
382
            return self.submodels[name]
 
383
        orig = self.original
 
384
        if name not in orig:
 
385
            return None
 
386
        sm = adaptToIModel(orig[name], self, name)
 
387
        self.submodels[name] = sm
 
388
        return sm
 
389
 
 
390
    def setSubmodel(self, request=None, name=None, value=None):
 
391
        if value is None:
 
392
            warnings.warn("Warning!")
 
393
            value = name
 
394
            name = request
 
395
            request = None
 
396
        self.original[name] = value
 
397
 
 
398
 
 
399
class AttributeWrapper(Wrapper):
 
400
    """
 
401
    I wrap an attribute named "name" of the given parent object.
 
402
    """
 
403
    def __init__(self, parent, name):
 
404
        self.original = None
 
405
        parent = ObjectWrapper(parent)
 
406
        Wrapper.__init__(self, parent.getSubmodel(None, name))
 
407
        self.parent = parent
 
408
        self.name = name
 
409
 
 
410
 
 
411
class ObjectWrapper(Wrapper):
 
412
    """
 
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.
 
415
    """
 
416
    def getSubmodel(self, request=None, name=None):
 
417
        if name is None and type(request) is type(""):
 
418
            warnings.warn("Warning!")
 
419
            name = request
 
420
            request = None
 
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
 
425
        return sm
 
426
 
 
427
    def setSubmodel(self, request=None, name=None, value=None):
 
428
        if value is None:
 
429
            warnings.warn("Warning!")
 
430
            value = name
 
431
            name = request
 
432
            request = None
 
433
        setattr(self.original, name, value)
 
434
 
 
435
class UnsafeObjectWrapper(ObjectWrapper):
 
436
    """
 
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!
 
441
    """
 
442
    def getSubmodel(self, request=None, name=None):
 
443
        if name is None and type(request) is type(""):
 
444
            warnings.warn("Warning!")
 
445
            name = request
 
446
            request = None
 
447
        if self.submodels.has_key(name):
 
448
            return self.submodels[name]
 
449
        value = getattr(self.original, name)
 
450
        if callable(value):
 
451
            return value()
 
452
        sm = adaptToIModel(value, self, name)
 
453
        self.submodels = sm
 
454
        return sm
 
455
 
 
456
 
 
457
class DeferredWrapper(Wrapper):
 
458
    def setData(self, request=None, data=_Nothing):
 
459
        if data is _Nothing:
 
460
            warnings.warn("setData should be called with request as first arg")
 
461
            data = request
 
462
            request = None
 
463
        if isinstance(data, defer.Deferred):
 
464
            self.original = data
 
465
        else:
 
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
 
471
 
 
472
class Link(AttributeModel):
 
473
    def __init__(self, href, text):
 
474
        AttributeModel.__init__(self)
 
475
        self.href = href
 
476
        self.text = text
 
477
 
 
478
try:
 
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)
 
485
except ValueError:
 
486
    # The adapters were already registered
 
487
    pass