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 |