Package turbogears :: Package widgets :: Module forms

Source Code for Module turbogears.widgets.forms

   1  from cherrypy import request 
   2  from turbogears import validators, expose 
   3  from turbogears.util import setlike, Bunch, request_available 
   4  from turbogears.widgets.base import Widget, CompoundWidget, WidgetsList, \ 
   5                                      CoreWD, RenderOnlyWD 
   6   
   7  __all__ = ["InputWidget", "CompoundInputWidget", "RepeatingInputWidget", 
   8             "FormField", "FormFieldsContainer", "CompoundFormField", 
   9             "RepeatingFormField", "Label", "TextField", "PasswordField", 
  10             "HiddenField", "FileField", "Button", "SubmitButton", 
  11             "ResetButton", "ImageButton", "CheckBox", "TextArea", 
  12             "SelectionField", "SingleSelectField", "MultipleSelectField", 
  13             "RadioButtonList", "CheckBoxList", "FieldSet", 
  14             "RepeatingFieldSet", "Form", "TableForm", "ListForm", 
  15             "WidgetsList"] 
16 17 18 ############################################################################# 19 # Functions and classes to manage input widgets # 20 ############################################################################# 21 22 -def append_to_path(widget, repetition):
23 path = [] 24 if request_available(): 25 if hasattr(request, "tg_widgets_path"): 26 path = request.tg_widgets_path 27 else: 28 request.tg_widgets_path = path 29 if not path or path[-1].widget is not widget: 30 path.append(Bunch(widget=widget, 31 repetition=repetition)) 32 return True 33 else: 34 return False
35
36 -def pop_from_path():
37 if request_available() and hasattr(request, "tg_widgets_path"): 38 request.tg_widgets_path.pop()
39
40 -def get_path(default_widget, default_repetition):
41 default_path = [Bunch(widget=default_widget, 42 repetition=default_repetition)] 43 return getattr(request, "tg_widgets_path", default_path)
44
45 -def update_path(func):
46 def _update_path(self, *args, **kw): 47 update = append_to_path(self, None) 48 returnval = func(self, *args, **kw) 49 if update: 50 pop_from_path() 51 return returnval
52 return _update_path 53
54 -def adapt_path(path):
55 return [(i.widget.name, i.repetition) for i in path]
56
57 -def path_from_item(item, base_path=None):
58 path = [] 59 if isinstance(item, basestring): 60 path = item.split('.') 61 index = 0 62 for key in path: 63 if '-' in key: 64 key, num = key.split('-') 65 path[index] = key, int(num) 66 else: 67 path[index] = key, None 68 index += 1 69 elif hasattr(item, "name"): 70 path = [(item.name, None)] 71 if base_path: 72 if not isinstance(base_path[0], tuple): 73 base_path = adapt_path(base_path) 74 path = base_path + path 75 return path
76
77 -def retrieve_value_by_path(value, path):
78 if not path: 79 return None 80 else: 81 if not isinstance(path[0], tuple): 82 path = adapt_path(path) 83 returnval = value 84 for name, index in path: 85 if isinstance(returnval, dict): 86 returnval = returnval.get(name) 87 if index is not None: 88 if isinstance(returnval, list): 89 try: 90 returnval = returnval[index] 91 except IndexError: 92 returnval = None 93 else: 94 returnval = None 95 return returnval
96
97 -def retrieve_params_by_path(params, path):
98 if not path: 99 return None 100 else: 101 if not isinstance(path[0], tuple): 102 path = adapt_path(path) 103 for name, index in path: 104 params_to_parse = params.copy() 105 params = {} 106 for k,v in params_to_parse.iteritems(): 107 if isinstance(v, dict): 108 if name in v: 109 params[k] = v[name] 110 if index is not None: 111 if isinstance(params[k], list): 112 try: 113 params[k] = params[k][index] 114 except IndexError: 115 params[k] = None 116 else: 117 params[k] = None 118 return params
119
120 -def build_name_from_path(path, repeating_marker='-', nesting_marker='.'):
121 name = [] 122 for item in path: 123 if item.repetition is not None: 124 name.append(item.widget.name + repeating_marker + str(item.repetition)) 125 else: 126 name.append(item.widget.name) 127 return nesting_marker.join(name)
128
129 130 ############################################################################### 131 # Base class for a widget that can generate input for the app. 132 ############################################################################### 133 134 -class InputWidget(Widget):
135 validator = None 136 params = ["convert"] 137 params_doc = {'convert': 'Should the value be coerced by the validator at ' 138 'display?'} 139 convert = True 140
141 - def __init__(self, name=None, validator=None, **params):
142 """Initialize an input widget. 143 144 It accepts the following parameters (besides those listed at params): 145 146 name: 147 Name of the input element. Will be the name of the variable 148 the widget's input will be available at when submitted. 149 150 validator: 151 Formencode validator that will validate and coerce the input 152 this widget generates. 153 154 """ 155 if name is not None and ('-' in name or '.' in name): 156 raise ValueError, ("The name of an InputWidget must not contain " 157 "the '-' or '.' characters") 158 159 super(InputWidget, self).__init__(name, **params) 160 161 if validator: 162 self.validator = validator
163 164 @property 165 @update_path
166 - def path(self):
167 return get_path(self, None)[:]
168 169 @property
170 - def name_path(self):
171 if self.path and getattr(self.path[0].widget, "form", False): 172 return self.path[1:] 173 else: 174 return self.path
175 176 @property
177 - def is_validated(self):
178 if self.path: 179 validated_form = getattr(request, "validated_form", None) 180 return self.path[0].widget is validated_form 181 else: 182 return False
183
185 root_widget = self.path[0].widget 186 if root_widget is self: 187 return self.validator 188 else: 189 if getattr(root_widget, "form", False): 190 name_path = self.name_path 191 else: 192 name_path = self.name_path[1:] 193 validator = root_widget.validator 194 for name in [i.widget.name for i in name_path]: 195 if hasattr(validator, "fields"): 196 validator = validator.fields.get(name) 197 elif hasattr(validator, "validators"): 198 for v in validator.validators: 199 if hasattr(v, "fields") and (name in v.fields): 200 validator = v.fields[name] 201 break 202 else: 203 break 204 return validator
205
206 - def adjust_value(self, value, **params):
207 if hasattr(request, "input_values") and self.is_validated: 208 input_submitted = True 209 iv = retrieve_value_by_path(request.input_values, self.name_path) 210 else: 211 input_submitted = False 212 iv = None 213 # there are some input fields that when nothing is checked/selected 214 # instead of sending a nice name="" are totally missing from 215 # input_values, this little workaround let's us manage them nicely 216 # without interfering with other types of fields, we need this to 217 # keep track of their empty status otherwise if the form is going to be 218 # redisplayed for some errors they end up to use their defaults values 219 # instead of being empty since FE doesn't validate a failing Schema. 220 # posterity note: this is also why we need if_missing=None in 221 # validators.Schema, see ticket #696. 222 no_input_if_empty = getattr(self, "no_input_if_empty", False) 223 if iv is not None or (input_submitted and no_input_if_empty): 224 value = iv 225 else: 226 if self.validator and params["convert"] and not input_submitted: 227 value = self.validator.from_python(value) 228 return value
229 230 @update_path
231 - def display(self, value=None, **params):
232 return super(InputWidget, self).display(value, **params)
233 234 @property 235 @update_path
236 - def fq_name(self):
237 return build_name_from_path(self.name_path)
238 239 @property 240 @update_path
241 - def error(self):
242 errors = getattr(request, "validation_errors", {}) 243 return retrieve_value_by_path(errors, self.name_path)
244
245 - def update_params(self, d):
246 super(InputWidget, self).update_params(d) 247 d["name"] = build_name_from_path(self.name_path) 248 errors = getattr(request, "validation_errors", {}) 249 d["error"] = retrieve_value_by_path(errors, self.name_path)
250
251 252 -class CompoundInputWidget(CompoundWidget, InputWidget):
253
254 - def update_params(self, params):
255 super(CompoundInputWidget, self).update_params(params) 256 params['error_for'] = lambda f: self.error_for(f, True)
257
258 - def value_for(self, item, value):
259 """Return the value for a child widget. 260 261 ``item`` is the child widget instance or it's name, ``value`` is a 262 dict containing the value for this compound widget. 263 264 """ 265 path = path_from_item(item) 266 return retrieve_value_by_path(value, path)
267
268 - def params_for(self, item, **params):
269 """Return the parameters for a child widget. 270 271 ``item`` is the child widget instance or it's name, ``params`` is a 272 dict containing the parameters passed to this compound widget. 273 274 """ 275 path = path_from_item(item) 276 return retrieve_params_by_path(params, path)
277
278 - def error_for(self, item, suppress_errors=False):
279 """Return the Invalid exception associated with a child widget. 280 281 The exception is stored in the request local storage. ``item`` is the 282 child widget instance or it's name. 283 284 """ 285 if self.is_validated: 286 path = path_from_item(item, self.name_path) 287 errors = getattr(request, "validation_errors", {}) 288 returnval = retrieve_value_by_path(errors, path) 289 if suppress_errors and \ 290 isinstance(returnval, dict) or isinstance(returnval, list): 291 return None 292 else: 293 return returnval 294 else: 295 return None
296
297 - def dictify_value(self, value):
298 """Convert value into a dict suitable for propagating values to child 299 widgets. 300 301 If value is a dict it will pass through, if it's another kind of 302 object, attributes which match child widgets' names will tried to be 303 fetched. 304 305 """ 306 if isinstance(value, dict): 307 return value 308 else: 309 value_as_dict = {} 310 for w in self.iter_member_widgets(): 311 try: 312 value_as_dict[w.name] = getattr(value, w.name) 313 except AttributeError: 314 pass 315 return value_as_dict
316
317 - def adjust_value(self, value=None, **params):
318 """Adjust a value for displaying in a widget.""" 319 if value is not None: 320 value = self.dictify_value(value) 321 return super(CompoundWidget, self).adjust_value(value, **params)
322
323 324 -class RepeatingRange(object):
325
326 - def __init__(self, repetitions, bunch):
327 self.__sequence = repetitions 328 self.__next_value = 0 329 self.__bunch = bunch
330
331 - def __iter__(self):
332 return self
333
334 - def next(self):
335 try: 336 value = self.__sequence[self.__next_value] 337 except IndexError: 338 raise StopIteration 339 else: 340 self.__next_value += 1 341 self.__bunch.repetition = value 342 return value
343
344 345 -class RepeatingInputWidget(CompoundInputWidget):
346 """Base class for a compound widget which can be repeated.""" 347 348 repeating = True 349 params = ["repetitions"] 350 params_doc = {'repetitions': 'Number of repetitions that should be ' 351 'rendered'} 352 repetitions = 1 353
354 - def update_params(self, d):
355 path_reference = self.path[-1] 356 repetition = path_reference.repetition 357 if repetition is not None: 358 path_reference.repetition = None 359 super(RepeatingInputWidget, self).update_params(d) 360 if repetition is None: 361 repetitions = d.pop("repetitions") 362 if isinstance(repetitions, int): 363 repetitions = range(repetitions) 364 else: 365 repetitions = [repetition] 366 d["repetitions"] = RepeatingRange(repetitions, path_reference)
367
368 - def value_for(self, item, value):
369 """Return the value for a child widget. 370 371 ``item`` is the child widget instance or it's name, ``value`` is a dict 372 containing the value for this compound widget. 373 374 """ 375 if isinstance(value, list): 376 try: 377 value = value[self.path[-1].repetition] 378 except IndexError: 379 value = None 380 else: 381 value = None 382 path = path_from_item(item) 383 return retrieve_value_by_path(value, path)
384
385 - def params_for(self, item, **params):
386 """Return the parameters for a child widget. 387 388 ``item`` is the child widget instance or it's name, ``params`` is a 389 dict containing the parameters passed to this compound widget. 390 391 """ 392 item_params = {} 393 for k,v in params.iteritems(): 394 if isinstance(v, list) and k is not "repetitions": 395 try: 396 item_params[k] = v[self.path[-1].repetition] 397 except IndexError: 398 pass 399 path = path_from_item(item) 400 return retrieve_params_by_path(item_params, path)
401
402 - def dictify_value(self, value):
403 """Convert list of values into a list of dicts suitable for propagating 404 values to child widgets. 405 406 If value is a list of dicts it will pass through, if it's another kind 407 of object, attributes which match child widgets' names will tried to be 408 fetched. 409 410 """ 411 return [super(RepeatingInputWidget, self).dictify_value(v) 412 for v in value]
413
414 415 ############################################################################# 416 # Base classes # 417 ############################################################################# 418 419 -class FormField(InputWidget):
420 """An input widget that can be included inside a Form or Fieldset. 421 422 It accepts the following parameters (besides those listed at params): 423 424 label 425 The label text that the container form/fieldset will generate for the 426 widget. If empty, the capitalized name will be used. 427 help_text 428 The help text that the container form/fieldset will generate for the 429 widget. 430 431 """ 432 _name = "widget" 433 label = None 434 help_text = None 435 params = ["field_class", "css_classes"] 436 params_doc = {'field_class' : 'CSS class for the field', 437 'css_classes' : 'List of extra CSS classes for the field'} 438 field_class = None 439 css_classes = [] 440
441 - def _get_name(self):
442 return self._name
443
444 - def _set_name(self, name):
445 self._name = name 446 if self.label is None: 447 self.label = name.capitalize()
448 name = property(_get_name, _set_name) 449 450 @property
451 - def is_required(self):
452 validator = self._retrieve_validator_from_validation_schema() 453 if validator is None: 454 return False 455 else: 456 try: 457 validator.to_python('') 458 except validators.Invalid: 459 return True 460 else: 461 return False
462 463 @property
464 - def field_id(self):
465 return build_name_from_path(self.path, '_', '_')
466
467 - def __init__(self, name=None, label=None, help_text=None, **kw):
468 super(FormField, self).__init__(name, **kw) 469 if label is not None: 470 self.label = label 471 472 if help_text is not None: 473 self.help_text = help_text 474 475 if self.field_class is None: 476 self.field_class = self.__class__.__name__.lower()
477
478 - def update_params(self, d):
479 super(FormField, self).update_params(d) 480 if self.is_required: 481 d["field_class"] = " ".join([d["field_class"], "requiredfield"]) 482 if d.get('error', None): 483 d["field_class"] = " ".join([d["field_class"], "erroneousfield"]) 484 if d["css_classes"]: 485 d["field_class"] = " ".join([d["field_class"]] + d["css_classes"]) 486 d["label"] = self.label 487 d["help_text"] = self.help_text 488 d["field_id"] = self.field_id
489
490 491 # A decorator that provides field_for functionality to the 492 # decorated FormFieldsContainer's method 493 -def retrieve_field_for(func):
494 @update_path 495 def retrieve_field_for(self=None, item=None, *args, **kw): 496 path = path_from_item(item) 497 field = self 498 for name, index in path: 499 if field is not None: 500 field = field.get_field_by_name(name) 501 append_to_path(field, index) 502 if field is not None: 503 returnval = func(self, field, *args, **kw) 504 else: 505 returnval = "Field for '%s' not found." % item 506 for i in range(len(path)): 507 pop_from_path() 508 return returnval
509 return retrieve_field_for 510
511 512 -class FormFieldsContainer(CompoundInputWidget):
513 """A container for FormFields. 514 515 Has two member_widgets lists: 516 - fields 517 - hidden_fields 518 519 It provides the template with 3 useful functions: 520 - field_for(name) 521 Returns the child named ``name``. 522 - value_for(name) 523 Returns the value for the child named ``name`` (name can also be 524 a widget instance so fields can be iterated). 525 - params_for(name) 526 Returns the display-time parameters for the child named ``name`` 527 (can also be a widget instance so fields can be iterated). 528 - display_field_for(name) 529 Displays the child named ``name`` automatically propagating value 530 an params. ``name`` can also be a widget instance. 531 - error_for(name) 532 Returns the validation error the child named ``name`` generated. 533 Again, ``name`` can also be a widget instance. 534 535 """ 536 member_widgets = ["fields", "hidden_fields"] 537 fields = [] 538 hidden_fields = [] 539 params = ["disabled_fields"] 540 disabled_fields = set() 541 542 @property
543 - def file_upload(self):
544 for widget in self.iter_member_widgets(): 545 if getattr(widget, "file_upload", False): 546 return True 547 return False
548
549 - def get_field_by_name(self, name, default=None):
550 for widget in self.iter_member_widgets(): 551 if widget.name == name: 552 return widget 553 return default
554 555 @retrieve_field_for
556 - def display_field_for(self, item, value=None, **params):
557 return item.display(value, **params)
558 559 @retrieve_field_for
560 - def render_field_for(self, item, value=None, format="html", **params):
561 return item.render(value, format, **params)
562
563 - def _field_for(self, item):
564 """Return the member widget named item. 565 566 This function should *only* be used inside a FormFieldsContainer 567 template, really, else the path acrobatics will lead to unexpected 568 results. 569 570 """ 571 field = self.get_field_by_name(item, None) 572 if field is None: 573 raise ValueError("Field for '%s' not found." % item) 574 return field
575
576 - def update_params(self, d):
577 super(FormFieldsContainer, self).update_params(d) 578 d["display_field_for"] = lambda f: self.display_field_for(f, 579 d["value_for"](f), **d["params_for"](f)) 580 d["render_field_for"] = lambda f: self.display_field_for(f, 581 d["value_for"](f), **d["params_for"](f)) 582 d["field_for"] = self._field_for 583 visible_fields = [] 584 hidden_fields = [] 585 #XXX: Ugly hack, this badly needs a better fix. Note to meself: 586 # CompoundFormField has no fields or hidden_fields member_widgets, 587 # related to [1736]'s refactoring. 588 for field in d.get("fields", []) + d.get("hidden_fields", []): 589 if field.name not in d["disabled_fields"]: 590 if getattr(field, "hidden", False): 591 hidden_fields.append(field) 592 else: 593 visible_fields.append(field) 594 d["fields"] = visible_fields 595 d["hidden_fields"] = hidden_fields
596
597 598 -class CompoundFormField(FormFieldsContainer, FormField):
599 is_required = False
600
601 602 -class RepeatingFormField(RepeatingInputWidget, CompoundFormField):
603 pass
604
605 606 ############################################################################# 607 # Fields # 608 ############################################################################# 609 610 -class Label(FormField):
611 """A simple label for a form field.""" 612 613 template = """ 614 <label xmlns:py="http://purl.org/kid/ns#" 615 id="${field_id}" 616 class="${field_class}" 617 py:content="value" 618 py:attrs="attrs" 619 /> 620 """ 621 params = ["attrs"] 622 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 623 'the label tag'} 624 attrs = {}
625
626 627 -class LabelDesc(CoreWD):
628 for_widget = Label(default="Sample Label")
629
630 631 -class TextField(FormField):
632 """A standard, single-line text field.""" 633 634 template = """ 635 <input xmlns:py="http://purl.org/kid/ns#" 636 type="text" 637 name="${name}" 638 class="${field_class}" 639 id="${field_id}" 640 value="${value}" 641 py:attrs="attrs" 642 /> 643 """ 644 params = ["attrs"] 645 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 646 ' the input tag'} 647 attrs = {}
648
649 650 -class TextFieldDesc(CoreWD):
651 name = "Text Field" 652 for_widget = TextField("your_name", 653 default="Your Name Here", 654 attrs=dict(size="30"))
655
656 657 -class PasswordField(FormField):
658 """A password field which masks letters with * characters.""" 659 660 template = """ 661 <input xmlns:py="http://purl.org/kid/ns#" 662 type="password" 663 name="${name}" 664 class="${field_class}" 665 id="${field_id}" 666 value="${value}" 667 py:attrs="attrs" 668 /> 669 """ 670 params = ["attrs"] 671 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 672 'the password input tag'} 673 attrs = {}
674
675 676 -class PasswordFieldDesc(CoreWD):
677 name = "Password Field" 678 for_widget = PasswordField("your_secret", 679 default="Top Secret Password")
680
681 682 -class HiddenField(FormField):
683 template = """ 684 <input xmlns:py="http://purl.org/kid/ns#" 685 type="hidden" 686 name="${name}" 687 class="${field_class}" 688 id="${field_id}" 689 value="${value}" 690 py:attrs="attrs" 691 /> 692 """ 693 params = ["attrs"] 694 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 695 ' the hidden input tag'} 696 attrs = {} 697 hidden = True
698
699 700 -class HiddenFieldDesc(CoreWD, RenderOnlyWD):
701 name = "Hidden Field" 702 for_widget = HiddenField("hidden_one")
703
704 705 -class FileField(FormField):
706 template = """ 707 <input xmlns:py="http://purl.org/kid/ns#" 708 type="file" 709 name="${name}" 710 class="${field_class}" 711 id="${field_id}" 712 py:attrs="attrs" 713 /> 714 """ 715 params = ["attrs"] 716 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 717 ' the file input tag'} 718 attrs = {} 719 file_upload = True 720
721 - def display(self, value=None, **params):
722 return super(FileField, self).display(None, **params)
723
724 725 -class FileFieldDesc(CoreWD):
726 name = "File Field" 727 for_widget = FileField("your_filefield", attrs=dict(size="30"))
728
729 730 -class Button(FormField):
731 template = """ 732 <input xmlns:py="http://purl.org/kid/ns#" 733 type="button" 734 class="${field_class}" 735 value="${value}" 736 py:attrs="attrs" 737 /> 738 """ 739 params = ["attrs"] 740 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 741 ' the button input tag'} 742 attrs = {} 743
744 - def update_params(self, d):
745 super(Button, self).update_params(d) 746 if self.is_named: 747 d['attrs']['name'] = d["name"] 748 d['attrs']['id'] = d["field_id"]
749
750 751 -class ButtonDesc(CoreWD):
752 name = "Button" 753 for_widget = Button(default="Button Value")
754
755 756 -class SubmitButton(Button):
757 template = """ 758 <input xmlns:py="http://purl.org/kid/ns#" 759 type="submit" 760 class="${field_class}" 761 value="${value}" 762 py:attrs="attrs" 763 /> 764 """
765
766 767 -class SubmitButtonDesc(CoreWD):
768 name = "Submit Button" 769 for_widget = SubmitButton(default="Submit Button Value")
770
771 772 -class ResetButton(Button):
773 template = """ 774 <input xmlns:py="http://purl.org/kid/ns#" 775 type="reset" 776 class="${field_class}" 777 value="${value}" 778 py:attrs="attrs" 779 /> 780 """
781
782 783 -class ResetButtonDesc(CoreWD):
784 name = "Reset Button" 785 for_widget = ResetButton(default="Reset Button Value")
786
787 788 -class ImageButton(Button):
789 template = """ 790 <input xmlns:py="http://purl.org/kid/ns#" 791 type="image" 792 src="${src}" 793 width="${width}" 794 height="${height}" 795 alt="${alt}" 796 class="${field_class}" 797 value="${value}" 798 py:attrs="attrs" 799 /> 800 """ 801 params = ["src", "width", "height", "alt"] 802 params_doc = {'src' : 'Source of the image', 803 'width' : 'Width of the image', 804 'height' : 'Height of the image', 805 'alt': 'Alternate text for the image'} 806 src = None 807 width = None 808 height = None 809 alt = None
810
811 812 -class ImageButtonDesc(CoreWD):
813 name = "Image Button" 814 for_widget = ImageButton("your_image_button", src="/tg_static/images/toolbox_logo.png")
815
816 817 -class CheckBox(FormField):
818 template = """ 819 <input xmlns:py="http://purl.org/kid/ns#" 820 type="checkbox" 821 name="${name}" 822 class="${field_class}" 823 id="${field_id}" 824 py:attrs="attrs" 825 /> 826 """ 827 # an unchecked checkbox doesn't submit anything 828 no_input_if_empty = True 829 params = ["attrs"] 830 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 831 ' the checkbox input tag'} 832 attrs = {} 833
834 - def __init__(self, *args, **kw):
835 super(CheckBox, self).__init__(*args, **kw) 836 if not self.validator: 837 self.validator = validators.Bool()
838
839 - def update_params(self, d):
840 super(CheckBox, self).update_params(d) 841 try: 842 value = self.validator.to_python(d['value']) 843 except validators.Invalid: 844 value = False 845 if value: 846 d['attrs']['checked'] = 'checked'
847
848 849 -class CheckBoxDesc(CoreWD):
850 for_widget = CheckBox(name="your_checkbox", 851 default=True, 852 help_text="Just click me...") 853 template = """ 854 <div xmlns:py="http://purl.org/kid/ns#"> 855 ${for_widget.display()} 856 <label for="${for_widget.field_id}" 857 py:content="for_widget.help_text" 858 /> 859 </div> 860 """
861
862 863 -class TextArea(FormField):
864 template = """ 865 <textarea xmlns:py="http://purl.org/kid/ns#" 866 name="${name}" 867 class="${field_class}" 868 id="${field_id}" 869 rows="${rows}" 870 cols="${cols}" 871 py:attrs="attrs" 872 py:content="value" 873 /> 874 """ 875 params = ["rows", "cols", "attrs"] 876 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 877 ' the textarea tag', 878 'rows' : 'Number of rows to render', 879 'cols' : 'Number of columns to render'} 880 rows = 7 881 cols = 50 882 attrs = {}
883
884 885 -class TextAreaDesc(CoreWD):
886 name = "Text Area" 887 for_widget = TextArea(name="your_textarea", 888 default="Your Comment Here", 889 rows=5, cols=40)
890
891 892 -class SelectionField(FormField):
893 # an empty selection doesn't submit anything 894 no_input_if_empty = True 895 _multiple_selection = False 896 _selected_verb = None 897 params = ["options"] 898 params_doc = {'options' : 'A list of tuples with the options for the select' 899 'field'} 900 options = [] 901 convert = False 902
903 - def __init__(self, *args, **kw):
904 super(SelectionField, self).__init__(*args, **kw) 905 if not self.validator: 906 try: 907 self.validator = self._guess_validator() 908 except Exception, err: 909 raise ValueError( 910 "No validator specified and couldn't guess one:\n%s\n" 911 "Please explicitly specify a validator for the %s." 912 % (err, self.__class__.__name__)) 913 # Only override the user-provided validator if it's not a ForEach one, 914 # which usually means the user needs to perform validation on the list 915 # as a whole. 916 if (self._multiple_selection and 917 not isinstance(self.validator, validators.ForEach)): 918 self.validator = validators.MultipleSelection(self.validator)
919
920 - def _guess_validator(self):
921 """Inspect sample option value to guess validator (crude).""" 922 sample_option = self._get_sample_option() 923 if isinstance(sample_option, int): 924 return validators.Int() 925 elif isinstance(sample_option, basestring): 926 return validators.String() 927 else: 928 raise TypeError("Unknown option type in SelectionField: %r" 929 % (sample_option,))
930
931 - def _get_sample_option(self):
932 options = self._extend_options(self.options) 933 if options: 934 if isinstance(options[0][1], list): 935 sample = options[0][1][0] 936 else: 937 sample = options[0] 938 return sample[0] 939 else: 940 return None
941
942 - def _extend_options(self, opts):
943 if (len(opts) > 0) and not isinstance(opts[0], (tuple,list)): 944 new_opts = [] 945 for opt in opts: 946 new_opts.append((opt,opt)) 947 return new_opts 948 return opts
949
950 - def update_params(self, d):
951 super(SelectionField, self).update_params(d) 952 grouped_options = [] 953 options = [] 954 d['options'] = self._extend_options(d['options']) 955 for optgroup in d["options"]: 956 if isinstance(optgroup[1], list): 957 group = True 958 optlist = optgroup[1][:] 959 else: 960 group = False 961 optlist = [optgroup] 962 for i, option in enumerate(optlist): 963 option_value = option[0] 964 option_attrs = len(option) > 2 and dict(option[2]) or {} 965 if self._is_option_selected(option_value, d['value']): 966 option_attrs[self._selected_verb] = self._selected_verb 967 option_value = self.validator.from_python(option_value) 968 if self._multiple_selection: 969 if option_value: 970 option_value = option_value[0] 971 else: 972 option_value = None 973 if option_value is None: 974 option_value = '' 975 optlist[i] = (option_value, option[1], option_attrs) 976 options.extend(optlist) 977 if group: 978 grouped_options.append((optgroup[0], optlist)) 979 # options provides a list of *flat* options leaving out any eventual 980 # group, useful for backward compatibility and simpler widgets 981 d["options"] = options 982 if grouped_options: 983 d["grouped_options"] = grouped_options 984 else: 985 d["grouped_options"] = [(None, options)]
986
987 - def _is_option_selected(self, option_value, value):
988 if self._multiple_selection: 989 if isinstance(value, basestring): 990 # single unconverted value 991 try: 992 value = self.validator.to_python(value) 993 except validators.Invalid: 994 return False 995 if not value: 996 return False 997 else: 998 if not value: 999 return False 1000 # multiple values - check whether list or set may 1001 # need conversion by looking at its first item 1002 for v in value: 1003 if isinstance(v, basestring): 1004 try: 1005 value = self.validator.to_python(value) 1006 except validators.Invalid: 1007 return False 1008 if not value: 1009 return False 1010 break 1011 if option_value is None: 1012 for v in value: 1013 if v is None: 1014 return True 1015 return False 1016 return option_value in value 1017 else: 1018 if isinstance(value, basestring): 1019 # value may need conversion 1020 try: 1021 value = self.validator.to_python(value) 1022 except validators.Invalid: 1023 return False 1024 if option_value is None: 1025 return value is None 1026 return option_value == value
1027
1028 1029 -class SingleSelectField(SelectionField):
1030 template = """ 1031 <select xmlns:py="http://purl.org/kid/ns#" 1032 name="${name}" 1033 class="${field_class}" 1034 id="${field_id}" 1035 py:attrs="attrs" 1036 > 1037 <optgroup py:for="group, options in grouped_options" 1038 label="${group}" 1039 py:strip="not group" 1040 > 1041 <option py:for="value, desc, attrs in options" 1042 value="${value}" 1043 py:attrs="attrs" 1044 py:content="desc" 1045 /> 1046 </optgroup> 1047 </select> 1048 """ 1049 _selected_verb = 'selected' 1050 params = ["attrs"] 1051 params_doc = {'attrs' : 'Dictionary containing extra (X)HTML attributes for' 1052 ' the select tag'} 1053 attrs = {}
1054
1055 1056 -class SingleSelectFieldDesc(CoreWD):
1057 name = "Single Select Field" 1058 for_widget = SingleSelectField("your_single_select_field", 1059 options=[(1, "Python"), 1060 (2, "Java"), 1061 (3, "Pascal"), 1062 (4, "Ruby")], 1063 default=2)
1064
1065 1066 -class MultipleSelectField(SelectionField):
1067 template = """ 1068 <select xmlns:py="http://purl.org/kid/ns#" 1069 multiple="multiple" 1070 size="${size}" 1071 name="${name}" 1072 class="${field_class}" 1073 id="${field_id}" 1074 py:attrs="attrs" 1075 > 1076 <optgroup py:for="group, options in grouped_options" 1077 label="${group}" 1078 py:strip="not group" 1079 > 1080 <option py:for="value, desc, attrs in options" 1081 value="${value}" 1082 py:attrs="attrs" 1083 py:content="desc" 1084 /> 1085 </optgroup> 1086 </select> 1087 """ 1088 _multiple_selection = True 1089 _selected_verb = 'selected' 1090 params = ["size", "attrs"] 1091 params_doc = {'size' : 'Number of options to show without scrolling'} 1092 attrs = {} 1093 size = 5
1094
1095 1096 -class MultipleSelectFieldDesc(CoreWD):
1097 name = "Multiple Select Field" 1098 for_widget = MultipleSelectField("your_multiple_select_field", 1099 options=[("a", "Python"), 1100 ("b", "Java"), 1101 ("c", "Pascal"), 1102 ("d", "Ruby")], 1103 default=["a","c","d"])
1104
1105 1106 -class RadioButtonList(SelectionField):
1107 template = """ 1108 <ul xmlns:py="http://purl.org/kid/ns#" 1109 class="${field_class}" 1110 id="${field_id}" 1111 py:attrs="list_attrs" 1112 > 1113 <li py:for="value, desc, attrs in options"> 1114 <input type="radio" 1115 name="${name}" 1116 id="${field_id}_${value}" 1117 value="${value}" 1118 py:attrs="attrs" 1119 /> 1120 <label for="${field_id}_${value}" py:content="desc" /> 1121 </li> 1122 </ul> 1123 """ 1124 _selected_verb = 'checked' 1125 params = ["list_attrs"] 1126 params_doc = {'list_attrs' : 'Extra (X)HTML attributes for the ul tag'} 1127 list_attrs = {}
1128
1129 1130 -class RadioButtonListDesc(CoreWD):
1131 name = "RadioButton List" 1132 for_widget = RadioButtonList("your_radiobutton_list", 1133 options=list(enumerate("Python Java Pascal Ruby".split())), default=3)
1134
1135 1136 -class CheckBoxList(SelectionField):
1137 template = """ 1138 <ul xmlns:py="http://purl.org/kid/ns#" 1139 class="${field_class}" 1140 id="${field_id}" 1141 py:attrs="list_attrs" 1142 > 1143 <li py:for="value, desc, attrs in options"> 1144 <input type="checkbox" 1145 name="${name}" 1146 id="${field_id}_${value}" 1147 value="${value}" 1148 py:attrs="attrs" 1149 /> 1150 <label for="${field_id}_${value}" py:content="desc" /> 1151 </li> 1152 </ul> 1153 """ 1154 _multiple_selection = True 1155 _selected_verb = 'checked' 1156 params = ["list_attrs"] 1157 params_doc = {'list_attrs' : 'Extra (X)HTML attributes for the ul tag'} 1158 list_attrs = {}
1159
1160 1161 -class CheckBoxListDesc(CoreWD):
1162 name = "CheckBox List" 1163 for_widget = CheckBoxList("your_checkbox_list", 1164 options=list(enumerate("Python Java Pascal Ruby".split())), 1165 default=[0,3])
1166
1167 1168 -class FieldSet(CompoundFormField):
1169 template=""" 1170 <fieldset xmlns:py="http://purl.org/kid/ns#" 1171 class="${field_class}" 1172 id="${field_id}" 1173 > 1174 <legend py:if="legend" py:content="legend" /> 1175 <div py:for="field in hidden_fields" 1176 py:replace="field.display(value_for(field), **params_for(field))" 1177 /> 1178 <div py:for="field in fields"> 1179 <label class="fieldlabel" for="${field.field_id}" py:content="field.label" /> 1180 <span py:replace="field.display(value_for(field), **params_for(field))" /> 1181 <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" /> 1182 <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" /> 1183 </div> 1184 </fieldset> 1185 """ 1186 params = ["legend"] 1187 params_doc = {'legend' : 'Text to display as the legend for the fieldset'} 1188 legend = None
1189
1190 1191 -class FieldSetDesc(CoreWD):
1192 name = "FieldSet" 1193 for_widget = FieldSet("your_fieldset", 1194 legend="Range", 1195 fields=[TextField(name="lower_limit", 1196 label="Lower Limit"), 1197 TextField(name="upper_limit", 1198 label="Upper Limit")])
1199
1200 1201 -class RepeatingFieldSet(RepeatingFormField):
1202 template=""" 1203 <div xmlns:py="http://purl.org/kid/ns#"> 1204 <fieldset py:for="repetition in repetitions" 1205 class="${field_class}" 1206 id="${field_id}_${repetition}" 1207 > 1208 <legend py:if="legend" py:content="legend" /> 1209 <div py:for="field in hidden_fields" 1210 py:replace="field.display(value_for(field), **params_for(field))" 1211 /> 1212 <div py:for="field in fields"> 1213 <label class="fieldlabel" for="${field.field_id}" py:content="field.label" /> 1214 <span py:replace="field.display(value_for(field), **params_for(field))" /> 1215 <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" /> 1216 <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" /> 1217 </div> 1218 </fieldset> 1219 </div> 1220 """ 1221 params = ["legend"] 1222 params_doc = {'legend' : 'Text to display as the legend for the fieldset'} 1223 legend = None
1224
1225 1226 -class RepeatingFieldSetDesc(CoreWD):
1227 name = "Repeating FieldSet" 1228 for_widget = RepeatingFieldSet("your_repeating_fieldset", 1229 legend='Range', 1230 repetitions=3, 1231 fields=[TextField(name="lower_limit", 1232 label="Lower Limit"), 1233 TextField(name="upper_limit", 1234 label="Upper Limit")])
1235
1236 1237 ############################################################################# 1238 # Forms # 1239 ############################################################################# 1240 1241 -class Form(FormFieldsContainer):
1242 form = True 1243 name = "form" 1244 member_widgets = ["submit"] 1245 params = ["action", "method", "form_attrs", "use_name", "submit_text"] 1246 params_doc = {'action': 'Url to POST/GET the form data', 1247 'method': 'HTTP request method', 1248 'form_attrs': 'Extra (X)HTML attributes for the form tag', 1249 'use_name': 'Whether to use the name instead of the id attribute', 1250 'submit_text': 'Text for the submit button', 1251 'disabled_fields': 'List of names of the fields we want to disable'} 1252 submit = SubmitButton() 1253 action = '' 1254 method = 'post' 1255 form_attrs = {} 1256 use_name = False # because this is deprecated in HTML and invalid in XHTML 1257 submit_text = None 1258
1259 - def update_params(self, d):
1260 name = d.get('name') 1261 super(Form, self).update_params(d) 1262 d['name'] = name or self.name 1263 if self.file_upload: 1264 d['form_attrs']['enctype'] = 'multipart/form-data'
1265
1266 - def validate(self, value, state=None):
1267 if self.validator: 1268 return self.validator.to_python(value, state)
1269 1270 @property
1271 - def errors(self):
1272 return getattr(request, 'validation_errors', None)
1273
1274 1275 -class TableForm(Form):
1276 """Form with simple table layout.""" 1277 1278 # note that even hidden fields must live inside a block element 1279 template = """ 1280 <form xmlns:py="http://purl.org/kid/ns#" 1281 name="${use_name and name or None}" 1282 id="${not use_name and name or None}" 1283 action="${action}" 1284 method="${method}" 1285 class="tableform" 1286 py:attrs="form_attrs" 1287 > 1288 <div py:if="hidden_fields" style="display:none"> 1289 <div py:for="field in hidden_fields" 1290 py:replace="field.display(value_for(field), **params_for(field))" 1291 /> 1292 </div> 1293 <table border="0" cellspacing="0" cellpadding="2" py:attrs="table_attrs"> 1294 <tr py:for="i, field in enumerate(fields)" class="${i % 2 and 'odd' or 'even'}"> 1295 <th> 1296 <label class="fieldlabel" for="${field.field_id}" py:content="field.label" /> 1297 </th> 1298 <td> 1299 <span py:replace="field.display(value_for(field), **params_for(field))" /> 1300 <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" /> 1301 <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" /> 1302 </td> 1303 </tr> 1304 <tr> 1305 <td>&#160;</td> 1306 <td py:content="submit.display(submit_text)" /> 1307 </tr> 1308 </table> 1309 </form> 1310 """ 1311 params = ["table_attrs"] 1312 params_doc = {'table_attrs' : 'Extra (X)HTML attributes for the Table tag'} 1313 table_attrs = {}
1314
1315 1316 -class TableFormDesc(CoreWD):
1317 name = "Table Form" 1318 full_class_name = "turbogears.widgets.TableForm" 1319 field1 = TextField("name") 1320 field2 = TextField("address") 1321 field3 = TextField("occupation") 1322 field4 = PasswordField("password") 1323 field5 = PasswordField("reserved") # will never show 1324 field6 = HiddenField("hidden_info") 1325 for_widget = TableForm("TableForm", 1326 fields=[field1, field2, field3, field4, field5, field6], 1327 action="%s/save" % full_class_name, 1328 submit_text="Submit Me") 1329 template = """ 1330 <div> 1331 ${for_widget.display(disabled_fields=["reserved"])} 1332 </div> 1333 """ 1334 1335 @expose()
1336 - def save(self, **kw):
1337 return """Received data from TableForm:<br />%r""" % kw
1338
1339 1340 -class ListForm(Form):
1341 """Form with simple list layout.""" 1342 1343 # note that even hidden fields must live inside a block element 1344 template = """ 1345 <form xmlns:py="http://purl.org/kid/ns#" 1346 name="${use_name and name or None}" 1347 id="${not use_name and name or None}" 1348 action="${action}" 1349 method="${method}" 1350 class="listform" 1351 py:attrs="form_attrs" 1352 > 1353 <div py:if="hidden_fields" style="display:none"> 1354 <div py:for="field in hidden_fields" 1355 py:replace="field.display(value_for(field), **params_for(field))" 1356 /> 1357 </div> 1358 <ul py:attrs="list_attrs"> 1359 <li py:for="i, field in enumerate(fields)" class="${i % 2 and 'odd' or 'even'}"> 1360 <label class="fieldlabel" for="${field.field_id}" py:content="field.label" /> 1361 <span py:replace="field.display(value_for(field), **params_for(field))" /> 1362 <span py:if="error_for(field)" class="fielderror" py:content="error_for(field)" /> 1363 <span py:if="field.help_text" class="fieldhelp" py:content="field.help_text" /> 1364 </li> 1365 <li py:content="submit.display(submit_text)" /> 1366 </ul> 1367 </form> 1368 """ 1369 params = ["list_attrs"] 1370 params_doc = {'list_attrs' : 'Extra (X)HTML attributes for the ul tag'} 1371 list_attrs = {}
1372
1373 1374 -class ListFormDesc(CoreWD):
1375 name = "List Form" 1376 full_class_name = "turbogears.widgets.ListForm" 1377 field1 = TextField("name") 1378 field2 = TextField("address") 1379 field3 = TextField("occupation") 1380 field4 = PasswordField("password") 1381 field5 = PasswordField("reserved") # will never show 1382 field6 = HiddenField("hidden_info") 1383 for_widget = ListForm("ListForm", 1384 fields=[field1, field2, field3, field4, field5, field6], 1385 action="%s/save" % full_class_name, 1386 submit_text="Submit Me") 1387 template = """ 1388 <div> 1389 ${for_widget.display(disabled_fields=["reserved"])} 1390 </div> 1391 """ 1392 1393 @expose()
1394 - def save(self, **kw):
1395 return """Received data from ListForm:<br />%r""" % kw
1396