| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2
3 """ S3 Mobile Forms API
4
5 @copyright: 2016-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: integrate S3XForms API
30 """
31
32 __all__ = ("S3MobileFormList",
33 "S3MobileSchema",
34 "S3MobileForm",
35 "S3MobileCRUD",
36 )
37
38 import json
39
40 from gluon import *
41 from s3datetime import s3_parse_datetime
42 from s3forms import S3SQLForm, S3SQLCustomForm, S3SQLDummyField, S3SQLField
43 from s3rest import S3Method
44 from s3utils import s3_get_foreign_key, s3_str
45 from s3validators import SEPARATORS
46
47 DEFAULT = lambda: None
48
49 # JSON-serializable table settings (SERIALIZABLE_OPTS)
50 # which require preprocessing before they can be passed
51 # to the mobile client (e.g. i18n)
52 #PREPROCESS_OPTS = ("subheadings", )
53 PREPROCESS_OPTS = []
54
55 # =============================================================================
56 -class S3MobileFormList(object):
57 """
58 Form List Generator
59 """
60
62 """
63 Constructor
64 """
65
66 T = current.T
67 s3db = current.s3db
68 settings = current.deployment_settings
69
70 formlist = []
71 formdict = {}
72
73 forms = settings.get_mobile_forms()
74 if forms:
75 keys = set()
76 for item in forms:
77
78 # Parse the configuration
79 options = {}
80 if isinstance(item, (tuple, list)):
81 if len(item) == 2:
82 title, tablename = item
83 if isinstance(tablename, dict):
84 tablename, options = title, tablename
85 title = None
86 elif len(item) == 3:
87 title, tablename, options = item
88 else:
89 continue
90 else:
91 title, tablename = None, item
92
93 # Make sure table exists
94 table = s3db.table(tablename)
95 if not table:
96 current.log.warning("Mobile forms: non-existent resource %s" % tablename)
97 continue
98
99 # Determine controller and function
100 c, f = tablename.split("_", 1)
101 c = options.get("c") or c
102 f = options.get("f") or f
103
104 # Only expose if target module is enabled
105 if not settings.has_module(c):
106 continue
107
108 # Determine the form name
109 name = options.get("name")
110 if not name:
111 name = "%s_%s" % (c, f)
112
113 # Stringify URL query vars
114 url_vars = options.get("vars")
115 if url_vars:
116 items = []
117 for k in url_vars:
118 v = s3_str(url_vars[k])
119 url_vars[k] = v
120 items.append("%s=%s" % (k, v))
121 query = "&".join(sorted(items))
122 else:
123 query = ""
124
125 # Deduplicate by target URL
126 key = (c, f, query)
127 if key in keys:
128 continue
129 keys.add(key)
130
131 # Determine form title
132 if title is None:
133 title = " ".join(w.capitalize() for w in f.split("_"))
134 if isinstance(title, basestring):
135 title = T(title)
136
137 # Provides (master-)data for download?
138 data = True if options.get("data") else False
139
140 # Exposed for data entry (or just for reference)?
141 main = False if options.get("data_only", False) else True
142
143 # Append to form list
144 url = {"c": c, "f": f}
145 if url_vars:
146 url["v"] = url_vars
147 mform = {"n": name,
148 "l": s3_str(title),
149 "t": tablename,
150 "r": url,
151 "d": data,
152 "m": main,
153 }
154 formlist.append(mform)
155 formdict[name] = mform
156
157 dynamic_tables = settings.get_mobile_dynamic_tables()
158 if dynamic_tables:
159
160 # Select all dynamic tables which have mobile_form=True
161 ttable = s3db.s3_table
162 query = (ttable.mobile_form == True) & \
163 (ttable.deleted != True)
164 rows = current.db(query).select(ttable.name,
165 ttable.title,
166 ttable.mobile_data,
167 )
168 for row in rows:
169
170 tablename = row.name
171 suffix = tablename.split("_", 1)[-1]
172
173 # Form title
174 title = row.title
175 if not title:
176 title = " ".join(s.capitalize() for s in suffix.split("_"))
177
178 # URL
179 # @todo: make c+f configurable?
180 url = {"c": "default",
181 "f": "table/%s" % suffix,
182 }
183
184 # Append to form list
185 mform = {"n": tablename,
186 "l": title,
187 "t": tablename,
188 "r": url,
189 "d": row.mobile_data,
190 }
191 formlist.append(mform)
192 formdict[name] = mform
193
194 self.formlist = formlist
195 self.forms = formdict
196
197 # -------------------------------------------------------------------------
199 """
200 Serialize the form list as JSON (EdenMobile)
201
202 @returns: a JSON string
203 """
204
205 return json.dumps(self.formlist, separators=SEPARATORS)
206
207 # =============================================================================
208 -class S3MobileSchema(object):
209 """
210 Table schema for a mobile resource
211 """
212
213 # Field types supported for mobile resources
214 SUPPORTED_FIELD_TYPES = ("string",
215 "text",
216 "integer",
217 "double",
218 "date",
219 "datetime",
220 "boolean",
221 "reference",
222 "upload",
223 )
224
225 # -------------------------------------------------------------------------
227 """
228 Constructor
229
230 @param resource - the S3Resource
231 """
232
233 self.resource = resource
234
235 # Initialize reference map
236 self._references = {}
237
238 # Initialize the schema
239 self._schema = None
240
241 # Initialize the form description
242 self._form = None
243
244 # Initialize subheadings
245 self._subheadings = DEFAULT
246
247 # Initialize settings
248 self._settings = None
249
250 # Initialize lookup list attributes
251 self._lookup_only = None
252 self._llrepr = None
253
254 # -------------------------------------------------------------------------
256 """
257 Serialize the table schema
258
259 @return: a JSON-serializable dict containing the table schema
260 """
261
262 schema = self._schema
263 if schema is None:
264
265 # Initialize
266 schema = {}
267 self._references = {}
268
269 if not self.lookup_only:
270 # Introspect and build schema
271 fields = self.fields()
272 for field in fields:
273 description = self.describe(field)
274 if description:
275 schema[field.name] = description
276
277 # Store schema
278 self._schema = schema
279
280 return schema
281
282 # -------------------------------------------------------------------------
283 @property
285 """
286 Tables (and records) referenced in this schema (lazy property)
287
288 @return: a dict {tablename: [recordID, ...]} of all
289 referenced tables and records
290 """
291
292 if self._references is None:
293 # Trigger introspection to gather all references
294 self.serialize()
295
296 return self._references
297
298 # -------------------------------------------------------------------------
299 @property
301 """
302 The mobile form (field order) for the resource (lazy property)
303 """
304
305 if self._form is None:
306 self.serialize()
307
308 return self._form
309
310 # -------------------------------------------------------------------------
311 @property
313 """
314 The subheadings for the mobile form (lazy property)
315 """
316
317 subheadings = self._subheadings
318
319 if subheadings is DEFAULT:
320 subheadings = self._subheadings = self.resource.get_config("subheadings")
321
322 return subheadings
323
324 # -------------------------------------------------------------------------
325 @property
327 """
328 Directly-serializable settings from s3db.configure (lazy property)
329 """
330
331 settings = self._settings
332
333 if settings is None:
334
335 settings = self._settings = {}
336 resource = self.resource
337
338 from s3model import SERIALIZABLE_OPTS
339 for key in SERIALIZABLE_OPTS:
340 if key not in PREPROCESS_OPTS:
341 setting = resource.get_config(key, DEFAULT)
342 if setting is not DEFAULT:
343 settings[key] = setting
344
345 return settings
346
347 # -------------------------------------------------------------------------
348 # Introspection methods
349 # -------------------------------------------------------------------------
351 """
352 Construct a field description for the schema
353
354 @param field: a Field instance
355
356 @return: the field description as JSON-serializable dict
357 """
358
359 fieldtype = str(field.type)
360 SUPPORTED_FIELD_TYPES = set(self.SUPPORTED_FIELD_TYPES)
361
362 # Check if foreign key
363 superkey = False
364 reftype = None
365 if fieldtype[:9] == "reference":
366
367 s3db = current.s3db
368
369 is_foreign_key = True
370
371 # Get referenced table/field name
372 ktablename, key = s3_get_foreign_key(field)[:2]
373
374 # Get referenced table
375 ktable = current.s3db.table(ktablename)
376 if not ktable:
377 return None
378
379 if "instance_type" in ktable.fields:
380 # Super-key
381
382 tablename = str(field).split(".", 1)[0]
383 supertables = s3db.get_config(tablename, "super_entity")
384 if not supertables:
385 supertables = set()
386 elif not isinstance(supertables, (list, tuple)):
387 supertables = [supertables]
388
389 if ktablename in supertables and key == ktable._id.name:
390 # This is the super-id of the instance table => skip
391 return None
392 else:
393 # This is a super-entity reference
394 fieldtype = "objectkey"
395
396 # @todo: add instance types if limited in validator
397 superkey = True
398 reftype = (ktablename,) # [])
399 else:
400 # Regular foreign key
401
402 # Store schema reference
403 references = self._references
404 if ktablename not in references:
405 references[ktablename] = set()
406 else:
407 is_foreign_key = False
408 ktablename = None
409
410 # Check that field type is supported
411 if fieldtype in SUPPORTED_FIELD_TYPES or is_foreign_key:
412 supported = True
413 else:
414 supported = False
415 if not supported:
416 return None
417
418 # Create a field description
419 description = {"type": fieldtype,
420 "label": str(field.label),
421 }
422
423 # Add type for super-entity references (=object keys)
424 if reftype:
425 description["reftype"] = reftype
426
427 # Add field options to description
428 options = self.get_options(field, lookup=ktablename)
429 if options:
430 description["options"] = options
431
432 # Add default value to description
433 default = self.get_default(field, lookup=ktablename, superkey=superkey)
434 if default:
435 description["default"] = default
436
437 # Add readable/writable settings if False (True is assumed)
438 if not field.readable:
439 description["readable"] = False
440 if not field.writable:
441 description["writable"] = False
442
443 if hasattr(field.widget, "mobile"):
444 description["widget"] = field.widget.mobile
445
446 # Add required flag if True (False is assumed)
447 if self.is_required(field):
448 description["required"] = True
449
450 # @todo: add tooltip to description
451
452 # @todo: if field.represent is a base-class S3Represent
453 # (i.e. no custom lookup, no custom represent),
454 # and its field list is not just "name" => pass
455 # that field list as description["represent"]
456
457 # Add field's mobile settings to description (Dynamic Fields)
458 msettings = hasattr(field, "s3_settings") and \
459 field.s3_settings and \
460 field.s3_settings.get("mobile")
461 if msettings:
462 description["settings"] = msettings
463
464 return description
465
466 # -------------------------------------------------------------------------
467 @staticmethod
469 """
470 Determine whether a value is required for a field
471
472 @param field: the Field
473
474 @return: True|False
475 """
476
477 required = field.notnull
478
479 if not required and field.requires:
480 error = field.validate("")[1]
481 if error is not None:
482 required = True
483
484 return required
485
486 # -------------------------------------------------------------------------
488 """
489 Get the options for a field with IS_IN_SET
490
491 @param field: the Field
492 @param lookup: the name of the lookup table
493
494 @return: a list of tuples (key, label) with the field options
495 """
496
497 requires = field.requires
498 if not requires:
499 return None
500 if isinstance(requires, (list, tuple)):
501 requires = requires[0]
502 if isinstance(requires, IS_EMPTY_OR):
503 requires = requires.other
504
505 fieldtype = str(field.type)
506 if fieldtype[:9] == "reference":
507
508 # Foreign keys have no fixed options
509 # => must expose the lookup table with data=True in order
510 # to share current field options with the mobile client;
511 # this is better done explicitly in order to run the
512 # data download through the lookup table's controller
513 # for proper authorization, customise_* and filtering
514 return None
515
516 elif fieldtype in ("string", "integer"):
517
518 # Check for IS_IN_SET, and extract the options
519 if isinstance(requires, IS_IN_SET):
520 options = []
521 for value, label in requires.options():
522 if value is not None:
523 options.append((value, s3_str(label)))
524 return options
525 else:
526 return None
527
528 else:
529 # @todo: add other types (may require special option key encoding)
530 return None
531
532 # -------------------------------------------------------------------------
534 """
535 Get the default value for a field
536
537 @param field: the Field
538 @param lookup: the name of the lookup table
539 @param superkey: lookup table is a super-entity
540
541 @returns: the default value for the field
542 """
543
544 default = field.default
545
546 if default is not None:
547
548 fieldtype = str(field.type)
549
550 if fieldtype[:9] == "reference":
551
552 # Look up the UUID for the default
553 uuid = self.get_uuid(lookup, default)
554 if uuid:
555
556 if superkey:
557 # Get the instance record ID
558 prefix, name, record_id = current.s3db.get_instance(lookup, default)
559 if record_id:
560 tablename = "%s_%s" % (prefix, name)
561 else:
562 record_id = default
563 tablename = lookup
564
565 if record_id:
566
567 # Export the default lookup record as dependency
568 # (make sure the corresponding table schema is exported)
569 references = self.references.get(tablename) or set()
570 references.add(record_id)
571 self.references[tablename] = references
572
573 # Resolve as UUID
574 default = uuid
575
576 else:
577 default = None
578
579 else:
580 default = None
581
582 elif fieldtype in ("date", "datetime", "time"):
583
584 # @todo: implement this
585 # => typically using a dynamic default (e.g. "now"), which
586 # will need special encoding and handling on the mobile
587 # side
588 # => static defaults must be encoded in ISO-Format
589 default = None
590
591 else:
592
593 # Use field default as-is
594 default = field.default
595
596 return default
597
598 # -------------------------------------------------------------------------
600 """
601 Determine which fields need to be included in the schema
602
603 @returns: a list of Field instances
604 """
605
606 resource = self.resource
607 resolve_selector = resource.resolve_selector
608
609 tablename = resource.tablename
610
611 fields = []
612 mobile_form = self._form = []
613 mappend = mobile_form.append
614
615 # Prevent duplicates
616 fnames = set()
617 include = fnames.add
618
619 form = self.mobile_form(resource)
620 for element in form.elements:
621
622 if isinstance(element, S3SQLField):
623 rfield = resolve_selector(element.selector)
624
625 fname = rfield.fname
626
627 if rfield.tname == tablename and fname not in fnames:
628 fields.append(rfield.field)
629 mappend(fname)
630 include(fname)
631
632 elif isinstance(element, S3SQLDummyField):
633 field = {"type": "dummy",
634 "name": element.selector,
635 }
636 mappend(field)
637
638 if resource.parent and not resource.linktable:
639
640 # Include the parent key
641 fkey = resource.fkey
642 if fkey not in fnames:
643 fields.append(resource.table[fkey])
644 #include(fkey)
645
646 return fields
647
648 # -------------------------------------------------------------------------
649 @staticmethod
651 """
652 Check whether a table exposes a mobile form
653
654 @param tablename: the table name
655
656 @return: True|False
657 """
658
659 from s3model import DYNAMIC_PREFIX
660 if tablename.startswith(DYNAMIC_PREFIX):
661
662 ttable = current.s3db.s3_table
663 query = (ttable.name == tablename) & \
664 (ttable.mobile_form == True) & \
665 (ttable.deleted != True)
666 row = current.db(query).select(ttable.id,
667 limitby = (0, 1),
668 ).first()
669 if row:
670 return True
671 else:
672
673 forms = current.deployment_settings.get_mobile_forms()
674 for spec in forms:
675 if isinstance(spec, (tuple, list)):
676 if len(spec) > 1 and not isinstance(spec[1], dict):
677 tn = spec[1]
678 else:
679 tn = spec[0]
680 else:
681 tn = spec
682 if tn == tablename:
683 return True
684
685 return False
686
687 # -------------------------------------------------------------------------
688 @staticmethod
690 """
691 Get the mobile form for a resource
692
693 @param resource: the S3Resource
694 @returns: an S3SQLForm instance
695 """
696
697 # Get the form definition from "mobile_form" table setting
698 form = resource.get_config("mobile_form")
699 if not form or not isinstance(form, S3SQLCustomForm):
700 # Fallback
701 form = resource.get_config("crud_form")
702
703 if not form:
704 # No mobile form configured, or is a S3SQLDefaultForm
705 # => construct a custom form that includes all readable fields
706 readable_fields = resource.readable_fields()
707 fields = [field.name for field in readable_fields
708 if field.type != "id"]
709 form = S3SQLCustomForm(*fields)
710
711 return form
712
713 # -------------------------------------------------------------------------
714 @property
716 """
717 Whether the resource shall be exposed as mere lookup list
718 without mobile form (lazy property)
719 """
720
721 lookup_only = self._lookup_only
722 if lookup_only is None:
723
724 resource = self.resource
725
726 mform = resource.get_config("mobile_form")
727 if mform is False:
728 from s3fields import S3Represent
729 self._llrepr = S3Represent(lookup=resource.tablename)
730 lookup_only = True
731 elif callable(mform) and not isinstance(mform, S3SQLForm):
732 self._llrepr = mform
733 lookup_only = True
734 else:
735 lookup_only = False
736
737 self._lookup_only = lookup_only
738
739 return lookup_only
740
741 # -------------------------------------------------------------------------
742 @property
744 """
745 The lookup list representation method for the resource
746 """
747
748 return self._llrepr if self.lookup_only else None
749
750 # -------------------------------------------------------------------------
751 # Utility functions
752 # -------------------------------------------------------------------------
753 @staticmethod
755 """
756 Look up the UUID of a record
757
758 @param tablename: the table name
759 @param record_id: the record ID
760
761 @return: the UUID of the specified record, or None if
762 the record does not exist or has no UUID
763 """
764
765 table = current.s3db.table(tablename)
766 if not table or "uuid" not in table.fields:
767 return None
768
769 query = (table._id == record_id)
770 if "deleted" in table.fields:
771 query &= (table.deleted == False)
772
773 row = current.db(query).select(table.uuid,
774 limitby = (0, 1),
775 ).first()
776
777 return row.uuid or None if row else None
778
779 # =============================================================================
780 -class S3MobileForm(object):
781 """
782 Mobile representation of an S3SQLForm
783 """
784
786 """
787 Constructor
788
789 @param resource: the S3Resource
790 @param form: an S3SQLForm instance to override settings
791 """
792
793 self.resource = resource
794 self._form = form
795
796 self._config = DEFAULT
797
798 # -------------------------------------------------------------------------
799 @property
801 """
802 The mobile form configuration (lazy property)
803
804 @returns: a dict {tablename, title, options}
805 """
806
807 config = self._config
808 if config is DEFAULT:
809
810 tablename = self.resource.tablename
811 config = {"tablename": tablename,
812 "title": None,
813 "options": {},
814 }
815
816 forms = current.deployment_settings.get_mobile_forms()
817 if forms:
818 for form in forms:
819 options = None
820 if isinstance(form, (tuple, list)):
821 if len(form) == 2:
822 title, tablename_ = form
823 if isinstance(tablename_, dict):
824 tablename_, options = title, tablename_
825 title = None
826 elif len(form) == 3:
827 title, tablename_, options = form
828 else:
829 # Invalid => skip
830 continue
831 else:
832 title, tablename_ = None, form
833
834 if tablename_ == tablename:
835 config["title"] = title
836 if options:
837 config["options"] = options
838 break
839
840 self._config = config
841
842 return config
843
844 # -------------------------------------------------------------------------
846 """
847 Serialize the mobile form configuration for the target resource
848
849 @param msince: include look-up records only if modified
850 after this datetime ("modified since")
851
852 @return: a JSON-serialiable dict containing the mobile form
853 configuration for export to the mobile client
854 """
855
856 s3db = current.s3db
857 resource = self.resource
858 tablename = resource.tablename
859
860 super_entities = self.super_entities
861
862 ms = S3MobileSchema(resource)
863 schema = ms.serialize()
864
865 main = {"tablename": tablename,
866 "schema": schema,
867 "types": super_entities(tablename),
868 "form": ms.form,
869 }
870
871 # Add CRUD strings
872 strings = self.strings()
873 if strings:
874 main["strings"] = strings
875
876 # Add subheadings
877 subheadings = ms.subheadings
878 if subheadings:
879 main["subheadings"] = subheadings
880
881 # Add directly-serializable settings
882 settings = ms.settings
883 if settings:
884 main["settings"] = settings
885
886 # Required and provided schemas
887 required = set(ms.references.keys())
888 provided = {resource.tablename: (ms, main)}
889
890 # Add schemas for components
891 components = self.components()
892 for alias in components:
893
894 # Get the component resource
895 cresource = resource.components.get(alias)
896 if not cresource:
897 continue
898 ctablename = cresource.tablename
899
900 # Serialize the table schema
901 schema = S3MobileSchema(cresource)
902
903 # Add schema, super entities and directly-serializable settings
904 spec = components[alias]
905 spec["schema"] = schema.serialize()
906 spec["types"] = super_entities(ctablename)
907 settings = schema.settings
908 if settings:
909 spec["settings"] = settings
910
911 # If the component has a link table, add it to required schemas
912 link = spec.get("link")
913 if link:
914 required.add(link)
915
916 # Add component reference schemas
917 for tname in schema.references:
918 required.add(tname)
919
920 # Mark as provided
921 provided[tablename] = (schema, spec)
922
923 # Add schemas for referenced tables
924 references = {}
925 required = list(required)
926 while required:
927
928 # Get the referenced resource
929 ktablename = required.pop()
930 if ktablename in provided:
931 # Already provided
932 continue
933 kresource = s3db.resource(ktablename)
934
935 # Serialize the table schema
936 schema = S3MobileSchema(kresource)
937
938 # Add schema, super entities and directly-serializable settings
939 spec = {"schema": schema.serialize(),
940 "types": super_entities(ktablename),
941 }
942 settings = schema.settings
943 if settings:
944 spec["settings"] = settings
945
946 # Check for unresolved dependencies
947 for reference in schema.references:
948 if reference not in provided:
949 required.append(reference)
950
951 # Add to references
952 references[ktablename] = spec
953
954 # Mark as provided
955 provided[ktablename] = (schema, spec)
956
957 # Collect all required records (e.g. foreign key defaults)
958 required_records = {}
959 for ktablename in provided:
960 schema = provided[ktablename][0]
961 for tn, record_ids in schema.references.items():
962 if record_ids:
963 all_ids = (required_records.get(tn) or set()) | record_ids
964 required_records[tn] = all_ids
965
966 # Export required records and add them to the specs
967 for tn, record_ids in required_records.items():
968 kresource = s3db.resource(tn, id=list(record_ids))
969 spec = provided[tn][1]
970 fields = spec["schema"].keys()
971 tree = kresource.export_tree(fields = fields,
972 references = fields,
973 msince = msince,
974 )
975 if len(tree.getroot()):
976 data = current.xml.tree2json(tree, as_dict=True)
977 spec["data"] = data
978
979 # Complete the mobile schema spec
980 form = {"main": main,
981 }
982 if references:
983 form["references"] = references
984 if components:
985 form["components"] = components
986
987 return form
988
989 # -------------------------------------------------------------------------
991 """
992 Add CRUD strings for mobile form
993
994 @return: a dict with CRUD strings for the resource
995 """
996
997 tablename = self.resource.tablename
998
999 # Use the label/plural specified in deployment setting
1000 config = self.config
1001 options = config["options"]
1002 label = options.get("label")
1003 plural = options.get("plural")
1004
1005 # Fall back to CRUD title_list
1006 if not plural or not label:
1007 crud_strings = current.response.s3.crud_strings.get(tablename)
1008 if crud_strings:
1009 if not label:
1010 label = crud_strings.get("title_display")
1011 if not plural:
1012 plural = crud_strings.get("title_list")
1013
1014 # Fall back to the title specified in deployment setting
1015 if not plural:
1016 plural = config.get("title")
1017
1018 # Fall back to capitalized table name
1019 if not label:
1020 name = tablename.split("_", 1)[-1]
1021 label = " ".join(word.capitalize() for word in name.split("_"))
1022
1023 # Build strings-dict
1024 strings = {}
1025 if label:
1026 strings["label"] = s3_str(label)
1027 if plural:
1028 strings["plural"] = s3_str(plural)
1029
1030 return strings
1031
1032 # -------------------------------------------------------------------------
1034 """
1035 Add component declarations to the mobile form
1036
1037 @return: a dict with component declarations for the resource
1038 """
1039
1040 resource = self.resource
1041 tablename = resource.tablename
1042 pkey = resource._id.name
1043
1044 options = self.config.get("options")
1045
1046 aliases = set()
1047 components = {}
1048
1049 # Dynamic components, exposed if:
1050 # - "dynamic_components" is True for the master table, and
1051 # - "mobile_component" for the component key is not set to False
1052 dynamic_components = resource.get_config("dynamic_components")
1053 if dynamic_components:
1054
1055 # Dynamic components of this table and all its super-entities
1056 tablenames = [tablename]
1057 supertables = resource.get_config("super_entity")
1058 if supertables:
1059 if isinstance(supertables, (list, tuple)):
1060 tablenames.extend(supertables)
1061 elif supertables:
1062 tablenames.append(supertables)
1063
1064 # Look up corresponding component keys in s3_fields
1065 s3db = current.s3db
1066 ftable = s3db.s3_field
1067 ttable = s3db.s3_table
1068 join = ttable.on(ttable.id == ftable.table_id)
1069 query = (ftable.component_key == True) & \
1070 (ftable.master.belongs(tablenames)) & \
1071 (ftable.deleted == False)
1072 rows = current.db(query).select(ftable.name,
1073 ftable.component_alias,
1074 ftable.settings,
1075 ttable.name,
1076 join = join,
1077 )
1078
1079 for row in rows:
1080 component_key = row.s3_field
1081
1082 # Skip if mobile_component is set to False
1083 settings = component_key.settings
1084 if settings and settings.get("mobile_component") is False:
1085 continue
1086
1087 alias = component_key.component_alias
1088 if not alias:
1089 # Default component alias
1090 alias = row.s3_table.name.split("_", 1)[-1]
1091 aliases.add(alias)
1092
1093 # Static components, exposed if
1094 # - configured in "components" option of settings.mobile.forms
1095 static = options.get("components") if options else None
1096 if static:
1097 aliases |= set(static)
1098
1099 # Construct component descriptions for schema export
1100 if aliases:
1101 T = current.T
1102 hooks = current.s3db.get_components(tablename, names=aliases)
1103 for alias, hook in hooks.items():
1104
1105 description = {"table": hook.tablename,
1106 "multiple": hook.multiple,
1107 }
1108 if hook.label:
1109 description["label"] = s3_str(T(hook.label))
1110 if hook.plural:
1111 description["plural"] = s3_str(T(hook.plural))
1112
1113 if hook.pkey != pkey:
1114 description["pkey"] = hook.pkey
1115
1116 linktable = hook.linktable
1117 if linktable:
1118 description.update({"link": str(linktable),
1119 "joinby": hook.lkey,
1120 "key": hook.rkey,
1121 })
1122 if hook.fkey != "id":
1123 description["fkey"] = hook.fkey
1124 else:
1125 description["joinby"] = hook.fkey
1126
1127 components[alias] = description
1128
1129 return components
1130
1131 # -------------------------------------------------------------------------
1132 @staticmethod
1134 """
1135 Helper method to determine the super entities of a table
1136
1137 @param tablename: the table name
1138
1139 @return: a dict {super-table: super-key}
1140 """
1141
1142 s3db = current.s3db
1143
1144 supertables = s3db.get_config(tablename, "super_entity")
1145 if not supertables:
1146 supertables = set()
1147 elif not isinstance(supertables, (tuple, list)):
1148 supertables = [supertables]
1149
1150 super_entities = {}
1151 for tablename in supertables:
1152 table = s3db.table(tablename)
1153 if table:
1154 super_entities[tablename] = table._id.name
1155
1156 return super_entities
1157
1158 # =============================================================================
1159 -class S3MobileCRUD(S3Method):
1160 """
1161 Mobile Data Handler
1162
1163 responds to GET /prefix/name/mform.json (Schema download)
1164 """
1165
1166 # -------------------------------------------------------------------------
1168 """
1169 Entry point for REST interface.
1170
1171 @param r: the S3Request instance
1172 @param attr: controller attributes
1173 """
1174
1175 http = r.http
1176 method = r.method
1177 representation = r.representation
1178
1179 output = {}
1180
1181 if method == "mform":
1182 if representation == "json":
1183 if http == "GET":
1184 output = self.mform(r, **attr)
1185 else:
1186 r.error(405, current.ERROR.BAD_METHOD)
1187 else:
1188 r.error(415, current.ERROR.BAD_FORMAT)
1189 else:
1190 r.error(405, current.ERROR.BAD_METHOD)
1191
1192 return output
1193
1194 # -------------------------------------------------------------------------
1196 """
1197 Get the mobile form for the target resource
1198
1199 @param r: the S3Request instance
1200 @param attr: controller attributes
1201
1202 @returns: a JSON string
1203 """
1204
1205 resource = self.resource
1206
1207 msince = r.get_vars.get("msince")
1208 if msince:
1209 msince = s3_parse_datetime(msince)
1210
1211 # Get the mobile form
1212 mform = S3MobileForm(resource).serialize(msince=msince)
1213
1214 # Add controller and function for data exchange
1215 mform["controller"] = r.controller
1216 mform["function"] = r.function
1217
1218 # Convert to JSON
1219 output = json.dumps(mform, separators=SEPARATORS)
1220
1221 current.response.headers = {"Content-Type": "application/json"}
1222 return output
1223
1224 # END =========================================================================
1225
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Mar 15 08:51:58 2019 | http://epydoc.sourceforge.net |