| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.web.test.test_woven -*- | |
| 2 # | |
| 3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 4 # See LICENSE for details. | |
| 5 | |
| 6 | |
| 7 # dominput | |
| 8 | |
| 9 import os | |
| 10 import inspect | |
| 11 | |
| 12 from twisted.internet import defer | |
| 13 from twisted.python import log | |
| 14 from twisted.python.reflect import qual | |
| 15 | |
| 16 from twisted.web import domhelpers | |
| 17 from twisted.web.woven import template, controller, utils | |
| 18 | |
| 19 __version__ = "$Revision: 1.34 $"[11:-2] | |
| 20 | |
| 21 controllerFactory = controller.controllerFactory | |
| 22 | |
| 23 | |
| 24 class InputHandler(controller.Controller): | |
| 25 """ | |
| 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 | |
| 29 C{self.model.foo}. | |
| 30 | |
| 31 The handler's job is to interpret the request and: | |
| 32 | |
| 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. | |
| 41 """ | |
| 42 invalidErrorText = "Error!" | |
| 43 setupStacks = 0 | |
| 44 def __init__(self, model=None, | |
| 45 parent=None, | |
| 46 name=None, | |
| 47 check=None, | |
| 48 commit = None, | |
| 49 invalidErrorText = None, | |
| 50 submodel=None, | |
| 51 controllerStack=None): | |
| 52 self.controllerStack = controllerStack | |
| 53 controller.Controller.__init__(self, model) | |
| 54 self._check = check | |
| 55 self._commit = commit | |
| 56 self._errback = None | |
| 57 self._parent = parent | |
| 58 if invalidErrorText is not None: | |
| 59 self.invalidErrorText = invalidErrorText | |
| 60 if submodel is not None: | |
| 61 self.submodel = submodel | |
| 62 if name is not None: | |
| 63 self.inputName = name | |
| 64 | |
| 65 def initialize(self): | |
| 66 pass | |
| 67 | |
| 68 def setNode(self, node): | |
| 69 self.node = node | |
| 70 | |
| 71 def getInput(self, request): | |
| 72 """ | |
| 73 Return the data associated with this handler from the request, if any. | |
| 74 """ | |
| 75 name = getattr(self, 'inputName', self.submodel) | |
| 76 input = request.args.get(name, None) | |
| 77 if input: | |
| 78 return input | |
| 79 | |
| 80 def handle(self, request): | |
| 81 self.initialize() | |
| 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) | |
| 87 return success | |
| 88 self.dispatchCheckResult(success, request, data) | |
| 89 | |
| 90 def dispatchCheckResult(self, success, request, data): | |
| 91 if success is not None: | |
| 92 if success: | |
| 93 result = self.handleValid(request, data) | |
| 94 else: | |
| 95 result = self.handleInvalid(request, data) | |
| 96 if isinstance(result, defer.Deferred): | |
| 97 return result | |
| 98 | |
| 99 def check(self, request, data): | |
| 100 """ | |
| 101 Check whether the input in the request is valid for this handler | |
| 102 and return a boolean indicating validity. | |
| 103 """ | |
| 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) | |
| 109 | |
| 110 def handleValid(self, request, data): | |
| 111 """ | |
| 112 It has been determined that the input for this handler is valid; | |
| 113 however, that does not mean the entire form is valid. | |
| 114 """ | |
| 115 self._parent.aggregateValid(request, self, data) | |
| 116 | |
| 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 | |
| 121 and then fires. | |
| 122 """ | |
| 123 self._parent.aggregateValid(request, inputhandler, data) | |
| 124 | |
| 125 def handleInvalid(self, request, data): | |
| 126 """ | |
| 127 Once it has been determined that the input is invalid, we should | |
| 128 tell our view to report this fact to the user. | |
| 129 """ | |
| 130 self._parent.aggregateInvalid(request, self, data) | |
| 131 self.view.setError(request, self.invalidErrorText) | |
| 132 | |
| 133 def aggregateInvalid(self, request, inputhandler, data): | |
| 134 """By default we just pass this method call all the way up to the root | |
| 135 Controller. | |
| 136 """ | |
| 137 self._parent.aggregateInvalid(request, inputhandler, data) | |
| 138 | |
| 139 def commit(self, request, node, data): | |
| 140 """ | |
| 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. | |
| 143 """ | |
| 144 if self._commit is None: | |
| 145 data = str(data) | |
| 146 if data != self.view.getData(): | |
| 147 self.model.setData(data) | |
| 148 self.model.notify({'request': request, self.submodel: data}) | |
| 149 else: | |
| 150 func = self._commit | |
| 151 if hasattr(func, 'im_func'): | |
| 152 func = func.im_func | |
| 153 args, varargs, varkw, defaults = inspect.getargspec(func) | |
| 154 if args[1] == 'request': | |
| 155 self._commit(request, data) | |
| 156 else: | |
| 157 self._commit(data) | |
| 158 | |
| 159 | |
| 160 class DefaultHandler(InputHandler): | |
| 161 def handle(self, request): | |
| 162 """ | |
| 163 By default, we don't do anything | |
| 164 """ | |
| 165 pass | |
| 166 | |
| 167 | |
| 168 class SingleValue(InputHandler): | |
| 169 def getInput(self, request): | |
| 170 name = getattr(self, 'inputName', self.submodel) | |
| 171 input = request.args.get(name, None) | |
| 172 if input: | |
| 173 return input[0] | |
| 174 | |
| 175 | |
| 176 class Anything(SingleValue): | |
| 177 """ | |
| 178 Handle anything except for None | |
| 179 """ | |
| 180 def check(self, request, data): | |
| 181 if data is not None: | |
| 182 return 1 | |
| 183 return None | |
| 184 | |
| 185 | |
| 186 class Integer(SingleValue): | |
| 187 """ | |
| 188 Only allow a single integer | |
| 189 """ | |
| 190 def check(self, request, data): | |
| 191 if data is None: return None | |
| 192 try: | |
| 193 int(data) | |
| 194 return 1 | |
| 195 except (TypeError, ValueError): | |
| 196 return 0 | |
| 197 | |
| 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) | |
| 201 | |
| 202 | |
| 203 class Float(SingleValue): | |
| 204 """ | |
| 205 Only allow a single float | |
| 206 """ | |
| 207 def check(self, request, data): | |
| 208 if data is None: return None | |
| 209 try: | |
| 210 float(data) | |
| 211 return 1 | |
| 212 except (TypeError, ValueError): | |
| 213 return 0 | |
| 214 | |
| 215 def handleInvalid(self, request, data): | |
| 216 self.invalidErrorText = "%s is not an float. Please enter a float." % da
ta | |
| 217 SingleValue.handleInvalid(self, request, data) | |
| 218 | |
| 219 | |
| 220 class List(InputHandler): | |
| 221 def check(self, request, data): | |
| 222 return None | |
| 223 | |
| 224 | |
| 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. | |
| 230 | |
| 231 Usage example:: | |
| 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" /> | |
| 238 </form> | |
| 239 | |
| 240 def theCommitFunction(anInteger=None, aString=None): | |
| 241 '''Note how the keyword arguments match up with the leaf model | |
| 242 names above | |
| 243 ''' | |
| 244 print "Yay", anInteger, aString | |
| 245 | |
| 246 class CMyController(controller.Controller): | |
| 247 def wcfactory_theForm(self, request, node, m): | |
| 248 return input.FormAggregator(m, commit=theCommitFunction) | |
| 249 """ | |
| 250 def aggregateValid(self, request, inputhandler, data): | |
| 251 """Aggregate valid input from inputhandlers below us, into a dictionary. | |
| 252 """ | |
| 253 self._valid[inputhandler] = data | |
| 254 | |
| 255 def aggregateInvalid(self, request, inputhandler, data): | |
| 256 self._invalid[inputhandler] = data | |
| 257 | |
| 258 def exit(self, request): | |
| 259 """This is the node complete message | |
| 260 """ | |
| 261 if self._commit: | |
| 262 # Introspect the commit function to see what | |
| 263 # keyword arguments it takes | |
| 264 func = self._commit | |
| 265 if hasattr(func, 'im_func'): | |
| 266 func = func.im_func | |
| 267 args, varargs, varkw, defaults = inspect.getargspec( | |
| 268 func) | |
| 269 wantsRequest = len(args) > 1 and args[1] == 'request' | |
| 270 | |
| 271 if self._invalid: | |
| 272 # whoops error!!!1 | |
| 273 if self._errback: | |
| 274 self._errback(request, self._invalid) | |
| 275 elif self._valid: | |
| 276 # We've got all the input | |
| 277 # Gather it into a dict and call the commit function | |
| 278 results = {} | |
| 279 for item in self._valid: | |
| 280 results[item.model.name] = self._valid[item] | |
| 281 if self._commit: | |
| 282 if wantsRequest: | |
| 283 self._commit(request, **results) | |
| 284 else: | |
| 285 self._commit(**results) | |
| 286 else: | |
| 287 self._parent.aggregateValid(request, self, results) | |
| 288 return results | |
| 289 | |
| 290 | |
| 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. | |
| 297 """ | |
| 298 if not hasattr(self, '_validList'): | |
| 299 self._validList = [] | |
| 300 self._validList.append(data) | |
| 301 | |
| 302 def aggregateInvalid(self, request, inputhandler, data): | |
| 303 if not hasattr(self, '_invalidList'): | |
| 304 self._invalidList = [] | |
| 305 self._invalidList.append(data) | |
| 306 | |
| 307 def exit(self, request): | |
| 308 if self._commit: | |
| 309 # Introspect the commit function to see what | |
| 310 #arguments it takes | |
| 311 func = self._commit | |
| 312 if hasattr(func, 'im_func'): | |
| 313 func = func.im_func | |
| 314 args, varargs, varkw, defaults = inspect.getargspec(func) | |
| 315 self.numArgs = len(args) | |
| 316 wantsRequest = args[1] == 'request' | |
| 317 if wantsRequest: | |
| 318 numArgs -= 1 | |
| 319 else: | |
| 320 # Introspect the template to see if we still have | |
| 321 # controllers that will be giving us input | |
| 322 | |
| 323 # aggregateValid is called before the view renders the node, so | |
| 324 # we can count the number of controllers below us the first time | |
| 325 # we are called | |
| 326 if not hasattr(self, 'numArgs'): | |
| 327 self.numArgs = len(domhelpers.findElementsWithAttributeShallow( | |
| 328 self.view.node, "controller")) | |
| 329 | |
| 330 if self._invalidList: | |
| 331 self._parent.aggregateInvalid(request, self, self._invalidList) | |
| 332 else: | |
| 333 if self._commit: | |
| 334 if wantsRequest: | |
| 335 self._commit(request, *self._validList) | |
| 336 else: | |
| 337 self._commit(*self._validList) | |
| 338 self._parent.aggregateValid(request, self, self._invalidList) | |
| 339 | |
| 340 def commit(self, request, node, data): | |
| 341 """If we're using the ListAggregator, we don't want the list of items | |
| 342 to be rerendered | |
| 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 | |
| 345 """ | |
| 346 pass | |
| 347 | |
| OLD | NEW |