Package turbogears :: Package widgets :: Module forms

Source Code for Module turbogears.widgets.forms

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