Package s3 :: Module s3navigation
[frames] | no frames]

Source Code for Module s3.s3navigation

   1  # -*- coding: utf-8 -*- 
   2   
   3  """ S3 Navigation Module 
   4   
   5      @copyright: 2011-2019 (c) Sahana Software Foundation 
   6      @license: MIT 
   7   
   8      Permission is hereby granted, free of charge, to any person 
   9      obtaining a copy of this software and associated documentation 
  10      files (the "Software"), to deal in the Software without 
  11      restriction, including without limitation the rights to use, 
  12      copy, modify, merge, publish, distribute, sublicense, and/or sell 
  13      copies of the Software, and to permit persons to whom the 
  14      Software is furnished to do so, subject to the following 
  15      conditions: 
  16   
  17      The above copyright notice and this permission notice shall be 
  18      included in all copies or substantial portions of the Software. 
  19   
  20      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  21      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  22      OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  23      NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
  24      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
  25      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
  26      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
  27      OTHER DEALINGS IN THE SOFTWARE. 
  28   
  29      @todo: - refine check_selected (e.g. must be False if link=False) 
  30             - implement collapse-flag (render only components in a TAG[""]) 
  31             - implement convenience methods for breadcrumbs (+renderer)) 
  32             - add site-map generator and renderer (easy) 
  33             - rewrite action buttons/links as S3NavigationItems 
  34             - ...and any todo's in the code 
  35  """ 
  36   
  37  __all__ = ("S3NavigationItem", 
  38             "S3ScriptItem", 
  39             "S3ResourceHeader", 
  40             "s3_rheader_tabs", 
  41             "s3_rheader_resource", 
  42             ) 
  43   
  44  from gluon import * 
  45  from gluon.storage import Storage 
  46  from s3utils import s3_str 
