Home | Trees | Indices | Help |
|
---|
|
1 import itertools 2 import os 3 import warnings 4 5 import pkg_resources 6 7 from cherrypy import request 8 import tgmochikit 9 10 from turbogears import config, startup, view 11 from turbogears.util import (setlike, to_unicode, copy_if_mutable, 12 get_package_name, request_available) 13 from turbogears.i18n.utils import get_locale 14 from turbogears.widgets.meta import MetaWidget, load_kid_template 15 16 __all__ = ['load_widgets', 'all_widgets', 'Widget', 'CompoundWidget', 17 'WidgetsList', 'register_static_directory', 'static', 18 'Resource', 'Link', 'CSSLink', 'JSLink', 19 'Source', 'CSSSource', 'JSSource', 20 'js_location', 'mochikit', 'jsi18nwidget', 21 'WidgetDescription', 'set_with_self'] 22 2325 """Enum used at js_locations. 26 27 This is less strict than ``turbogears.utils.Enum`` and serves our 28 purposes as well as allowing any object with ``retrieve_javascript``, 29 ``retrieve_css``, and ``location`` attributes to provide resources to 30 the template when scanned in ``turbogears.controllers._process_output``. 31 32 Example: 33 34 >>> locations = Enum('bodytop', 'bodybottom', head') 35 >>> locations.head == locations.head 36 True 37 >>> locations.head == locations.bodybottom 38 False 39 >>> locations.head in locations 40 True 41 >>> locations.foo 42 Traceback (most recent call last): 43 ... 44 AttributeError 45 46 """ 49 5457 58 # Keeps a count of the each declared widget in a WidgetsList instance 59 # so their order is preserved. 60 counter = itertools.count() 61 62 # Load all widgets provided by the widget entry point64 for widget_mod in pkg_resources.iter_entry_points("turbogears.widgets"): 65 try: 66 widget_mod.load() 67 except Exception, e: 68 raise ImportError, 'Error loading plugin "%s"\n%s: %s' % ( 69 widget_mod, e.__class__.__name__, e)70 71 all_widgets = set() 72 73 PlainHTML = load_kid_template(""" 74 <html xmlns:py="http://purl.org/kid/ns#" py:replace="elements"/> 75 """, modname='turbogears.widgets.plainhtml')[0] 76 77 78 ############################################################################# 79 # Widgets base classes # 80 ############################################################################# 8183 """A TurboGears Widget. 84 85 '__init__' and 'update_params' are the only methods you might need to 86 care extending. 87 88 Attributes you should know about: 89 90 name : The name of the widget. 91 template : Kid template for the widget. 92 default : Default value for the widget. 93 css : List of CSSLinks and CSSSources for the widget. These 94 will be automatically pulled and inserted in the 95 page template that displays it. 96 javascript: List of JSLinks and JSSources for the widget. Same as css. 97 is_named : A property returning True if the widget has overrided it's 98 default name. 99 params : All parameter names listed here will be treated as special 100 parameters. This list is updated by the metaclass and 101 always contains *all* params from the widget itself and 102 all it's bases. Can be used as a quick reminder of all 103 the params your widget has at it's disposal. They all 104 behave the same and have the same priorities regarding their 105 overridal. Read on... 106 params_doc: A dictionary containing 'params' names as keys and their 107 docstring as value. For documentation at the widget browser. 108 109 All initialization parameters listed at the class attribute "params" can be 110 defined as class attributes, overriden at __init__ or at display time. They 111 will be treated as special params for the widget, which means: 112 113 1) You can fix default values for them when sublcassing Widget. 114 115 2) If passed as **params to the constructor, the will be bound automatically 116 to the widget instance, taking preference over any class attributes 117 previously defined. Mutable attributes (dicts and lists) defined as class 118 attributes are safe to modify as care is taken to copy them so the class 119 attribute remains unchanged. 120 121 3) They can be further overrided by passing them as keyword args to 122 display(). This will only affect that display() call in a thread-safe 123 way. 124 125 4) A callable can be passed and it will be called automatically when sending 126 variables to the template. This can be handy to pick up parameters which 127 change in every request or affect many widgets simultaneously. 128 129 """ 130 __metaclass__ = MetaWidget 131 132 name = "widget" 133 template = None 134 default = None 135 css = [] 136 javascript = [] 137 params = [] 138 params_doc = {} 139364 365141 """Widget initialization. 142 143 All initialization has to take place in this method. 144 It's not thread-safe to mutate widget's attributes outside this method 145 or anytime after widget's first display. 146 147 *Must* call super(MyWidget, self).__init__(*args, **kw) cooperatively, 148 unless, of course, your know what you're doing. Preferably this should 149 be done before any actual work is done in the method. 150 151 Parameters: 152 153 name : The widget's name. In input widgets, this will also be the 154 name of the variable that the form will send to the 155 controller. This is the only param that is safe to pass as a 156 positional argument to __init__. 157 template : The template that the widget should use to display itself. 158 Currently only Kid templates are supported. You can both 159 initialize with a template string or with the path to a 160 file-base template: "myapp.templates.widget_tmpl" 161 default : Default value to display when no value is passed at display 162 time. 163 **params : Keyword arguments specific to your widget or to any of it's 164 bases. If listed at class attribute 'params' the will be 165 bound automatically to the widget instance. 166 167 Note: Do not confuse these parameters with parameters listed at 168 "params". Some widgets accept parameters at the constructor which are 169 not listed params, these parameter won't be passed to the template, be 170 automatically called, etc.. 171 172 """ 173 self._declaration_counter = counter.next() 174 if name: 175 self.name = name 176 177 if template: 178 self.template_c, self.template = load_kid_template(template) 179 180 if default is not None: 181 self.default = default 182 183 # logic for managing the params attribute 184 for param in self.__class__.params: 185 if param in params: 186 # make sure we don't keep references to mutables 187 setattr(self, param, copy_if_mutable(params.pop(param))) 188 189 else: 190 # make sure we don't alter mutable class attributes 191 value, mutable = copy_if_mutable( 192 getattr(self.__class__, param), True) 193 if mutable: 194 # re-set it only if mutable 195 setattr(self, param, value) 196 197 for unused in params.iterkeys(): 198 warnings.warn('keyword argument "%s" is unused at %r instance' % ( 199 unused, self.__class__.__name__))200 204206 """Update the template parameters. 207 208 This method will have the last chance to update the variables sent to 209 the template for the specific request. All parameters listed at class 210 attribute 'params' will be available at the 'params' dict this method 211 receives. 212 213 *Must* call super(MyWidget, self).update_params(params) cooperatively, 214 unless, of course, your know what you're doing. Preferably this should 215 be done before any actual work is done in the method. 216 217 """ 218 pass219 220 # The following methods are needed to be a well behaved widget, however, 221 # there is rarely the need to override or extend them if inheritting 222 # directly or indirectly from Widget. 223225 """Delegate to display. Used as an alias to avoid tiresome typing.""" 226 return self.display(*args, **params)227229 """Display the widget in a Kid template. 230 231 Returns an elementtree node instance. If you need serialized output 232 in a string, call 'render' instead. 233 234 Probably you will not need to override or extend if inheriting from 235 Widget. 236 237 @params: 238 239 value : The value to display in the widget. 240 **params: Extra parameters specific to the widget. All keyword params 241 supplied will pass through the update_params method which 242 will have a last chance to modify them before reaching the 243 template. 244 245 """ 246 if not getattr(self, 'template_c', False): 247 warnings.warn('kid' in view.engines 248 and "Widget instance %r has no template defined" % self 249 or "Trying to render a widget, but the Kid" 250 " templating engine is not installed or not yet loaded.") 251 return None 252 253 # logic for managing the params attribute 254 for param in self.__class__.params: 255 if param in params: 256 param_value = params[param] 257 if callable(param_value): 258 param_value = param_value() 259 260 else: 261 # if the param hasn't been overridden (passed as a keyword 262 # argument inside **params) put the corresponding instance 263 # value inside params. 264 param_value = getattr(self, param, None) 265 266 # make sure we don't pass a reference to mutables 267 params[param] = copy_if_mutable(param_value) 268 269 if not params.get('name'): 270 params['name'] = self.name 271 272 if value is None: 273 value = self.default 274 if callable(value): 275 value = value() 276 params['value'] = to_unicode(self.adjust_value(value, **params)) 277 278 self.update_params(params) 279 280 try: 281 transform = view.engines['kid'].transform 282 except (KeyError, AttributeError): 283 # this can happen if you render a widget before application startup 284 # when view.load_engines() has not yet been called 285 raise RuntimeError("Trying to render a widget, but the Kid" 286 " templating engine is not installed or not yet loaded.") 287 288 # If the page template is Genshi, we keep track of the nesting level, 289 # because Genshi cannot display Kid's ElementTree elements directly. 290 if request_available() and request.tg_template_enginename == 'genshi': 291 display_level = getattr(request, 'tg_widgets_display_level', 0) 292 request.tg_widgets_display_level = display_level + 1 293 else: 294 display_level = None 295 296 try: 297 output = transform(params, self.template_c) 298 if display_level == 0: 299 # On the topmost level, we create a Genshi markup stream 300 # from Kid's ElementTree element to make Genshi really happy. 301 # This automatism makes wrapping widget output with ET(...) 302 # calls in Genshi page templates unnecessary. 303 output = view.genshi_et(output) 304 finally: 305 if display_level is not None: 306 request.tg_widgets_display_level = display_level 307 308 return output309311 """Exactly the same as display() but return serialized output instead. 312 313 Useful for debugging or to display the widget in a non-Kid template 314 like Cheetah, STAN, ... 315 316 """ 317 elem = self.display(value, **params) 318 template = PlainHTML(elements=elem) 319 return template.serialize(output=format, fragment=True)320322 """Return the needed JavaScript ressources. 323 324 Return a setlike instance containing all the JSLinks and JSSources 325 the widget needs. 326 327 """ 328 scripts = setlike() 329 for script in self.javascript: 330 scripts.add(script) 331 return scripts332334 """Return the needed CSS ressources. 335 336 Return a setlike instance with all the CSSLinks and CSSSources 337 the widget needs. 338 339 """ 340 css = setlike() 341 for cssitem in self.css: 342 css.add(cssitem) 343 return css344346 """Return True if the widget has overridden its default name.""" 347 if self.name != "widget": 348 return True 349 else: 350 return False351 is_named = property(_get_is_named) 352354 if self._locked: 355 raise ValueError, \ 356 "It is not threadsafe to modify widgets in a request" 357 else: 358 return super(Widget, self).__setattr__(key, value)359361 return "%s(%s)" % (self.__class__.__name__, ', '.join( 362 ["%s=%r" % (var, getattr(self, var)) 363 for var in ['name'] + self.__class__.params]))367 """A widget that can contain other widgets. 368 369 A compound widget is a widget that can group several widgets to make a 370 complex widget. Child widget names must be listed at their widget's 371 ``member_widgets`` attribute. 372 373 """ 374 compound = True 375 member_widgets = [] 376466 467 468 ############################################################################# 469 # Declarative widgets support # 470 ############################################################################# 471378 # logic for managing the member_widgets attribute 379 for member in self.__class__.member_widgets: 380 if member in kw: 381 setattr(self, member, kw.pop(member)) 382 elif not hasattr(self, member): 383 setattr(self, member, None) 384 super(CompoundWidget, self).__init__(*args, **kw)385387 """Iterates over all the widget's children""" 388 for member in self.__class__.member_widgets: 389 attr = getattr(self, member, None) 390 if isinstance(attr, list): 391 for widget in attr: 392 yield widget 393 elif attr is not None: 394 yield attr395397 params["member_widgets_params"] = params.copy() 398 # logic for managing the member_widgets attribute 399 for member in self.__class__.member_widgets: 400 params[member] = getattr(self, member, None) 401 return super(CompoundWidget, self).display(value, **params)402404 super(CompoundWidget, self).update_params(d) 405 d['value_for'] = lambda f: self.value_for(f, d['value']) 406 widgets_params = d['member_widgets_params'] 407 d['params_for'] = lambda f: self.params_for(f, **widgets_params)408410 """Get value for member widget. 411 412 Pick up the value for a given member_widget 'item' from the 413 value dict passed to this widget. 414 415 """ 416 name = getattr(item, "name", item) 417 if isinstance(value, dict): 418 return value.get(name) 419 else: 420 return None421423 """Get params for member widget. 424 425 Pick up the params for the given member_widget 'item' from 426 the params dict passed to this widget. 427 428 """ 429 name = getattr(item, "name", item) 430 item_params = {} 431 for k, v in params.iteritems(): 432 if isinstance(v, dict): 433 if name in v: 434 item_params[k] = v[name] 435 return item_params436438 """Get JavaScript for the member widgets. 439 440 Retrieve the JavaScript for all the member widgets and 441 get an ordered union of them. 442 443 """ 444 scripts = setlike() 445 for script in self.javascript: 446 scripts.add(script) 447 for widget in self.iter_member_widgets(): 448 for script in widget.retrieve_javascript(): 449 scripts.add(script) 450 return scripts451453 """Get CSS for the member widgets. 454 455 Retrieve the CSS for all the member widgets and 456 get an ordered union of them. 457 458 """ 459 css = setlike() 460 for cssitem in self.css: 461 css.add(cssitem) 462 for widget in self.iter_member_widgets(): 463 for cssitem in widget.retrieve_css(): 464 css.add(cssitem) 465 return css473 """Metaclass for WidgetLists. 474 475 Takes care that the resulting WidgetList has all widgets in the same order 476 as they were declared. 477 478 """494 495480 declared_widgets = [] 481 for base in bases: 482 declared_widgets.extend(getattr(base, 'declared_widgets', [])) 483 for name, value in class_dict.items(): 484 if isinstance(value, Widget): 485 if not value.is_named: 486 value.name = name 487 declared_widgets.append(value) 488 # we keep the widget accessible as attribute here, 489 # i.e. we don't del class_dict[name] as we did before 490 declared_widgets.sort(key=lambda w: w._declaration_counter) 491 cls = type.__new__(meta, class_name, bases, class_dict) 492 cls.declared_widgets = declared_widgets 493 return cls497 """A widget list. 498 499 That's really all. A plain old list that you can declare as a classs 500 with widgets ordered as attributes. Syntactic sugar for an unsweet world. 501 502 """ 503 __metaclass__ = MetaWidgetsList 504527 528 529 ############################################################################# 530 # CSS, JS and mochikit stuff # 531 ############################################################################# 532506 super(WidgetsList, self).__init__(self.declared_widgets) 507 if args: 508 if len(args) == 1: 509 args = args[0] 510 if isinstance(args, Widget): 511 args = [args] 512 self.extend(args) 513 if not self: 514 warnings.warn("You have declared an empty WidgetsList")515 516 # Make WidgetsList instances usable as Widget containers in the dict 517 # returned by controller methods, so that the JS/CSS resources of all 518 # contained widgets get picked up by the templates: 519521 return itertools.chain(*(w.retrieve_javascript() for w in self 522 if callable(getattr(w, 'retrieve_javascript', None))))523525 return itertools.chain(*(w.retrieve_css() for w in self 526 if callable(getattr(w, 'retrieve_css', None))))534 """Set up a static directory for JavaScript and CSS files. 535 536 You can refer to this static directory in templates as 537 ${tg.widgets}/modulename 538 539 """ 540 directory = os.path.abspath(directory) 541 config.update({'/tg_widgets/%s' % modulename: { 542 'static_filter.on' : True, 543 'static_filter.dir': directory 544 }})545 546 static = "turbogears.widgets" 547 548 register_static_directory(static, 549 pkg_resources.resource_filename(__name__, 'static')) 550 551 register_static_directory('turbogears', 552 pkg_resources.resource_filename('turbogears', 'static')) 553 554 559 560562 """A resource for your widget. 563 564 For example, this can be a link to an external JavaScript/CSS file 565 or inline source to include at the template the widget is displayed. 566 567 """ 568 order = 0 569 params = ['order'] 570 params_doc = {'order': "JS and CSS are sorted in this 'order' at render time"}571 572574 """A widget resource that is a link.""" 575 579591 592581 super(Link, self).update_params(d) 582 d["link"] = "/%stg_widgets/%s/%s" % (startup.webpath, 583 self.mod, self.name)584586 return hash(self.mod + self.name)587589 return self.mod == getattr(other, "mod", None) and \ 590 self.name == getattr(other, "name", None)594 """A CSS link.""" 595 596 template = """ 597 <link rel="stylesheet" 598 type="text/css" 599 href="$link" 600 media="$media" 601 /> 602 """ 603 params = ["media"] 604 params_doc = {'media': 'Specify the media attribute for the css link tag'} 605 media = "all" 606 607 retrieve_css = set_with_self608 609 610 js_location = Enum('head', 'bodytop', 'bodybottom') 611 612614 """A JavaScript link.""" 615 616 template = """ 617 <script type="text/javascript" src="$link" 618 charset="$charset" defer="${defer and 'defer' or None}"/> 619 """ 620 params = ["charset", "defer"] 621 params_doc = {'charset': 'The character encoding of the linked script', 622 'defer': 'If true, browser may defer execution of the script'} 623 charset = None 624 defer = False 625 626 location = js_location.head 627637 638629 location = kw.pop('location', None) 630 super(JSLink, self).__init__(*args, **kw) 631 if location: 632 #if location not in js_location: 633 # raise ValueError, "JSLink location should be in %s" % js_location 634 self.location = location635 636 retrieve_javascript = set_with_self640 """This JSLink includes MochkKit by means of the tgMochiKit project. 641 642 Depending on three config options, you can decide 643 644 ``tg_mochikit.version`` -- ``"1.3.1"`` 645 Which version of MochiKit should be used. 646 ``tg_mochikit.packed`` -- ``False`` 647 Whether to deliver the MochiKit JS code packed or unpacked. 648 ``tg_mochikit.xhtml`` -- ``False`` 649 Whether to be XHTML-compliant or not when ``tg_mochikit.packed`` is 650 ``False``, i.e. selects whether the ``turbogears.mochikit`` widget 651 should include all of MochiKit's submodules as separate JavaScript 652 resources or just the main ``MochiKit.js`` file. 653 654 For more explanation of the options, see the tgMochiKit documentation at 655 656 http://docs.turbogears.org/tgMochiKit 657 658 """ 659 660 template = """<script xmlns:py="http://purl.org/kid/ns#" 661 py:for="js in javascripts" py:replace="js.display()" 662 /> 663 """ 664681 682 mochikit = TGMochiKit("tgmochikit") 683 684666 tgmochikit.init(register_static_directory, config) 667 if config.get('tg.mochikit_suppress', False): 668 if 'turbogears.mochikit' in config.get('tg.include_widgets', []): 669 warnings.warn("""\ 670 tg.mochikit_suppress == True, but 'turbogears.mochikit' is to be included via 671 'tg.include_widgets'. This is a contradiction, and mochikit_suppress overrides 672 the inclusion to prevent subtle errors due to version mixing of MochiKit.""") 673 return [] 674 javascripts = [JSLink("tgmochikit", path) 675 for path in tgmochikit.get_paths()] 676 return javascripts677686 """JavaScript i18n support widget. 687 688 This is a widget that can be used to add support for 689 internationalization of JavaScript texts. 690 691 It will basically add an implementation of the 692 _ function that then can be used to wrap text literals 693 in javascript-code. 694 695 Additionally, message-catalogs are looked up and included 696 depending on the current locale. 697 698 To include the widget, put 699 700 tg.include_widgets = ['turbogears.jsi18nwidget'] 701 702 in your application configuration. 703 704 """ 705 params = ['locale_catalog_providers'] 706 707 locale_catalog_providers = [] 708 712750 751 self.locale_catalog_providers.append(PackageProvider()) 752715 716 parent = self 717 718 class PackageProvider(object): 719 def __init__(self): 720 self._initialized = False721 722 def __call__(self, locale): 723 if not self._initialized: 724 # the leading underscore is to prevent shadowing of the 725 # register_package_provider-arguments 726 if package is None: 727 _package = get_package_name() 728 else: 729 _package = package 730 if directory is None: 731 _directory = "static/javascript" 732 else: 733 _directory = directory 734 if domain is None: 735 _domain = config.get('i18n.domain', 'messages') 736 else: 737 _domain = domain 738 js_dir = pkg_resources.resource_filename(_package, _directory) 739 register_static_directory(_package, js_dir) 740 self._package_name = _package 741 self._domain = _domain 742 self._initialized = True 743 js = [] 744 for l in locale, locale[:2]: 745 link = JSLink(self._package_name, "%s-%s.js" % (self._domain, l)) 746 if parent.linked_file_exists(link): 747 js.append(link) 748 break 749 return js754 if not self._initialized: 755 if not config.get('i18n.suppress_default_package_provider', False): 756 self.register_package_provider() 757 self._initialized = True 758 js = super(JSI18NWidget, self).retrieve_javascript() 759 js.add(JSLink("turbogears", 'js/i18n_base.js')) 760 locale = get_locale() 761 for pp in self.locale_catalog_providers: 762 js.extend(pp(locale)) 763 return js764766 static_dir = config.get('static_filter.dir', 767 path='/tg_widgets/%s' % widget.mod) 768 if static_dir: 769 return os.path.exists(os.path.join(static_dir, widget.name)) 770 else: 771 return False772 773 jsi18nwidget = JSI18NWidget() 774 775777 params = ["src"] 778 782788 789784 return hash(self.src)785787 return self.src == getattr(other, "src", None)791 """A CSS source snippet.""" 792 793 template = """ 794 <style type="text/css" media="$media">$src</style> 795 """ 796 params = ["media"] 797 params_doc = {'src': 'The CSS source for the link', 798 'media' : 'Specify the media the css source link is for'} 799 media = "all" 800 801 retrieve_css = set_with_self802 803805 """A JavaScript source snippet.""" 806 807 template = """ 808 <script type="text/javascript" 809 defer="${defer and 'defer' or None}">$src</script> 810 """ 811 params = ["defer"] 812 params_doc = {'defer': 'If true, browser may defer execution of the script'} 813 defer = False 814 815 location = js_location.head 816825 826 827 ############################################################################# 828 # Classes for supporting the toolbox widget browser # 829 ############################################################################# 830 831 just_the_widget = load_kid_template(""" 832 <div xmlns:py="http://purl.org/kid/ns#" py:content="for_widget.display()"/> 833 """)[0] 834 835818 if location: 819 if location not in js_location: 820 raise ValueError, "JSSource location should be in %s" % js_location 821 self.location = location 822 super(JSSource, self).__init__(src, **kw)823 824 retrieve_javascript = set_with_self837 """Metaclass for widget descriptions. 838 839 Makes sure the widget browser knows about all of them as soon as they 840 come into existence. 841 842 """848 849844 super(MetaDescription, cls).__init__(name, bases, dct) 845 register = dct.get("register", True) 846 if name != "WidgetDescription" and register: 847 all_widgets.add(cls)851 """A description for a Widget. 852 853 Makes the 'for_widget' widget appear in the browser. It's a nice way to 854 show off to your friends your coolest new widgets and to have a testing 855 platform while developing them. 856 857 """ 858 __metaclass__ = MetaDescription 859 860 template = just_the_widget 861 for_widget = None 862 member_widgets = ["for_widget"] 863 show_separately = False 864892 893 901 902 917 918 919 ############################################################################# 920 # CSS and JS WidgetDescription's # 921 ############################################################################# 922 926 927 931 932 936 937 941866 return self.for_widget_class.__name__867 name = property(_get_name) 868870 return self.for_widget.__class__871 for_widget_class = property(_get_widget_class) 872 875 description = property(_get_description) 876 880 full_class_name = property(_get_full_class_name) 881 885 source = property(_get_source) 886 889
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Thu Jul 14 21:45:40 2011 | http://epydoc.sourceforge.net |