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

« back to all changes in this revision

Viewing changes to twisted/web/widgets.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_web -*-
 
2
#
 
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
 
 
7
"""A twisted web component framework.
 
8
 
 
9
This module is DEPRECATED.
 
10
"""
 
11
 
 
12
import warnings
 
13
warnings.warn("This module is deprecated, please use Woven instead.", DeprecationWarning)
 
14
 
 
15
# System Imports
 
16
import string, time, types, traceback, pprint, sys, os
 
17
import linecache
 
18
import re
 
19
from cStringIO import StringIO
 
20
 
 
21
# Twisted Imports
 
22
from twisted.python import failure, log, rebuild, reflect, util
 
23
from twisted.internet import defer
 
24
from twisted.web import http
 
25
 
 
26
# Sibling Imports
 
27
import html, resource, error
 
28
import util as webutil
 
29
 
 
30
#backwards compatibility
 
31
from util import formatFailure, htmlrepr, htmlUnknown, htmlDict, htmlList,\
 
32
                 htmlInst, htmlString, htmlReprTypes
 
33
 
 
34
 
 
35
 
 
36
from server import NOT_DONE_YET
 
37
 
 
38
True = (1==1)
 
39
False = not True
 
40
 
 
41
 
 
42
# magic value that sez a widget needs to take over the whole page.
 
43
 
 
44
FORGET_IT = 99
 
45
 
 
46
def listify(x):
 
47
    return [x]
 
48
def _ellipsize(x):
 
49
    y = repr(x)
 
50
    if len(y) > 1024:
 
51
        return y[:1024]+"..."
 
52
    return y
 
53
 
 
54
 
 
55
class Widget:
 
56
    """A component of a web page.
 
57
    """
 
58
    title = None
 
59
    def getTitle(self, request):
 
60
        return self.title or reflect.qual(self.__class__)
 
61
 
 
62
    def display(self, request):
 
63
        """Implement me to represent your widget.
 
64
 
 
65
        I must return a list of strings and twisted.internet.defer.Deferred
 
66
        instances.
 
67
        """
 
68
        raise NotImplementedError("%s.display" % reflect.qual(self.__class__))
 
69
 
 
70
class StreamWidget(Widget):
 
71
    """A 'streamable' component of a webpage.
 
72
    """
 
73
 
 
74
    def stream(self, write, request):
 
75
        """Call 'write' multiple times with a string argument to represent this widget.
 
76
        """
 
77
        raise NotImplementedError("%s.stream" % reflect.qual(self.__class__))
 
78
 
 
79
    def display(self, request):
 
80
        """Produce a list containing a single string.
 
81
        """
 
82
        l = []
 
83
        try:
 
84
            result = self.stream(l.append, request)
 
85
            if result is not None:
 
86
                return result
 
87
            return l
 
88
        except:
 
89
            return [webutil.formatFailure(failure.Failure())]
 
90
 
 
91
class WidgetMixin(Widget):
 
92
    """A mix-in wrapper for a Widget.
 
93
 
 
94
    This mixin can be used to wrap functionality in any other widget with a
 
95
    method of your choosing.  It is designed to be used for mix-in classes that
 
96
    can be mixed in to Form, StreamWidget, Presentation, etc, to augment the
 
97
    data available to the 'display' methods of those classes, usually by adding
 
98
    it to a Session.
 
99
    """
 
100
 
 
101
    def display(self):
 
102
        raise NotImplementedError("%s.display" % self.__class__)
 
103
 
 
104
    def displayMixedWidget(self, request):
 
105
        for base in reflect.allYourBase(self.__class__):
 
106
            if issubclass(base, Widget) and not issubclass(base, WidgetMixin):
 
107
                return base.display(self, request)
 
108
 
 
109
class Presentation(Widget):
 
110
    """I am a widget which formats a template with interspersed python expressions.
 
111
    """
 
112
    template = '''
 
113
    Hello, %%%%world%%%%.
 
114
    '''
 
115
    world = "you didn't assign to the 'template' attribute"
 
116
    def __init__(self, template=None, filename=None):
 
117
        if filename:
 
118
            self.template = open(filename).read()
 
119
        elif template:
 
120
            self.template = template
 
121
        self.variables = {}
 
122
        self.tmpl = string.split(self.template, "%%%%")
 
123
 
 
124
    def addClassVars(self, namespace, Class):
 
125
        for base in Class.__bases__:
 
126
            # Traverse only superclasses that know about Presentation.
 
127
            if issubclass(base, Presentation) and base is not Presentation:
 
128
                self.addClassVars(namespace, base)
 
129
        # 'lower' classes in the class heirarchy take precedence.
 
130
        for k in Class.__dict__.keys():
 
131
            namespace[k] = getattr(self, k)
 
132
 
 
133
    def addVariables(self, namespace, request):
 
134
        self.addClassVars(namespace, self.__class__)
 
135
 
 
136
    def prePresent(self, request):
 
137
        """Perform any tasks which must be done before presenting the page.
 
138
        """
 
139
 
 
140
    def formatTraceback(self, tb):
 
141
        return [html.PRE(tb)]
 
142
 
 
143
    def streamCall(self, call, *args, **kw):
 
144
        """Utility: Call a method like StreamWidget's 'stream'.
 
145
        """
 
146
        io = StringIO()
 
147
        apply(call, (io.write,) + args, kw)
 
148
        return io.getvalue()
 
