1
# -*- test-case-name: twisted.web.test.test_web -*-
3
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
"""A twisted web component framework.
9
This module is DEPRECATED.
13
warnings.warn("This module is deprecated, please use Woven instead.", DeprecationWarning)
16
import string, time, types, traceback, pprint, sys, os
19
from cStringIO import StringIO
22
from twisted.python import failure, log, rebuild, reflect, util
23
from twisted.internet import defer
24
from twisted.web import http
27
import html, resource, error
28
import util as webutil
30
#backwards compatibility
31
from util import formatFailure, htmlrepr, htmlUnknown, htmlDict, htmlList,\
32
htmlInst, htmlString, htmlReprTypes
36
from server import NOT_DONE_YET
42
# magic value that sez a widget needs to take over the whole page.
56
"""A component of a web page.
59
def getTitle(self, request):
60
return self.title or reflect.qual(self.__class__)
62
def display(self, request):
63
"""Implement me to represent your widget.
65
I must return a list of strings and twisted.internet.defer.Deferred
68
raise NotImplementedError("%s.display" % reflect.qual(self.__class__))
70
class StreamWidget(Widget):
71
"""A 'streamable' component of a webpage.
74
def stream(self, write, request):
75
"""Call 'write' multiple times with a string argument to represent this widget.
77
raise NotImplementedError("%s.stream" % reflect.qual(self.__class__))
79
def display(self, request):
80
"""Produce a list containing a single string.
84
result = self.stream(l.append, request)
85
if result is not None:
89
return [webutil.formatFailure(failure.Failure())]
91
class WidgetMixin(Widget):
92
"""A mix-in wrapper for a Widget.
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
102
raise NotImplementedError("%s.display" % self.__class__)
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)
109
class Presentation(Widget):
110
"""I am a widget which formats a template with interspersed python expressions.
113
Hello, %%%%world%%%%.
115
world = "you didn't assign to the 'template' attribute"
116
def __init__(self, template=None, filename=None):
118
self.template = open(filename).read()
120
self.template = template
122
self.tmpl = string.split(self.template, "%%%%")
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)
133
def addVariables(self, namespace, request):
134
self.addClassVars(namespace, self.__class__)
136
def prePresent(self, request):
137
"""Perform any tasks which must be done before presenting the page.
140
def formatTraceback(self, tb):
141
return [html.PRE(tb)]
143
def streamCall(self, call, *args, **kw):
144
"""Utility: Call a method like StreamWidget's 'stream'.
147
apply(call, (io.write,) + args, kw)
150
def display(self, request):
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:
166
x = eval(elem, namespace, namespace)
169
tm.append(webutil.formatFailure(failure.Failure()))
171
if isinstance(x, types.ListType):
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)))
179
# Only two allowed types here should be deferred and
185
def htmlFor_hidden(write, name, value):
186
write('<INPUT TYPE="hidden" NAME="%s" VALUE="%s" />' % (name, value))
188
def htmlFor_file(write, name, value):
189
write('<INPUT SIZE="60" TYPE="file" NAME="%s" />' % name)
191
def htmlFor_string(write, name, value):
192
write('<INPUT SIZE="60" TYPE="text" NAME="%s" VALUE="%s" />' % (name, value))
194
def htmlFor_password(write, name, value):
195
write('<INPUT SIZE="60" TYPE="password" NAME="%s" />' % name)
197
def htmlFor_text(write, name, value):
198
write('<textarea COLS="60" ROWS="10" NAME="%s" WRAP="virtual">%s</textarea>' % (name, value))
200
def htmlFor_menu(write, name, value, allowMultiple=False):
201
"Value of the format [(optionName, displayName[, selected]), ...]"
203
write(' <select NAME="%s"%s>\n' %
204
(name, (allowMultiple and " multiple") or ''))
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))
212
write(' <option VALUE=""></option>\n')
213
write(" </select>\n")
215
def htmlFor_multimenu(write, name, value):
216
"Value of the format [(optionName, displayName[, selected]), ...]"
217
return htmlFor_menu(write, name, value, True)
219
def htmlFor_checkbox(write, name, value):
222
value = 'checked = "1"'
225
write('<INPUT TYPE="checkbox" NAME="__checkboxes__" VALUE="%s" %s />\n' % (name, value))
227
def htmlFor_checkgroup(write, name, value):
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))
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))
239
class FormInputError(Exception):
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::
249
| from twisted.web import widgets
250
| class HelloForm(widgets.Form):
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))
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.
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,
290
# do we raise an error when we get extra args or not?
291
formAcceptExtraArgs = 0
293
def getFormFields(self, request, fieldSet = None):
294
"""I return a list of lists describing this form, or a Deferred.
296
This information is used both to display the form and to process it.
297
The list is in the following format::
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']
303
Valid values for 'Input Type' are:
305
- 'hidden': a hidden field that contains a string that the user won't change
307
- 'string': a short string
309
- 'int': an integer, e.g. 1, 0, 25 or -23
311
- 'float': a float, e.g. 1.0, 2, -3.45, or 28.4324231
313
- 'text': a longer text field, suitable for entering paragraphs
315
- 'menu': an HTML SELECT input, a list of choices
317
- 'multimenu': an HTML SELECT input allowing multiple choices
319
- 'checkgroup': a group of checkboxes
321
- 'radio': a group of radio buttons
323
- 'password': a 'string' field where the contents are not visible as the user types
325
- 'file': a file-upload form (EXPERIMENTAL)
327
'Display Name' is a descriptive string that will be used to
328
identify the field to the user.
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
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.
340
The 'Description' field is an (optional) string which describes the form
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.
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::
353
| def getFormFields(self, request):
354
| myFormFields = [self.myFieldCalculator()]
355
| return widgets.Form.getFormFields(self, request, myFormFields)
360
fieldSet = self.formFields
361
if not self.shouldProcess(request):
364
for field in fieldSet:
366
inputType, displayName, inputName, inputValue, description = field
368
inputType, displayName, inputName, inputValue = field
371
if inputType == 'checkbox':
372
if request.args.has_key('__checkboxes__'):
373
if inputName in request.args['__checkboxes__']:
379
elif inputType in ('checkgroup', 'radio'):
380
if request.args.has_key(inputName):
381
keys = request.args[inputName]
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']:
393
inputValue.remove(iv)
394
inputValue.insert(0, iv)
397
fields.append([inputType, displayName, inputName, inputValue, description])
400
submitNames = ['Submit']
403
def format(self, form, write, request):
404
"""I display an HTML FORM according to the result of self.getFormFields.
406
write('<form ENCTYPE="multipart/form-data" METHOD="post" ACTION="%s">\n'
407
'<table BORDER="0">\n' % (self.actionURI or request.uri))
411
inputType, displayName, inputName, inputValue, description = field
413
inputType, displayName, inputName, inputValue = field
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)
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()
430
write('<INPUT TYPE="hidden" NAME="__formid__" VALUE="%s" />\n' % fid)
434
"""Override me: I disambiguate between multiple forms of the same type.
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::
443
| def getFormID(self):
444
| return str(id(self.frobnitz))
446
By default, this method will return None, since distinct Form instances
447
may be identical as far as the application is concerned.
450
def process(self, write, request, submit, **kw):
451
"""Override me: I process a form.
453
I will only be called when the correct form input data to process this
454
form has been received.
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.
461
The remainder of my arguments must be correctly named. They will each be named after one of the
464
write("<pre>Submit: %s <br /> %s</pre>" % (submit, html.PRE(pprint.PrettyPrinter().pformat(kw))))
466
def _doProcess(self, form, write, request):
467
"""(internal) Prepare arguments for self.process.
469
args = request.args.copy()
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__']:
481
elif inputType in ['checkgroup', 'radio', 'multimenu']:
482
if args.has_key(inputName):
483
formData = args[inputName]
488
if not args.has_key(inputName):
489
raise FormInputError("missing field %s." % repr(inputName))
490
formData = 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)
498
formData = method(formData)
500
raise FormInputError("%s: %s" % (displayName, "error"))
501
kw[inputName] = formData
502
submitAction = args.get('submit')
504
submitAction = submitAction[0]
505
for field in ['submit', '__formtype__', '__checkboxes__']:
506
if args.has_key(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)
512
def formatError(self,error):
513
"""Format an error message.
515
By default, this will make the message appear in red, bold italics.
517
return '<font color="#f00"><b><i>%s</i></b></font><br />\n' % error
519
def shouldProcess(self, request):
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
529
def tryAgain(self, err, req):
530
"""Utility method for re-drawing the form with an error message.
532
This is handy in forms that process Deferred results. Normally you can
533
just raise a FormInputError() and this will happen by default.
538
w(self.formatError(err))
539
self.format(self.getFormFields(req), w, req)
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))
549
form.addCallback(lambda form, f=self._displayFormat, r=request: f(r, form))
552
if self.shouldProcess(request):
553
return self._displayProcess(request, form)
555
return self._displayFormat(request, form)
557
def _displayProcess(self, request, form):
561
val = self._doProcess(form, write, request)
564
except FormInputError, fie:
565
write(self.formatError(str(fie)))
568
def _displayFormat(self, request, form):
570
self.format(form, l.append, request)
575
class DataWidget(Widget):
576
def __init__(self, data):
578
def display(self, request):
582
def display(self, request):
583
return [time.ctime(time.time())]
585
class Container(Widget):
586
def __init__(self, *widgets):
587
self.widgets = widgets
589
def display(self, request):
591
for widget in self.widgets:
592
d = widget.display(request)
596
class _RequestDeferral:
598
self.deferred = defer.Deferred()
600
self.write = self.io.write
603
self.deferred.callback([self.io.getvalue()])
605
def possiblyDeferWidget(widget, request):
606
# web in my head get it out get it out
608
disp = widget.display(request)
609
# if this widget wants to defer anything -- well, I guess we've got to
612
if isinstance(elem, defer.Deferred):
613
req = _RequestDeferral()
614
RenderSession(disp, req)
616
return string.join(disp, '')
619
traceback.print_exc(file=io)
620
return html.PRE(io.getvalue())
623
"""I handle rendering of a list of deferreds, outputting their
624
results in correct order."""
629
def __init__(self, lst, request):
631
self.request = request
632
self.needsHeaders = 0
636
for i in range(len(self.lst)):
638
if isinstance(item, defer.Deferred):
639
self._addDeferred(item, self.lst, i)
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
651
lst[idx] = sentinel, deferred
653
self.pauseList.append(deferred)
654
deferred.addCallbacks(self.callback, self.callback,
655
callbackArgs=args, errbackArgs=args)
658
def callback(self, result, sentinel, decNeedsHeaders):
661
if result != FORGET_IT:
662
self.needsHeaders = self.needsHeaders - decNeedsHeaders
666
# Make sure result is a sequence,
667
if not type(result) in (types.ListType, types.TupleType):
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
675
if result[0] is NOT_DONE_YET:
678
if not type(result) in (types.ListType, types.TupleType):
683
for i in xrange(len(result)):
685
if isinstance(item, defer.Deferred):
686
self._addDeferred(item, result, i)
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:
694
raise AssertionError('Sentinel for Deferred not found!')
697
self.lst[position:position+1] = result
699
self.lst[position:position] = result
704
def keepRendering(self):
705
while self.pauseList:
712
if self.needsHeaders:
713
# short circuit actual rendering process until we're sure no
714
# more deferreds need to set headers...
717
assert self.lst is not None, "This shouldn't happen."
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!
726
if isinstance(item, types.StringType):
728
self.request.write(item)
729
elif type(item) is types.TupleType and len(item) > 0:
730
if isinstance(item[0], self.Sentinel):
732
elif isinstance(item, failure.Failure):
733
self.request.write(webutil.formatFailure(item))
736
unknown = html.PRE(repr(item))
737
self.request.write("RENDERING UNKNOWN: %s" % unknown)
740
if len(self.lst) == 0:
742
self.request.finish()
746
## XXX: is this needed?
747
class WidgetResource(resource.Resource):
748
def __init__(self, widget):
750
resource.Resource.__init__(self)
752
def render(self, request):
753
RenderSession(self.widget.display(request), request)
757
class Page(resource.Resource, Presentation):
760
resource.Resource.__init__(self)
761
Presentation.__init__(self)
763
def render(self, request):
764
displayed = self.display(request)
765
RenderSession(displayed, request)
769
class WidgetPage(Page):
771
I am a Page that takes a Widget in its constructor, and displays that
772
Widget wrapped up in a simple HTML template.
777
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
779
text-decoration: none;
784
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
786
text-decoration: none;
792
font-family: "Courier New", Courier, monospace;
795
p, body, td, ol, ul, menu, blockquote, div
797
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
804
<title>%%%%self.title%%%%</title>
806
%%%%self.stylesheet%%%%
808
<base href="%%%%request.prePathURL()%%%%">
812
<h1>%%%%self.title%%%%</h1>
821
def __init__(self, widget):
824
if hasattr(widget, 'stylesheet'):
825
self.stylesheet = widget.stylesheet
827
def prePresent(self, request):
828
self.title = self.widget.getTitle(request)
830
def render(self, request):
831
displayed = self.display(request)
832
RenderSession(displayed, request)
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.
844
resource.Resource.__init__(self)
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])))
860
def putWidget(self, path, widget):
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.
866
self.widgets[path] = widget
868
#this is an obsolete function
869
def addFile(self, path):
872
Add a static path to this Gadget. This method is obsolete, use
873
Gadget.putPath instead.
876
log.msg("Gadget.addFile() is deprecated.")
877
self.paths[path] = path
879
def putPath(self, path, pathname):
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.
885
self.paths[path] = pathname
887
def getWidget(self, path, request):
888
return self.widgets.get(path)
890
def pageFactory(self, *args, **kwargs):
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.
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)
902
return apply(WidgetPage, args, kwargs)
904
def getChild(self, path, request):
907
if isinstance(self, Widget):
908
return self.pageFactory(self)
909
widget = self.getWidget(path, request)
911
if isinstance(widget, resource.Resource):
914
p = self.pageFactory(widget)
915
p.isLeaf = getattr(widget,'isLeaf',0)
917
elif self.paths.has_key(path):
918
prefix = getattr(sys.modules[self.__module__], '__file__', '')
920
prefix = os.path.abspath(os.path.dirname(prefix))
921
return static.File(os.path.join(prefix, self.paths[path]))
923
elif path == '__reload__':
924
return self.pageFactory(Reloader(map(reflect.namedModule, [self.__module__] + self.modules)))
926
return error.NoResource("No such child resource in gadget.")
929
class TitleBox(Presentation):
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>\
939
borderColor = '#000000'
940
titleTextColor = '#ffffff'
941
boxTextColor = '#000000'
943
widthOption = 'width="100%"'
948
def __init__(self, title, widget):
949
"""Wrap a widget with a given title.
953
Presentation.__init__(self)
956
class Reloader(Presentation):
960
%%%%reload(request)%%%%
963
def __init__(self, modules):
964
Presentation.__init__(self)
965
self.modules = modules
967
def reload(self, request):
968
request.redirect("..")
971
for module in self.modules:
972
rebuild.rebuild(module)
973
write('<li>reloaded %s<br />' % module.__name__)
976
class Sidebar(StreamWidget):
979
['mirror', 'http://coopweb.org/ssd/twisted/'],
980
['mailing list', 'cgi-bin/mailman/listinfo/twisted-python']
984
headingColor = 'ffffff'
985
headingTextColor = '000000'
986
activeHeadingColor = '000000'
987
activeHeadingTextColor = 'ffffff'
988
sectionColor = '000088'
989
sectionTextColor = '008888'
990
activeSectionColor = '0000ff'
991
activeSectionTextColor = '00ffff'
993
def __init__(self, highlightHeading, highlightSection):
994
self.highlightHeading = highlightHeading
995
self.highlightSection = highlightSection
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
1008
headingColor = self.headingColor
1009
headingTextColor = self.headingTextColor
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
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))
1027
# moved from template.py
1028
from twisted.web.woven import template
1029
from twisted.python import components
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.
1035
def generate(self, request, node):
1037
displayed = widget.display(request)
1039
html = string.join(displayed)
1043
#strList = pr.display(request)
1044
html = string.join(displayed)
1045
stringMutator = template.StringNodeMutator(html)
1046
return stringMutator.generate(request, node)
1048
components.registerAdapter(WebWidgetNodeMutator, Widget, template.INodeMutator)