Home | Trees | Indices | Help |
|
---|
|
1 """Base TurboGears widget implementation""" 2 3 __all__ = ['load_widgets', 'all_widgets', 'Widget', 'CompoundWidget', 4 'WidgetsList', 'register_static_directory', 'static', 5 'Resource', 'Link', 'CSSLink', 'JSLink', 6 'Source', 'CSSSource', 'JSSource', 7 'js_location', 'mochikit', 'jsi18nwidget', 8 'WidgetDescription', 'set_with_self'] 9 10 import os 11 import itertools 12 import warnings 13 14 import pkg_resources 15 16 from cherrypy import request 17 import tgmochikit 18 19 from turbogears import config, startup, view 20 from turbogears.util import (setlike, to_unicode, copy_if_mutable, 21 get_package_name, request_available) 22 from turbogears.i18n.utils import get_locale 23 from turbogears.widgets.meta import MetaWidget, load_template27 """Enum used at js_locations. 28 29 This is less strict than ``turbogears.utils.Enum`` and serves our 30 purposes as well as allowing any object with ``retrieve_javascript``, 31 ``retrieve_css``, and ``location`` attributes to provide resources to 32 the template when scanned in ``turbogears.controllers._process_output``. 33 34 Example: 35 36 >>> locations = Enum('bodytop', 'bodybottom', head') 37 >>> locations.head == locations.head 38 True 39 >>> locations.head == locations.bodybottom 40 False 41 >>> locations.head in locations 42 True 43 >>> locations.foo 44 Traceback (most recent call last): 45 ... 46 AttributeError 47 48 """ 4960 61 62 # Set up a counter for every declared widget in a WidgetsList instance, 63 # so their order is preserved. 64 counter = itertools.count()51 set.__init__(self, args)52 5768 """Load all widgets provided by the widget entry point.""" 69 for widget_mod in pkg_resources.iter_entry_points('turbogears.widgets'): 70 try: 71 widget_mod.load() 72 except Exception, e: 73 raise ImportError('Error loading plugin "%s"\n%s: %s' 74 % (widget_mod, e.__class__.__name__, e))75 76 all_widgets = set()77 78 79 ############################################################################# 80 # Widgets base classes # 81 ############################################################################# 82 83 -class Widget(object):84 """A TurboGears Widget. 85 86 '__init__' and 'update_params' are the only methods you might need to 87 care extending. 88 89 Attributes you should know about: 90 91 name: The name of the widget. 92 template: The Genshi or Kid template for the widget. 93 engine_name: The name of the templating engine to be used for 94 rendering the template. You can also specify the engine 95 by adding a 'genshi:' or 'kid:' prefix to the template. 96 If the engine is not specified, and cannot be derived 97 from a xmlns:py attribute in the template source, then 98 the global default_engine ('genshi') will be used. 99 default: Default value for the widget. 100 css: List of CSSLinks and CSSSources for the widget. These 101 will be automatically pulled and inserted in the 102 page template that displays it. 103 javascript: List of JSLinks and JSSources for the widget. Same as css. 104 is_named: A property returning True if the widget has overridden its 105 default name. 106 params: All parameter names listed here will be treated as special 107 parameters. This list is updated by the metaclass and 108 always contains *all* params from the widget itself and 109 all its bases. Can be used as a quick reminder of all 110 the params your widget has at its disposal. They all 111 behave the same and have the same priorities regarding their 112 overridal. Read on... 113 params_doc: A dictionary containing 'params' names as keys and their 114 docstring as value. For documentation at the widget browser. 115 116 All initialization parameters listed at the class attribute "params" can be 117 defined as class attributes, overridden at __init__ or at display time. 118 They will be treated as special params for the widget, which means: 119 120 1) You can fix default values for them when subclassing Widget. 121 122 2) If passed as **params to the constructor, the will be bound automatically 123 to the widget instance, taking preference over any class attributes 124 previously defined. Mutable attributes (dicts and lists) defined as class 125 attributes are safe to modify as care is taken to copy them so the class 126 attribute remains unchanged. 127 128 3) They can be further overriden by passing them as keyword arguments to the 129 display() method. This will only affect that display() method call in a 130 thread-safe way. 131 132 4) A callable can be passed and it will be called automatically when sending 133 variables to the template. This can be handy to pick up parameters which 134 change in every request or affect many widgets simultaneously. 135 136 """ 137 138 __metaclass__ = MetaWidget 139 140 name = "widget" 141 template = None 142 engine_name = None 143 default = None 144 css = [] 145 javascript = [] 146 params = [] 147 params_doc = {} 148410151 """Widget initialization. 152 153 All initialization has to take place in this method. 154 It's not thread-safe to mutate widget's attributes outside this method 155 or anytime after widget's first display. 156 157 *Must* call super(MyWidget, self).__init__(*args, **kw) cooperatively, 158 unless, of course, you know what you're doing. Preferably this should 159 be done before any actual work is done in the method. 160 161 Parameters: 162 163 name: The widget's name. In input widgets, this will also be the 164 name of the variable that the form will send to the 165 controller. This is the only param that is safe to pass as a 166 positional argument to __init__. 167 template: The template that the widget should use to display itself. 168 Currently only Genshi and Kid templates are supported. You 169 can both initialize with a template string or with the path 170 to a file-base template: 'myapp.templates.widget_tmpl' 171 engine_name: The engine to be used for rendering the template, if not 172 specified in the template already. 173 default: Default value to display when no value is passed at display 174 time. 175 **params: Keyword arguments specific to your widget or to any of its 176 bases. If listed at class attribute 'params' the will be 177 bound automatically to the widget instance. 178 179 Note: Do not confuse these parameters with parameters listed at 180 "params". Some widgets accept parameters at the constructor which are 181 not listed params, these parameter won't be passed to the template, be 182 automatically called, etc. 183 184 """ 185 self._declaration_counter = counter.next() 186 187 if name: 188 self.name = name 189 if engine_name and engine_name != self.engine_name: 190 if self.template and not template: 191 # load the template again with a different engine 192 template = self.template 193 self.engine_name = engine_name 194 if template: 195 self.template_c, self.template, self.engine_name = load_template( 196 template, self.engine_name) 197 if default is not None: 198 self.default = default 199 200 # logic for managing the params attribute 201 for param in self.__class__.params: 202 if param in params: 203 # make sure we don't keep references to mutables 204 setattr(self, param, copy_if_mutable(params.pop(param))) 205 else: 206 # make sure we don't alter mutable class attributes 207 value, mutable = copy_if_mutable( 208 getattr(self.__class__, param), True) 209 if mutable: 210 # re-set it only if mutable 211 setattr(self, param, value) 212 213 for unused in params.iterkeys(): 214 warnings.warn('keyword argument "%s" is unused at %r instance' % ( 215 unused, self.__class__.__name__))216 220222 """Update the template parameters. 223 224 This method will have the last chance to update the variables sent to 225 the template for the specific request. All parameters listed at class 226 attribute 'params' will be available at the 'params' dict this method 227 receives. 228 229 *Must* call super(MyWidget, self).update_params(params) cooperatively, 230 unless, of course, your know what you're doing. Preferably this should 231 be done before any actual work is done in the method. 232 233 """ 234 pass235 236 # The following methods are needed to be a well behaved widget, however, 237 # there is rarely the need to override or extend them if inheriting 238 # directly or indirectly from Widget. 239241 """Delegate to display. Used as an alias to avoid tiresome typing.""" 242 return self.display(*args, **params)243245 """Prepare the widget params for rendering the template.""" 246 for param in self.__class__.params: 247 if param in params: 248 param_value = params[param] 249 if callable(param_value): 250 param_value = param_value() 251 else: 252 # if the param hasn't been overridden (passed as a keyword 253 # argument inside **params) put the corresponding instance 254 # value inside params. 255 param_value = getattr(self, param, None) 256 # make sure we don't pass a reference to mutables 257 params[param] = copy_if_mutable(param_value) 258 if not params.get('name'): 259 params['name'] = self.name 260 if value is None: 261 value = self.default 262 if callable(value): 263 value = value() 264 params['value'] = to_unicode(self.adjust_value(value, **params)) 265 self.update_params(params)266268 """Display the widget in a Genshi or Kid template. 269 270 Returns a Genshi Markup stream or an ElementTree node instance, 271 depending on the template in which the widget shall be displayed. 272 If you need serialized output in a string, call 'render' instead. 273 274 Probably you will not need to override or extend if inheriting from 275 Widget. 276 277 @params: 278 279 value : The value to display in the widget. 280 **params: Extra parameters specific to the widget. All keyword params 281 supplied will pass through the update_params method which 282 will have a last chance to modify them before reaching the 283 template. 284 285 """ 286 engine_name = self.engine_name 287 if not getattr(self, 'template_c', False): 288 warnings.warn(engine_name in view.engines 289 and "Widget instance %r has no template defined" % self 290 or "Trying to render a widget, but the %s templating engine" 291 " is not installed or not yet loaded." % engine_name) 292 return None 293 self._prepare_params(value, params) 294 try: 295 transform = view.engines[engine_name].transform 296 except (KeyError, AttributeError): 297 # this can happen if you render a widget before application startup 298 # when view.load_engines() has not yet been called 299 raise RuntimeError( 300 "Trying to render a widget, but the %s templating engine" 301 " is not installed or not yet loaded." % engine_name) 302 303 # If the widget template is Kid, but the page template is Genshi, 304 # we need to keep track of the nesting level, because Genshi cannot 305 # display Kid's ElementTree elements directly. 306 try: 307 engine_names = request.tg_template_engine_names 308 except AttributeError: 309 request.tg_template_engine_names = engine_names = [] 310 engine_names.append(engine_name) 311 try: 312 output = transform(params, self.template_c) 313 # convert output when switching between templating engines 314 # and make sure parameters are evaluated on time in Genshi 315 # (operating with streams doesn't work well with the path 316 # acrobatics performed for properly handling nested form fields) 317 if engine_names: 318 adapter = { 319 'kid-genshi': view.kid_xml, 320 'genshi-kid': view.genshi_et, 321 'genshi-genshi': list 322 }.get('-'.join(engine_names[-2:])) 323 if adapter: 324 output = adapter(output) 325 finally: 326 if engine_names: 327 engine_names.pop() 328 329 return output330332 """Exactly the same as display() but return serialized output instead. 333 334 Useful for debugging or to display the widget in a template other 335 than Genshi or Kid, like Cheetah, Mako, Nevow Stan, ... 336 337 """ 338 engine_name = self.engine_name 339 if not getattr(self, 'template_c', False): 340 warnings.warn(engine_name in view.engines 341 and "Widget instance %r has no template defined" % self 342 or "Trying to render a widget, but the %s templating engine" 343 " is not installed or not yet loaded." % engine_name) 344 return None 345 self._prepare_params(value, params) 346 try: 347 render = view.engines[engine_name].render 348 except (KeyError, AttributeError): 349 # this can happen if you render a widget before application startup 350 # when view.load_engines() has not yet been called 351 raise RuntimeError( 352 "Trying to render a widget, but the %s templating engine" 353 " is not installed or not yet loaded." % engine_name) 354 355 try: 356 engine_names = request.tg_template_engine_names 357 except AttributeError: 358 request.tg_template_engine_names = engine_names = [] 359 engine_names.append(engine_name) 360 361 try: 362 output = render(params, 363 format=format, fragment=True, template=self.template_c) 364 finally: 365 if engine_names: 366 engine_names.pop() 367 368 return output369371 """Return the needed JavaScript resources. 372 373 Return a setlike instance containing all the JSLinks and JSSources 374 the widget needs. 375 376 """ 377 scripts = setlike() 378 for script in self.javascript: 379 scripts.add(script) 380 return scripts381383 """Return the needed CSS resources. 384 385 Return a setlike instance with all the CSSLinks and CSSSources 386 the widget needs. 387 388 """ 389 css = setlike() 390 for cssitem in self.css: 391 css.add(cssitem) 392 return css393 394 @property396 """Return True if the widget has overridden its default name.""" 397 return self.name != "widget"398400 if self._locked: 401 raise ValueError( 402 "It is not threadsafe to modify widgets in a request") 403 else: 404 return super(Widget, self).__setattr__(key, value)405407 return "%s(%s)" % (self.__class__.__name__, ', '.join( 408 ["%s=%r" % (var, getattr(self, var)) 409 for var in ['name'] + self.__class__.params]))413 """A widget that can contain other widgets. 414 415 A compound widget is a widget that can group several widgets to make a 416 complex widget. Child widget names must be listed at their widget's 417 ``member_widgets`` attribute. 418 419 """ 420 421 compound = True 422 member_widgets = [] 423520425 # logic for managing the member_widgets attribute 426 for member in self.__class__.member_widgets: 427 if member in kw: 428 setattr(self, member, kw.pop(member)) 429 elif not hasattr(self, member): 430 setattr(self, member, None) 431 super(CompoundWidget, self).__init__(*args, **kw)432434 """Iterates over all the widget's children""" 435 for member in self.__class__.member_widgets: 436 attr = getattr(self, member, None) 437 if isinstance(attr, list): 438 for widget in attr: 439 yield widget 440 elif attr is not None: 441 yield attr442444 params["member_widgets_params"] = params.copy() 445 # logic for managing the member_widgets attribute 446 for member in self.__class__.member_widgets: 447 params[member] = getattr(self, member, None) 448 return super(CompoundWidget, self).display(value, **params)449451 params["member_widgets_params"] = params.copy() 452 # logic for managing the member_widgets attribute 453 for member in self.__class__.member_widgets: 454 params[member] = getattr(self, member, None) 455 return super(CompoundWidget, self).render(value, format, **params)456458 super(CompoundWidget, self).update_params(d) 459 d['value_for'] = lambda f: self.value_for(f, d['value']) 460 widgets_params = d['member_widgets_params'] 461 d['params_for'] = lambda f: self.params_for(f, **widgets_params)462464 """Get value for member widget. 465 466 Pick up the value for a given member_widget 'item' from the 467 value dict passed to this widget. 468 469 """ 470 name = getattr(item, "name", item) 471 if isinstance(value, dict): 472 return value.get(name) 473 else: 474 return None475477 """Get params for member widget. 478 479 Pick up the params for the given member_widget 'item' from 480 the params dict passed to this widget. 481 482 """ 483 name = getattr(item, "name", item) 484 item_params = {} 485 for k, v in params.iteritems(): 486 if isinstance(v, dict): 487 if name in v: 488 item_params[k] = v[name] 489 return item_params490492 """Get JavaScript for the member widgets. 493 494 Retrieve the JavaScript for all the member widgets and 495 get an ordered union of them. 496 497 """ 498 scripts = setlike() 499 for script in self.javascript: 500 scripts.add(script) 501 for widget in self.iter_member_widgets(): 502 for script in widget.retrieve_javascript(): 503 scripts.add(script) 504 return scripts505507 """Get CSS for the member widgets. 508 509 Retrieve the CSS for all the member widgets and 510 get an ordered union of them. 511 512 """ 513 css = setlike() 514 for cssitem in self.css: 515 css.add(cssitem) 516 for widget in self.iter_member_widgets(): 517 for cssitem in widget.retrieve_css(): 518 css.add(cssitem) 519 return css521 522 ############################################################################# 523 # Declarative widgets support # 524 ############################################################################# 525 526 -class MetaWidgetsList(type):527 """Metaclass for WidgetLists. 528 529 Takes care that the resulting WidgetList has all widgets in the same order 530 as they were declared. 531 532 """ 533549535 declared_widgets = [] 536 for base in bases: 537 declared_widgets.extend(getattr(base, 'declared_widgets', [])) 538 for name, value in class_dict.items(): 539 if isinstance(value, Widget): 540 if not value.is_named: 541 value.name = name 542 declared_widgets.append(value) 543 # we keep the widget accessible as attribute here, 544 # i.e. we don't del class_dict[name] as we did before 545 declared_widgets.sort(key=lambda w: w._declaration_counter) 546 cls = type.__new__(mcs, class_name, bases, class_dict) 547 cls.declared_widgets = declared_widgets 548 return cls552 """A widget list. 553 554 That's really all. A plain old list that you can declare as a classs 555 with widgets ordered as attributes. Syntactic sugar for an unsweet world. 556 557 """ 558 559 __metaclass__ = MetaWidgetsList 560583562 super(WidgetsList, self).__init__(self.declared_widgets) 563 if args: 564 if len(args) == 1: 565 args = args[0] 566 if isinstance(args, Widget): 567 args = [args] 568 self.extend(args) 569 if not self: 570 warnings.warn("You have declared an empty WidgetsList")571 572 # Make WidgetsList instances usable as Widget containers in the dict 573 # returned by controller methods, so that the JS/CSS resources of all 574 # contained widgets get picked up by the templates: 575577 return itertools.chain(*(w.retrieve_javascript() for w in self 578 if callable(getattr(w, 'retrieve_javascript', None))))579581 return itertools.chain(*(w.retrieve_css() for w in self 582 if callable(getattr(w, 'retrieve_css', None))))584 585 ############################################################################# 586 # CSS, JS and mochikit stuff # 587 ############################################################################# 588 589 -def register_static_directory(modulename, directory):590 """Set up a static directory for JavaScript and CSS files. 591 592 You can refer to this static directory in templates as 593 ${tg.widgets}/modulename 594 595 """ 596 directory = os.path.abspath(directory) 597 config.update({'/tg_widgets/%s' % modulename: { 598 'tools.staticdir.on' : True, 599 'tools.staticdir.dir': directory 600 }})601 602 static = "turbogears.widgets" 603 604 register_static_directory(static, 605 pkg_resources.resource_filename(__name__, 'static')) 606 607 register_static_directory('turbogears', 608 pkg_resources.resource_filename('turbogears', 'static')) 615618 """A resource for your widget. 619 620 For example, this can be a link to an external JavaScript/CSS file 621 or inline source to include at the template the widget is displayed. 622 623 """ 624 order = 0 625 params = ['order'] 626 params_doc = { 627 'order': "JS and CSS are sorted in this 'order' at render time" 628 }629632 """A widget resource that is a link.""" 633 637649639 super(Link, self).update_params(d) 640 d["link"] = "%s/tg_widgets/%s/%s" % (startup.webpath, 641 self.mod, self.name)642644 return hash(self.mod + self.name)645647 return (self.mod == getattr(other, "mod", None) 648 and self.name == getattr(other, "name", None))652 """A CSS link.""" 653 654 template = """ 655 <link rel="stylesheet" 656 type="text/css" 657 href="$link" 658 media="$media" 659 /> 660 """ 661 params = ["media"] 662 params_doc = {'media': 'Specify the media attribute for the CSS link tag'} 663 media = "all" 664 retrieve_css = set_with_self665 666 667 js_location = Enum('head', 'bodytop', 'bodybottom')671 """A JavaScript link.""" 672 673 template = """ 674 <script type="text/javascript" src="$link" 675 charset="$charset" defer="${defer and 'defer' or None}"/> 676 """ 677 params = ["charset", "defer"] 678 params_doc = { 679 'charset': 'The character encoding of the linked script', 680 'defer': 'If true, browser may defer execution of the script' 681 } 682 charset = None 683 defer = False 684 location = js_location.head 685696687 location = kw.pop('location', None) 688 super(JSLink, self).__init__(*args, **kw) 689 if location: 690 if location not in js_location: 691 raise ValueError( 692 "JSLink location should be in %s" % js_location) 693 self.location = location694 695 retrieve_javascript = set_with_self699 """This JSLink includes MochkKit by means of the tgMochiKit project. 700 701 Depending on three config options, you can decide 702 703 ``tg_mochikit.version`` -- ``"1.3.1"`` 704 Which version of MochiKit should be used. 705 ``tg_mochikit.packed`` -- ``False`` 706 Whether to deliver the MochiKit JS code packed or unpacked. 707 ``tg_mochikit.xhtml`` -- ``False`` 708 Whether to be XHTML-compliant or not when ``tg_mochikit.packed`` is 709 ``False``, i.e. selects whether the ``turbogears.mochikit`` widget 710 should include all of MochiKit's submodules as separate JavaScript 711 resources or just the main ``MochiKit.js`` file. 712 713 For more explanation of the options, see the tgMochiKit documentation at 714 715 http://docs.turbogears.org/tgMochiKit 716 717 """ 718 719 template = """<script xmlns:py="http://genshi.edgewall.org/" 720 py:for="js in javascripts" py:replace="js.display()" 721 /> 722 """ 723740 741 mochikit = TGMochiKit('tgmochikit')725 tgmochikit.init(register_static_directory, config) 726 if config.get('tg.mochikit_suppress', False): 727 if 'turbogears.mochikit' in config.get('tg.include_widgets', []): 728 warnings.warn("""\ 729 tg.mochikit_suppress == True, but 'turbogears.mochikit' is to be included via 730 'tg.include_widgets'. This is a contradiction, and mochikit_suppress overrides 731 the inclusion to prevent subtle errors due to version mixing of MochiKit.""") 732 return [] 733 javascripts = [JSLink('tgmochikit', path) 734 for path in tgmochikit.get_paths()] 735 return javascripts736745 """JavaScript i18n support widget. 746 747 This is a widget that can be used to add support for 748 internationalization of JavaScript texts. 749 750 It will basically add an implementation of the 751 _ function that then can be used to wrap text literals 752 in javascript-code. 753 754 Additionally, message-catalogs are looked up and included 755 depending on the current locale. 756 757 To include the widget, put 758 759 tg.include_widgets = ['turbogears.jsi18nwidget'] 760 761 in your application configuration. 762 763 """ 764 765 params = ['locale_catalog_providers'] 766 locale_catalog_providers = [] 767 771811 812 self.locale_catalog_providers.append(PackageProvider()) 813774 775 parent = self 776 777 class PackageProvider(object): 778 def __init__(self): 779 self._initialized = False780 781 def __call__(self, locale): 782 if not self._initialized: 783 # the leading underscore is to prevent shadowing of the 784 # register_package_provider-arguments 785 if package is None: 786 _package = get_package_name() 787 else: 788 _package = package 789 if directory is None: 790 _directory = "static/javascript" 791 else: 792 _directory = directory 793 if domain is None: 794 _domain = config.get('i18n.domain', 'messages') 795 else: 796 _domain = domain 797 js_dir = pkg_resources.resource_filename( 798 _package, _directory) 799 register_static_directory(_package, js_dir) 800 self._package_name = _package 801 self._domain = _domain 802 self._initialized = True 803 js = [] 804 for loc in locale, locale[:2]: 805 link = JSLink( 806 self._package_name, "%s-%s.js" % (self._domain, loc)) 807 if parent.linked_file_exists(link): 808 js.append(link) 809 break 810 return js815 if not self._initialized: 816 if not config.get('i18n.suppress_default_package_provider', False): 817 self.register_package_provider() 818 self._initialized = True 819 js = super(JSI18NWidget, self).retrieve_javascript() 820 js.add(JSLink('turbogears', 'js/i18n_base.js')) 821 locale = get_locale() 822 for pp in self.locale_catalog_providers: 823 js.extend(pp(locale)) 824 return js825827 widget_path = config.app.get('/tg_widgets/%s' % widget.mod) 828 if widget_path: 829 static_dir = widget_path.get('tools.staticdir.dir') 830 if static_dir: 831 return os.path.exists(os.path.join(static_dir, widget.name)) 832 return False833 834 jsi18nwidget = JSI18NWidget()838 839 params = ["src"] 840 844850846 return hash(self.src)847849 return self.src == getattr(other, "src", None)853 """A CSS source snippet.""" 854 855 template = """ 856 <style type="text/css" media="$media">$src</style> 857 """ 858 params = ["media"] 859 params_doc = {'src': 'The CSS source for the link', 860 'media' : 'Specify the media the css source link is for'} 861 media = "all" 862 retrieve_css = set_with_self863866 """A JavaScript source snippet.""" 867 868 template = """ 869 <script type="text/javascript" 870 defer="${defer and 'defer' or None}">$src</script> 871 """ 872 params = ["defer"] 873 params_doc = {'defer': 'If true, browser may defer execution of the script'} 874 defer = False 875 location = js_location.head 876886878 if location: 879 if location not in js_location: 880 raise ValueError( 881 "JSSource location should be in %s" % js_location) 882 self.location = location 883 super(JSSource, self).__init__(src, **kw)884 885 retrieve_javascript = set_with_self887 888 ############################################################################# 889 # Classes for supporting the toolbox widget browser # 890 ############################################################################# 891 892 -class MetaDescription(MetaWidget):893 """Metaclass for widget descriptions. 894 895 Makes sure the widget browser knows about all of them as soon as they 896 come into existence. 897 898 """ 899905901 super(MetaDescription, mcs).__init__(name, bases, dct) 902 register = dct.get('register', True) 903 if name != 'WidgetDescription' and register: 904 all_widgets.add(mcs)908 """A description for a Widget. 909 910 Makes the 'for_widget' widget appear in the browser. It's a nice way to 911 show off to your friends your coolest new widgets and to have a testing 912 platform while developing them. 913 914 """ 915 916 __metaclass__ = MetaDescription 917 918 template = """ 919 <div xmlns:py="http://genshi.edgewall.org/" 920 py:content="for_widget.display()"/> 921 """ 922 for_widget = None 923 member_widgets = ["for_widget"] 924 show_separately = False 925 926 @property953 963928 return self.for_widget_class.__name__929 930 @property932 return self.for_widget.__class__933 934 @property 937 938 @property 942 943 @property 947 950966 967 register = False 968 template = """ 969 <div> 970 This widget will render like that:<br/><br/> 971 <tt class="rendered">${for_widget.render(value)}</tt> 972 </div> 973 """ 974980976 return setlike()977979 return setlike()981 982 ############################################################################# 983 # CSS and JS WidgetDescription's # 984 ############################################################################# 985 986 -class CSSLinkDesc(CoreWD, RenderOnlyWD):990 996 1002 1008
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Fri Jul 19 17:20:04 2019 | http://epydoc.sourceforge.net |