149
 
 
150
    def display(self, request):
 
151
        tm = []
 
152
        flip = 0
 
153
        namespace = {}
 
154
        self.prePresent(request)
 
155
        self.addVariables(namespace, request)
 
156
        # This variable may not be obscured...
 
157
        namespace['request'] = request
 
158
        namespace['self'] = self
 
159
        for elem in self.tmpl:
 
160
            flip = not flip
 
161
            if flip:
 
162
                if elem:
 
163
                    tm.append(elem)
 
164
            else:
 
165
                try:
 
166
                    x = eval(elem, namespace, namespace)
 
167
                except:
 
168
                    log.deferr()
 
169
                    tm.append(webutil.formatFailure(failure.Failure()))
 
170
                else:
 
171
                    if isinstance(x, types.ListType):
 
172
                        tm.extend(x)
 
173
                    elif isinstance(x, Widget):
 
174
                        val = x.display(request)
 
175
                        if not isinstance(val, types.ListType):
 
176
                            raise Exception("%s.display did not return a list, it returned %s!" % (x.__class__, repr(val)))
 
177
                        tm.extend(val)
 
178
                    else:
 
179
                        # Only two allowed types here should be deferred and
 
180
                        # string.
 
181
                        tm.append(x)
 
182
        return tm
 
183
 
 
184
 
 
185
def htmlFor_hidden(write, name, value):
 
186
    write('<INPUT TYPE="hidden" NAME="%s" VALUE="%s" />' % (name, value))
 
187
 
 
188
def htmlFor_file(write, name, value):
 
189
    write('<INPUT SIZE="60" TYPE="file" NAME="%s" />' % name)
 
190
 
 
191
def htmlFor_string(write, name, value):
 
192
    write('<INPUT SIZE="60" TYPE="text" NAME="%s" VALUE="%s" />' % (name, value))
 
193
 
 
194
def htmlFor_password(write, name, value):
 
195
    write('<INPUT SIZE="60" TYPE="password" NAME="%s" />' % name)
 
196
 
 
197
def htmlFor_text(write, name, value):
 
198
    write('<textarea COLS="60" ROWS="10" NAME="%s" WRAP="virtual">%s</textarea>' % (name, value))
 
199
 
 
200
def htmlFor_menu(write, name, value, allowMultiple=False):
 
201
    "Value of the format [(optionName, displayName[, selected]), ...]"
 
202
 
 
203
    write('  <select NAME="%s"%s>\n' %
 
204
          (name, (allowMultiple and " multiple") or ''))
 
205
 
 
206
    for v in value:
 
207
        optionName, displayName, selected = util.padTo(3, v)
 
208
        selected = (selected and " selected") or ''
 
209
        write('    <option VALUE="%s"%s>%s</option>\n' %
 
210
              (optionName, selected, displayName))
 
211
    if not value:
 
212
        write('    <option VALUE=""></option>\n')
 
213
    write("  </select>\n")
 
214
 
 
215
def htmlFor_multimenu(write, name, value):
 
216
    "Value of the format [(optionName, displayName[, selected]), ...]"
 
217
    return htmlFor_menu(write, name, value, True)
 
218
 
 
219
def htmlFor_checkbox(write, name, value):
 
220
    "A checkbox."
 
221
    if value:
 
222
        value = 'checked = "1"'
 
223
    else:
 
224
        value = ''
 
225
    write('<INPUT TYPE="checkbox" NAME="__checkboxes__" VALUE="%s" %s />\n' % (name, value))
 
226
 
 
227
def htmlFor_checkgroup(write, name, value):
 
228
    "A check-group."
 
229
    for optionName, displayName, checked in value:
 
230
        checked = (checked and 'checked = "1"') or ''
 
231
        write('<INPUT TYPE="checkbox" NAME="%s" VALUE="%s" %s />%s<br />\n' % (name, optionName, checked, displayName))
 
232
 
 
233
def htmlFor_radio(write, name, value):
 
234
    "A radio button group."
 
235
    for optionName, displayName, checked in value:
 
236
        checked = (checked and 'checked = "1"') or ''
 
237
        write('<INPUT TYPE="radio" NAME="%s" VALUE="%s" %s />%s<br />\n' % (name, optionName, checked, displayName))
 
238
 
 
239
class FormInputError(Exception):
 
240
    pass
 
241
 
 
242
class Form(Widget):
 
243
    """I am a web form.
 
244
 
 
245
    In order to use me, you probably want to set self.formFields (or override
 
246
    'getFormFields') and override 'process'.  In order to demonstrate how this
 
247
    is done, here is a small sample Form subclass::
 
248
 
 
249
      |  from twisted.web import widgets
 
250
      |  class HelloForm(widgets.Form):
 
251
      |      formFields = [
 
252
      |          ['string', 'Who to greet?', 'whoToGreet', 'World',
 
253
      |            'This is for choosing who to greet.'],
 
254
      |          ['menu', 'How to greet?', 'how', [('cheerfully', 'with a smile'),
 
255
      |                                            ('sullenly', 'without enthusiasm'),
 
256
      |                                            ('spontaneously', 'on the spur of the moment')]]
 
257
      |            'This is for choosing how to greet them.']
 
258
      |      def process(self, write, request, submit, whoToGreet, how):
 
259
      |          write('The web wakes up and %s says, \"Hello, %s!\"' % (how, whoToGreet))
 
260
 
 
261
    If you load this widget, you will see that it displays a form with 2 inputs
 
262
    derived from data in formFields.  Note the argument names to 'process':
 
263
    after 'write' and 'request', they are the same as the 3rd elements ('Input
 
264
    Name' parameters) of the formFields list.
 
265
    """
 
