from __future__ import nested_scopes
__version__ = "$Revision: 1.2 $"[11:-2]
import os
import cgi
import types
from twisted.python import log
from twisted.python import components
from twisted.python import failure
from twisted.web import resource, server, static
from twisted.web.woven import interfaces, utils
from twisted.web import woven
from twisted.web import microdom
from twisted.web.static import redirectTo, addSlash
import warnings
from time import time as now
def controllerFactory(controllerClass):
return lambda request, node, model: controllerClass(model)
def controllerMethod(controllerClass):
return lambda self, request, node, model: controllerClass(model)
class Controller(resource.Resource):
"""
A Controller which handles to events from the user. Such events
are `web request', `form submit', etc.
I should be the IResource implementor for your Models (and
L{registerControllerForModel} makes this so).
"""
__implements__ = (interfaces.IController, resource.IResource)
setupStacks = 1
addSlash = 1 controllerLibraries = []
viewFactory = None
templateDirectory = ""
def __init__(self, m, inputhandlers=None, view=None, controllers=None, templateDirectory = None):
resource.Resource.__init__(self)
self.model = m
self.view = None
self.subcontrollers = []
if self.setupStacks:
self.setupControllerStack()
if inputhandlers is None and controllers is None:
self._inputhandlers = []
elif inputhandlers:
print "The inputhandlers arg is deprecated, please use controllers instead"
self._inputhandlers = inputhandlers
else:
self._inputhandlers = controllers
if templateDirectory is not None:
self.templateDirectory = templateDirectory
self._valid = {}
self._invalid = {}
self._process = {}
self._parent = None
def setupControllerStack(self):
self.controllerStack = utils.Stack([])
from twisted.web.woven import input
if input not in self.controllerLibraries:
self.controllerLibraries.append(input)
for library in self.controllerLibraries:
self.importControllerLibrary(library)
self.controllerStack.push(self)
def importControllerLibrary(self, namespace):
if not hasattr(namespace, 'getSubcontroller'):
namespace.getSubcontroller = utils.createGetFunction(namespace)
self.controllerStack.push(namespace)
def getSubcontroller(self, request, node, model, controllerName):
controller = None
cm = getattr(self, 'wcfactory_' +
controllerName, None)
if cm is None:
cm = getattr(self, 'factory_' +
controllerName, None)
if cm is not None:
warnings.warn("factory_ methods are deprecated; please use "
"wcfactory_ instead", DeprecationWarning)
if cm:
if cm.func_code.co_argcount == 1 and not type(cm) == types.LambdaType:
warnings.warn("A Controller Factory takes "
"(request, node, model) "
"now instead of (model)", DeprecationWarning)
controller = controllerFactory(model)
else:
controller = cm(request, node, model)
return controller
def setSubcontrollerFactory(self, name, factory, setup=None):
setattr(self, "wcfactory_" + name, lambda request, node, m:
factory(m))
def setView(self, view):
self.view = view
def setNode(self, node):
self.node = node
def setUp(self, request, *args):
"""
@type request: L{twisted.web.server.Request}
"""
pass
def getChild(self, name, request):
"""
Look for a factory method to create the object to handle the
next segment of the URL. If a wchild_* method is found, it will
be called to produce the Resource object to handle the next
segment of the path. If a wchild_* method is not found,
getDynamicChild will be called with the name and request.
@param name: The name of the child being requested.
@type name: string
@param request: The HTTP request being handled.
@type request: L{twisted.web.server.Request}
"""
if not name:
method = "index"
else:
method = name.replace('.', '_')
f = getattr(self, "wchild_%s" % method, None)
if f:
return f(request)
else:
child = self.getDynamicChild(name, request)
if child is None:
return resource.Resource.getChild(self, name, request)
else:
return child
def getDynamicChild(self, name, request):
"""
This method is called when getChild cannot find a matching wchild_*
method in the Controller. Override me if you wish to have dynamic
handling of child pages. Should return a Resource if appropriate.
Return None to indicate no resource found.
@param name: The name of the child being requested.
@type name: string
@param request: The HTTP request being handled.
@type request: L{twisted.web.server.Request}
"""
pass
def wchild_index(self, request):
"""By default, we return ourself as the index.
Override this to provide different behavior
for a URL that ends in a slash.
"""
self.addSlash = 0
return self
def render(self, request):
"""
Trigger any inputhandlers that were passed in to this Page,
then delegate to the View for traversing the DOM. Finally,
call gatheredControllers to deal with any InputHandlers that
were constructed from any controller= tags in the
DOM. gatheredControllers will render the page to the browser
when it is done.
"""
if self.addSlash and request.uri.split('?')[0][-1] != '/':
return redirectTo(addSlash(request), request)
for ih in self._inputhandlers:
ih._parent = self
ih.handle(request)
self._inputhandlers = []
for key, value in self._valid.items():
key.commit(request, None, value)
self._valid = {}
return self.renderView(request)
def makeView(self, model, templateFile=None, parentCount=0):
if self.viewFactory is None:
self.viewFactory = self.__class__
v = self.viewFactory(model, templateFile=templateFile, templateDirectory=self.templateDirectory)
v.parentCount = parentCount
v.tapestry = self
v.importViewLibrary(self)
return v
def renderView(self, request):
if self.view is None:
if self.viewFactory is not None:
self.setView(self.makeView(self.model, None))
else:
self.setView(components.getAdapter(self.model, interfaces.IView, None))
self.view.setController(self)
return self.view.render(request, doneCallback=self.gatheredControllers)
def gatheredControllers(self, v, d, request):
process = {}
request.args = {}
for key, value in self._valid.items():
key.commit(request, None, value)
process[key.submodel] = value
self.process(request, **process)
self.pageRenderComplete(request)
utils.doSendPage(v, d, request)
def aggregateValid(self, request, input, data):
self._valid[input] = data
def aggregateInvalid(self, request, input, data):
self._invalid[input] = data
def process(self, request, **kwargs):
if kwargs:
log.msg("Processing results: ", kwargs)
def setSubmodel(self, submodel):
self.submodel = submodel
def handle(self, request):
"""
By default, we don't do anything
"""
pass
def exit(self, request):
"""We are done handling the node to which this controller was attached.
"""
pass
def domChanged(self, request, widget, node):
parent = getattr(self, '_parent', None)
if parent is not None:
parent.domChanged(request, widget, node)
def pageRenderComplete(self, request):
"""Override this to recieve notification when the view rendering
process is complete.
"""
pass
WOVEN_PATH = os.path.split(woven.__file__)[0]
class LiveController(Controller):
"""A Controller that encapsulates logic that makes it possible for this
page to be "Live". A live page can have it's content updated after the
page has been sent to the browser, and can translate client-side
javascript events into server-side events.
"""
pageSession = None
def render(self, request):
"""First, check to see if this request is attempting to hook up the
output conduit. If so, do it. Otherwise, unlink the current session's
View from the MVC notification infrastructure, then render the page
normally.
"""
sess = request.getSession(interfaces.IWovenLivePage)
if request.args.has_key('woven_hookupOutputConduitToThisFrame'):
sess.hookupOutputConduit(request)
return server.NOT_DONE_YET
if request.args.has_key('woven_clientSideEventName'):
try:
request.d = microdom.parseString('<xml/>', caseInsensitive=0, preserveCase=0)
eventName = request.args['woven_clientSideEventName'][0]
eventTarget = request.args['woven_clientSideEventTarget'][0]
eventArgs = request.args.get('woven_clientSideEventArguments', [])
return self.clientToServerEvent(request, eventName, eventTarget, eventArgs)
except:
fail = failure.Failure()
self.view.renderFailure(fail, request)
return server.NOT_DONE_YET
page = sess.getCurrentPage()
if page is not None:
page.view.unlinkViews()
sess.setCurrentPage(None)
self.pageSession = None
return Controller.render(self, request)
def clientToServerEvent(self, request, eventName, eventTarget, eventArgs):
"""The client sent an asynchronous event to the server.
Locate the View object targeted by this event and attempt
to call onEvent on it.
"""
sess = request.getSession(interfaces.IWovenLivePage)
self.view = sess.getCurrentPage().view
print "clientToServerEvent", eventTarget
target = self.view.subviews[eventTarget]
print "target, parent", target, target.parent
scriptOutput = []
orig = sess.sendScript
sess.sendScript = scriptOutput.append
target.onEvent(request, eventName, *eventArgs)
sess.sendScript = orig
scriptOutput.append('parent.woven_clientToServerEventComplete()')
return '''<html>
<body>
<script language="javascript">
%s
</script>
%s event sent to %s (%s) with arguments %s.
</body>
</html>''' % ('\n'.join(scriptOutput), eventName, cgi.escape(str(target)), eventTarget, eventArgs)
def gatheredControllers(self, v, d, request):
Controller.gatheredControllers(self, v, d, request)
sess = request.getSession(interfaces.IWovenLivePage)
self.pageSession = sess
sess.setCurrentPage(self)
sess.currentId = request.currentId
def domChanged(self, request, widget, node):
sess = request.getSession(interfaces.IWovenLivePage)
print "domchanged"
if sess is not None:
if not hasattr(node, 'getAttribute'):
return
page = sess.getCurrentPage()
if page is None:
return
nodeId = node.getAttribute('id')
nodeXML = node.toxml()
nodeXML = nodeXML.replace("\\", "\\\\")
nodeXML = nodeXML.replace("'", "\\'")
nodeXML = nodeXML.replace('"', '\\"')
nodeXML = nodeXML.replace('\n', '\\n')
nodeXML = nodeXML.replace('\r', ' ')
nodeXML = nodeXML.replace('\b', ' ')
nodeXML = nodeXML.replace('\t', ' ')
nodeXML = nodeXML.replace('\000', ' ')
nodeXML = nodeXML.replace('\v', ' ')
nodeXML = nodeXML.replace('\f', ' ')
js = "parent.woven_replaceElement('%s', '%s')" % (nodeId, nodeXML)
oldNode = page.view.subviews[nodeId]
for id, subview in oldNode.subviews.items():
subview.unlinkViews()
topSubviews = page.view.subviews
if widget.subviews:
def recurseSubviews(w):
topSubviews.update(w.subviews)
for id, sv in w.subviews.items():
recurseSubviews(sv)
recurseSubviews(widget)
sess.sendScript(js)
def wchild_WebConduit2_js(self, request):
h = request.getHeader("user-agent")
if h.count("MSIE"):
fl = "WebConduit2_msie.js"
else:
fl = "WebConduit2_mozilla.js"
return static.File(os.path.join(WOVEN_PATH, fl))
def wchild_FlashConduit_swf(self, request):
h = request.getHeader("user-agent")
if h.count("MSIE"):
fl = "FlashConduit.swf"
else:
fl = "FlashConduit.swf"
return static.File(os.path.join(WOVEN_PATH, fl))
def wchild_input_html(self, request):
return BlankPage()
class BlankPage(resource.Resource):
def render(self, request):
return "<html>This space intentionally left blank</html>"
WController = Controller
def registerControllerForModel(controller, model):
"""
Registers `controller' as an adapter of `model' for IController, and
optionally registers it for IResource, if it implements it.
@param controller: A class that implements L{interfaces.IController}, usually a
L{Controller} subclass. Optionally it can implement
L{resource.IResource}.
@param model: Any class, but probably a L{twisted.web.woven.model.Model}
subclass.
"""
components.registerAdapter(controller, model, interfaces.IController)
if components.implements(controller, resource.IResource):
components.registerAdapter(controller, model, resource.IResource)