1
# -*- test-case-name: twisted.web.test.test_woven -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
from __future__ import nested_scopes
9
__version__ = "$Revision: 1.91 $"[11:-2]
15
from utils import doSendPage
19
from twisted.internet import defer
20
from twisted.python import components
21
from twisted.python import log
22
from twisted.web import resource, microdom, html, error
23
from twisted.web.server import NOT_DONE_YET
24
from zope.interface import implements
27
import cPickle as pickle
42
while it is None and stack is not None:
52
def filterStack(stack):
54
while stack is not None:
61
def viewFactory(viewClass):
62
return lambda request, node, model: viewClass(model)
64
def viewMethod(viewClass):
65
return lambda self, request, node, model: viewClass(model)
72
implements(resource.IResource, interfaces.IView)
73
# wvfactory_xxx method signature: request, node, model; returns Widget
74
# wvupdate_xxx method signature: request, widget, data; mutates widget
75
# based on data (not necessarily an IModel;
76
# has been unwrapped at this point)
78
wantsAllNotifications = 0
80
templateDirectory = ''
85
def getChild(self, path, request):
86
return error.NoResource("No such child resource.")
88
def getChildWithDefault(self, path, request):
89
return self.getChild(path, request)
96
def __init__(self, m, templateFile=None, templateDirectory=None, template=None, controller=None, doneCallback=None, modelStack=None, viewStack=None, controllerStack=None):
98
A view must be told what its model is, and may be told what its
99
controller is, but can also look up its controller if none specified.
101
if not interfaces.IModel.providedBy(m):
102
m = model.adaptToIModel(m, None, None)
103
self.model = self.mainModel = m
104
# It's the responsibility of the calling code to make sure
105
# setController is called on this view before it's rendered.
106
self.controller = None
109
self.modelStack = None
110
self.viewStack = None
111
self.controllerStack = None
112
if doneCallback is None and self.doneCallback is None:
113
self.doneCallback = doSendPage
115
print "DoneCallback", doneCallback
116
self.doneCallback = doneCallback
117
if template is not None:
118
self.template = template
119
if templateFile is not None:
120
self.templateFile = templateFile
121
if templateDirectory is not None:
122
self.templateDirectory = templateDirectory
124
self.outstandingCallbacks = 0
125
self.outstandingNodes = []
127
self.setupMethods = []
129
def setupAllStacks(self):
130
self.modelStack = (self.model, None)
131
self.controllerStack = (self.controller, (input, None))
132
self.setupViewStack()
134
def setUp(self, request, d):
137
def setupViewStack(self):
138
self.viewStack = None
139
if widgets not in self.viewLibraries:
140
self.viewLibraries.append(widgets)
141
for library in self.viewLibraries:
142
if not hasattr(library, 'getSubview'):
143
library.getSubview = utils.createGetFunction(library)
144
self.viewStack = (library, self.viewStack)
145
self.viewStack = (self, self.viewStack)
147
def importViewLibrary(self, namespace):
148
self.viewLibraries.append(namespace)
151
def render(self, request, doneCallback=None):
152
if not getattr(request, 'currentId', 0):
153
request.currentId = 0
154
request.currentPage = self
155
if self.controller is None:
156
self.controller = controller.Controller(self.model)
157
if doneCallback is not None:
158
self.doneCallback = doneCallback
160
self.doneCallback = doSendPage
161
self.setupAllStacks()
162
template = self.getTemplate(request)
164
self.d = microdom.parseString(template, caseInsensitive=0, preserveCase=0)
166
if not self.templateFile:
167
raise AttributeError, "%s does not define self.templateFile to operate on" % self.__class__
168
self.d = self.lookupTemplate(request)
170
self.handleDocument(request, self.d)
173
def getTemplate(self, request):
175
Override this if you want to have your subclass look up its template
176
using a different method.
180
def lookupTemplate(self, request):
182
Use acquisition to look up the template named by self.templateFile,
183
located anywhere above this object in the heirarchy, and use it
184
as the template. The first time the template is used it is cached
188
return microdom.parseString(self.template, caseInsensitive=0, preserveCase=0)
189
if not self.templateDirectory:
190
mod = sys.modules[self.__module__]
191
if hasattr(mod, '__file__'):
192
self.templateDirectory = os.path.split(mod.__file__)[0]
193
# First see if templateDirectory + templateFile is a file
194
templatePath = os.path.join(self.templateDirectory, self.templateFile)
195
if not os.path.exists(templatePath):
196
raise RuntimeError, "The template %r was not found." % templatePath
197
# Check to see if there is an already parsed copy of it
198
mtime = os.path.getmtime(templatePath)
199
cachedTemplate = templateCache.get(templatePath, None)
200
compiledTemplate = None
202
if cachedTemplate is not None:
203
if cachedTemplate[0] == mtime:
204
compiledTemplate = templateCache[templatePath][1].cloneNode(deep=1)
206
if compiledTemplate is None:
207
compiledTemplate = microdom.parse(templatePath, caseInsensitive=0, preserveCase=0)
208
templateCache[templatePath] = (mtime, compiledTemplate.cloneNode(deep=1))
209
return compiledTemplate
211
def handleDocument(self, request, document):
212
"""Handle the root node, and send the page if there are no
213
outstanding callbacks when it returns.
217
self.setUp(request, document)
218
# Don't let outstandingCallbacks get to 0 until the
219
# entire tree has been recursed
220
# If you don't do this, and any callback has already
221
# completed by the time the dispatchResultCallback
222
# is added in dispachResult, then sendPage will be
223
# called prematurely within dispatchResultCallback
224
# resulting in much gnashing of teeth.
225
self.outstandingNodes = document.childNodes[:] + [1]
226
self.outstandingNodes.reverse()
228
self.outstandingCallbacks += 1
229
self.handleOutstanding(request)
230
self.outstandingCallbacks -= 1
231
if not self.outstandingCallbacks:
232
return self.sendPage(request)
234
self.renderFailure(None, request)
236
def handleOutstanding(self, request):
237
while self.outstandingNodes:
238
node = self.outstandingNodes.pop()
240
self.modelStack = self.modelStack[1]
241
self.viewStack = self.viewStack[1]
242
if self.controllerStack is not None:
243
controller, self.controllerStack = self.controllerStack
244
if controller is not None:
245
controller.exit(request)
246
attrs = getattr(node, 'attributes', None)
247
if (attrs is not None and
248
(attrs.get('model') or attrs.get('view') or attrs.get('controller'))):
249
self.outstandingNodes.append(1)
250
self.handleNode(request, node)
252
if attrs is not None and (attrs.get('view') or attrs.get('controller')):
253
self.outstandingNodes.append(node)
254
if hasattr(node, 'childNodes') and node.childNodes:
255
self.recurseChildren(request, node)
257
def recurseChildren(self, request, node):
258
"""If this node has children, handle them.
260
new = node.childNodes[:]
262
self.outstandingNodes.extend(new)
264
def dispatchResult(self, request, node, result):
265
"""Check a given result from handling a node and look up a NodeMutator
266
adapter which will convert the result into a node and insert it
267
into the DOM tree. Return the new node.
269
if not isinstance(result, defer.Deferred):
270
if node.parentNode is not None:
271
node.parentNode.replaceChild(result, node)
273
raise RuntimeError, "We're dying here, please report this immediately"
275
self.outstandingCallbacks += 1
276
result.addCallback(self.dispatchResultCallback, request, node)
277
result.addErrback(self.renderFailure, request)
278
# Got to wait until the callback comes in
281
def modelChanged(self, changed):
282
"""Rerender this view, because our model has changed.
284
# arg. this needs to go away.
285
request = changed.get('request', None)
287
#import pdb; pdb.set_trace()
288
newNode = self.generate(request, oldNode)
289
returnNode = self.dispatchResult(request, oldNode, newNode)
290
self.handleNewNode(request, returnNode)
291
self.handleOutstanding(request)
292
self.controller.domChanged(request, self, returnNode)
294
def generate(self, request, node):
295
"""Allow a view to be used like a widget. Will look up the template
296
file and return it in place of the incoming node.
298
newNode = self.lookupTemplate(request).childNodes[0]
299
newNode.setAttribute('id', 'woven_id_%s' % id(self))
303
def setController(self, controller):
304
self.controller = controller
305
self.controllerStack = (controller, self.controllerStack)
307
def setNode(self, node):
308
if self.templateNode == None:
309
self.templateNode = node
312
def setSubmodel(self, name):
315
def getNodeModel(self, request, node, submodel):
317
Get the model object associated with this node. If this node has a
318
model= attribute, call getSubmodel on the current model object.
319
If not, return the top of the model stack.
324
m = peek(self.modelStack)
326
modelStack = self.modelStack
327
while modelStack is not None:
328
parent, modelStack = modelStack
331
m = parent.lookupSubmodel(request, submodel)
336
raise Exception("Node had a model=%s "
337
"attribute, but the submodel was not "
338
"found in %s." % (submodel,
339
filterStack(self.modelStack)))
342
self.modelStack = (m, self.modelStack)
344
# print "M NAME", m.name
345
# if parent is not m:
347
# if not getattr(m, 'name', None):
350
#print `submodel`, self.getTopOfModelStack()
352
return peek(self.modelStack)
355
def getNodeController(self, request, node, submodel, model):
357
Get a controller object to handle this node. If the node has no
358
controller= attribute, first check to see if there is an IController
359
adapter for our model.
361
controllerName = node.attributes.get('controller')
365
model = peek(self.modelStack)
367
# Look up a controller factory.
369
#if not node.hasAttribute('name'):
370
# warnings.warn("POTENTIAL ERROR: %s had a controller, but not a "
371
# "'name' attribute." % node)
372
controllerStack = self.controllerStack
373
while controllerStack is not None:
374
namespace, controllerStack = controllerStack
375
if namespace is None:
377
controller = namespace.getSubcontroller(request, node, model, controllerName)
378
if controller is not None:
381
raise NotImplementedError("You specified controller name %s on "
382
"a node, but no wcfactory_%s method "
383
"was found in %s." % (controllerName,
385
filterStack(self.controllerStack)
387
elif node.attributes.get("model"):
388
# If no "controller" attribute was specified on the node, see if
389
# there is a IController adapter registerred for the model.
390
controller = interfaces.IController(
397
def getSubview(self, request, node, model, viewName):
398
"""Get a sub-view from me.
400
@returns: L{widgets.Widget}
403
vm = getattr(self, 'wvfactory_' + viewName, None)
405
vm = getattr(self, 'factory_' + viewName, None)
407
warnings.warn("factory_ methods are deprecated; please use "
408
"wvfactory_ instead", DeprecationWarning)
410
if vm.func_code.co_argcount == 3 and not type(vm) == types.LambdaType:
411
warnings.warn("wvfactory_ methods take (request, node, "
412
"model) instead of (request, node) now. \n"
413
"Please instantiate your widgets with a "
414
"reference to model instead of self.model",
417
view = vm(request, node)
418
self.model = self.mainModel
420
view = vm(request, node, model)
422
setupMethod = getattr(self, 'wvupdate_' + viewName, None)
425
view = widgets.Widget(model)
426
view.setupMethods.append(setupMethod)
430
def getNodeView(self, request, node, submodel, model):
432
viewName = node.attributes.get('view')
435
model = peek(self.modelStack)
437
# Look up a view factory.
439
viewStack = self.viewStack
440
while viewStack is not None:
441
namespace, viewStack = viewStack
442
if namespace is None:
445
view = namespace.getSubview(request, node, model, viewName)
446
except AttributeError:
447
# Was that from something in the viewStack that didn't
449
if not hasattr(namespace, "getSubview"):
450
log.msg("Warning: There is no getSubview on %r" %
454
# No, something else is really broken.
459
raise NotImplementedError(
460
"You specified view name %s on a node %s, but no "
461
"wvfactory_%s method was found in %s. (Or maybe they were "
462
"found but they returned None.)" % (
463
viewName, node, viewName,
464
filterStack(self.viewStack)))
465
elif node.attributes.get("model"):
466
# If no "view" attribute was specified on the node, see if there
467
# is a IView adapter registerred for the model.
468
# First, see if the model is Componentized.
469
if isinstance(model, components.Componentized):
470
view = model.getAdapter(interfaces.IView)
471
if not view and hasattr(model, '__class__'):
472
view = interfaces.IView(model, None)
476
def handleNode(self, request, node):
477
submodelName = node.attributes.get('model')
478
if submodelName is None:
480
model = self.getNodeModel(request, node, submodelName)
481
view = self.getNodeView(request, node, submodelName, model)
482
controller = self.getNodeController(request, node, submodelName, model)
483
if view or controller:
485
model = peek(self.modelStack)
486
if not view or not isinstance(view, View):
487
view = widgets.DefaultWidget(model)
490
controller = input.DefaultHandler(model)
492
prevView, stack = self.viewStack
493
while isinstance(prevView, widgets.DefaultWidget) and stack is not None:
494
prevView, stack = stack
498
prevMod = prevView.model
499
if model is not prevMod and not isinstance(view, widgets.DefaultWidget):
501
submodelList = [x.name for x in filterStack(self.modelStack) if x.name]
502
submodelList.reverse()
503
submodelName = '/'.join(submodelList)
504
if not getattr(view, 'submodel', None):
505
view.submodel = submodelName
507
theId = node.attributes.get("id")
508
if self.livePage and not theId:
509
theId = "woven_id_%d" % id(view)
510
view.setupMethods.append(utils.createSetIdFunction(theId))
511
view.outgoingId = theId
512
#print "SET AN ID", theId
513
self.subviews[theId] = view
514
view.parent = peek(self.viewStack)
515
view.parent.subviews[theId] = view
516
# If a Widget was constructed directly with a model that so far
517
# is not in modelspace, we should put it on the stack so other
518
# Widgets below this one can find it.
519
if view.model is not peek(self.modelStack):
520
self.modelStack = poke(self.modelStack, view.model)
522
cParent = peek(self.controllerStack)
523
if controller._parent is None or cParent != controller:
524
controller._parent = cParent
526
self.controllerStack = (controller, self.controllerStack)
527
self.viewStack = (view, self.viewStack)
529
view.viewStack = self.viewStack
530
view.controllerStack = self.controllerStack
531
view.modelStack = self.modelStack
533
view.setController(controller)
536
if not getattr(controller, 'submodel', None):
537
controller.setSubmodel(submodelName)
539
controller.setView(view)
540
controller.setNode(node)
542
controllerResult = controller.handle(request)
543
if controllerResult is not None:
546
self.outstandingCallbacks += 1
547
controllerResult.addCallback(
548
self.handleControllerResults,
553
controllerResult.addErrback(self.renderFailure, request)
555
viewResult = view.generate(request, node)
556
returnNode = self.dispatchResult(request, node, viewResult)
557
self.handleNewNode(request, returnNode)
559
self.controllerStack = (controller, self.controllerStack)
560
self.viewStack = (view, self.viewStack)
562
def handleControllerResults(self,
563
controllerResult, request, node, controller, view):
564
"""Handle a deferred from a controller.
566
self.outstandingCallbacks -= 1
567
if isinstance(controllerResult, defer.Deferred):
568
self.outstandingCallbacks += 1
569
controllerResult.addCallback(
570
self.handleControllerResults,
575
controllerResult.addErrback(self.renderFailure, request)
577
viewResult = view.generate(request, node)
578
returnNode = self.dispatchResult(request, node, viewResult)
579
self.handleNewNode(request, returnNode)
580
return controllerResult
582
def handleNewNode(self, request, returnNode):
583
if not isinstance(returnNode, defer.Deferred):
584
self.recurseChildren(request, returnNode)
586
# TODO: Need to handle deferreds here?
589
def sendPage(self, request):
591
Check to see if handlers recorded any errors before sending the page
593
self.doneCallback(self, self.d, request)
595
def setSubviewFactory(self, name, factory, setup=None, *args, **kwargs):
596
setattr(self, "wvfactory_" + name, lambda request, node, m:
597
factory(m, *args, **kwargs))
599
setattr(self, "wvupdate_" + name, setup)
601
def __setitem__(self, key, value):
604
def unlinkViews(self):
605
#print "unlinking views"
606
self.model.removeView(self)
607
for key, value in self.subviews.items():
609
# value.model.removeView(value)
611
def dispatchResultCallback(self, result, request, node):
612
"""Deal with a callback from a deferred, checking to see if it is
613
ok to send the page yet or not.
615
self.outstandingCallbacks -= 1
616
#node = self.dispatchResult(request, node, result)
617
#self.recurseChildren(request, node)
618
if not self.outstandingCallbacks:
619
self.sendPage(request)
622
def renderFailure(self, failure, request):
624
xml = request.d.toprettyxml()
627
# if not hasattr(request, 'channel'):
628
# log.msg("The request got away from me before I could render an error page.")
634
request.write("<html><head><title>%s: %s</title></head><body>\n" % (html.escape(str(failure.type)), html.escape(str(failure.value))))
636
request.write("<html><head><title>Failure!</title></head><body>\n")
637
utils.renderFailure(failure, request)
638
request.write("<h3>Here is the partially processed DOM:</h3>")
639
request.write("\n<pre>\n")
640
request.write(html.escape(xml))
641
request.write("\n</pre>\n")
642
request.write("</body></html>")
646
class LiveView(View):
648
def wvfactory_webConduitGlue(self, request, node, m):
649
if request.getHeader("user-agent").count("MSIE"):
650
return View(m, templateFile="FlashConduitGlue.html")
652
return View(m, templateFile="WebConduitGlue.html")
654
def wvupdate_woven_flashConduitSessionView(self, request, wid, mod):
655
#print "updating flash thingie"
656
uid = request.getSession().uid
658
if n.attributes.has_key('src'):
659
n.attributes['src'] = n.attributes.get('src') + '?twisted_session=' + str(uid)
661
n.attributes['value'] = n.attributes.get('value') + '?twisted_session=' + str(uid)
662
#print wid.templateNode.toxml()
665
#backwards compatibility
669
def registerViewForModel(view, model):
671
Registers `view' as an adapter of `model' for L{interfaces.IView}.
673
components.registerAdapter(view, model, interfaces.IView)
674
# adapter = resource.IResource(model, None)
675
# if adapter is None and resource.IResource.providedBy(view):
676
# components.registerAdapter(view, model, resource.IResource)
682
# If no widget/handler was found in the container controller or view, these
683
# modules will be searched.