266
 
 
267
    formGen = {
 
268
        'hidden': htmlFor_hidden,
 
269
        'file': htmlFor_file,
 
270
        'string': htmlFor_string,
 
271
        'int': htmlFor_string,
 
272
        'float': htmlFor_string,
 
273
        'text': htmlFor_text,
 
274
        'menu': htmlFor_menu,
 
275
        'multimenu': htmlFor_multimenu,
 
276
        'password': htmlFor_password,
 
277
        'checkbox': htmlFor_checkbox,
 
278
        'checkgroup': htmlFor_checkgroup,
 
279
        'radio': htmlFor_radio,
 
280
    }
 
281
 
 
282
    formParse = {
 
283
        'int': int,
 
284
        'float': float,
 
285
    }
 
286
 
 
287
    formFields = [
 
288
    ]
 
289
 
 
290
    # do we raise an error when we get extra args or not?
 
291
    formAcceptExtraArgs = 0
 
292
 
 
293
    def getFormFields(self, request, fieldSet = None):
 
294
        """I return a list of lists describing this form, or a Deferred.
 
295
 
 
296
        This information is used both to display the form and to process it.
 
297
        The list is in the following format::
 
298
 
 
299
          | [['Input Type',   'Display Name',   'Input Name',   'Input Value', 'Description'],
 
300
          |  ['Input Type 2', 'Display Name 2', 'Input Name 2', 'Input Value 2', 'Description 2']
 
301
          |  ...]
 
302
 
 
303
        Valid values for 'Input Type' are:
 
304
 
 
305
          - 'hidden': a hidden field that contains a string that the user won't change
 
306
 
 
307
          - 'string': a short string
 
308
 
 
309
          - 'int': an integer, e.g. 1, 0, 25 or -23
 
310
 
 
311
          - 'float': a float, e.g. 1.0, 2, -3.45, or 28.4324231
 
312
 
 
313
          - 'text': a longer text field, suitable for entering paragraphs
 
314
 
 
315
          - 'menu': an HTML SELECT input, a list of choices
 
316
 
 
317
          - 'multimenu': an HTML SELECT input allowing multiple choices
 
318
 
 
319
          - 'checkgroup': a group of checkboxes
 
320
 
 
321
          - 'radio': a group of radio buttons
 
322
 
 
323
          - 'password': a 'string' field where the contents are not visible as the user types
 
324
 
 
325
          - 'file': a file-upload form (EXPERIMENTAL)
 
326
 
 
327
        'Display Name' is a descriptive string that will be used to
 
328
        identify the field to the user.
 
329
 
 
330
        The 'Input Name' must be a legal Python identifier that describes both
 
331
        the value's name on the HTML form and the name of an argument to
 
332
        'self.process()'.
 
333
 
 
334
        The 'Input Value' is usually a string, but its value can depend on the
 
335
        'Input Type'.  'int' it is an integer, 'menu' it is a list of pairs of
 
336
        strings, representing (value, name) pairs for the menu options.  Input
 
337
        value for 'checkgroup' and 'radio' should be a list of ('inputName',
 
338
        'Display Name', 'checked') triplets.
 
339
 
 
340
        The 'Description' field is an (optional) string which describes the form
 
341
        item to the user.
 
342
 
 
343
        If this result is statically determined for your Form subclass, you can
 
344
        assign it to FormSubclass.formFields; if you need to determine it
 
345
        dynamically, you can override this method.
 
346
 
 
347
        Note: In many cases it is desirable to use user input for defaults in
 
348
        the form rather than those supplied by your calculations, which is what
 
349
        this method will do to self.formFields.  If this is the case for you,
 
350
        but you still need to dynamically calculate some fields, pass your
 
351
        results back through this method by doing::
 
352
 
 
353
          |  def getFormFields(self, request):
 
354
          |      myFormFields = [self.myFieldCalculator()]
 
355
          |      return widgets.Form.getFormFields(self, request, myFormFields)
 
356
 
 
357
        """
 
358
        fields = []
 
359
        if fieldSet is None:
 
360
            fieldSet = self.formFields
 
361
        if not self.shouldProcess(request):
 
362
            return fieldSet
 
363
 
 
364
        for field in fieldSet:
 
365
            if len(field)==5:
 
366
                inputType, displayName, inputName, inputValue, description = field
 
367
            else:
 
368
                inputType, displayName, inputName, inputValue = field
 
369
                description = ""
 
370
 
 
371
            if inputType == 'checkbox':
 
372
                if request.args.has_key('__checkboxes__'):
 
373
                    if inputName in request.args['__checkboxes__']:
 
374
                        inputValue = 1
 
375
                    else:
 
376
                        inputValue = 0
 
377
                else:
 
378
                    inputValue = 0
 
379
            elif inputType in ('checkgroup', 'radio'):
 
380
                if request.args.has_key(inputName):
 
381
                    keys = request.args[inputName]
 
382
                else:
 
383
                    keys = []
 
384
                iv = inputValue
 
385
                inputValue = []
 
386
                for optionName, optionDisplayName, checked in iv:
 
387
                    checked = optionName in keys
 
388
                    inputValue.append([optionName, optionDisplayName, checked])
 
