1
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
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
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
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
76 output = self.profile(r, **attr)
77 return output
78
79 elif r.representation not in ("dl", "aadata"):
80
81 redirect(r.url(method=""))
82
83 else:
84
85 r.error(404, current.ERROR.BAD_RECORD)
86 else:
87 r.error(405, current.ERROR.BAD_METHOD)
88
89
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
104 widgets = get_config(tablename, "profile_widgets")
105 if not widgets and not header:
106
107 if r.representation not in ("dl", "aadata"):
108
109 redirect(r.url(method="read"))
110 else:
111
112 r.error(405, current.ERROR.BAD_METHOD)
113
114
115 for index, widget in enumerate(widgets):
116 widget["index"] = index
117
118 if r.representation == "dl":
119
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
128
129 datalist = self._datalist(r, widgets[index], **attr)
130 output = {"item": datalist}
131
132 elif r.representation == "aadata":
133
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
142
143 datatable = self._datatable(r, widgets[index], **attr)
144 return datatable
145
146 else:
147
148
149
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
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
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
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
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
243 continue
244
245 if row is None:
246
247 row = DIV(_class="row profile")
248 row_cols = 0
249
250
251 row.append(w)
252 colspan = widget.get("colspan", 1)
253 row_cols += colspan
254 if row_cols == cols:
255
256 append(row)
257 row = None
258
259 if row:
260
261 append(row)
262 output["rows"] = rows
263
264
265
266
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
294 s = "(location)$path"
295
296
297
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
302
303
304
305
306 else:
307
308 s = "(%s)" % context
309 query = (FS(s) == record_id)
310
311
312 resource = current.s3db.resource(tablename, filter=query)
313 r.customise_resource(tablename)
314 return resource, query
315
316
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
363
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
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
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
403
404
405
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
421 list_id = "profile-list-%s-%s" % (tablename, widget["index"])
422
423
424 pagesize = widget_get("pagesize", 4)
425 representation = r.representation
426 if representation == "dl":
427
428 get_vars = r.get_vars
429 record_id = get_vars.get("record", None)
430 if record_id is not None:
431
432 resource.add_filter(FS("id") == record_id)
433 start, limit = 0, 1
434 else:
435
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
448 start, limit = 0, pagesize
449
450
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
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
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
480 current.response.view = "plain.html"
481 return data
482
483
484 label = widget_get("label", "")
485
486
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
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
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
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
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
558 context = widget_get("context")
559 tablename = widget_get("tablename")
560 resource, context = self._resolve_context(r, tablename, context)
561
562
563 list_fields = widget_get("list_fields")
564 if not list_fields:
565
566
567 list_fields = resource.list_fields()
568
569
570 widget_filter = widget_get("filter")
571 if widget_filter:
572 resource.add_filter(widget_filter)
573
574
575 list_id = "profile-list-%s-%s" % (tablename, widget["index"])
576
577
578
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
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
608 else:
609
610 start = None
611
612 dtargs = attr.get("dtargs", {})
613
614 if r.interactive:
615 s3 = current.response.s3
616
617
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
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
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
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
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
685 create_popup = self._create_popup(r,
686 widget,
687 list_id,
688 resource,
689 context,
690 totalrows)
691
692
693 label = widget_get("label", "")
694
695
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
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
720 searchq, orderby, left = resource.datatable_filter(list_fields,
721 get_vars)
722
723
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
732 if searchq is not None:
733 totalrows = resource.count()
734 resource.add_filter(searchq)
735 else:
736 totalrows = None
737
738
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
753 draw = int(get_vars.get("draw") or 0)
754
755
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
773 r.error(415, current.ERROR.BAD_FORMAT)
774
775
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
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)
894 bbox = widget_get("bbox", {})
895
896
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
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
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
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
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
996 fullscreen = A(ICON("fullscreen"),
997 _href=URL(c="gis", f="map_viewing_client"),
998 _class="gis_fullscreen_map-btn",
999
1000
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
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
1034 context = widget_get("context", None)
1035 tablename = widget_get("tablename", None)
1036 resource, context = self._resolve_context(r, tablename, context)
1037
1038
1039 widget_filter = widget_get("filter", None)
1040 if widget_filter:
1041 resource.add_filter(widget_filter)
1042
1043
1044 widget_id = "profile-report-%s-%s" % (tablename, widget["index"])
1045
1046
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
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
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
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
1093 return "profile-widget span%s" % span
1094
1095
1096 return "profile-widget medium-%s columns" % span
1097
1098
1099 @staticmethod
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
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
1138 create_ok = permit("update", r.table, record_id=r.id, c=c, f=f)
1139 if create_ok:
1140
1141
1142
1143
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
1151 if context:
1152 filters = context.serialize_url(resource)
1153 for selector in filters:
1154 url_vars[selector] = filters[selector]
1155
1156
1157 default = widget_get("default")
1158 if default:
1159 k, v = default.split("=", 1)
1160 url_vars[k] = v
1161
1162
1163 url_vars.refresh = list_id
1164
1165
1166 url_vars.profile = r.tablename
1167
1168
1169 label_create = widget_get("label_create", None)
1170
1171
1172 if label_create:
1173 label_create = current.T(label_create)
1174 else:
1175 label_create = S3CRUD.crud_string(tablename, "label_create")
1176
1177
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
1187 create = insert(r, list_id, label_create, add_url)
1188
1189 elif current.deployment_settings.ui.formstyle == "bootstrap":
1190
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
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
1206
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
1214
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
1228