# -*- test-case-name: twisted.test.test_woven -*-
#
# Twisted, the Framework of Your Internet
# Copyright (C) 2000-2002 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from __future__ import nested_scopes
__version__ = "$Revision: 1.67 $"[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 # Should this controller add a slash to the url automatically?
controllerLibraries = []
viewFactory = None
templateDirectory = ""
def __init__(self, m, inputhandlers=None, view=None, controllers=None, templateDirectory = None):
#self.start = now()
resource.Resource.__init__(self)
self.model = m
# It's the responsibility of the calling code to make sure setView is
# called on this controller before it's rendered.
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)
# Handle any inputhandlers that were passed in to the controller first
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)
#log.msg("Sending page!")
self.pageRenderComplete(request)
utils.doSendPage(v, d, request)
#v.unlinkViews()
#print "Page time: ", now() - self.start
#return view.View.render(self, request, block=0)
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.
"""
# Check to see if we're hooking up an output conduit
sess = request.getSession(interfaces.IWovenLivePage)
#print "REQUEST.ARGS", request.args
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('