389
            elif request.args.has_key(inputName):
 
390
                iv = request.args[inputName][0]
 
391
                if inputType in ['menu', 'multimenu']:
 
392
                    if iv in inputValue:
 
393
                        inputValue.remove(iv)
 
394
                        inputValue.insert(0, iv)
 
395
                else:
 
396
                    inputValue = iv
 
397
            fields.append([inputType, displayName, inputName, inputValue, description])
 
398
        return fields
 
399
 
 
400
    submitNames = ['Submit']
 
401
    actionURI = ''
 
402
 
 
403
    def format(self, form, write, request):
 
404
        """I display an HTML FORM according to the result of self.getFormFields.
 
405
        """
 
406
        write('<form ENCTYPE="multipart/form-data" METHOD="post" ACTION="%s">\n'
 
407
              '<table BORDER="0">\n' % (self.actionURI or request.uri))
 
408
 
 
409
        for field in form:
 
410
            if len(field) == 5:
 
411
                inputType, displayName, inputName, inputValue, description = field
 
412
            else:
 
413
                inputType, displayName, inputName, inputValue = field
 
414
                description = ""
 
415
            write('<tr>\n<td ALIGN="right" VALIGN="top"><B>%s</B></td>\n'
 
416
                  '<td VALIGN="%s">\n' %
 
417
                  (displayName, ((inputType == 'text') and 'top') or 'middle'))
 
418
            self.formGen[inputType](write, inputName, inputValue)
 
419
            write('\n<br />\n<font size="-1">%s</font></td>\n</tr>\n' % description)
 
420
 
 
421
 
 
422
        write('<tr><td></td><td ALIGN="left"><hr />\n')
 
423
        for submitName in self.submitNames:
 
424
            write('<INPUT TYPE="submit" NAME="submit" VALUE="%s" />\n' % submitName)
 
425
        write('</td></tr>\n</table>\n'
 
426
              '<INPUT TYPE="hidden" NAME="__formtype__" VALUE="%s" />\n'
 
427
              % (reflect.qual(self.__class__)))
 
428
        fid = self.getFormID()
 
429
        if fid:
 
430
            write('<INPUT TYPE="hidden" NAME="__formid__" VALUE="%s" />\n' % fid)
 
431
        write("</form>\n")
 
432
 
 
433
    def getFormID(self):
 
434
        """Override me: I disambiguate between multiple forms of the same type.
 
435
 
 
436
        In order to determine which form an HTTP POST request is for, you must
 
437
        have some unique identifier which distinguishes your form from other
 
438
        forms of the same class.  An example of such a unique identifier would
 
439
        be: on a page with multiple FrobConf forms, each FrobConf form refers
 
440
        to a particular Frobnitz instance, which has a unique id().  The
 
441
        FrobConf form's getFormID would probably look like this::
 
442
 
 
443
          |  def getFormID(self):
 
444
          |      return str(id(self.frobnitz))
 
445
 
 
446
        By default, this method will return None, since distinct Form instances
 
447
        may be identical as far as the application is concerned.
 
448
        """
 
449
 
 
450
    def process(self, write, request, submit, **kw):
 
451
        """Override me: I process a form.
 
452
 
 
453
        I will only be called when the correct form input data to process this
 
454
        form has been received.
 
455
 
 
456
        I take a variable number of arguments, beginning with 'write',
 
457
        'request', and 'submit'.  'write' is a callable object that will append
 
458
        a string to the response, 'request' is a twisted.web.request.Request
 
459
        instance, and 'submit' is the name of the submit action taken.
 
460
 
 
461
        The remainder of my arguments must be correctly named.  They will each be named after one of the
 
462
 
 
463
        """
 
464
        write("<pre>Submit: %s <br /> %s</pre>" % (submit, html.PRE(pprint.PrettyPrinter().pformat(kw))))
 
465
 
 
466
    def _doProcess(self, form, write, request):
 
467
        """(internal) Prepare arguments for self.process.
 
468
        """
 
469
        args = request.args.copy()
 
470
        kw = {}
 
471
        for field in form:
 
472
            inputType, displayName, inputName, inputValue = field[:4]
 
473
            if inputType == 'checkbox':
 
474
                if request.args.has_key('__checkboxes__'):
 
475
                    if inputName in request.args['__checkboxes__']:
 
476
                        formData = 1
 
477
                    else:
 
478
                        formData = 0
 
479
                else:
 
480
                    formData = 0
 
481
            elif inputType in ['checkgroup', 'radio', 'multimenu']:
 
482
                if args.has_key(inputName):
 
483
                    formData = args[inputName]
 
484
                    del args[inputName]
 
485
                else:
 
486
                    formData = []
 
487
            else:
 
488
                if not args.has_key(inputName):
 
489
                    raise FormInputError("missing field %s." % repr(inputName))
 
490
                formData = args[inputName]
 
491
                del args[inputName]
 
492
                if not len(formData) == 1:
 
493
                    raise FormInputError("multiple values for field %s." %repr(inputName))
 
494
                formData = formData[0]
 
495
                method = self.formParse.get(inputType)
 
496
                if method:
 
497
                    try:
 
498
                        formData = method(formData)
 
499
                    except:
 
500
                        raise FormInputError("%s: %s" % (displayName, "error"))
 
501
            kw[inputName] = formData
 
502
        submitAction = args.get('submit')
 
503
        if submitAction:
 
504
            submitAction = submitAction[0]
 
