OLD | NEW |
| (Empty) |
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.", Deprecatio
nWarning) | |
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 expressio
ns. | |
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, i
t 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' % (n
ame, 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' % (n
ame, 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 enthusi
asm'), | |
256 | ('spontaneously', 'on the spu
r 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, wh
oToGreet)) | |
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 t
he 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 = fie
ld | |
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, descri
ption]) | |
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 = fie
ld | |
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' % descript
ion) | |
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' % submitN
ame) | |
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 b
e named after one of the | |
462 | |
463 """ | |
464 write("<pre>Submit: %s <br /> %s</pre>" % (submit, html.PRE(pprint.Prett
yPrinter().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(i
nputName)) | |
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 reques
t | |
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.__mo
dule__] + 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.titleTextCo
lor%%%%">%%%%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), sec
tionTextColor, 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 | |
OLD | NEW |