1
# -*- test-case-name: twisted.web.test.test_woven -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
12
from twisted.internet import defer
13
from twisted.python import log
14
from twisted.python.reflect import qual
16
from twisted.web import domhelpers
17
from twisted.web.woven import template, controller, utils
19
__version__ = "$Revision: 1.34 $"[11:-2]
21
controllerFactory = controller.controllerFactory
24
class InputHandler(controller.Controller):
26
An InputHandler is like a controller, but it operates on something
27
contained inside of C{self.model} instead of directly on C{self.model}.
28
For example, a Handler whose C{model} has been set to C{"foo"} will handle
31
The handler's job is to interpret the request and:
33
1. Check for valid input
34
2. If the input is valid, update the model
35
3. Use any special API of the view widget to change the view (other
36
than what the view updates automatically from the model) e.g. in the
37
case of an error, tell the view to report an error to the user
38
4. Return a success value; by default these values are simply recorded
39
and the page is rendered, but these values could be used to determine
40
what page to display next, etc.
42
invalidErrorText = "Error!"
44
def __init__(self, model=None,
49
invalidErrorText = None,
51
controllerStack=None):
52
self.controllerStack = controllerStack
53
controller.Controller.__init__(self, model)
58
if invalidErrorText is not None:
59
self.invalidErrorText = invalidErrorText
60
if submodel is not None:
61
self.submodel = submodel
68
def setNode(self, node):
71
def getInput(self, request):
73
Return the data associated with this handler from the request, if any.
75
name = getattr(self, 'inputName', self.submodel)
76
input = request.args.get(name, None)
80
def handle(self, request):
82
data = self.getInput(request)
83
success = self.check(request, data)
84
if isinstance(success, defer.Deferred):
85
success.addCallback(self.dispatchCheckResult, request, data)
86
success.addErrback(utils.renderFailure, request)
88
self.dispatchCheckResult(success, request, data)
90
def dispatchCheckResult(self, success, request, data):
91
if success is not None:
93
result = self.handleValid(request, data)
95
result = self.handleInvalid(request, data)
96
if isinstance(result, defer.Deferred):
99
def check(self, request, data):
101
Check whether the input in the request is valid for this handler
102
and return a boolean indicating validity.
104
if self._check is None:
105
raise NotImplementedError(qual(self.__class__)+'.check')
106
# self._check is probably a bound method or simple function that
107
# doesn't have a reference to this InputHandler; pass it
108
return self._check(self, request, data)
110
def handleValid(self, request, data):
112
It has been determined that the input for this handler is valid;
113
however, that does not mean the entire form is valid.
115
self._parent.aggregateValid(request, self, data)
117
def aggregateValid(self, request, inputhandler, data):
118
"""By default we just pass the method calls all the way up to the root
119
Controller. However, an intelligent InputHandler could override this
120
and implement a state machine that waits for all data to be collected
123
self._parent.aggregateValid(request, inputhandler, data)
125
def handleInvalid(self, request, data):
127
Once it has been determined that the input is invalid, we should
128
tell our view to report this fact to the user.
130
self._parent.aggregateInvalid(request, self, data)
131
self.view.setError(request, self.invalidErrorText)
133
def aggregateInvalid(self, request, inputhandler, data):
134
"""By default we just pass this method call all the way up to the root
137
self._parent.aggregateInvalid(request, inputhandler, data)
139
def commit(self, request, node, data):
141
It has been determined that the input for the entire form is completely
142
valid; it is now safe for all handlers to commit changes to the model.
144
if self._commit is None:
146
if data != self.view.getData():
147
self.model.setData(data)
148
self.model.notify({'request': request, self.submodel: data})
151
if hasattr(func, 'im_func'):
153
args, varargs, varkw, defaults = inspect.getargspec(func)
154
if args[1] == 'request':
155
self._commit(request, data)
160
class DefaultHandler(InputHandler):
161
def handle(self, request):
163
By default, we don't do anything
168
class SingleValue(InputHandler):
169
def getInput(self, request):
170
name = getattr(self, 'inputName', self.submodel)
171
input = request.args.get(name, None)
176
class Anything(SingleValue):
178
Handle anything except for None
180
def check(self, request, data):
186
class Integer(SingleValue):
188
Only allow a single integer
190
def check(self, request, data):
191
if data is None: return None
195
except (TypeError, ValueError):
198
def handleInvalid(self, request, data):
199
self.invalidErrorText = "%s is not an integer. Please enter an integer." % data
200
SingleValue.handleInvalid(self, request, data)
203
class Float(SingleValue):
205
Only allow a single float
207
def check(self, request, data):
208
if data is None: return None
212
except (TypeError, ValueError):
215
def handleInvalid(self, request, data):
216
self.invalidErrorText = "%s is not an float. Please enter a float." % data
217
SingleValue.handleInvalid(self, request, data)
220
class List(InputHandler):
221
def check(self, request, data):
225
class DictAggregator(Anything):
226
"""An InputHandler for a <form> tag, for triggering a function
227
when all of the form's individual inputs have been validated.
228
Also for use gathering a dict of arguments to pass to a parent's
229
aggregateValid if no commit function is passed.
232
<form controller="theForm" action="">
233
<input controller="Integer"
234
view="InputText" model="anInteger" />
235
<input controller="Anything"
236
view="InputText" model="aString" />
237
<input type="submit" />
240
def theCommitFunction(anInteger=None, aString=None):
241
'''Note how the keyword arguments match up with the leaf model
244
print "Yay", anInteger, aString
246
class CMyController(controller.Controller):
247
def wcfactory_theForm(self, request, node, m):
248
return input.FormAggregator(m, commit=theCommitFunction)
250
def aggregateValid(self, request, inputhandler, data):
251
"""Aggregate valid input from inputhandlers below us, into a dictionary.
253
self._valid[inputhandler] = data
255
def aggregateInvalid(self, request, inputhandler, data):
256
self._invalid[inputhandler] = data
258
def exit(self, request):
259
"""This is the node complete message
262
# Introspect the commit function to see what
263
# keyword arguments it takes
265
if hasattr(func, 'im_func'):
267
args, varargs, varkw, defaults = inspect.getargspec(
269
wantsRequest = len(args) > 1 and args[1] == 'request'
274
self._errback(request, self._invalid)
276
# We've got all the input
277
# Gather it into a dict and call the commit function
279
for item in self._valid:
280
results[item.model.name] = self._valid[item]
283
self._commit(request, **results)
285
self._commit(**results)
287
self._parent.aggregateValid(request, self, results)
291
class ListAggregator(Anything):
292
def aggregateValid(self, request, inputhandler, data):
293
"""Aggregate valid input from inputhandlers below us into a
294
list until we have all input from controllers below us to pass
295
to the commit function that was passed to the constructor or
296
our parent's aggregateValid.
298
if not hasattr(self, '_validList'):
300
self._validList.append(data)
302
def aggregateInvalid(self, request, inputhandler, data):
303
if not hasattr(self, '_invalidList'):
304
self._invalidList = []
305
self._invalidList.append(data)
307
def exit(self, request):
309
# Introspect the commit function to see what
312
if hasattr(func, 'im_func'):
314
args, varargs, varkw, defaults = inspect.getargspec(func)
315
self.numArgs = len(args)
316
wantsRequest = args[1] == 'request'
320
# Introspect the template to see if we still have
321
# controllers that will be giving us input
323
# aggregateValid is called before the view renders the node, so
324
# we can count the number of controllers below us the first time
326
if not hasattr(self, 'numArgs'):
327
self.numArgs = len(domhelpers.findElementsWithAttributeShallow(
328
self.view.node, "controller"))
330
if self._invalidList:
331
self._parent.aggregateInvalid(request, self, self._invalidList)
335
self._commit(request, *self._validList)
337
self._commit(*self._validList)
338
self._parent.aggregateValid(request, self, self._invalidList)
340
def commit(self, request, node, data):
341
"""If we're using the ListAggregator, we don't want the list of items
343
xxx Need to have a "node complete" message sent to the controller
344
so we can reset state, so controllers can be re-run or ignore input the second time