505
        for field in ['submit', '__formtype__', '__checkboxes__']:
 
506
            if args.has_key(field):
 
507
                del args[field]
 
508
        if args and not self.formAcceptExtraArgs:
 
509
            raise FormInputError("unknown fields: %s" % repr(args))
 
510
        return apply(self.process, (write, request, submitAction), kw)
 
511
 
 
512
    def formatError(self,error):
 
513
        """Format an error message.
 
514
 
 
515
        By default, this will make the message appear in red, bold italics.
 
516
        """
 
517
        return '<font color="#f00"><b><i>%s</i></b></font><br />\n' % error
 
518
 
 
519
    def shouldProcess(self, request):
 
520
        args = request.args
 
521
        fid = self.getFormID()
 
522
        return (args and # there are arguments to the request
 
523
                args.has_key('__formtype__') and # this is a widgets.Form request
 
524
                args['__formtype__'][0] == reflect.qual(self.__class__) and # it is for a form of my type
 
525
                ((not fid) or # I am only allowed one form per page
 
526
                 (args.has_key('__formid__') and # if I distinguish myself from others, the request must too
 
527
                  args['__formid__'][0] == fid))) # I am in fact the same
 
528
 
 
529
    def tryAgain(self, err, req):
 
530
        """Utility method for re-drawing the form with an error message.
 
531
 
 
532
        This is handy in forms that process Deferred results.  Normally you can
 
533
        just raise a FormInputError() and this will happen by default.
 
534
 
 
535
        """
 
536
        l = []
 
537
        w = l.append
 
538
        w(self.formatError(err))
 
539
        self.format(self.getFormFields(req), w, req)
 
540
        return l
 
541
 
 
542
    def display(self, request):
 
543
        """Display the form."""
 
544
        form = self.getFormFields(request)
 
545
        if isinstance(form, defer.Deferred):
 
546
            if self.shouldProcess(request):
 
547
                form.addCallback(lambda form, f=self._displayProcess, r=request: f(r, form))
 
548
            else:
 
549
                form.addCallback(lambda form, f=self._displayFormat, r=request: f(r, form))
 
550
            return [form]
 
551
        else:
 
552
            if self.shouldProcess(request):
 
553
                return self._displayProcess(request, form)
 
554
            else:
 
555
                return self._displayFormat(request, form)
 
556
 
 
557
    def _displayProcess(self, request, form):
 
558
        l = []
 
559
        write = l.append
 
560
        try:
 
561
            val = self._doProcess(form, write, request)
 
562
            if val:
 
563
                l.extend(val)
 
564
        except FormInputError, fie:
 
565
            write(self.formatError(str(fie)))
 
566
        return l
 
567
 
 
568
    def _displayFormat(self, request, form):
 
569
        l = []
 
570
        self.format(form, l.append, request)
 
571
        return l
 
572
 
 
573
 
 
574
 
 
575
class DataWidget(Widget):
 
576
    def __init__(self, data):
 
577
        self.data = data
 
578
    def display(self, request):
 
579
        return [self.data]
 
580
 
 
581
class Time(Widget):
 
582
    def display(self, request):
 
583
        return [time.ctime(time.time())]
 
584
 
 
585
class Container(Widget):
 
586
    def __init__(self, *widgets):
 
587
        self.widgets = widgets
 
588
 
 
589
    def display(self, request):
 
590
        value = []
 
591
        for widget in self.widgets:
 
592
            d = widget.display(request)
 
593
            value.extend(d)
 
594
        return value
 
595
 
 
596
class _RequestDeferral:
 
597
    def __init__(self):
 
598
        self.deferred = defer.Deferred()
 
599
        self.io = StringIO()
 
600
        self.write = self.io.write
 
601
 
 
602
    def finish(self):
 
603
        self.deferred.callback([self.io.getvalue()])
 
604
 
 
605
def possiblyDeferWidget(widget, request):
 
606
    # web in my head get it out get it out
 
607
    try:
 
608
        disp = widget.display(request)
 
609
        # if this widget wants to defer anything -- well, I guess we've got to
 
610
        # defer it.
 
611
        for elem in disp:
 
612
            if isinstance(elem, defer.Deferred):
 
613
                req = _RequestDeferral()
 
614
                RenderSession(disp, req)
 
615
                return req.deferred
 
616
        return string.join(disp, '')
 
617
    except:
 
618
        io = StringIO()
 
619
        traceback.print_exc(file=io)
 
620
        return html.PRE(io.getvalue())
 
621
 
 
622
class RenderSession:
 
623
    """I handle rendering of a list of deferreds, outputting their
 
624
    results in correct order."""
 
625
 
 
626
    class Sentinel:
 
627
        pass
 
628
 
 
629
    def __init__(self, lst, request):
 
630
        self.lst = lst
 
631
        self.request = request
 
632
        self.needsHeaders = 0
 
633
        self.beforeBody = 1
 
634
        self.forgotten = 0
 
635
        self.pauseList = []
 
636
        for i in range(len(self.lst)):
 
637
            item = self.lst[i]
 
638
            if isinstance(item, defer.Deferred):
 
639
                self._addDeferred(item, self.lst, i)
 
640
        self.keepRendering()
 
641
 
 
642
    def _addDeferred(self, deferred, lst, idx):
 
643
        sentinel = self.Sentinel()
 
644
        if hasattr(deferred, 'needsHeader'):
 
