~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web/woven/input.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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." % data
 
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