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

Source Code for Module s3.s3profile

   1  # -*- coding: utf-8 -*- 
   2   
   3  """ S3 Profile 
   4   
   5      @copyright: 2009-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   
  30  from gluon import current, redirect 
  31  from gluon.html import * 
  32  from gluon.storage import Storage 
  33   
  34  from s3crud import S3CRUD 
  35  from s3report import S3Report 
  36  from s3query import FS 
  37  from s3utils import s3_str 
  38  from s3widgets import ICON 
39 40 # ============================================================================= 41 -class S3Profile(S3CRUD):
42 """ 43 Interactive Method Handler for Profile Pages 44 45 Configure widgets using s3db.configure(tablename, profile_widgets=[]) 46 47 @ToDo: Make more configurable: 48 * Currently uses internal widgets rather than S3Method widgets 49 50 @todo: 51 - unify datalist and datatable methods with the superclass 52 methods (requires re-design of the superclass methods) 53 - allow as default handler for interactive single-record-no-method 54 GET requests (include read/update from superclass) 55 """ 56 57 # -------------------------------------------------------------------------
58 - def apply_method(self, r, **attr):
59 """ 60 API entry point 61 62 @param r: the S3Request instance 63 @param attr: controller attributes for the request 64 """ 65 66 if r.http in ("GET", "POST", "DELETE"): 67 if r.record: 68 # Initialize CRUD form 69 self.settings = current.response.s3.crud 70 self.sqlform = sqlform = self._config("crud_form") 71 if not sqlform: 72 from s3forms import S3SQLDefaultForm 73 self.sqlform = S3SQLDefaultForm() 74 75 # Render page 76 output = self.profile(r, **attr) 77 return output 78 79 elif r.representation not in ("dl", "aadata"): 80 # Redirect to the List View 81 redirect(r.url(method="")) 82 83 else: 84 # No point redirecting 85 r.error(404, current.ERROR.BAD_RECORD) 86 else: 87 r.error(405, current.ERROR.BAD_METHOD)
88 89 # -------------------------------------------------------------------------
90 - def profile(self, r, **attr):
91 """ 92 Generate a Profile page 93 94 @param r: the S3Request instance 95 @param attr: controller attributes for the request 96 """ 97 98 tablename = self.tablename 99 get_config = current.s3db.get_config 100 101 header = get_config(tablename, "profile_header") 102 103 # Get the page widgets 104 widgets = get_config(tablename, "profile_widgets") 105 if not widgets and not header: 106 # Profile page not configured: 107 if r.representation not in ("dl", "aadata"): 108 # Redirect to the Read View 109 redirect(r.url(method="read")) 110 else: 111 # No point redirecting 112 r.error(405, current.ERROR.BAD_METHOD) 113 114 # Index the widgets by their position in the config 115 for index, widget in enumerate(widgets): 116 widget["index"] = index 117 118 if r.representation == "dl": 119 # Ajax-update of one datalist 120 index = r.get_vars.get("update", None) 121 if index: 122 try: 123 index = int(index) 124 except ValueError: 125 datalist = "" 126 else: 127 # @ToDo: Check permissions to the Resource & do 128 # something different if no permission 129 datalist = self._datalist(r, widgets[index], **attr) 130 output = {"item": datalist} 131 132 elif r.representation == "aadata": 133 # Ajax-update of one datatable 134 index = r.get_vars.get("update", None) 135 if index: 136 try: 137 index = int(index) 138 except ValueError: 139 datalist = "" 140 else: 141 # @ToDo: Check permissions to the Resource & do 142 # something different if no permission 143 datatable = self._datatable(r, widgets[index], **attr) 144 return datatable 145 146 else: 147 # Default page-load 148 149 # Page Title 150 title = get_config(tablename, "profile_title") 151 if not title: 152 try: 153 title = r.record.name 154 except: 155 title = current.T("Profile Page") 156 elif callable(title): 157 title = title(r) 158 159 # Page Header 160 if not header: 161 header = H2(title, _class="profile-header") 162 elif callable(header): 163 header = header(r) 164 165 output = {"title": title, 166 "header": header, 167 } 168 169 # Update Form, if configured 170 update = get_config(tablename, "profile_update") 171 if update: 172 editable = get_config(tablename, "editable", True) 173 authorised = self._permitted(method="update") 174 if authorised and editable: 175 show = self.crud_string(tablename, "title_update") 176 hide = current.T("Hide Form") 177 form = self.update(r, **attr)["form"] 178 else: 179 show = self.crud_string(tablename, "title_display") 180 hide = current.T("Hide Details") 181 form = self.read(r, **attr)["item"] 182 183 if update == "visible": 184 hidden = False 185 label = hide 186 style_hide, style_show = None, "display:none" 187 else: 188 hidden = True 189 label = show 190 style_hide, style_show = "display:none", None 191 192 toggle = A(SPAN(label, 193 data = {"on": show, 194 "off": hide, 195 }, 196 ), 197 ICON("down", _style=style_show), 198 ICON("up", _style=style_hide), 199 data = {"hidden": hidden}, 200 _class="form-toggle action-lnk", 201 ) 202 form.update(_style=style_hide) 203 output["form"] = DIV(toggle, 204 form, 205 _class="profile-update", 206 ) 207 else: 208 output["form"] = "" 209 210 # Widgets 211 response = current.response 212 rows = [] 213 append = rows.append 214 row = None 215 cols = get_config(tablename, "profile_cols") 216 if not cols: 217 cols = 2 218 row_cols = 0 219 for widget in widgets: 220 221 # Render the widget 222 w_type = widget["type"] 223 if w_type == "comments": 224 w = self._comments(r, widget, **attr) 225 elif w_type == "datalist": 226 w = self._datalist(r, widget, **attr) 227 elif w_type == "datatable": 228 w = self._datatable(r, widget, **attr) 229 elif w_type == "form": 230 w = self._form(r, widget, **attr) 231 elif w_type == "map": 232 w = self._map(r, widget, widgets, **attr) 233 elif w_type == "report": 234 w = self._report(r, widget, **attr) 235 elif w_type == "custom": 236 w = self._custom(r, widget, **attr) 237 else: 238 if response.s3.debug: 239 raise SyntaxError("Unsupported widget type %s" % 240 w_type) 241 else: 242 # ignore 243 continue 244 245 if row is None: 246 # Start new row 247 row = DIV(_class="row profile") 248 row_cols = 0 249 250 # Append widget to row 251 row.append(w) 252 colspan = widget.get("colspan", 1) 253 row_cols += colspan 254 if row_cols == cols: 255 # Close this row 256 append(row) 257 row = None 258 259 if row: 260 # We have an incomplete row of widgets 261 append(row) 262 output["rows"] = rows 263 264 # Activate this if a project needs it 265 #response.view = get_config(tablename, "profile_view") or \ 266 # self._view(r, "profile.html") 267 response.view = self._view(r, "profile.html") 268 269 return output
270 271 # ------------------------------------------------------------------------- 272 @staticmethod
273 - def _resolve_context(r, tablename, context):
274 """ 275 Resolve a context filter 276 277 @param context: the context (as a string) 278 @param id: the record_id 279 """ 280 281 record_id = r.id 282 if not record_id: 283 return None 284 285 if not context: 286 query = None 287 288 elif type(context) is tuple: 289 context, field = context 290 query = (FS(context) == r.record[field]) 291 292 elif context == "location": 293 # Show records linked to this Location & all it's Child Locations 294 s = "(location)$path" 295 # This version doesn't serialize_url 296 #m = ("%(id)s/*,*/%(id)s/*" % dict(id=id)).split(",") 297 #filter = (FS(s).like(m)) | (FS(s) == id) 298 m = ("%(id)s,%(id)s/*,*/%(id)s/*,*/%(id)s" % dict(id=record_id)).split(",") 299 m = [f.replace("*", "%") for f in m] 300 query = (FS(s).like(m)) 301 # @ToDo: 302 #elif context == "organisation": 303 # # Show records linked to this Organisation and all it's Branches 304 # s = "(%s)" % context 305 # query = (FS(s) == id) 306 else: 307 # Normal: show just records linked directly to this master resource 308 s = "(%s)" % context 309 query = (FS(s) == record_id) 310 311 # Define target resource 312 resource = current.s3db.resource(tablename, filter=query) 313 r.customise_resource(tablename) 314 return resource, query
315 316 # -------------------------------------------------------------------------
317 - def _comments(self, r, widget, **attr):
318 """ 319 Generate a Comments widget 320 321 @param r: the S3Request instance 322 @param widget: the widget definition as dict 323 @param attr: controller attributes for the request 324 325 @ToDo: Configurable to use either Disqus or internal Comments 326 """ 327 328 label = widget.get("label", "") 329 # Activate if-required 330 #if label and isinstance(label, basestring): 331 if label: 332 label = current.T(label) 333 icon = widget.get("icon", "") 334 if icon: 335 icon = ICON(icon) 336 337 _class = self._lookup_class(r, widget) 338 339 comments = "@ToDo" 340 341 # Render the widget 342 output = DIV(H4(icon, 343 label, 344 _class="profile-sub-header"), 345 DIV(comments, 346 _class="card-holder"), 347 _class=_class) 348 349 return output
350 351 # -------------------------------------------------------------------------
352 - def _custom(self, r, widget, **attr):
353 """ 354 Generate a Custom widget 355 356 @param r: the S3Request instance 357 @param widget: the widget definition as dict 358 @param attr: controller attributes for the request 359 """ 360 361 label = widget.get("label", "") 362 # Activate if-required 363 #if label and isinstance(label, basestring): 364 if label: 365 label = current.T(label) 366 icon = widget.get("icon", "") 367 if icon: 368 icon = ICON(icon) 369 370 _class = self._lookup_class(r, widget) 371 372 contents = widget["fn"](r, **attr) 373 374 # Render the widget 375 output = DIV(H4(icon, 376 label, 377 _class="profile-sub-header"), 378 DIV(contents, 379 _class="card-holder"), 380 _class=_class) 381 382 return output
383 384 # -------------------------------------------------------------------------
385 - def _datalist(self, r, widget, **attr):
386 """ 387 Generate a data list 388 389 @param r: the S3Request instance 390 @param widget: the widget definition as dict 391 @param attr: controller attributes for the request 392 """ 393 394 T = current.T 395 396 widget_get = widget.get 397 398 context = widget_get("context") 399 tablename = widget_get("tablename") 400 resource, context = self._resolve_context(r, tablename, context) 401 402 # Config Options: 403 # 1st choice: Widget 404 # 2nd choice: get_config 405 # 3rd choice: Default 406 config = resource.get_config 407 list_fields = widget_get("list_fields", 408 config("list_fields", None)) 409 list_layout = widget_get("list_layout", 410 config("list_layout", None)) 411 orderby = widget_get("orderby", 412 config("list_orderby", 413 config("orderby", 414 ~resource.table.created_on))) 415 416 widget_filter = widget_get("filter") 417 if widget_filter: 418 resource.add_filter(widget_filter) 419 420 # Use the widget-index to create a unique ID 421 list_id = "profile-list-%s-%s" % (tablename, widget["index"]) 422 423 # Page size 424 pagesize = widget_get("pagesize", 4) 425 representation = r.representation 426 if representation == "dl": 427 # Ajax-update 428 get_vars = r.get_vars 429 record_id = get_vars.get("record", None) 430 if record_id is not None: 431 # Ajax-update of a single record 432 resource.add_filter(FS("id") == record_id) 433 start, limit = 0, 1 434 else: 435 # Ajax-update of full page 436 start = get_vars.get("start", None) 437 limit = get_vars.get("limit", None) 438 if limit is not None: 439 try: 440 start = int(start) 441 limit = int(limit) 442 except ValueError: 443 start, limit = 0, pagesize 444 else: 445 start = None 446 else: 447 # Page-load 448 start, limit = 0, pagesize 449 450 # Ajax-delete items? 451 if representation == "dl" and r.http in ("DELETE", "POST"): 452 if "delete" in r.get_vars: 453 return self._dl_ajax_delete(r, resource) 454 else: 455 r.error(405, current.ERROR.BAD_METHOD) 456 457 # dataList 458 datalist, numrows = resource.datalist(fields = list_fields, 459 start = start, 460 limit = limit, 461 list_id = list_id, 462 orderby = orderby, 463 layout = list_layout, 464 ) 465 # Render the list 466 ajaxurl = r.url(vars={"update": widget["index"]}, 467 representation="dl") 468 data = datalist.html(ajaxurl=ajaxurl, 469 pagesize=pagesize, 470 empty = P(ICON("folder-open-alt"), 471 BR(), 472 self.crud_string(tablename, 473 "msg_no_match"), 474 _class="empty_card-holder" 475 ), 476 ) 477 478 if representation == "dl": 479 # This is an Ajax-request, so we don't need the wrapper 480 current.response.view = "plain.html" 481 return data 482 483 # Interactive only below here 484 label = widget_get("label", "") 485 # Activate if-required 486 #if label and isinstance(label, basestring): 487 if label: 488 label = T(label) 489 icon = widget_get("icon", "") 490 if icon: 491 icon = ICON(icon) 492 493 if pagesize and numrows > pagesize: 494 # Button to display the rest of the records in a Modal 495 more = numrows - pagesize 496 get_vars_new = {} 497 if context: 498 filters = context.serialize_url(resource) 499 for f in filters: 500 get_vars_new[f] = filters[f] 501 if widget_filter: 502 filters = widget_filter.serialize_url(resource) 503 for f in filters: 504 get_vars_new[f] = filters[f] 505 c, f = tablename.split("_", 1) 506 f = widget_get("function", f) 507 url = URL(c=c, f=f, args=["datalist.popup"], 508 vars=get_vars_new) 509 more = DIV(A(BUTTON("%s (%s)" % (T("see more"), more), 510 _class="btn btn-mini tiny button", 511 _type="button", 512 ), 513 _class="s3_modal", 514 _href=url, 515 _title=label, 516 ), 517 _class="more_profile") 518 else: 519 more = "" 520 521 # Link for create-popup 522 create_popup = self._create_popup(r, 523 widget, 524 list_id, 525 resource, 526 context, 527 numrows) 528 529 _class = self._lookup_class(r, widget) 530 531 # Render the widget 532 output = DIV(create_popup, 533 H4(icon, 534 label, 535 _class="profile-sub-header"), 536 DIV(data, 537 more, 538 _class="card-holder"), 539 _class=_class) 540 541 return output
542 543 # -------------------------------------------------------------------------
544 - def _datatable(self, r, widget, **attr):
545 """ 546 Generate a data table. 547 548 @param r: the S3Request instance 549 @param widget: the widget definition as dict 550 @param attr: controller attributes for the request 551 552 @todo: fix export formats 553 """ 554 555 widget_get = widget.get 556 557 # Parse context 558 context = widget_get("context") 559 tablename = widget_get("tablename") 560 resource, context = self._resolve_context(r, tablename, context) 561 562 # List fields 563 list_fields = widget_get("list_fields") 564 if not list_fields: 565 # @ToDo: Set the parent so that the fkey gets removed from the list_fields 566 #resource.parent = s3db.resource("") 567 list_fields = resource.list_fields() 568 569 # Widget filter option 570 widget_filter = widget_get("filter") 571 if widget_filter: 572 resource.add_filter(widget_filter) 573 574 # Use the widget-index to create a unique ID 575 list_id = "profile-list-%s-%s" % (tablename, widget["index"]) 576 577 # Default ORDERBY 578 # - first field actually in this table 579 def default_orderby(): 580 for f in list_fields: 581 selector = f[1] if isinstance(f, tuple) else f 582 if selector == "id": 583 continue 584 rfield = resource.resolve_selector(selector) 585 if rfield.field: 586 return rfield.field 587 return None
588 589 # Pagination 590 representation = r.representation 591 get_vars = self.request.get_vars 592 if representation == "aadata": 593 start = get_vars.get("displayStart", None) 594 limit = get_vars.get("pageLength", 0) 595 else: 596 start = get_vars.get("start", None) 597 limit = get_vars.get("limit", 0) 598 if limit: 599 if limit.lower() == "none": 600 limit = None 601 else: 602 try: 603 start = int(start) 604 limit = int(limit) 605 except (ValueError, TypeError): 606 start = None 607 limit = 0 # use default 608 else: 609 # Use defaults 610 start = None 611 612 dtargs = attr.get("dtargs", {}) 613 614 if r.interactive: 615 s3 = current.response.s3 616 617 # How many records per page? 618 if s3.dataTable_pageLength: 619 display_length = s3.dataTable_pageLength 620 else: 621 display_length = widget.get("pagesize", 10) 622 dtargs["dt_lengthMenu"] = [[10, 25, 50, -1], 623 [10, 25, 50, s3_str(current.T("All"))] 624 ] 625 626 # ORDERBY fallbacks: widget->resource->default 627 orderby = widget_get("orderby") 628 if not orderby: 629 orderby = resource.get_config("orderby") 630 if not orderby: 631 orderby = default_orderby() 632 633 # Server-side pagination? 634 if not s3.no_sspag: 635 dt_pagination = "true" 636 if not limit and display_length is not None: 637 limit = 2 * display_length 638 else: 639 limit = None 640 else: 641 dt_pagination = "false" 642 643 # Get the data table 644 dt, totalrows = resource.datatable(fields=list_fields, 645 start=start, 646 limit=limit, 647 orderby=orderby, 648 ) 649 displayrows = totalrows 650 651 if dt.empty: 652 empty_str = self.crud_string(tablename, 653 "msg_list_empty") 654 else: 655 empty_str = self.crud_string(tablename, 656 "msg_no_match") 657 empty = DIV(empty_str, _class="empty") 658 659 dtargs["dt_pagination"] = dt_pagination 660 dtargs["dt_pageLength"] = display_length 661 # @todo: fix base URL (make configurable?) to fix export options 662 s3.no_formats = True 663 dtargs["dt_base_url"] = r.url(method="", vars={}) 664 get_vars.update(update = widget["index"]) 665 dtargs["dt_ajax_url"] = r.url(vars=get_vars, 666 representation="aadata") 667 actions = widget_get("actions") 668 if callable(actions): 669 actions = actions(r, list_id) 670 if actions: 671 dtargs["dt_row_actions"] = actions 672 673 datatable = dt.html(totalrows, 674 displayrows, 675 id=list_id, 676 **dtargs) 677 678 if dt.data: 679 empty.update(_style="display:none") 680 else: 681 datatable.update(_style="display:none") 682 contents = DIV(datatable, empty, _class="dt-contents") 683 684 # Link for create-popup 685 create_popup = self._create_popup(r, 686 widget, 687 list_id, 688 resource, 689 context, 690 totalrows) 691 692 # Card holder label and icon 693 label = widget_get("label", "") 694 # Activate if-required 695 #if label and isinstance(label, basestring): 696 if label: 697 label = current.T(label) 698 else: 699 label = self.crud_string(tablename, "title_list") 700 icon = widget_get("icon", "") 701 if icon: 702 icon = ICON(icon) 703 704 _class = self._lookup_class(r, widget) 705 706 # Render the widget 707 output = DIV(create_popup, 708 H4(icon, label, 709 _class="profile-sub-header"), 710 DIV(contents, 711 _class="card-holder"), 712 _class=_class, 713 ) 714 715 return output 716 717 elif representation == "aadata": 718 719 # Parse datatable filter/sort query 720 searchq, orderby, left = resource.datatable_filter(list_fields, 721 get_vars) 722 723 # ORDERBY fallbacks - datatable->widget->resource->default 724 if not orderby: 725 orderby = widget_get("orderby") 726 if not orderby: 727 orderby = resource.get_config("orderby") 728 if not orderby: 729 orderby = default_orderby() 730 731 # DataTable filtering 732 if searchq is not None: 733 totalrows = resource.count() 734 resource.add_filter(searchq) 735 else: 736 totalrows = None 737 738 # Get the data table 739 if totalrows != 0: 740 dt, displayrows = resource.datatable(fields = list_fields, 741 start = start, 742 limit = limit, 743 left = left, 744 orderby = orderby, 745 ) 746 else: 747 dt, displayrows = None, 0 748 749 if totalrows is None: 750 totalrows = displayrows 751 752 # Echo 753 draw = int(get_vars.get("draw") or 0) 754 755 # Representation 756 if dt is not None: 757 data = dt.json(totalrows, 758 displayrows, 759 list_id, 760 draw, 761 **dtargs) 762 else: 763 data = '{"recordsTotal":%s,' \ 764 '"recordsFiltered":0,' \ 765 '"dataTable_id":"%s",' \ 766 '"draw":%s,' \ 767 '"data":[]}' % (totalrows, list_id, draw) 768 769 return data 770 771 else: 772 # Really raise an exception here? 773 r.error(415, current.ERROR.BAD_FORMAT)
774 775 # -------------------------------------------------------------------------
776 - def _form(self, r, widget, **attr):
777 """ 778 Generate a Form widget 779 780 @param r: the S3Request instance 781 @param widget: the widget definition as dict 782 @param attr: controller attributes for the request 783 """ 784 785 widget_get = widget.get 786 787 label = widget_get("label", "") 788 # Activate if-required 789 #if label and isinstance(label, basestring): 790 if label: 791 label = current.T(label) 792 icon = widget_get("icon", "") 793 if icon: 794 icon = ICON(icon) 795 796 context = widget_get("context") 797 tablename = widget_get("tablename") 798 resource, context = self._resolve_context(r, tablename, context) 799 800 # Widget filter option 801 widget_filter = widget_get("filter") 802 if widget_filter: 803 resource.add_filter(widget_filter) 804 805 record = resource.select(["id"], limit=1, as_rows=True).first() 806 if record: 807 record_id = record.id 808 else: 809 record_id = None 810 811 if record_id: 812 readonly = not current.auth.s3_has_permission("update", tablename, record_id) 813 else: 814 readonly = not current.auth.s3_has_permission("create", tablename) 815 816 sqlform = widget.get("sqlform") 817 if not sqlform: 818 sqlform = resource.get_config("crud_form") 819 if not sqlform: 820 from s3forms import S3SQLDefaultForm 821 sqlform = S3SQLDefaultForm() 822 823 get_config = current.s3db.get_config 824 if record_id: 825 # Update form 826 onvalidation = get_config(tablename, "create_onvalidation") or \ 827 get_config(tablename, "onvalidation") 828 onaccept = get_config(tablename, "create_onaccept") or \ 829 get_config(tablename, "onaccept") 830 else: 831 # Create form 832 onvalidation = get_config(tablename, "create_onvalidation") or \ 833 get_config(tablename, "onvalidation") 834 onaccept = get_config(tablename, "create_onaccept") or \ 835 get_config(tablename, "onaccept") 836 837 form = sqlform(request = r, 838 resource = resource, 839 record_id = record_id, 840 readonly = readonly, 841 format = "html", 842 onvalidation = onvalidation, 843 onaccept = onaccept, 844 ) 845 _class = self._lookup_class(r, widget) 846 847 # Render the widget 848 output = DIV(H4(icon, 849 label, 850 _class="profile-sub-header"), 851 DIV(form, 852 _class="form-container thumbnail"), 853 _class=_class, 854 ) 855 856 return output
857 858 # -------------------------------------------------------------------------
859 - def _map(self, r, widget, widgets, **attr):
860 """ 861 Generate a Map widget 862 863 @param r: the S3Request instance 864 @param widget: the widget as a tuple: (label, type, icon) 865 @param attr: controller attributes for the request 866 """ 867 868 T = current.T 869 db = current.db 870 s3db = current.s3db 871 872 widget_get = widget.get 873 874 label = widget_get("label", "") 875 if label and isinstance(label, basestring): 876 label = T(label) 877 icon = widget_get("icon", "") 878 if icon: 879 icon = ICON(icon) 880 881 _class = self._lookup_class(r, widget) 882 883 context = widget_get("context", None) 884 # Map widgets have no separate tablename 885 tablename = r.tablename 886 resource, context = self._resolve_context(r, tablename, context) 887 if context: 888 cserialize_url = context.serialize_url 889 else: 890 cserialize_url = lambda res: {} 891 892 height = widget_get("height", 383) 893 width = widget_get("width", 568) # span6 * 99.7% 894 bbox = widget_get("bbox", {}) 895 896 # Default to showing all the resources in datalist widgets as separate layers 897 ftable = s3db.gis_layer_feature 898 mtable = s3db.gis_marker 899 feature_resources = [] 900 fappend = feature_resources.append 901 s3dbresource = s3db.resource 902 for widget in widgets: 903 if widget["type"] not in ("datalist", "datatable", "report"): 904 continue 905 show_on_map = widget_get("show_on_map", True) 906 if not show_on_map: 907 continue 908 # @ToDo: Check permission to access layer (both controller/function & also within Map Config) 909 tablename = widget["tablename"] 910 list_id = "profile-list-%s-%s" % (tablename, widget["index"]) 911 layer = {"name": T(widget["label"]), 912 "id": list_id, 913 "active": True, 914 } 915 filter = widget_get("filter", None) 916 marker = widget_get("marker", None) 917 if marker: 918 marker = db(mtable.name == marker).select(mtable.image, 919 mtable.height, 920 mtable.width, 921 limitby=(0, 1)).first() 922 layer_id = None 923 layer_name = widget_get("layer", None) 924 if layer_name: 925 row = db(ftable.name == layer_name).select(ftable.layer_id, 926 limitby=(0, 1)).first() 927 if row: 928 layer_id = row.layer_id 929 if layer_id: 930 layer["layer_id"] = layer_id 931 resource = s3dbresource(tablename) 932 filter_url = "" 933 first = True 934 if context: 935 filters = cserialize_url(resource) 936 for f in filters: 937 sep = "" if first else "&" 938 filter_url = "%s%s%s=%s" % (filter_url, sep, f, filters[f]) 939 first = False 940 if filter: 941 filters = filter.serialize_url(resource) 942 for f in filters: 943 sep = "" if first else "&" 944 filter_url = "%s%s%s=%s" % (filter_url, sep, f, filters[f]) 945 first = False 946 if filter_url: 947 layer["filter"] = filter_url 948 else: 949 layer["tablename"] = tablename 950 map_url = widget.get("map_url", None) 951 if not map_url: 952 # Build one 953 c, f = tablename.split("_", 1) 954 map_url = URL(c=c, f=f, extension="geojson") 955 resource = s3dbresource(tablename) 956 first = True 957 if context: 958 filters = cserialize_url(resource) 959 for f in filters: 960 sep = "?" if first else "&" 961 map_url = "%s%s%s=%s" % (map_url, sep, f, filters[f]) 962 first = False 963 if filter: 964 filters = filter.serialize_url(resource) 965 for f in filters: 966 sep = "?" if first else "&" 967 map_url = "%s%s%s=%s" % (map_url, sep, f, filters[f]) 968 first = False 969 layer["url"] = map_url 970 971 if marker: 972 layer["marker"] = marker 973 974 fappend(layer) 975 976 # Additional layers, e.g. for primary resource 977 profile_layers = s3db.get_config(r.tablename, "profile_layers") 978 if profile_layers: 979 for layer in profile_layers: 980 fappend(layer) 981 982 # Default viewport 983 lat = widget_get("lat", None) 984 lon = widget_get("lon", None) 985 986 map = current.gis.show_map(height=height, 987 lat=lat, 988 lon=lon, 989 width=width, 990 bbox=bbox, 991 collapsed=True, 992 feature_resources=feature_resources, 993 ) 994 995 # Button to go full-screen 996 fullscreen = A(ICON("fullscreen"), 997 _href=URL(c="gis", f="map_viewing_client"), 998 _class="gis_fullscreen_map-btn", 999 # If we need to support multiple maps on a page 1000 #_map="default", 1001 _title=T("View full screen"), 1002 ) 1003 s3 = current.response.s3 1004 if s3.debug: 1005 script = "/%s/static/scripts/S3/s3.gis.fullscreen.js" % current.request.application 1006 else: 1007 script = "/%s/static/scripts/S3/s3.gis.fullscreen.min.js" % current.request.application 1008 s3.scripts.append(script) 1009 1010 # Render the widget 1011 output = DIV(fullscreen, 1012 H4(icon, 1013 label, 1014 _class="profile-sub-header"), 1015 DIV(map, 1016 _class="card-holder"), 1017 _class=_class) 1018 1019 return output
1020 1021 # -------------------------------------------------------------------------
1022 - def _report(self, r, widget, **attr):
1023 """ 1024 Generate a Report widget 1025 1026 @param r: the S3Request instance 1027 @param widget: the widget as a tuple: (label, type, icon) 1028 @param attr: controller attributes for the request 1029 """ 1030 1031 widget_get = widget.get 1032 1033 # Parse context 1034 context = widget_get("context", None) 1035 tablename = widget_get("tablename", None) 1036 resource, context = self._resolve_context(r, tablename, context) 1037 1038 # Widget filter option 1039 widget_filter = widget_get("filter", None) 1040 if widget_filter: 1041 resource.add_filter(widget_filter) 1042 1043 # Use the widget-index to create a unique ID 1044 widget_id = "profile-report-%s-%s" % (tablename, widget["index"]) 1045 1046 # Define the Pivot Table 1047 report = S3Report() 1048 report.resource = resource 1049 ajaxurl = widget_get("ajaxurl", None) 1050 contents = report.widget(r, 1051 widget_id=widget_id, 1052 ajaxurl=ajaxurl, 1053 **attr) 1054 1055 # Card holder label and icon 1056 label = widget_get("label", "") 1057 if label and isinstance(label, basestring): 1058 label = current.T(label) 1059 icon = widget_get("icon", "") 1060 if icon: 1061 icon = ICON(icon) 1062 1063 _class = self._lookup_class(r, widget) 1064 1065 # Render the widget 1066 output = DIV(H4(icon, label, 1067 _class="profile-sub-header"), 1068 DIV(contents, 1069 _class="card-holder"), 1070 _class=_class) 1071 1072 return output
1073 1074 # ------------------------------------------------------------------------- 1075 @staticmethod
1076 - def _lookup_class(r, widget):
1077 """ 1078 Provide the column-width class for the widgets 1079 1080 @param r: the S3Request 1081 @param widget: the widget config (dict) 1082 """ 1083 1084 page_cols = current.s3db.get_config(r.tablename, "profile_cols") 1085 if not page_cols: 1086 page_cols = 2 1087 widget_cols = widget.get("colspan", 1) 1088 span = int(12 / page_cols) * widget_cols 1089 1090 formstyle = current.deployment_settings.ui.get("formstyle", "default") 1091 if current.deployment_settings.ui.get("formstyle") == "bootstrap": 1092 # Bootstrap 1093 return "profile-widget span%s" % span 1094 1095 # Default (=foundation) 1096 return "profile-widget medium-%s columns" % span
1097 1098 # ------------------------------------------------------------------------- 1099 @staticmethod
1100 - def _create_popup(r, widget, list_id, resource, context, numrows):
1101 """ 1102 Render an action link for a create-popup (used in data lists 1103 and data tables). 1104 1105 @param r: the S3Request instance 1106 @param widget: the widget definition as dict 1107 @param list_id: the list ID 1108 @param resource: the target resource 1109 @param context: the context filter 1110 @param numrows: the total number of rows in the list/table 1111 """ 1112 1113 create = "" 1114 1115 widget_get = widget.get 1116 1117 insert = widget_get("insert", True) 1118 if not insert: 1119 return create 1120 1121 table = resource.table 1122 tablename = resource.tablename 1123 1124 # Default to primary REST controller for the resource being added 1125 c, f = tablename.split("_", 1) 1126 create_controller = widget_get("create_controller") 1127 if create_controller: 1128 c = create_controller 1129 create_function = widget_get("create_function") 1130 if create_function: 1131 f = create_function 1132 1133 permit = current.auth.s3_has_permission 1134 create_ok = permit("create", table, c=c, f=f) 1135 if create_ok: 1136 if not create_controller or not create_function: 1137 # Assume not component context 1138 create_ok = permit("update", r.table, record_id=r.id, c=c, f=f) 1139 if create_ok: 1140 #if tablename = "org_organisation": 1141 # @ToDo: Special check for creating resources on Organisation profile 1142 1143 # URL-serialize the widget filter 1144 widget_filter = widget_get("filter") 1145 if widget_filter: 1146 url_vars = widget_filter.serialize_url(resource) 1147 else: 1148 url_vars = Storage() 1149 1150 # URL-serialize the context filter 1151 if context: 1152 filters = context.serialize_url(resource) 1153 for selector in filters: 1154 url_vars[selector] = filters[selector] 1155 1156 # URL-serialize the widget default 1157 default = widget_get("default") 1158 if default: 1159 k, v = default.split("=", 1) 1160 url_vars[k] = v 1161 1162 # URL-serialize the list ID (refresh-target of the popup) 1163 url_vars.refresh = list_id 1164 1165 # Indicate that popup comes from profile (and which) 1166 url_vars.profile = r.tablename 1167 1168 # CRUD string 1169 label_create = widget_get("label_create", None) 1170 # Activate if-required 1171 #if label_create and isinstance(label_create, basestring): 1172 if label_create: 1173 label_create = current.T(label_create) 1174 else: 1175 label_create = S3CRUD.crud_string(tablename, "label_create") 1176 1177 # Popup URL 1178 component = widget_get("create_component", None) 1179 if component: 1180 args = [r.id, component, "create.popup"] 1181 else: 1182 args = ["create.popup"] 1183 add_url = URL(c=c, f=f, args=args, vars=url_vars) 1184 1185 if callable(insert): 1186 # Custom widget 1187 create = insert(r, list_id, label_create, add_url) 1188 1189 elif current.deployment_settings.ui.formstyle == "bootstrap": 1190 # Bootstrap-style action icon 1191 create = A(ICON("plus-sign", _class="small-add"), 1192 _href=add_url, 1193 _class="s3_modal", 1194 _title=label_create, 1195 ) 1196 else: 1197 # Standard action button 1198 create = A(label_create, 1199 _href=add_url, 1200 _class="action-btn profile-add-btn s3_modal", 1201 ) 1202 1203 if widget_get("type") == "datalist": 1204 1205 # If this is a multiple=False widget and we already 1206 # have a record, we hide the create-button 1207 multiple = widget_get("multiple", True) 1208 if not multiple and hasattr(create, "update"): 1209 if numrows: 1210 create.update(_style="display:none") 1211 else: 1212 create.update(_style="display:block") 1213 # Script to hide/unhide the create-button on Ajax 1214 # list updates 1215 createid = create["_id"] 1216 if not createid: 1217 createid = "%s-add-button" % list_id 1218 create.update(_id=createid) 1219 script = \ 1220 '''$('#%(list_id)s').on('listUpdate',function(){ 1221 $('#%(createid)s').css({display:$(this).datalist('getTotalItems')?'none':'block'}) 1222 })''' % dict(list_id=list_id, createid=createid) 1223 current.response.s3.jquery_ready.append(script) 1224 1225 return create
1226 1227 # END ========================================================================= 1228