645
            # You might want to set a header from a deferred, in which
 
646
            # case you have to set an attribute -- needsHeader.
 
647
            self.needsHeaders = self.needsHeaders + 1
 
648
            args = (sentinel, 1)
 
649
        else:
 
650
            args = (sentinel, 0)
 
651
        lst[idx] = sentinel, deferred
 
652
        deferred.pause()
 
653
        self.pauseList.append(deferred)
 
654
        deferred.addCallbacks(self.callback, self.callback,
 
655
                              callbackArgs=args, errbackArgs=args)
 
656
 
 
657
 
 
658
    def callback(self, result, sentinel, decNeedsHeaders):
 
659
        if self.forgotten:
 
660
            return
 
661
        if result != FORGET_IT:
 
662
            self.needsHeaders = self.needsHeaders - decNeedsHeaders
 
663
        else:
 
664
            result = [FORGET_IT]
 
665
 
 
666
        # Make sure result is a sequence,
 
667
        if not type(result) in (types.ListType, types.TupleType):
 
668
            result = [result]
 
669
 
 
670
        # If the deferred does not wish to produce its result all at
 
671
        # once, it can give us a partial result as
 
672
        #  (NOT_DONE_YET, partial_result)
 
673
        ## XXX: How would a deferred go about producing the result in multiple
 
674
        ## stages?? --glyph
 
675
        if result[0] is NOT_DONE_YET:
 
676
            done = 0
 
677
            result = result[1]
 
678
            if not type(result) in (types.ListType, types.TupleType):
 
679
                result = [result]
 
680
        else:
 
681
            done = 1
 
682
 
 
683
        for i in xrange(len(result)):
 
684
            item = result[i]
 
685
            if isinstance(item, defer.Deferred):
 
686
                self._addDeferred(item, result, i)
 
687
 
 
688
        for position in range(len(self.lst)):
 
689
            item = self.lst[position]
 
690
            if type(item) is types.TupleType and len(item) > 0:
 
691
                if item[0] is sentinel:
 
692
                    break
 
693
        else:
 
694
            raise AssertionError('Sentinel for Deferred not found!')
 
695
 
 
696
        if done:
 
697
            self.lst[position:position+1] = result
 
698
        else:
 
699
            self.lst[position:position] = result
 
700
 
 
701
        self.keepRendering()
 
702
 
 
703
 
 
704
    def keepRendering(self):
 
705
        while self.pauseList:
 
706
            pl = self.pauseList
 
707
            self.pauseList = []
 
708
            for deferred in pl:
 
709
                deferred.unpause()
 
710
            return
 
711
 
 
712
        if self.needsHeaders:
 
713
            # short circuit actual rendering process until we're sure no
 
714
            # more deferreds need to set headers...
 
715
            return
 
716
 
 
717
        assert self.lst is not None, "This shouldn't happen."
 
718
        while 1:
 
719
            item = self.lst[0]
 
720
            if self.beforeBody and FORGET_IT in self.lst:
 
721
                # If I haven't moved yet, and the widget wants to take
 
722
                # over the page, let it do so!
 
723
                self.forgotten = 1
 
724
                return
 
725
 
 
726
            if isinstance(item, types.StringType):
 
727
                self.beforeBody = 0
 
728
                self.request.write(item)
 
729
            elif type(item) is types.TupleType and len(item) > 0:
 
730
                if isinstance(item[0], self.Sentinel):
 
731
                    return
 
732
            elif isinstance(item, failure.Failure):
 
733
                self.request.write(webutil.formatFailure(item))
 
734
            else:
 
735
                self.beforeBody = 0
 
736
                unknown = html.PRE(repr(item))
 
737
                self.request.write("RENDERING UNKNOWN: %s" % unknown)
 
738
 
 
739
            del self.lst[0]
 
740
            if len(self.lst) == 0:
 
741
                self.lst = None
 
742
                self.request.finish()
 
743
                return
 
744
 
 
745
 
 
746
## XXX: is this needed?
 
747
class WidgetResource(resource.Resource):
 
748
    def __init__(self, widget):
 
749
        self.widget = widget
 
750
        resource.Resource.__init__(self)
 
751
 
 
752
    def render(self, request):
 
753
        RenderSession(self.widget.display(request), request)
 
754
        return NOT_DONE_YET
 
755
 
 
756
 
 
757
class Page(resource.Resource, Presentation):
 
758
 
 
759
    def __init__(self):
 
760
        resource.Resource.__init__(self)
 
761
        Presentation.__init__(self)
 
762
 
 
763
    def render(self, request):
 
764
        displayed = self.display(request)
 
765
        RenderSession(displayed, request)
 
766
        return NOT_DONE_YET
 
767
 
 
768
 
 
769
class WidgetPage(Page):
 
770
    """
 
771
    I am a Page that takes a Widget in its constructor, and displays that
 
772
    Widget wrapped up in a simple HTML template.
 
773
    """
 
774
    stylesheet = '''
 
775
    a
 
776
    {
 
777
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
778
        color: #369;
 
779
        text-decoration: none;
 
780
    }
 
781
 
 
782
    th
 
783
    {
 
784
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
785
        font-weight: bold;
 
786
        text-decoration: none;
 
787
        text-align: left;
 
788
    }
 
789
 
 
790
    pre, code
 
791
    {
 
792
        font-family: "Courier New", Courier, monospace;
 
793
    }
 
794
 
 
795
    p, body, td, ol, ul, menu, blockquote, div
 
796
    {
 
797
        font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
 
798
        color: #000;
 
799
    }
 
800
    '''
 