47 48 # ============================================================================= 49 -class S3NavigationItem(object):
50 """ 51 Base class and API for navigation items. 52 53 Navigation items are GUI elements to navigate the application, 54 typically represented as hyperlinks. Besides form elements, 55 navigation items are most common type of GUI controls. 56 57 This base class can be used to implement both nagivation items 58 and navigation item containers (e.g. menus), each as subclasses. 59 60 Subclasses should implement the layout() method to render the item as 61 HTML (ideally using the web2py helpers). There is no default layout, 62 items will not be rendered at all unless the subclass implements a 63 layout method or the particular instance receives a renderer as 64 parameter. 65 66 Additionally, subclasses should implement the check_*() methods: 67 68 Method: Checks whether: 69 70 check_active this item belongs to the requested page 71 check_enabled this item is enabled 72 check_permission the user is permitted to access this item 73 check_selected the item has been selected to request the page 74 75 check_active is the first check run, and the only method that 76 actually deactivates the item completely, whereas the other 77 methods just set flags for the renderer. All of these methods 78 must return True or False for the respective condition. 79 80 There are default check_*() methods in this base class which support 81 a menu-alike behavior of the item - which may though not fit for all 82 navigation elements. 83 84 For more details, see the S3Navigation wiki page: 85 http://eden.sahanafoundation.org/wiki/S3/S3Navigation 86 """ 87 88 # ------------------------------------------------------------------------- 89 # Construction 90 #
91 - def __init__(self, 92 label=None, 93 c=None, 94 f=None, 95 args=None, 96 vars=None, 97 extension=None, 98 a=None, 99 r=None, 100 m=None, 101 p=None, 102 t=None, 103 url=None, 104 tags=None, 105 parent=None, 106 translate=True, 107 layout=None, 108 check=None, 109 restrict=None, 110 link=True, 111 mandatory=False, 112 ltr=False, 113 **attributes):
114 """ 115 Constructor 116 117 @param label: the label 118 119 @param c: the controller 120 @param f: the function 121 @param args: the arguments list 122 @param vars: the variables Storage 123 @param extension: the request extension 124 @param a: the application (defaults to current.request.application) 125 @param r: the request to default to 126 127 @param m: the URL method (will be appended to args) 128 @param p: the method to check authorization for 129 (will not be appended to args) 130 @param t: the table concerned by this request 131 (overrides c_f for auth) 132 133 @param url: a URL to use instead of building one manually 134 - e.g. for external websites or mailto: links 135 136 @param tags: list of tags for this item 137 @param parent: the parent item 138 139 @param translate: translate the label 140 @param layout: the layout 141 @param check: a condition or list of conditions to automatically 142 enable/disable this item 143 @param restrict: restrict to roles (role UID or list of role UIDs) 144 @param link: item has its own URL 145 @param mandatory: item is always active 146 @param ltr: item is always rendered LTR 147 148 @param attributes: attributes to use in layout 149 """ 150 151 # Label 152 if isinstance(label, basestring) and translate: 153 self.label = current.T(label) 154 else: 155 self.label = label 156 157 # Register tags 158 if tags: 159 if type(tags) is not list: 160 tags = [tags] 161 self.tags = tags 162 else: 163 self.tags = [] 164 165 # Request parameters 166 if r is not None: 167 self.r = r 168 if a is None: 169 a = r.application 170 if c is None: 171 c = r.controller 172 if f is None: 173 f = r.function 174 if args is None: 175 args = r.args 176 if vars is None: 177 vars = r.vars 178 else: 179 self.r = current.request 180 181 # Application, controller, function, args, vars, extension 182 self.application = a 183 184 if isinstance(c, (list, tuple)) and len(c): 185 self.controller = c[0] 186 self.match_controller = c 187 else: 188 self.controller = c 189 self.match_controller = [c] 190 191 if isinstance(f, (list, tuple)) and len(f): 192 self.function = f[0] 193 self.match_function = f 194 else: 195 self.function = f 196 self.match_function = [f] 197 198 if args is None: 199 args = [] 200 elif isinstance(args, str): 201 args = args.split("/") 202 self.args = args 203 if vars: 204 self.vars = vars 205 else: 206 self.vars = Storage() 207 self.extension = extension 208 209 # Table and method 210 self.tablename = t 211 self.method = m 212 if m is not None: 213 if not len(args): 214 self.args = [m] 215 elif args[-1] != m: 216 self.args.append(m) 217 if p is not None: 218 self.p = p 219 else: 220 self.p = m 221 222 self.override_url = url 223 224 # Layout attributes and options 225 attr = Storage() 226 opts = Storage() 227 for k, v in attributes.iteritems(): 228 if k[0] == "_": 229 attr[k] = v 230 else: 231 opts[k] = v 232 self.attr = attr 233 self.opts = opts 234 235 # Initialize parent and components 236 self.parent = parent 237 self.components = [] 238 239 # Flags 240 self.enabled = True # Item is enabled/disabled 241 self.selected = None # Item is in the current selected-path 242 self.visible = None # Item is visible 243 self.link = link # Item shall be linked 244 self.mandatory = mandatory # Item is always active 245 self.ltr = ltr # Item is always rendered LTR 246 self.authorized = None # True|False after authorization 247 248 # Role restriction 249 self.restrict = restrict 250 if restrict is not None: 251 if not isinstance(restrict, (list, tuple)): 252 self.restrict = [restrict] 253 254 # Hook in custom-checks 255 self.check = check 256 257 # Set the renderer (override with set_layout()) 258 renderer = None 259 if layout is not None: 260 # Custom layout for this particular instance 261 renderer = layout 262 elif hasattr(self.__class__, "OVERRIDE"): 263 # Theme layout 264 renderer = self.get_layout(self.OVERRIDE) 265 if renderer is None and hasattr(self.__class__, "layout"): 266 # Default layout 267 renderer = self.layout 268 self.renderer = renderer
269 270 # ------------------------------------------------------------------------- 271 @staticmethod
272 - def get_layout(name):
273 """ 274 Check whether the current theme has a custom layout for this 275 class, and if so, store it in current.layouts 276 277 @param: the name of the custom layout 278 @return: the layout or None if not present 279 """ 280 281 if hasattr(current, "layouts"): 282 layouts = current.layouts 283 else: 284 layouts = {} 285 if layouts is False: 286 return None 287 if name in layouts: 288 return layouts[name] 289 290 # Try to find custom layout in theme 291 application = current.request.application 292 theme = current.deployment_settings.get_theme_layouts().replace("/", ".") 293 package = "applications.%s.modules.templates.%s.layouts" % \ 294 (application, theme) 295 try: 296 override = getattr(__import__(package, fromlist=[name]), name) 297 except ImportError: 298 # No layouts in theme - no point to try again 299 current.layouts = False 300 return None 301 except AttributeError: 302 override = None 303 304 if override and \ 305 hasattr(override, "layout") and \ 306 type(override.layout) == type(lambda:None): 307 layout = override.layout 308 else: 309 layout = None 310 311 layouts[name] = layout 312 current.layouts = layouts 313 314 return layout
315 316 # -------------------------------------------------------------------------
317 - def clone(self):
318 """ Clone this item and its components """ 319 320 item = self.__class__() 321 item.label = self.label 322 item.tags = self.tags 323 324 item.r = self.r 325 326 item.application = self.application 327 item.controller = self.controller 328 item.function = self.function 329 330 item.match_controller = [c for c in self.match_controller] 331 item.match_function = [f for f in self.match_function] 332 333 item.args = [a for a in self.args] 334 item.vars = Storage(**self.vars) 335 336 item.extension = self.extension 337 item.tablename = self.tablename 338 item.method = self.method 339 item.p = self.p 340 341 item.override_url = self.override_url 342 343 item.attr = Storage(**self.attr) 344 item.opts = Storage(**self.opts) 345 346 item.parent = self.parent 347 item.components = [i.clone() for i in self.components] 348 349 item.enabled = self.enabled 350 item.selected = self.selected 351 item.visible = self.visible 352 item.link = self.link 353 item.mandatory = self.mandatory 354 if self.restrict is not None: 355 item.restrict = [r for r in self.restrict] 356 else: 357 item.restrict = None 358 359 item.check = self.check 360 item.renderer = self.renderer 361 362 return item
363 364 # ------------------------------------------------------------------------- 365 # Check methods 366 #
367 - def check_active(self, request=None):
368 """ 369 Check whether this item belongs to the requested page (request). 370 371 If this check returns False, then the item will be deactivated 372 entirely, i.e. no further checks will be run and the renderer 373 will never be called. 374 375 @param request: the request object (defaults to current.request) 376 """ 377 378 # Deactivate the item if its target controller is deactivated 379 c = self.get("controller") 380 if c: 381 return current.deployment_settings.has_module(c) 382 383 # Fall back to current.request 384 if request is None: 385 request = current.request 386 387 parent = self.parent 388 if parent is not None: 389 # For component items, the parent's status applies 390 return parent.check_active(request) 391 392 elif self.mandatory: 393 # mandatory flag overrides request match 394 return True 395 396 elif self.match(request): 397 # item is active if it matches the request 398 return True 399 400 return False
401 402 # -------------------------------------------------------------------------
403 - def check_enabled(self):
404 """ 405 Check whether this item is enabled. 406 407 This check does not directly disable the item, but rather 408 sets the enabled-flag in the item which can then be used 409 by the renderer. 410 411 This function is called as the very last action immediately 412 before rendering the item. If it returns True, then the 413 enabled-flag of the item remains unchanged, otherwise 414 it gets set to False (False-override). 415 """ 416 417 # to be implemented in subclass, default True 418 return True
419 420 # -------------------------------------------------------------------------
421 - def check_permission(self):
422 """ 423 Check whether the user is permitted to access this item. 424 425 This check does not directly disable the item, but rather 426 sets the authorized-flag in the item which can then be used 427 by the renderer. 428 """ 429 430 authorized = False 431 432 restrict = self.restrict 433 if restrict: 434 authorized = current.auth.s3_has_roles(restrict) 435 else: 436 authorized = True 437 438 if self.accessible_url() == False: 439 authorized = False 440 return authorized
441 442 # -------------------------------------------------------------------------
443 - def check_selected(self, request=None):
444 """ 445 Check whether this item is in the selected path (i.e. whether it 446 is or contains the item used to trigger the request). 447 448 This check doesn't change the processing of the item, but 449 rather sets the selected-flag which can then be used by the 450 renderer 451 452 If this is a top-level item, then this check sets the 453 selected-flags for the whole selected path down to the leaf 454 item that has triggered the request. 455 456 Note that this doesn't currently reset selected-flags, so this 457 check can be performed only once per subtree and request. If 458 it would be really neccessary to perform this check more than 459 once, then it should be easy to implement a reset_flags method. 460 461 @param request: the request object, defaults to current.request 462 """ 463 464 if self.selected is not None: 465 # Already selected 466 return self.selected 467 if request is None: 468 request = current.request 469 if self.parent is None: 470 # If this is the root item, then set the selected path 471 branch = self.branch(request) 472 if branch is not None: 473 branch.select() 474 if not self.selected: 475 self.selected = False 476 else: 477 # Otherwise: check the root item 478 root = self.get_root() 479 if root.selected is None: 480 root.check_selected(request) 481 482 return True if self.selected else False
483 484 # -------------------------------------------------------------------------
485 - def check_hook(self):
486 """ 487 Run hooked-in checks 488 """ 489 490 cond = True 491 check = self.check 492 if check is not None: 493 if not isinstance(check, (list, tuple)): 494 check = [check] 495 for condition in check: 496 if callable(condition) and not condition(self): 497 cond = False 498 elif not condition: 499 cond = False 500 return cond
501 502 # ------------------------------------------------------------------------- 503 # Tag methods, allows to enable/disable/alter items by tag 504 #
505 - def __contains__(self, tag):
506 """ Check whether a tag is present in any item of the subtree """ 507 508 components = self.components 509 for i in components: 510 if tag in i.tags or tag in i: 511 return 1 512 return 0
513 514 # -------------------------------------------------------------------------
515 - def findall(self, tag):
516 """ 517 Find all items within the tree with the specified tag 518 519 @param tag: the tag 520 """ 521 522 items = [] 523 if tag in self.tags: 524 items.append(self) 525 components = self.components 526 for c in components: 527 _items = c.findall(tag) 528 items.extend(_items) 529 return items
530 531 # -------------------------------------------------------------------------
532 - def enable(self, tag=None):
533 """ 534 Enable items 535 536 @param tag: enable all items in the subtree with this tag 537 (no tag enables only this item) 538 """ 539 540 if tag is not None: 541 items = self.findall(tag) 542 for item in items: 543 item.enable() 544 else: 545 self.enabled = True 546 return
547 548 # -------------------------------------------------------------------------
549 - def disable(self, tag=None):
550 """ 551 Disable items 552 553 @param tag: disable all items in the subtree with this tag 554 (no tag disables only this item) 555 """ 556 557 if tag is not None: 558 items = self.findall(tag) 559 for item in items: 560 item.disable() 561 else: 562 self.enabled = False 563 return
564 565 # -------------------------------------------------------------------------
566 - def select(self, tag=None):
567 """ 568 Select an item. If given a tag, this selects the first matching 569 descendant (depth-first search), otherwise selects this item. 570 571 Propagates the selection up the path to the root item (including 572 the root item) 573 574 @param tag: a string 575 """ 576 577 selected = None 578 if tag is None: 579 parent = self.parent 580 if parent: 581 parent.select() 582 else: 583 self.deselect_all() 584 selected = True 585 else: 586 for item in self.components: 587 if not selected: 588 selected = item.select(tag=tag) 589 else: 590 item.deselect_all() 591 if not selected and tag in self.tags: 592 selected = True 593 self.selected = selected 594 return selected
595 596 # -------------------------------------------------------------------------
597 - def deselect_all(self):
598 """ De-select this item and all its descendants """ 599 600 self.selected = None 601 for item in self.components: 602 item.deselect_all() 603 return
604 605 # -------------------------------------------------------------------------
606 - def set_layout(self, layout, recursive=False, tag=None):
607 """ 608 Alter the renderer for a tagged subset of items in the subtree. 609 610 @param layout: the layout (renderer) 611 @param recursive: set this layout recursively for the subtree 612 @param tag: set this layout only for items with this tag 613 """ 614 615 if layout is not None: 616 if tag is None or tag in self.tags: 617 self.renderer = layout 618 if recursive: 619 for c in self.components: 620 if tag is None or tag in c.tags: 621 c.set_layout(layout, recursive=recursive, tag=tag) 622 return
623 624 # ------------------------------------------------------------------------- 625 # Activation methods 626 # 627 # -------------------------------------------------------------------------
628 - def get(self, name, default=None):
629 """ 630 Get a Python-attribute of this item instance, falls back 631 to the same attribute in the parent item if not set in 632 this instance, used to inherit attributes to components 633 634 @param name: the attribute name 635 """ 636 637 if name in self.__dict__: 638 value = self.__dict__[name] 639 else: 640 value = None 641 if value is not None: 642 return value 643 if name[:2] == "__": 644 raise AttributeError 645 parent = self.parent 646 if parent is not None: 647 return parent.get(name) 648 return default
649 650 # -------------------------------------------------------------------------
651 - def match(self, request=None):
652 """ 653 Match this item against request (uses GET vars) 654 655 @param request: the request object (defaults to current.request) 656 657 @return: the match level (integer): 658 0=no match 659 1=controller 660 2=controller+function 661 3=controller+function+args 662 4=controller+function+args+vars 663 664 @note: currently ignores numerical arguments in the request, 665 which is though subject to change (in order to support 666 numerical arguments in the item) 667 """ 668 669 level = 0 670 args = self.args 671 link_vars = self.vars 672 673 if self.application is not None and \ 674 self.application != request.application: 675 # Foreign application links never match 676 return 0 677 678 if self.opts.selectable is False: 679 return 0 680 681 # Check hook and enabled 682 check = self.check_hook() 683 if check: 684 enabled = self.check_enabled() 685 if not enabled: 686 check = False 687 if not check: 688 # Hook failed or disabled: doesn't match in any case 689 return 0 690 691 if request is None: 692 request = current.request 693 694 c = self.get("controller") 695 mc = self.get("match_controller") 696 697 # Top-level items with no controller setting 698 # (=application level items) match any controller, but not more 699 if not c and self.parent is None: 700 return 1 701 702 703 rvars = request.get_vars 704 controller = request.controller 705 function = request.function 706 707 # Handle "viewing" (foreign controller in a tab) 708 # NOTE: this tries to match the item against the resource name 709 # in "viewing", so if the target controller/function of the item 710 # are different from prefix/name in the resource name, then this 711 # may require additional match_controller/match_function to be 712 # set for this item! (beware ambiguity then, though) 713 if "viewing" in rvars: 714 try: 715 tn = rvars["viewing"].split(".", 1)[0] 716 controller, function = tn.split("_", 1) 717 except (AttributeError, ValueError): 718 pass 719 720 # Controller 721 if controller == c or controller in mc: 722 level = 1 723 724 # Function 725 if level == 1: 726 f = self.get("function") 727 mf = self.get("match_function") 728 if function == f or function in mf: 729 level = 2 730 elif f == "index" or "index" in mf: 731 # "weak" match: homepage link matches any function 732 return 1 733 elif f is not None: 734 return 0 735 736 # Args and vars 737 # Match levels (=order of preference): 738 # 0 = args mismatch 739 # 1 = last arg mismatch (numeric instead of method) 740 # 2 = no args in item and vars mismatch 741 # 3 = no args and no vars in item 742 # 4 = no args in item but vars match 743 # 5 = args match but vars mismatch 744 # 6 = args match and no vars in item 745 # 7 = args match and vars match 746 if level == 2: 747 extra = 1 748 for k, v in link_vars.iteritems(): 749 if k not in rvars or k in rvars and rvars[k] != s3_str(v): 750 extra = 0 751 break 752 else: 753 extra = 2 754 rargs = request.args 755 if rargs: 756 if args: 757 largs = [a for a in request.args if not a.isdigit()] 758 if len(args) == len(largs) and \ 759 all([args[i] == largs[i] for i in xrange(len(args))]): 760 level = 5 761 else: 762 if len(rargs) >= len(args) > 0 and \ 763 rargs[len(args)-1].isdigit() and \ 764 not str(args[-1]).isdigit(): 765 level = 1 766 else: 767 return 0 768 else: 769 level = 3 770 elif args: 771 return 0 772 else: 773 level = 5 774 level += extra 775 776 return level
777 778 # -------------------------------------------------------------------------
779 - def branch(self, request=None):
780 """ 781 Get the matching branch item for request 782 783 @param request: the request object (defaults to current.request) 784 """ 785 786 if request is None: 787 request = current.request 788 789 leaf, level = self.__branch(request) 790 if level: 791 return leaf 792 else: 793 return None
794 795 # -------------------------------------------------------------------------
796 - def __branch(self, request):
797 """ 798 Find the best match for request within the subtree, recursive 799 helper method for branch(). 800 801 @param request: the request object 802 """ 803 804 items = self.get_all(enabled=True) 805 l = self.match(request) 806 if not items: 807 return self, l 808 else: 809 match, maxlevel = None, l - 1 810 for i in items: 811 item, level = i.__branch(request) 812 if item is not None and level > maxlevel: 813 match = item 814 maxlevel = level 815 if match is not None: 816 return match, maxlevel 817 else: 818 return self, l
819 820 # ------------------------------------------------------------------------- 821 # Representation methods 822 # 823 # -------------------------------------------------------------------------
824 - def __repr__(self):
825 """ String representation of this item """ 826 827 components = [str(c) for c in self.components] 828 if self.enabled: 829 label = str(self.label) 830 else: 831 label = "%s (disabled)" % self.label 832 label = "%s:%s" % (self.__class__.__name__, label) 833 if components: 834 return "<%s {%s}>" % (label, ",".join(components)) 835 else: 836 return "<%s>" % label
837 838 # -------------------------------------------------------------------------
839 - def url(self, extension=None, **kwargs):
840 """ 841 Return the target URL for this item, doesn't check permissions 842 843 @param extension: override the format extension 844 @param kwargs: override URL query vars 845 """ 846 847 if not self.link: 848 return None 849 850 if self.override_url: 851 return self.override_url 852 853 args = self.args 854 if self.vars: 855 link_vars = Storage(self.vars) 856 link_vars.update(kwargs) 857 else: 858 link_vars = Storage(kwargs) 859 if extension is None: 860 extension = self.extension 861 a = self.get("application") 862 if a is None: 863 a = current.request.application 864 c = self.get("controller") 865 if c is None: 866 c = "default" 867 f = self.get("function") 868 if f is None: 869 f = "index" 870 f, args = self.__format(f, args, extension) 871 return URL(a=a, c=c, f=f, args=args, vars=link_vars)
872 873 # -------------------------------------------------------------------------
874 - def accessible_url(self, extension=None, **kwargs):
875 """ 876 Return the target URL for this item if accessible by the 877 current user, otherwise False 878 879 @param extension: override the format extension 880 @param kwargs: override URL query vars 881 """ 882 883 aURL = current.auth.permission.accessible_url 884 885 if not self.link: 886 return None 887 888 args = self.args 889 if self.vars: 890 link_vars = Storage(self.vars) 891 link_vars.update(kwargs) 892 else: 893 link_vars = Storage(kwargs) 894 if extension is None: 895 extension = self.extension 896 a = self.get("application") 897 if a is None: 898 a = current.request.application 899 c = self.get("controller") 900 if c is None: 901 c = "default" 902 f = self.get("function") 903 if f is None: 904 f = "index" 905 f, args = self.__format(f, args, extension) 906 return aURL(c=c, f=f, p=self.p, a=a, t=self.tablename, 907 args=args, vars=link_vars)
908 909 # ------------------------------------------------------------------------- 910 @staticmethod
911 - def __format(f, args, ext):
912 """ 913 Append the format extension to the last argument 914 915 @param f: the function 916 @param args: argument list 917 @param ext: the format extension 918 919 @return: tuple (f, args) 920 """ 921 922 if not ext or ext == "html": 923 return f, args 924 items = [f] 925 if args: 926 items += args 927 items = [i.rsplit(".", 1)[0] for i in items] 928 items.append("%s.%s" % (items.pop(), ext)) 929 return (items[0], items[1:])
930 931 # -------------------------------------------------------------------------
932 - def render(self, request=None):
933 """ 934 Perform the checks and render this item. 935 936 @param request: the request object (defaults to current.request) 937 """ 938 939 renderer = self.renderer 940 output = None 941 942 if request is None: 943 request = current.request 944 945 if self.check_active(request): 946 947 # Run the class' check_permission method 948 self.authorized = self.check_permission() 949 950 # Run check_selected 951 self.selected = self.check_selected() 952 953 # Check custom conditions (hook), these methods 954 # can alter any prior flags, which can then only be 955 # overridden by the class' check_enabled method 956 cond = self.check_hook() 957 958 if cond: 959 # Run the class' check_enabled method: 960 # if this returns False, then this overrides any prior status 961 enabled = self.check_enabled() 962 if not enabled: 963 self.enabled = False 964 965 # Render the item 966 if renderer is not None: 967 output = renderer(self) 968 969 return output
970 971 # -------------------------------------------------------------------------
972 - def render_components(self):
973 """ 974 Render the components of this item and return the results as list 975 """ 976 977 items = [] 978 for c in self.components: 979 i = c.render() 980 if i is not None: 981 if type(i) is list: 982 items.extend(i) 983 else: 984 items.append(i) 985 return items
986 987 # -------------------------------------------------------------------------
988 - def xml(self):
989 """ 990 Invokes the renderer and serializes the output for the web2py 991 template parser, returns a string to be written to the response 992 body, uses the xml() method of the renderer output, if present. 993 """ 994 995 output = self.render() 996 if output is None: 997 return "" 998 elif hasattr(output, "xml"): 999 return output.xml() 1000 else: 1001 return str(output)
1002 1003 # ------------------------------------------------------------------------- 1004 # Tree construction methods 1005 #
1006 - def set_parent(self, p=None, i=None):
1007 """ 1008 Set a parent for this item, base method for tree construction 1009 1010 @param p: the parent 1011 @param i: the list index where to insert the item 1012 """ 1013 1014 if p is None: 1015 p = self.parent 1016 if p is None: 1017 return 1018 parent = self.parent 1019 if parent is not None and parent !=p: 1020 while self in parent.components: 1021 parent.components.remove(self) 1022 if i is not None: 1023 p.component.insert(i, self) 1024 else: 1025 p.components.append(self) 1026 self.parent = p 1027 return
1028 1029 # -------------------------------------------------------------------------
1030 - def append(self, item=None):
1031 """ 1032 Append a component 1033 1034 @param item: the component 1035 """ 1036 1037 if item is not None: 1038 if type(item) is list: 1039 for i in item: 1040 self.append(i) 1041 else: 1042 item.set_parent(self) 1043 return self
1044 1045 # -------------------------------------------------------------------------
1046 - def insert(self, i, item=None):
1047 """ 1048 Insert a component item at position i 1049 1050 @param i: the index position 1051 @param item: the component item 1052 """ 1053 1054 if item is not None: 1055 item.set_parent(self, i=i) 1056 return self
1057 1058 # -------------------------------------------------------------------------
1059 - def extend(self, items):
1060 """ 1061 Extend this item with a list of components 1062 1063 @param items: list of component items 1064 """ 1065 1066 if items: 1067 for item in items: 1068 self.append(item) 1069 return self
1070 1071 # -------------------------------------------------------------------------
1072 - def __call__(self, *components):
1073 """ 1074 Convenience shortcut for extend 1075 1076 @param components: list of components 1077 """ 1078 1079 self.extend(components) 1080 return self
1081 1082 # -------------------------------------------------------------------------
1083 - def __add__(self, items):
1084 """ 1085 Append component items to this item 1086 1087 @param items: the items to append 1088 """ 1089 1090 if isinstance(items, (list, tuple)): 1091 self.extend(items) 1092 else: 1093 self.append(items) 1094 return self
1095 1096 # ------------------------------------------------------------------------- 1097 # Tree introspection and manipulation methods 1098 #
1099 - def __getitem__(self, i):
1100 """ 1101 Get the component item at position i 1102 1103 @param i: the index of the component item 1104 """ 1105 1106 return self.components.__getitem__(i)
1107 1108 # -------------------------------------------------------------------------
1109 - def __setitem__(self, i, item):
1110 """ 1111 Overwrite the component item at position i with item 1112 1113 @param i: the index within the component list 1114 @param item: the item 1115 """ 1116 1117 self.components.__setitem__(i, item)
1118 1119 # -------------------------------------------------------------------------
1120 - def pop(self, i=-1):
1121 """ 1122 Return the component at index i and remove it from the list 1123 1124 @param i: the component index 1125 """ 1126 1127 return self.components.pop(i)
1128 1129 # -------------------------------------------------------------------------
1130 - def get_root(self):
1131 """ 1132 Get the top level item of this navigation tree 1133 """ 1134 1135 parent = self.parent 1136 if parent: 1137 return parent.get_root() 1138 else: 1139 return self
1140 1141 # -------------------------------------------------------------------------
1142 - def path(self, sub=None):
1143 """ 1144 Get the full path to this item (=a list of items from the root 1145 item down to this item). 1146 """ 1147 1148 path = [self] 1149 if sub: 1150 path.extend(sub) 1151 if self.parent: 1152 return self.parent.path(sub=path) 1153 else: 1154 return path
1155 1156 # -------------------------------------------------------------------------
1157 - def get_all(self, **flags):
1158 """ 1159 Get all components with these flags 1160 1161 @param flags: dictionary of flags 1162 """ 1163 1164 items = [] 1165 for item in self.components: 1166 if not flags or \ 1167 all([getattr(item, f) == flags[f] for f in flags]): 1168 items.append(item) 1169 return items
1170 1171 # -------------------------------------------------------------------------
1172 - def get_first(self, **flags):
1173 """ 1174 Get the first component item with these flags 1175 1176 @param flags: dictionary of flags 1177 """ 1178 1179 for item in self.components: 1180 if not flags or \ 1181 all([getattr(item, f) == flags[f] for f in flags]): 1182 return item 1183 return None
1184 1185 # -------------------------------------------------------------------------
1186 - def get_last(self, **flags):
1187 """ 1188 Get the first component item with these flags 1189 1190 @param flags: dictionary of flags 1191 """ 1192 1193 components = list(self.components) 1194 components.reverse() 1195 for item in components: 1196 if not flags or \ 1197 all([getattr(item, f) == flags[f] for f in flags]): 1198 return item 1199 return None
1200 1201 # ------------------------------------------------------------------------- 1202 # List methods 1203 #
1204 - def __len__(self):
1205 """ The total number of components of this item """ 1206 1207 return len(self.components)
1208 1209 # -------------------------------------------------------------------------
1210 - def __nonzero__(self):
1211 """ 1212 To be used instead of __len__ to determine the boolean value 1213 if this item, should always return True for instances 1214 """ 1215 1216 return self is not None
1217 1218 # -------------------------------------------------------------------------
1219 - def index(self, item):
1220 """ 1221 Get the index of a component item within the component list 1222 1223 @param item: the item 1224 """ 1225 1226 return self.components.index(item)
1227 1228 # -------------------------------------------------------------------------
1229 - def pos(self):
1230 """ 1231 Get the position of this item within the parent's component 1232 list, reverse method for index() 1233 """ 1234 1235 if self.parent: 1236 return self.parent.index(self) 1237 else: 1238 return None
1239 1240 # -------------------------------------------------------------------------
1241 - def is_first(self, **flags):
1242 """ 1243 Check whether this is the first item within the parent's 1244 components list with these flags 1245 1246 @param flags: dictionary of flags 1247 """ 1248 1249 if not flags: 1250 return len(self.preceding()) == 0 1251 if not all([getattr(self, f) == flags[f] for f in flags]): 1252 return False 1253 preceding = self.preceding() 1254 if preceding: 1255 for item in preceding: 1256 if all([getattr(item, f) == flags[f] for f in flags]): 1257 return False 1258 return True
1259 1260 # -------------------------------------------------------------------------
1261 - def is_last(self, **flags):
1262 """ 1263 Check whether this is the last item within the parent's 1264 components list with these flags 1265 1266 @param flags: dictionary of flags 1267 """ 1268 1269 if not flags: 1270 return len(self.following()) == 0 1271 if not all([getattr(self, f) == flags[f] for f in flags]): 1272 return False 1273 following = self.following() 1274 if following: 1275 for item in following: 1276 if all([getattr(item, f) == flags[f] for f in flags]): 1277 return False 1278 return True
1279 1280 # -------------------------------------------------------------------------
1281 - def preceding(self):
1282 """ Get the preceding siblings within the parent's component list """ 1283 1284 parent = self.parent 1285 if parent: 1286 pos = self.pos() 1287 if pos is not None: 1288 return parent.components[:pos] 1289 return []
1290 1291 # -------------------------------------------------------------------------
1292 - def following(self):
1293 """ Get the following siblings within the parent's component list """ 1294 1295 parent = self.parent 1296 if parent: 1297 items = parent.components 1298 pos = self.pos() 1299 if pos is not None: 1300 pos = pos + 1 1301 if pos < len(items): 1302 return items[pos:] 1303 return []
1304 1305 # -------------------------------------------------------------------------
1306 - def get_prev(self, **flags):
1307 """ 1308 Get the previous item in the parent's component list with these 1309 flags 1310 1311 @param flags: dictionary of flags 1312 """ 1313 1314 preceding = self.preceding() 1315 preceding.reverse() 1316 for item in preceding: 1317 if not flags or \ 1318 all([getattr(item, f) == flags[f] for f in flags]): 1319 return item 1320 return None
1321 1322 # -------------------------------------------------------------------------
1323 - def get_next(self, **flags):
1324 """ 1325 Get the next item in the parent's component list with these flags 1326 1327 @param flags: dictionary of flags 1328 """ 1329 1330 following = self.following() 1331 for item in following: 1332 if not flags or \ 1333 all([getattr(item, f) == flags[f] for f in flags]): 1334 return item 1335 return None
1336
1337 # ============================================================================= 1338 -def s3_rheader_resource(r):
1339 """ 1340 Identify the tablename and record ID for the rheader 1341 1342 @param r: the current S3Request 1343 1344 """ 1345 1346 get_vars = r.get_vars 1347 1348 if "viewing" in get_vars: 1349 try: 1350 tablename, record_id = get_vars.viewing.rsplit(".", 1) 1351 except AttributeError: 1352 tablename = r.tablename 1353 record = r.record 1354 else: 1355 db = current.db 1356 record = db[tablename][record_id] 1357 else: 1358 tablename = r.tablename 1359 record = r.record 1360 1361 return (tablename, record)
1362
1363 # ============================================================================= 1364 -def s3_rheader_tabs(r, tabs=None):
1365 """ 1366 Constructs a DIV of component links for a S3RESTRequest 1367 1368 @param tabs: the tabs as list of tuples (title, component_name, vars), 1369 where vars is optional 1370 """ 1371 1372 rheader_tabs = S3ComponentTabs(tabs) 1373 return rheader_tabs.render(r)
1374
1375 # ============================================================================= 1376 -class S3ComponentTabs(object):
1377 """ Class representing a row of component tabs """ 1378
1379 - def __init__(self, tabs=None):
1380 """ 1381 Constructor 1382 1383 @param tabs: the tabs configuration as list of names or tuples 1384 (label, name) 1385 """ 1386 1387 if not tabs: 1388 self.tabs = [] 1389 else: 1390 self.tabs = [S3ComponentTab(t) for t in tabs if t]
1391 1392 # -------------------------------------------------------------------------
1393 - def render(self, r):
1394 """ 1395 Render the tabs row 1396 1397 @param r: the S3Request 1398 """ 1399 1400 rheader_tabs = [] 1401 1402 if r.resource.get_config("dynamic_components"): 1403 self.dynamic_tabs(r.resource.tablename) 1404 1405 tabs = [t for t in self.tabs if t.active(r)] 1406 1407 mtab = False 1408 if r.component is None: 1409 # Check whether there is a tab for the current URL method 1410 for t in tabs: 1411 if t.component == r.method: 1412 mtab = True 1413 break 1414 1415 record_id = r.id 1416 if not record_id and r.record: 1417 record_id = r.record[r.table._id] 1418 1419 for i, tab in enumerate(tabs): 1420 1421 # Determine the query variables for the tab URL 1422 vars_match = tab.vars_match(r) 1423 if vars_match: 1424 _vars = Storage(r.get_vars) 1425 else: 1426 _vars = Storage(tab.vars) 1427 if "viewing" in r.get_vars: 1428 _vars.viewing = r.get_vars.viewing 1429 1430 # Determine the controller function for the tab URL 1431 if tab.function is None: 1432 # Infer function from current request 1433 if "viewing" in _vars: 1434 tablename, record_id = _vars.viewing.split(".", 1) 1435 function = tablename.split("_", 1)[1] 1436 else: 1437 function = r.function 1438 else: 1439 # Tab defines controller function 1440 function = tab.function 1441 1442 # Is this the current tab? 1443 component = tab.component 1444 here = False 1445 if function == r.name or function == r.function: 1446 here = r.method == component or not mtab 1447 if component: 1448 if r.component and \ 1449 r.component.alias == component and \ 1450 vars_match: 1451 here = True 1452 elif not r.component and r.method == component: 1453 here = True 1454 else: 1455 here = False 1456 else: 1457 if r.component or not vars_match: 1458 here = False 1459 1460 # HTML class for the tab position 1461 if here: 1462 _class = "tab_here" 1463 elif i == len(tabs) - 1: 1464 _class = "tab_last" 1465 else: 1466 _class = "tab_other" 1467 1468 # Complete the tab URL with args, deal with "viewing" 1469 if component: 1470 if record_id: 1471 args = [record_id, component] 1472 else: 1473 args = [component] 1474 if "viewing" in _vars: 1475 del _vars["viewing"] 1476 _href = URL(function, args=args, vars=_vars) 1477 _id = "rheader_tab_%s" % component 1478 else: 1479 args = [] 1480 if function != r.name and not tab.native: 1481 if "viewing" not in _vars and r.id: 1482 _vars.update(viewing="%s.%s" % (r.tablename, r.id)) 1483 elif not tab.component and not tab.function: 1484 if "viewing" in _vars: 1485 del _vars["viewing"] 1486 args = [record_id] 1487 else: 1488 if "viewing" not in _vars and record_id: 1489 args = [record_id] 1490 _href = URL(function, args=args, vars=_vars) 1491 _id = "rheader_tab_%s" % function 1492 1493 # Render tab 1494 rheader_tabs.append(SPAN(A(tab.title, 1495 _href=_href, 1496 _id=_id, 1497 ), 1498 _class=_class, 1499 )) 1500 1501 # Render tab row 1502 if rheader_tabs: 1503 rheader_tabs = DIV(rheader_tabs, _class="tabs") 1504 else: 1505 rheader_tabs = "" 1506 return rheader_tabs
1507 1508 # -------------------------------------------------------------------------
1509 - def dynamic_tabs(self, master):
1510 """ 1511 Add dynamic tabs 1512 1513 @param master: the name of the master table 1514 """ 1515 1516 T = current.T 1517 s3db = current.s3db 1518 1519 tabs = self.tabs 1520 if not tabs: 1521 return 1522 1523 ftable = s3db.s3_field 1524 query = (ftable.component_key == True) & \ 1525 (ftable.component_tab == True) & \ 1526 (ftable.master == master) & \ 1527 (ftable.deleted == False) 1528 rows = current.db(query).select(ftable.component_alias, 1529 ftable.settings, 1530 ) 1531 for row in rows: 1532 alias = row.component_alias 1533 if not alias: 1534 continue 1535 1536 static_tab = False 1537 for tab in tabs: 1538 if tab.component == alias: 1539 static_tab = True 1540 break 1541 1542 if not static_tab: 1543 1544 label = None 1545 position = None 1546 1547 settings = row.settings 1548 if settings: 1549 1550 label = settings.get("tab_label") 1551 1552 position = settings.get("tab_position") 1553 if position is not None: 1554 try: 1555 position = int(position) 1556 except ValueError: 1557 position = None 1558 if position < 1 or position >= len(tabs): 1559 position = None 1560 1561 if not label: 1562 # Generate default label from component alias 1563 label = T(" ".join(s.capitalize() 1564 for s in alias.split("_") 1565 )) 1566 tab = S3ComponentTab((label, alias)) 1567 if not position: 1568 tabs.append(tab) 1569 else: 1570 tabs.insert(position, tab)
1571
1572 # ============================================================================= 1573 -class S3ComponentTab(object):
1574 """ Class representing a single Component Tab """ 1575
1576 - def __init__(self, tab):
1577 """ 1578 Constructor 1579 1580 @param tab: the component tab configuration as tuple 1581 (label, component_alias, {get_vars}), where the 1582 get_vars dict is optional. 1583 """ 1584 1585 # @todo: use component hook label/plural as fallback for title 1586 # (see S3Model.add_components) 1587 title, component = tab[:2] 1588 if component and component.find("/") > 0: 1589 function, component = component.split("/", 1) 1590 else: 1591 function = None 1592 1593 self.title = title 1594 1595 self.native = False 1596 1597 if function: 1598 self.function = function 1599 else: 1600 self.function = None 1601 1602 if component: 1603 self.component = component 1604 else: 1605 self.component = None 1606 1607 if len(tab) > 2: 1608 tab_vars = self.vars = Storage(tab[2]) 1609 if "native" in tab_vars: 1610 self.native = True if tab_vars["native"] else False 1611 del tab_vars["native"] 1612 else: 1613 self.vars = None
1614 1615 # -------------------------------------------------------------------------
1616 - def active(self, r):
1617 """ 1618 Check whether the this tab is active 1619 1620 @param r: the S3Request 1621 """ 1622 1623 s3db = current.s3db 1624 1625 get_components = s3db.get_components 1626 get_method = s3db.get_method 1627 get_vars = r.get_vars 1628 tablename = None 1629 if "viewing" in get_vars: 1630 try: 1631 tablename = get_vars["viewing"].split(".", 1)[0] 1632 except AttributeError: 1633 pass 1634 1635 resource = r.resource 1636 component = self.component 1637 function = self.function 1638 if component: 1639 clist = get_components(resource.table, names=[component]) 1640 is_component = False 1641 if component in clist: 1642 is_component = True 1643 elif tablename: 1644 clist = get_components(tablename, names=[component]) 1645 if component in clist: 1646 is_component = True 1647 if is_component: 1648 return self.authorised(clist[component]) 1649 handler = get_method(resource.prefix, 1650 resource.name, 1651 method=component) 1652 if handler is None and tablename: 1653 prefix, name = tablename.split("_", 1) 1654 handler = get_method(prefix, name, 1655 method=component) 1656 if handler is None: 1657 handler = r.get_handler(component) 1658 if handler is None: 1659 return component in ("create", "read", "update", "delete") 1660 1661 elif function: 1662 return current.auth.permission.has_permission("read", f=function) 1663 1664 return True
1665 1666 # -------------------------------------------------------------------------
1667 - def authorised(self, hook):
1668 """ 1669 Check permissions for component tabs (in order to deactivate 1670 tabs the user is not permitted to access) 1671 1672 @param hook: the component hook 1673 """ 1674 1675 READ = "read" 1676 has_permission = current.auth.s3_has_permission 1677 1678 # Must have access to the link table (if any): 1679 if hook.linktable and not has_permission(READ, hook.linktable): 1680 return False 1681 1682 # ...and to the component table itself: 1683 if has_permission(READ, hook.tablename): 1684 return True 1685 1686 return False
1687 1688 # -------------------------------------------------------------------------
1689 - def vars_match(self, r):
1690 """ 1691 Check whether the request GET vars match the GET vars in 1692 the URL of this tab 1693 1694 @param r: the S3Request 1695 """ 1696 1697 if self.vars is None: 1698 return True 1699 get_vars = r.get_vars 1700 for k, v in self.vars.iteritems(): 1701 if v is None: 1702 continue 1703 if k not in get_vars or \ 1704 k in get_vars and get_vars[k] != v: 1705 return False 1706 return True
1707
1708 # ============================================================================= 1709 -class S3ScriptItem(S3NavigationItem):
1710 """ 1711 Simple Navigation Item just for injecting scripts into HTML forms 1712 """ 1713 1714 # -------------------------------------------------------------------------
1715 - def __init__(self, 1716 script=None, 1717 **attributes):
1718 """ 1719 @param script: script to inject into jquery_ready when rendered 1720 """ 1721 1722 super(S3ScriptItem, self).__init__(attributes) 1723 self.script = script
1724 1725 # -------------------------------------------------------------------------
1726 - def xml(self):
1727 """ 1728 Injects associated script into jquery_ready. 1729 """ 1730 1731 if self.script: 1732 current.response.s3.jquery_ready.append(self.script) 1733 return ""
1734 1735 # ------------------------------------------------------------------------- 1736 @staticmethod
1737 - def inline(item):
1738 """ 1739 Present to ensure that script injected even in inline forms 1740 """ 1741 1742 return ""
1743
1744 # ============================================================================= 1745 -class S3ResourceHeader(object):
1746 """ Simple Generic Resource Header for tabbed component views """ 1747
1748 - def __init__(self, fields=None, tabs=None, title=None):
1749 """ 1750 Constructor 1751 1752 @param fields: the fields to display, list of lists of 1753 fieldnames, Field instances or callables 1754 @param tabs: the tabs 1755 @param title: the title fieldname, Field or callable 1756 1757 Fields are specified in order rows->cols, i.e. if written 1758 like: 1759 1760 [ 1761 ["fieldA", "fieldF", "fieldX"], 1762 ["fieldB", None, "fieldY"] 1763 ] 1764 1765 then that's exactly the screen order. Row or column spans are 1766 not supported - empty fields will be rendered as empty fields. 1767 If you need to construct more complex rheaders, you should 1768 implement a custom method. 1769 1770 Fields can be specified by field names, Field instances or 1771 as callables. Where a field specifier is a callable, it will 1772 be invoked with the record as parameter and is respected to 1773 return the representation value. 1774 1775 Where a field specifier is a tuple of two items, the first 1776 item is taken for the label (overriding the field label, if 1777 any), like in: 1778 1779 1780 [ 1781 [(T("Name"), s3_fullname)], 1782 ... 1783 ] 1784 1785 Where the second item is a callable, it maybe necessary to 1786 specify a label. 1787 1788 If you don't want any fields, specify this explicitly as: 1789 1790 rheader = S3ResourceHeader(fields=[]) 1791 1792 Where you don't specify any fields and the table contains a 1793 "name" field, the rheader defaults to: [["name"]]. 1794 """ 1795 1796 self.fields = fields 1797 self.tabs = tabs 1798 self.title = title
1799 1800 # -------------------------------------------------------------------------
1801 - def __call__(self, r, tabs=None, table=None, record=None, as_div=True):
1802 """ 1803 Return the HTML representation of this rheader 1804 1805 @param r: the S3Request instance to render the header for 1806 @param tabs: the tabs (overrides the original tabs definition) 1807 @param table: override r.table 1808 @param record: override r.record 1809 @param as_div: True: will return the rheader_fields and the 1810 rheader_tabs together as a DIV 1811 False will return the rheader_fields and the 1812 rheader_tabs as a tuple 1813 (rheader_fields, rheader_tabs) 1814 """ 1815 1816 if table is None: 1817 table = r.table 1818 if record is None: 1819 record = r.record 1820 1821 if tabs is None: 1822 tabs = self.tabs 1823 1824 if self.fields is None and "name" in table.fields: 1825 fields = [["name"]] 1826 else: 1827 fields = self.fields 1828 1829 if record: 1830 1831 if tabs is not None: 1832 rheader_tabs = s3_rheader_tabs(r, tabs) 1833 else: 1834 rheader_tabs = "" 1835 1836 trs = [] 1837 for row in fields: 1838 tr = TR() 1839 for col in row: 1840 if col is None: 1841 continue 1842 label, value, colspan = self.render_field(table, record, col) 1843 if value is False: 1844 continue 1845 if label is not None: 1846 tr.append(TH(("%s: " % label) if label else "")) 1847 tr.append(TD(value, _colspan=colspan) if colspan else TD(value)) 1848 trs.append(tr) 1849 1850 title = self.title 1851 if title: 1852 title = self.render_field(table, record, title)[1] 1853 1854 if title: 1855 content = DIV(H6(title, _class="rheader-title"), 1856 TABLE(trs), 1857 _class="rheader-content", 1858 ) 1859 else: 1860 content = TABLE(trs, _class="rheader-content") 1861 1862 rheader = (content, rheader_tabs) 1863 if as_div: 1864 rheader = DIV(*rheader) 1865 1866 else: 1867 rheader = None 1868 1869 return rheader
1870 1871 # -------------------------------------------------------------------------
1872 - def render_field(self, table, record, col):
1873 """ 1874 Render an rheader field 1875 1876 @param table: the table 1877 @param record: the record 1878 @param col: the column spec (field name or tuple (label, fieldname)) 1879 1880 @returns: tuple (label, value) 1881 """ 1882 1883 field = None 1884 label = True 1885 value = "" 1886 colspan = None 1887 1888 # Parse column spec: 1889 # fieldname|(label, fieldname)|(label,fieldname,colspan) 1890 # label can be either a T(), str, HTML, or: 1891 # True => automatic (use field label, default) 1892 # None => no label column 1893 # "" or False => empty label 1894 if isinstance(col, (tuple, list)): 1895 if len(col) == 2: 1896 label, f = col 1897 elif len(col) > 2: 1898 label, f, colspan = col 1899 else: 1900 f = col[0] 1901 else: 1902 f = col 1903 1904 # Get value 1905 # - value can be a fieldname, a Field instance or a callable to 1906 # extract the value from the record 1907 if callable(f): 1908 try: 1909 value = f(record) 1910 except: 1911 pass 1912 else: 1913 if isinstance(f, str): 1914 fn = f 1915 if "." in fn: 1916 fn = f.split(".", 1)[1] 1917 if fn not in record or fn not in table: 1918 return None, False, None 1919 field = table[fn] 1920 value = record[fn] 1921 # Field.Method? 1922 if callable(value): 1923 value = value() 1924 elif isinstance(f, Field) and f.name in record: 1925 field = f 1926 value = record[f.name] 1927 if hasattr(field, "represent") and field.represent is not None: 1928 value = field.represent(value) 1929 1930 # Render label 1931 if label is True: 1932 label = field.label if field is not None else False 1933 1934 # Render value 1935 if not isinstance(value, basestring) and \ 1936 not isinstance(value, DIV): 1937 value = s3_str(value) 1938 1939 return label, value, colspan
1940 1941 # END ========================================================================= 1942