801
 
 
802
    template = '''<html>
 
803
    <head>
 
804
    <title>%%%%self.title%%%%</title>
 
805
    <style>
 
806
    %%%%self.stylesheet%%%%
 
807
    </style>
 
808
    <base href="%%%%request.prePathURL()%%%%">
 
809
    </head>
 
810
 
 
811
    <body>
 
812
    <h1>%%%%self.title%%%%</h1>
 
813
    %%%%self.widget%%%%
 
814
    </body>
 
815
    </html>
 
816
    '''
 
817
 
 
818
    title = 'No Title'
 
819
    widget = 'No Widget'
 
820
 
 
821
    def __init__(self, widget):
 
822
        Page.__init__(self)
 
823
        self.widget = widget
 
824
        if hasattr(widget, 'stylesheet'):
 
825
            self.stylesheet = widget.stylesheet
 
826
 
 
827
    def prePresent(self, request):
 
828
        self.title = self.widget.getTitle(request)
 
829
 
 
830
    def render(self, request):
 
831
        displayed = self.display(request)
 
832
        RenderSession(displayed, request)
 
833
        return NOT_DONE_YET
 
834
 
 
835
class Gadget(resource.Resource):
 
836
    """I am a collection of Widgets, to be rendered through a Page Factory.
 
837
    self.pageFactory should be a Resource that takes a Widget in its
 
838
    constructor. The default is twisted.web.widgets.WidgetPage.
 
839
    """
 
840
 
 
841
    isLeaf = 0
 
842
 
 
843
    def __init__(self):
 
844
        resource.Resource.__init__(self)
 
845
        self.widgets = {}
 
846
        self.files = []
 
847
        self.modules = []
 
848
        self.paths = {}
 
849
 
 
850
    def render(self, request):
 
851
        #Redirect to view this entity as a collection.
 
852
        request.setResponseCode(http.FOUND)
 
853
        # TODO who says it's not https?
 
854
        request.setHeader("location","http%s://%s%s/" % (
 
855
            request.isSecure() and 's' or '',
 
856
            request.getHeader("host"),
 
857
            (string.split(request.uri,'?')[0])))
 
858
        return "NO DICE!"
 
859
 
 
860
    def putWidget(self, path, widget):
 
861
        """
 
862
        Gadget.putWidget(path, widget)
 
863
        Add a Widget to this Gadget. It will be rendered through the
 
864
        pageFactory associated with this Gadget, whenever 'path' is requested.
 
865
        """
 
866
        self.widgets[path] = widget
 
867
 
 
868
    #this is an obsolete function
 
869
    def addFile(self, path):
 
870
        """
 
871
        Gadget.addFile(path)
 
872
        Add a static path to this Gadget. This method is obsolete, use
 
873
        Gadget.putPath instead.
 
874
        """
 
875
 
 
876
        log.msg("Gadget.addFile() is deprecated.")
 
877
        self.paths[path] = path
 
878
 
 
879
    def putPath(self, path, pathname):
 
880
        """
 
881
        Gadget.putPath(path, pathname)
 
882
        Add a static path to this Gadget. Whenever 'path' is requested,
 
883
        twisted.web.static.File(pathname) is sent.
 
884
        """
 
885
        self.paths[path] = pathname
 
886
 
 
887
    def getWidget(self, path, request):
 
888
        return self.widgets.get(path)
 
889
 
 
890
    def pageFactory(self, *args, **kwargs):
 
891
        """
 
892
        Gadget.pageFactory(*args, **kwargs) -> Resource
 
893
        By default, this method returns self.page(*args, **kwargs). It
 
894
        is only for backwards-compatibility -- you should set the 'pageFactory'
 
895
        attribute on your Gadget inside of its __init__ method.
 
896
        """
 
897
        #XXX: delete this after a while.
 
898
        if hasattr(self, "page"):
 
899
            log.msg("Gadget.page is deprecated, use Gadget.pageFactory instead")
 
900
            return apply(self.page, args, kwargs)
 
901
        else:
 
902
            return apply(WidgetPage, args, kwargs)
 
903
 
 
904
    def getChild(self, path, request):
 
905
        if path == '':
 
906
            # ZOOP!
 
907
            if isinstance(self, Widget):
 
908
                return self.pageFactory(self)
 
909
        widget = self.getWidget(path, request)
 
910
        if widget:
 
911
            if isinstance(widget, resource.Resource):
 
912
                return widget
 
913
            else:
 
914
                p = self.pageFactory(widget)
 
915
                p.isLeaf = getattr(widget,'isLeaf',0)
 
916
                return p
 
917
        elif self.paths.has_key(path):
 
918
            prefix = getattr(sys.modules[self.__module__], '__file__', '')
 
919
            if prefix:
 
920
                prefix = os.path.abspath(os.path.dirname(prefix))
 
921
            return static.File(os.path.join(prefix, self.paths[path]))
 
922
 
 
923
        elif path == '__reload__':
 
924
            return self.pageFactory(Reloader(map(reflect.namedModule, [self.__module__] + self.modules)))
 
925
        else:
 
926
            return error.NoResource("No such child resource in gadget.")
 
927
 
 
928
 
 
929
class TitleBox(Presentation):
 
930
 
 
931
    template = '''\
 
932
<table %%%%self.widthOption%%%% cellpadding="1" cellspacing="0" border="0"><tr>\
 
933
<td bgcolor="%%%%self.borderColor%%%%"><center><font color="%%%%self.titleTextColor%%%%">%%%%self.title%%%%</font></center>\
 
934
<table width="100%" cellpadding="3" cellspacing="0" border="0"><tr>\
 
935
<td bgcolor="%%%%self.boxColor%%%%"><font color="%%%%self.boxTextColor%%%%">%%%%self.widget%%%%</font></td>\
 
936
</tr></table></td></tr></table>\
 
937
'''
 
938
 
 
939
    borderColor = '#000000'
 
940
    titleTextColor = '#ffffff'
 
941
    boxTextColor = '#000000'
 
942
    boxColor = '#ffffff'
 
943
    widthOption = 'width="100%"'
 
944
 
 
945
    title = 'No Title'
 
946
    widget = 'No Widget'
 
947
 
 
948
    def __init__(self, title, widget):
 
949
        """Wrap a widget with a given title.
 
950
        """
 
951
        self.widget = widget
 
952
        self.title = title
 
953
        Presentation.__init__(self)
 
954
 
 
955
 
 
956
class Reloader(Presentation):
 
957
    template = '''
 
958
    Reloading...
 
959
    <ul>
 
960
    %%%%reload(request)%%%%
 
961
    </ul> ... reloaded!
 
962
    '''
 
963
    def __init__(self, modules):
 
964
        Presentation.__init__(self)
 
965
        self.modules = modules
 
966
 
 
967
    def reload(self, request):
 
968
        request.redirect("..")
 
969
        x = []
 
970
        write = x.append
 
971
        for module in self.modules:
 
972
            rebuild.rebuild(module)
 
973
            write('<li>reloaded %s<br />' % module.__name__)
 
974
        return x
 
975
 
 
976
class Sidebar(StreamWidget):
 
977
    bar = [
 
978
        ['Twisted',
 
979
            ['mirror', 'http://coopweb.org/ssd/twisted/'],
 
980
            ['mailing list', 'cgi-bin/mailman/listinfo/twisted-python']
 
981
        ]
 
982
    ]
 
983
 
 
984
    headingColor = 'ffffff'
 
985
    headingTextColor = '000000'
 
986
    activeHeadingColor = '000000'
 
987
    activeHeadingTextColor = 'ffffff'
 
988
    sectionColor = '000088'
 
989
    sectionTextColor = '008888'
 
990
    activeSectionColor = '0000ff'
 
991
    activeSectionTextColor = '00ffff'
 
992
 
 
993
    def __init__(self, highlightHeading, highlightSection):
 
994
        self.highlightHeading = highlightHeading
 
995
        self.highlightSection = highlightSection
 
996
 
 
997
    def getList(self):
 
998
        return self.bar
 
999
 
 
1000
    def stream(self, write, request):
 
1001
        write("<table width=120 cellspacing=1 cellpadding=1 border=0>")
 
1002
        for each in self.getList():
 
1003
            if each[0] == self.highlightHeading:
 
1004
                headingColor = self.activeHeadingColor
 
1005
                headingTextColor = self.activeHeadingTextColor
 
1006
                canHighlight = 1
 
1007
            else:
 
1008
                headingColor = self.headingColor
 
1009
                headingTextColor = self.headingTextColor
 
1010
                canHighlight = 0
 
1011
            write('<tr><td colspan=2 bgcolor="#%s"><font color="%s">'
 
1012
                  '<strong>%s</strong>'
 
1013
                  '</font></td></td></tr>\n' % (headingColor, headingTextColor, each[0]))
 
1014
            for name, link in each[1:]:
 
1015
                if canHighlight and (name == self.highlightSection):
 
1016
                    sectionColor = self.activeSectionColor
 
1017
                    sectionTextColor = self.activeSectionTextColor
 
1018
                else:
 
1019
                    sectionColor = self.sectionColor
 
1020
                    sectionTextColor = self.sectionTextColor
 
1021
                write('<tr><td align=right bgcolor="#%s" width=6>-</td>'
 
1022
                      '<td bgcolor="#%s"><a href="%s"><font color="#%s">%s'
 
1023
                      '</font></a></td></tr>'
 
1024
                       % (sectionColor, sectionColor, request.sibLink(link), sectionTextColor, name))
 
1025
        write("</table>")
 
1026
 
 
1027
# moved from template.py
 
1028
from twisted.web.woven import template
 
1029
from twisted.python import components
 
1030
 
 
1031
class WebWidgetNodeMutator(template.NodeMutator):
 
1032
    """A WebWidgetNodeMutator replaces the node that is passed in to generate
 
1033
    with the result of generating the twisted.web.widget instance it adapts.
 
1034
    """
 
1035
    def generate(self, request, node):
 
1036
        widget = self.data
 
1037
        displayed = widget.display(request)
 
1038
        try:
 
1039
            html = string.join(displayed)
 
1040
        except:
 
1041
            pr = Presentation()
 
1042
            pr.tmpl = displayed
 
1043
            #strList = pr.display(request)
 
1044
            html = string.join(displayed)
 
1045
        stringMutator = template.StringNodeMutator(html)
 
1046
        return stringMutator.generate(request, node)
 
1047
 
 
1048
components.registerAdapter(WebWidgetNodeMutator, Widget, template.INodeMutator)
 
1049
 
 
1050
import static