Beispiel #1
0
    def json_message(success=True,
                     statuscode=None,
                     message=None,
                     **kwargs):
        """
            Provide a nicely-formatted JSON Message

            @param success: action succeeded or failed
            @param status_code: the HTTP status code
            @param message: the message text
            @param kwargs: other elements for the message

            @keyword tree: error tree to include as JSON object (rather
                           than as string) for easy decoding
        """

        if statuscode is None:
            statuscode = success and 200 or 404

        status = success and "success" or "failed"
        code = str(statuscode)

        output = {"status": status, "statuscode": str(code)}

        tree = kwargs.get("tree", None)
        if message:
            output["message"] = s3_str(message)
        for k, v in kwargs.items():
            if k != "tree":
                output[k] = v
        output = json.dumps(output)
        if message and tree:
            output = output[:-1] + ', "tree": %s}' % tree
        return output
Beispiel #2
0
    def strings(self):
        """
            Add CRUD strings for mobile form

            @return: a dict with CRUD strings for the resource
        """

        tablename = self.resource.tablename

        # Use the title specified in deployment setting
        config = self.config
        title = config.get("title")

        # Fall back to CRUD title_list
        if not title:
            crud_strings = current.response.s3.crud_strings.get(tablename)
            if crud_strings:
                title = crud_strings.get("title_list")

        # Fall back to capitalized table name
        if not title:
            name = tablename.split("_", 1)[-1]
            title = " ".join(word.capitalize() for word in name.split("_"))

        # Build strings-dict
        strings = {}
        if title:
            title = s3_str(title)
            strings["name"] = title
            strings["namePlural"] = title

        return strings
Beispiel #3
0
    def strings(self):
        """
            Add CRUD strings for mobile form

            @return: a dict with CRUD strings for the resource
        """

        tablename = self.resource.tablename

        # Use the title specified in deployment setting
        config = self.config
        title = config.get("title")

        # Fall back to CRUD title_list
        if not title:
            crud_strings = current.response.s3.crud_strings.get(tablename)
            if crud_strings:
                title = crud_strings.get("title_list")

        # Fall back to capitalized table name
        if not title:
            name = tablename.split("_", 1)[-1]
            title = " ".join(word.capitalize() for word in name.split("_"))

        # Build strings-dict
        strings = {}
        if title:
            title = s3_str(title)
            strings["name"] = title
            strings["namePlural"] = title

        return strings
Beispiel #4
0
    def subheadings_l10n(cls, setting):
        """
            Helper to translate form subheadings

            @param setting: the subheadings-setting (a dict)

            @return: the subheadings dict with translated headers
        """

        if setting is None:
            return None

        T = current.T
        output = {}

        for header, fields in setting.items():
            if isinstance(fields, dict):
                # Nested format => recurse
                subheadings = fields.get("subheadings")
                fields = {
                    "fields": fields.get("fields"),
                }
                if subheadings:
                    fields["subheadings"] = cls.subheadings_l10n(subheadings)
            output[s3_str(T(header))] = fields

        return output
Beispiel #5
0
    def selector(rules):
        """
            Generate the rule selector for anonymize-form

            @param rules: the list of configured rules

            @return: the selector (DIV)
        """

        T = current.T

        selector = DIV(_class="anonymize-select")

        for rule in rules:

            name = rule.get("name")
            if not name:
                continue

            title = T(rule.get("title", name))

            selector.append(DIV(INPUT(value = "on",
                                      _name = s3_str(name),
                                      _type = "checkbox",
                                      _class = "anonymize-rule",
                                      ),
                                LABEL(title),
                                _class = "anonymize-option",
                                ))

        return selector
Beispiel #6
0
    def subheadings_l10n(cls, setting):
        """
            Helper to translate form subheadings

            @param setting: the subheadings-setting (a dict)

            @return: the subheadings dict with translated headers
        """

        if setting is None:
            return None

        T = current.T
        output = {}

        for header, fields in setting.items():
            if isinstance(fields, dict):
                # Nested format => recurse
                subheadings = fields.get("subheadings")
                fields = {"fields": fields.get("fields"),
                          }
                if subheadings:
                    fields["subheadings"] = cls.subheadings_l10n(subheadings)
            output[s3_str(T(header))] = fields

        return output
Beispiel #7
0
    def get_options(self, field, lookup=None):
        """
            Get the options for a field with IS_IN_SET

            @param field: the Field
            @param lookup: the look-up table name (if field is a foreign key)

            @return: a list of tuples (key, label) with the field options
        """

        requires = field.requires
        if not requires:
            return None
        if isinstance(requires, (list, tuple)):
            requires = requires[0]
        if isinstance(requires, IS_EMPTY_OR):
            requires = requires.other

        fieldtype = str(field.type)
        if fieldtype[:9] == "reference":

            # For writable foreign keys, if the referenced table
            # does not expose a mobile form itself, look up all
            # valid options and report them as schema references:
            if field.writable and not self.has_mobile_form(lookup):
                add = self._references[lookup].add

                # @note: introspection only works with e.g. IS_ONE_OF,
                #        but not with widget-specific validators like
                #        IS_ADD_PERSON_WIDGET2 => should change these
                #        widgets to apply the conversion internally on
                #        the dummy input (like S3LocationSelector), and
                #        then have regular IS_ONE_OF's for the fields
                if hasattr(requires, "options"):
                    for value, label in requires.options():
                        if value:
                            add(long(value))

            # Foreign keys have no fixed options, however
            return None

        elif fieldtype in ("string", "integer"):

            # Check for IS_IN_SET, and extract the options
            if isinstance(requires, IS_IN_SET):
                options = []
                for value, label in requires.options():
                    if value is not None:
                        options.append((value, s3_str(label)))
                return options
            else:
                return None

        else:
            # @todo: add other types (may require special option key encoding)
            return None
Beispiel #8
0
    def get_options(self, field, lookup=None):
        """
            Get the options for a field with IS_IN_SET

            @param field: the Field
            @param lookup: the look-up table name (if field is a foreign key)

            @return: a list of tuples (key, label) with the field options
        """

        requires = field.requires
        if not requires:
            return None
        if isinstance(requires, (list, tuple)):
            requires = requires[0]
        if isinstance(requires, IS_EMPTY_OR):
            requires = requires.other

        fieldtype = str(field.type)
        if fieldtype[:9] == "reference":

            # For writable foreign keys, if the referenced table
            # does not expose a mobile form itself, look up all
            # valid options and report them as schema references:
            if field.writable and not self.has_mobile_form(lookup):
                add = self._references[lookup].add

                # @note: introspection only works with e.g. IS_ONE_OF,
                #        but not with widget-specific validators like
                #        IS_ADD_PERSON_WIDGET2 => should change these
                #        widgets to apply the conversion internally on
                #        the dummy input (like S3LocationSelector), and
                #        then have regular IS_ONE_OF's for the fields
                if hasattr(requires, "options"):
                    for value, label in requires.options():
                        if value:
                            add(long(value))

            # Foreign keys have no fixed options, however
            return None

        elif fieldtype in ("string", "integer"):

            # Check for IS_IN_SET, and extract the options
            if isinstance(requires, IS_IN_SET):
                options = []
                for value, label in requires.options():
                    if value is not None:
                        options.append((value, s3_str(label)))
                return options
            else:
                return None

        else:
            # @todo: add other types (may require special option key encoding)
            return None
Beispiel #9
0
    def strings(self):
        """
            Add CRUD strings for mobile form

            @return: a dict with CRUD strings for the resource
        """

        tablename = self.resource.tablename

        # Use the label/plural specified in deployment setting
        config = self.config
        options = config["options"]
        label = options.get("label")
        plural = options.get("plural")

        # Fall back to CRUD title_list
        if not plural or not label:
            crud_strings = current.response.s3.crud_strings.get(tablename)
            if crud_strings:
                if not label:
                    label = crud_strings.get("title_display")
                if not plural:
                    plural = crud_strings.get("title_list")

        # Fall back to the title specified in deployment setting
        if not plural:
            plural = config.get("title")

        # Fall back to capitalized table name
        if not label:
            name = tablename.split("_", 1)[-1]
            label = " ".join(word.capitalize() for word in name.split("_"))

        # Build strings-dict
        strings = {}
        if label:
            strings["label"] = s3_str(label)
        if plural:
            strings["plural"] = s3_str(plural)

        return strings
Beispiel #10
0
    def apply_method(self, r, **attr):
        """
            RESTful method handler

            @param r: the S3Request instance
            @param attr: controller attributes for the request
        """

        output = {}

        resource = r.resource
        if resource.tablename == self.TABLENAME:
            return resource.crud.select(r, **attr)

        elif resource.tablename == "sync_repository":
            # READ for sync log for this repository (currently not needed)
            pass

        else:
            if r.interactive:
                # READ for sync log for this resource
                here = "%s.%s" % (r.controller, r.function)
                sync_log = current.s3db[self.TABLENAME]
                sync_log.resource_name.readable = False
                query = (sync_log.resource_name == resource.tablename)
                r = r.factory(prefix="sync", name="log", args=[])
                s3 = current.response.s3
                s3.filter = query
                s3.prep = None
                s3.postp = None
                s3.actions = [
                    {
                        "label":
                        s3_str(current.T("Details")),
                        "_class":
                        "action-btn",
                        "url":
                        URL(
                            c="sync",
                            f="log",
                            args=["[id]"],
                            vars={"return": here},
                        )
                    },
                ]
                output = r(subtitle=None, rheader=self.rheader)
            else:
                r.error(415, current.ERROR.BAD_FORMAT)

        return output
Beispiel #11
0
    def get_options(self, field, lookup=None):
        """
            Get the options for a field with IS_IN_SET

            @param field: the Field
            @param lookup: the name of the lookup table

            @return: a list of tuples (key, label) with the field options
        """

        requires = field.requires
        if not requires:
            return None
        if isinstance(requires, (list, tuple)):
            requires = requires[0]
        if isinstance(requires, IS_EMPTY_OR):
            requires = requires.other

        fieldtype = str(field.type)
        if fieldtype[:9] == "reference":

            # Foreign keys have no fixed options
            # => must expose the lookup table with data=True in order
            #    to share current field options with the mobile client;
            #    this is better done explicitly in order to run the
            #    data download through the lookup table's controller
            #    for proper authorization, customise_* and filtering

            # @todo: deliver store uuid<=>label map instead, so that the
            #        mobile client has labels for fk options - unless the
            #        field has a base-class S3Represent with a field list
            #        that can be encoded in the field description
            return None

        elif fieldtype in ("string", "integer"):

            # Check for IS_IN_SET, and extract the options
            if isinstance(requires, IS_IN_SET):
                options = []
                for value, label in requires.options():
                    if value is not None:
                        options.append((value, s3_str(label)))
                return options
            else:
                return None

        else:
            # @todo: add other types (may require special option key encoding)
            return None
Beispiel #12
0
    def get_options(self, field, lookup=None):
        """
            Get the options for a field with IS_IN_SET

            @param field: the Field
            @param lookup: the name of the lookup table

            @return: a list of tuples (key, label) with the field options
        """

        requires = field.requires
        if not requires:
            return None
        if isinstance(requires, (list, tuple)):
            requires = requires[0]
        if isinstance(requires, IS_EMPTY_OR):
            requires = requires.other

        fieldtype = str(field.type)
        if fieldtype[:9] == "reference":

            # Foreign keys have no fixed options
            # => must expose the lookup table with data=True in order
            #    to share current field options with the mobile client;
            #    this is better done explicitly in order to run the
            #    data download through the lookup table's controller
            #    for proper authorization, customise_* and filtering

            # @todo: deliver store uuid<=>label map instead, so that the
            #        mobile client has labels for fk options - unless the
            #        field has a base-class S3Represent with a field list
            #        that can be encoded in the field description
            return None

        elif fieldtype in ("string", "integer"):

            # Check for IS_IN_SET, and extract the options
            if isinstance(requires, IS_IN_SET):
                options = []
                for value, label in requires.options():
                    if value is not None:
                        options.append((value, s3_str(label)))
                return options
            else:
                return None

        else:
            # @todo: add other types (may require special option key encoding)
            return None
Beispiel #13
0
    def apply_method(self, r, **attr):
        """
            RESTful method handler

            @param r: the S3Request instance
            @param attr: controller attributes for the request
        """

        output = {}

        resource = r.resource
        if resource.tablename == self.TABLENAME:
            return resource.crud.select(r, **attr)

        elif resource.tablename == "sync_repository":
            # READ for sync log for this repository (currently not needed)
            pass

        else:
            if r.interactive:
                # READ for sync log for this resource
                here = "%s.%s" % (r.controller, r.function)
                sync_log = current.s3db[self.TABLENAME]
                sync_log.resource_name.readable = False
                query = (sync_log.resource_name == resource.tablename)
                r = r.factory(prefix="sync", name="log", args=[])
                s3 = current.response.s3
                s3.filter = query
                s3.prep = None
                s3.postp = None
                s3.actions = [{"label": s3_str(current.T("Details")),
                               "_class": "action-btn",
                               "url": URL(c = "sync",
                                          f = "log",
                                          args = ["[id]"],
                                          vars = {"return":here},
                                          )
                               },
                              ]
                output = r(subtitle=None, rheader=self.rheader)
            else:
                r.error(415, current.ERROR.BAD_FORMAT)

        return output
Beispiel #14
0
    def add(self, name, obj):
        """
            Add an object to the archive

            @param name: the file name for the object inside the archive
            @param obj: the object to add (string or file-like object)

            @raises UserWarning: when adding a duplicate name (overwrites
                                 the existing object in the archive)
            @raises RuntimeError: if the archive is not writable, or
                                  no valid object name has been provided
            @raises TypeError: if the object is not a unicode, str or
                               file-like object
        """

        # Make sure the object name is an utf-8 encoded str
        if not name:
            raise RuntimeError("name is required")
        elif type(name) is not str:
            name = s3_str(name)

        # Make sure the archive is available
        archive = self.archive
        if not archive:
            raise RuntimeError("cannot add to closed archive")

        # Convert unicode objects to str
        if type(obj) is unicode:
            obj = obj.encode("utf-8")

        # Write the object
        if type(obj) is str:
            archive.writestr(name, obj)

        elif hasattr(obj, "read"):
            if hasattr(obj, "seek"):
                obj.seek(0)
            archive.writestr(name, obj.read())

        else:
            raise TypeError("invalid object type")
Beispiel #15
0
    def add(self, name, obj):
        """
            Add an object to the archive

            @param name: the file name for the object inside the archive
            @param obj: the object to add (string or file-like object)

            @raises UserWarning: when adding a duplicate name (overwrites
                                 the existing object in the archive)
            @raises RuntimeError: if the archive is not writable, or
                                  no valid object name has been provided
            @raises TypeError: if the object is not a unicode, str or
                               file-like object
        """

        # Make sure the object name is an utf-8 encoded str
        if not name:
            raise RuntimeError("name is required")
        elif type(name) is not str:
            name = s3_str(name)

        # Make sure the archive is available
        archive = self.archive
        if not archive:
            raise RuntimeError("cannot add to closed archive")

        # Convert unicode objects to str
        if type(obj) is unicode:
            obj = obj.encode("utf-8")

        # Write the object
        if type(obj) is str:
            archive.writestr(name, obj)

        elif hasattr(obj, "read"):
            if hasattr(obj, "seek"):
                obj.seek(0)
            archive.writestr(name, obj.read())

        else:
            raise TypeError("invalid object type")
Beispiel #16
0
    def inject_script(cls,
                      agent_id,
                      version=None,
                      widget_class="dashboardWidget",
                      options=None):
        """
            Helper method to inject the init script for a particular agent,
            usually called by widget() method.

            @param agent_id: the agent ID
            @param version: the config version key
            @param widget_class: the widget class to instantiate
            @param options: JSON-serializable dict of options to pass
                            to the widget instance
        """

        s3 = current.response.s3

        if not agent_id or not widget_class:
            return
        if not options:
            options = {}

        # Add the widget title (for the configuration popup)
        title = cls.title
        if title:
            options["title"] = s3_str(current.T(title))

        # Add the dashboard URL
        dashboard_url = URL(args=[], vars={})
        options["dashboardURL"] = dashboard_url

        # Add the config version key
        options["version"] = version

        script = """$("#%(agent_id)s").%(widget_class)s(%(options)s)""" % \
                    {"agent_id": agent_id,
                     "widget_class": widget_class,
                     "options": json.dumps(options),
                     }
        s3.jquery_ready.append(script)
Beispiel #17
0
    def inject_script(cls,
                      agent_id,
                      version=None,
                      widget_class="dashboardWidget",
                      options=None):
        """
            Helper method to inject the init script for a particular agent,
            usually called by widget() method.

            @param agent_id: the agent ID
            @param version: the config version key
            @param widget_class: the widget class to instantiate
            @param options: JSON-serializable dict of options to pass
                            to the widget instance
        """

        s3 = current.response.s3

        if not agent_id or not widget_class:
            return
        if not options:
            options = {}

        # Add the widget title (for the configuration popup)
        title = cls.title
        if title:
            options["title"] = s3_str(current.T(title))

        # Add the dashboard URL
        dashboard_url = URL(args=[], vars={})
        options["dashboardURL"] = dashboard_url

        # Add the config version key
        options["version"] = version

        script = """$("#%(agent_id)s").%(widget_class)s(%(options)s)""" % \
                    {"agent_id": agent_id,
                     "widget_class": widget_class,
                     "options": json.dumps(options),
                     }
        s3.jquery_ready.append(script)
Beispiel #18
0
    def describe(self, rfield):
        """
            Generate a description of a resource field (for mobile schemas)

            @param rfield: the S3ResourceField

            @returns: a JSON-serializable dict describing the field
        """

        field = rfield.field
        if not field:
            # Virtual field
            return None

        # Basic field description
        description = {
            "type": rfield.ftype,
            "label": s3_str(field.label),
        }

        # Field settings
        if field.notnull:
            description["notnull"] = True
        if not field.readable:
            description["readable"] = False
        if not field.writable:
            description["writable"] = False

        # @todo: options
        # @todo: minimum
        # @todo: maximum
        # @todo: default value
        # @todo: readable, writable
        # @todo: placeholder?
        # @todo: required

        return description
Beispiel #19
0
    def describe(self, rfield):
        """
            Generate a description of a resource field (for mobile schemas)

            @param rfield: the S3ResourceField

            @returns: a JSON-serializable dict describing the field
        """

        field = rfield.field
        if not field:
            # Virtual field
            return None

        # Basic field description
        description = {"type": rfield.ftype,
                       "label": s3_str(field.label),
                       }

        # Field settings
        if field.notnull:
            description["notnull"] = True
        if not field.readable:
            description["readable"] = False
        if not field.writable:
            description["writable"] = False

        # @todo: options
        # @todo: minimum
        # @todo: maximum
        # @todo: default value
        # @todo: readable, writable
        # @todo: placeholder?
        # @todo: required

        return description
Beispiel #20
0
    def __init__(self):
        """
            Constructor
        """

        T = current.T
        s3db = current.s3db
        settings = current.deployment_settings

        formlist = []
        formdict = {}

        forms = settings.get_mobile_forms()
        if forms:
            keys = set()
            for item in forms:

                # Parse the configuration
                options = {}
                if isinstance(item, (tuple, list)):
                    if len(item) == 2:
                        title, tablename = item
                        if isinstance(tablename, dict):
                            tablename, options = title, tablename
                            title = None
                    elif len(item) == 3:
                        title, tablename, options = item
                    else:
                        continue
                else:
                    title, tablename = None, item

                # Make sure table exists
                table = s3db.table(tablename)
                if not table:
                    current.log.warning(
                        "Mobile forms: non-existent resource %s" % tablename)
                    continue

                # Determine controller and function
                c, f = tablename.split("_", 1)
                c = options.get("c") or c
                f = options.get("f") or f

                # Only expose if target module is enabled
                if not settings.has_module(c):
                    continue

                # Determine the form name
                name = options.get("name")
                if not name:
                    name = "%s_%s" % (c, f)

                # Stringify URL query vars
                url_vars = options.get("vars")
                if url_vars:
                    items = []
                    for k in url_vars:
                        v = s3_str(url_vars[k])
                        url_vars[k] = v
                        items.append("%s=%s" % (k, v))
                    query = "&".join(sorted(items))
                else:
                    query = ""

                # Deduplicate by target URL
                key = (c, f, query)
                if key in keys:
                    continue
                keys.add(key)

                # Determine form title
                if title is None:
                    title = " ".join(w.capitalize() for w in f.split("_"))
                if isinstance(title, basestring):
                    title = T(title)

                # Provides (master-)data for download?
                data = True if options.get("data") else False

                # Append to form list
                url = {"c": c, "f": f}
                if url_vars:
                    url["v"] = url_vars
                mform = {
                    "n": name,
                    "l": s3_str(title),
                    "t": tablename,
                    "r": url,
                    "d": data,
                }
                formlist.append(mform)
                formdict[name] = mform

        dynamic_tables = settings.get_mobile_dynamic_tables()
        if dynamic_tables:

            # Select all dynamic tables which have mobile_form=True
            ttable = s3db.s3_table
            query = (ttable.mobile_form == True) & \
                    (ttable.deleted != True)
            rows = current.db(query).select(
                ttable.name,
                ttable.title,
            )
            for row in rows:

                tablename = row.name
                suffix = tablename.split("_", 1)[-1]

                # Form title
                title = row.title
                if not title:
                    title = " ".join(s.capitalize() for s in suffix.split("_"))

                # URL
                # @todo: make c+f configurable?
                url = {
                    "c": "default",
                    "f": "table/%s" % suffix,
                }

                # Append to form list
                mform = {
                    "n": tablename,
                    "l": title,
                    "t": tablename,
                    "r": url,
                }
                formlist.append(mform)
                formdict[name] = mform

        self.formlist = formlist
        self.forms = formdict
Beispiel #21
0
    def __init__(self):
        """
            Constructor
        """

        T = current.T
        s3db = current.s3db
        settings = current.deployment_settings

        formlist = []
        formdict = {}

        forms = settings.get_mobile_forms()
        if forms:
            keys = set()
            for item in forms:

                # Parse the configuration
                options = {}
                if isinstance(item, (tuple, list)):
                    if len(item) == 2:
                        title, tablename = item
                        if isinstance(tablename, dict):
                            tablename, options = title, tablename
                            title = None
                    elif len(item) == 3:
                        title, tablename, options = item
                    else:
                        continue
                else:
                    title, tablename = None, item

                # Make sure table exists
                table = s3db.table(tablename)
                if not table:
                    current.log.warning("Mobile forms: non-existent resource %s" % tablename)
                    continue

                # Determine controller and function
                c, f = tablename.split("_", 1)
                c = options.get("c") or c
                f = options.get("f") or f

                # Only expose if target module is enabled
                if not settings.has_module(c):
                    continue

                # Determine the form name
                name = options.get("name")
                if not name:
                    name = "%s_%s" % (c, f)

                # Stringify URL query vars
                url_vars = options.get("vars")
                if url_vars:
                    items = []
                    for k in url_vars:
                        v = s3_str(url_vars[k])
                        url_vars[k] = v
                        items.append("%s=%s" % (k, v))
                    query = "&".join(sorted(items))
                else:
                    query = ""

                # Deduplicate by target URL
                key = (c, f, query)
                if key in keys:
                    continue
                keys.add(key)

                # Determine form title
                if title is None:
                    title = " ".join(w.capitalize() for w in f.split("_"))
                if isinstance(title, basestring):
                    title = T(title)

                # Provides (master-)data for download?
                data = True if options.get("data") else False

                # Exposed for data entry (or just for reference)?
                main = False if options.get("data_only", False) else True

                # Append to form list
                url = {"c": c, "f": f}
                if url_vars:
                    url["v"] = url_vars
                mform = {"n": name,
                         "l": s3_str(title),
                         "t": tablename,
                         "r": url,
                         "d": data,
                         "m": main,
                         }
                formlist.append(mform)
                formdict[name] = mform

        dynamic_tables = settings.get_mobile_dynamic_tables()
        if dynamic_tables:

            # Select all dynamic tables which have mobile_form=True
            ttable = s3db.s3_table
            query = (ttable.mobile_form == True) & \
                    (ttable.deleted != True)
            rows = current.db(query).select(ttable.name,
                                            ttable.title,
                                            ttable.mobile_data,
                                            )
            for row in rows:

                tablename = row.name
                suffix = tablename.split("_", 1)[-1]

                # Form title
                title = row.title
                if not title:
                    title = " ".join(s.capitalize() for s in suffix.split("_"))

                # URL
                # @todo: make c+f configurable?
                url = {"c": "default",
                       "f": "table/%s" % suffix,
                       }

                # Append to form list
                mform = {"n": tablename,
                         "l": title,
                         "t": tablename,
                         "r": url,
                         "d": row.mobile_data,
                         }
                formlist.append(mform)
                formdict[name] = mform

        self.formlist = formlist
        self.forms = formdict
Beispiel #22
0
    def components(self):
        """
            Add component declarations to the mobile form

            @return: a dict with component declarations for the resource
        """

        resource = self.resource
        tablename = resource.tablename
        pkey = resource._id.name

        options = self.config.get("options")

        aliases = set()
        components = {}

        # Dynamic components, exposed if:
        # - "dynamic_components" is True for the master table, and
        # - "mobile_component" for the component key is not set to False
        dynamic_components = resource.get_config("dynamic_components")
        if dynamic_components:

            # Dynamic components of this table and all its super-entities
            tablenames = [tablename]
            supertables = resource.get_config("super_entity")
            if supertables:
                if isinstance(supertables, (list, tuple)):
                    tablenames.extend(supertables)
                elif supertables:
                    tablenames.append(supertables)

            # Look up corresponding component keys in s3_fields
            s3db = current.s3db
            ftable = s3db.s3_field
            ttable = s3db.s3_table
            join = ttable.on(ttable.id == ftable.table_id)
            query = (ftable.component_key == True) & \
                    (ftable.master.belongs(tablenames)) & \
                    (ftable.deleted == False)
            rows = current.db(query).select(ftable.name,
                                            ftable.component_alias,
                                            ftable.settings,
                                            ttable.name,
                                            join = join,
                                            )

            for row in rows:
                component_key = row.s3_field

                # Skip if mobile_component is set to False
                settings = component_key.settings
                if settings and settings.get("mobile_component") is False:
                    continue

                alias = component_key.component_alias
                if not alias:
                    # Default component alias
                    alias = row.s3_table.name.split("_", 1)[-1]
                aliases.add(alias)

        # Static components, exposed if
        # - configured in "components" option of settings.mobile.forms
        static = options.get("components") if options else None
        if static:
            aliases |= set(static)

        # Construct component descriptions for schema export
        if aliases:
            T = current.T
            hooks = current.s3db.get_components(tablename, names=aliases)
            for alias, hook in hooks.items():

                description = {"table": hook.tablename,
                               "multiple": hook.multiple,
                               }
                if hook.label:
                    description["label"] = s3_str(T(hook.label))
                if hook.plural:
                    description["plural"] =  s3_str(T(hook.plural))

                if hook.pkey != pkey:
                    description["pkey"] = hook.pkey

                linktable = hook.linktable
                if linktable:
                    description.update({"link": str(linktable),
                                        "joinby": hook.lkey,
                                        "key": hook.rkey,
                                        })
                    if hook.fkey != "id":
                        description["fkey"] = hook.fkey
                else:
                    description["joinby"] = hook.fkey

                components[alias] = description

        return components
Beispiel #23
0
    def htmlConfig(html,
                   id,
                   orderby,
                   rfields = None,
                   cache = None,
                   **attr
                   ):
        """
            Method to wrap the html for a dataTable in a form, add the export formats
            and the config details required by dataTables

            @param html: The html table
            @param id: The id of the table
            @param orderby: the sort details see http://datatables.net/reference/option/order
            @param rfields: The list of resource fields
            @param attr: dictionary of attributes which can be passed in
                   dt_lengthMenu: The menu options for the number of records to be shown
                   dt_pageLength : The default number of records that will be shown
                   dt_dom : The Datatable DOM initialisation variable, describing
                            the order in which elements are displayed.
                            See http://datatables.net/ref for more details.
                   dt_pagination : Is pagination enabled, dafault 'true'
                   dt_pagingType : How the pagination buttons are displayed
                   dt_searching: Enable or disable filtering of data.
                   dt_ajax_url: The URL to be used for the Ajax call
                   dt_action_col: The column where the action buttons will be placed
                   dt_bulk_actions: list of labels for the bulk actions.
                   dt_bulk_col: The column in which the checkboxes will appear,
                                by default it will be the column immediately
                                before the first data item
                   dt_group: The column(s) that is(are) used to group the data
                   dt_group_totals: The number of record in each group.
                                    This will be displayed in parenthesis
                                    after the group title.
                   dt_group_titles: The titles to be used for each group.
                                    These are a list of lists with the inner list
                                    consisting of two values, the repr from the
                                    db and the label to display. This can be more than
                                    the actual number of groups (giving an empty group).
                   dt_group_space: Insert a space between the group heading and the next group
                   dt_bulk_selected: A list of selected items
                   dt_actions: dictionary of actions
                   dt_styles: dictionary of styles to be applied to a list of ids
                              for example:
                              {"warning" : [1,3,6,7,9],
                               "alert" : [2,10,13]}
                   dt_text_maximum_len: The maximum length of text before it is condensed
                   dt_text_condense_len: The length displayed text is condensed down to
                   dt_shrink_groups: If set then the rows within a group will be hidden
                                     two types are supported, 'individual' and 'accordion'
                   dt_group_types: The type of indicator for groups that can be 'shrunk'
                                   Permitted valies are: 'icon' (the default) 'text' and 'none'
                   dt_base_url: base URL to construct export format URLs, resource
                                default URL without any URL method or query part

            @global current.response.s3.actions used to get the RowActions
        """

        from gluon.serializers import json as jsons

        s3 = current.response.s3
        settings = current.deployment_settings

        dataTableID = s3.dataTableID
        if not dataTableID or not isinstance(dataTableID, list):
            dataTableID = s3.dataTableID = [id]
        elif id not in dataTableID:
            dataTableID.append(id)

        # The configuration parameter from the server to the client will be
        # sent in a json object stored in an hidden input field. This object
        # will then be parsed by s3.dataTable.js and the values used.
        config = Storage()
        config.id = id
        _aget = attr.get
        config.dom = _aget("dt_dom", settings.get_ui_datatables_dom())
        config.lengthMenu = _aget("dt_lengthMenu",
                                  [[25, 50, -1],
                                   [25, 50, s3_str(current.T("All"))]
                                   ]
                                  )
        config.pageLength = _aget("dt_pageLength", s3.ROWSPERPAGE)
        config.pagination = _aget("dt_pagination", "true")
        config.pagingType = _aget("dt_pagingType",
                                  settings.get_ui_datatables_pagingType())
        config.searching = _aget("dt_searching", "true")

        ajaxUrl = _aget("dt_ajax_url", None)
        if not ajaxUrl:
            request = current.request
            url = URL(c=request.controller,
                      f=request.function,
                      args=request.args,
                      vars=request.get_vars,
                      )
            ajaxUrl = s3_set_extension(url, "aadata")
        config.ajaxUrl = ajaxUrl

        config.rowStyles = _aget("dt_styles", [])

        rowActions = _aget("dt_row_actions", s3.actions)
        if rowActions:
            config.rowActions = rowActions
        else:
            config.rowActions = []
        bulkActions = _aget("dt_bulk_actions", None)
        if bulkActions and not isinstance(bulkActions, list):
            bulkActions = [bulkActions]
        config.bulkActions = bulkActions
        config.bulkCol = bulkCol = _aget("dt_bulk_col", 0)
        action_col = _aget("dt_action_col", 0)
        if bulkActions and bulkCol <= action_col:
            action_col += 1
        config.actionCol = action_col

        group_list = _aget("dt_group", [])
        if not isinstance(group_list, list):
            group_list = [group_list]
        dt_group = []
        for group in group_list:
            if bulkActions and bulkCol <= group:
                group += 1
            if action_col >= group:
                group -= 1
            dt_group.append([group, "asc"])
        config.group = dt_group
        config.groupTotals = _aget("dt_group_totals", [])
        config.groupTitles = _aget("dt_group_titles", [])
        config.groupSpacing = _aget("dt_group_space", "false")
        for order in orderby:
            if bulkActions:
                if bulkCol <= order[0]:
                    order[0] += 1
            if action_col > 0 and action_col >= order[0]:
                order[0] -= 1
        config.order = orderby
        config.textMaxLength = _aget("dt_text_maximum_len", 80)
        config.textShrinkLength = _aget("dt_text_condense_len", 75)
        config.shrinkGroupedRows = _aget("dt_shrink_groups", "false")
        config.groupIcon = _aget("dt_group_types", [])

        # Wrap the table in a form and add some data in hidden fields
        form = FORM(_class="dt-wrapper")
        if not s3.no_formats:
            # @todo: move export-format update into drawCallback()
            # @todo: poor UX with onclick-JS, better to render real
            #        links which can be bookmarked, and then update them
            #        in drawCallback()
            permalink = _aget("dt_permalink", None)
            base_url = _aget("dt_base_url", None)
            export_formats = S3DataTable.export_formats(rfields,
                                                        permalink=permalink,
                                                        base_url=base_url)
            # Nb These can be moved around in initComplete()
            form.append(export_formats)

        form.append(html)

        # Add the configuration details for this dataTable
        form.append(INPUT(_type="hidden",
                          _id="%s_configurations" % id,
                          _name="config",
                          _value=jsons(config)))

        # If we have a cache set up then pass it in
        if cache:
            form.append(INPUT(_type="hidden",
                              _id="%s_dataTable_cache" %id,
                              _name="cache",
                              _value=jsons(cache)))

        # If we have bulk actions then add the hidden fields
        if bulkActions:
            form.append(INPUT(_type="hidden",
                              _id="%s_dataTable_bulkMode" % id,
                              _name="mode",
                              _value="Inclusive"))
            bulk_selected = _aget("dt_bulk_selected", "")
            if isinstance(bulk_selected, list):
                bulk_selected = ",".join(bulk_selected)
            form.append(INPUT(_type="hidden",
                              _id="%s_dataTable_bulkSelection" % id,
                              _name="selected",
                              _value="[%s]" % bulk_selected))
            form.append(INPUT(_type="hidden",
                              _id="%s_dataTable_filterURL" % id,
                              _class="dataTable_filterURL",
                              _name="filterURL",
                              _value="%s" % config.ajaxUrl))

        # Set callback?
        initComplete = settings.get_ui_datatables_initComplete()
        if initComplete:
            # Processed in views/dataTables.html
            s3.dataTable_initComplete = initComplete

        return form
Beispiel #24
0
    def __init__(self):
        """
            Constructor
        """

        T = current.T
        s3db = current.s3db
        settings = current.deployment_settings

        formlist = []

        forms = settings.get_mobile_forms()
        if forms:
            keys = set()
            for item in forms:

                options = {}
                if isinstance(item, (tuple, list)):

                    if len(item) == 2:
                        title, tablename = item
                        if isinstance(tablename, dict):
                            tablename, options = title, tablename
                            title = None
                    elif len(item) == 3:
                        title, tablename, options = item
                    else:
                        continue
                else:
                    title, tablename = None, item

                # Make sure table exists
                table = s3db.table(tablename)
                if not table:
                    current.log.warning(
                        "Mobile forms: non-existent resource %s" % tablename)
                    continue

                # Determine controller and function
                c, f = tablename.split("_", 1)
                c = options.get("c") or c
                f = options.get("f") or f

                # Only expose if target module is enabled
                if not settings.has_module(c):
                    continue

                # Determine the form name
                name = options.get("name")
                if not name:
                    name = "%s_%s" % (c, f)

                # Stringify URL query vars
                url_vars = options.get("vars")
                if url_vars:
                    items = []
                    for k in url_vars:
                        v = s3_str(url_vars[k])
                        url_vars[k] = v
                        items.append("%s=%s" % (k, v))
                    query = "&".join(sorted(items))
                else:
                    query = ""

                # Deduplicate by target URL
                key = (c, f, query)
                if key in keys:
                    continue
                keys.add(key)

                # Determine form title
                if title is None:
                    title = " ".join(w.capitalize() for w in f.split("_"))
                if isinstance(title, basestring):
                    title = T(title)

                # Append to form list
                url = {"c": c, "f": f}
                if url_vars:
                    url["v"] = url_vars
                formlist.append({
                    "n": name,
                    "l": s3_str(title),
                    "t": tablename,
                    "r": url,
                })

        self.formlist = formlist
Beispiel #25
0
    def _datatable(self, r, widget, **attr):
        """
            Generate a data table.

            @param r: the S3Request instance
            @param widget: the widget definition as dict
            @param attr: controller attributes for the request

            @todo: fix export formats
        """

        widget_get = widget.get

        # Parse context
        context = widget_get("context")
        tablename = widget_get("tablename")
        resource, context = self._resolve_context(r, tablename, context)

        # List fields
        list_fields = widget_get("list_fields")
        if not list_fields:
            # @ToDo: Set the parent so that the fkey gets removed from the list_fields
            #resource.parent = s3db.resource("")
            list_fields = resource.list_fields()

        # Widget filter option
        widget_filter = widget_get("filter")
        if widget_filter:
            resource.add_filter(widget_filter)

        # Use the widget-index to create a unique ID
        list_id = "profile-list-%s-%s" % (tablename, widget["index"])

        # Default ORDERBY
        # - first field actually in this table
        def default_orderby():
            for f in list_fields:
                selector = f[1] if isinstance(f, tuple) else f
                if selector == "id":
                    continue
                rfield = resource.resolve_selector(selector)
                if rfield.field:
                    return rfield.field
            return None

        # Pagination
        representation = r.representation
        get_vars = self.request.get_vars
        if representation == "aadata":
            start = get_vars.get("displayStart", None)
            limit = get_vars.get("pageLength", 0)
        else:
            start = get_vars.get("start", None)
            limit = get_vars.get("limit", 0)
        if limit:
            if limit.lower() == "none":
                limit = None
            else:
                try:
                    start = int(start)
                    limit = int(limit)
                except (ValueError, TypeError):
                    start = None
                    limit = 0  # use default
        else:
            # Use defaults
            start = None

        dtargs = attr.get("dtargs", {})

        if r.interactive:
            s3 = current.response.s3

            # How many records per page?
            if s3.dataTable_pageLength:
                display_length = s3.dataTable_pageLength
            else:
                display_length = widget.get("pagesize", 10)
            dtargs["dt_lengthMenu"] = [[10, 25, 50, -1],
                                       [10, 25, 50,
                                        s3_str(current.T("All"))]]

            # ORDERBY fallbacks: widget->resource->default
            orderby = widget_get("orderby")
            if not orderby:
                orderby = resource.get_config("orderby")
            if not orderby:
                orderby = default_orderby()

            # Server-side pagination?
            if not s3.no_sspag:
                dt_pagination = "true"
                if not limit and display_length is not None:
                    limit = 2 * display_length
                else:
                    limit = None
            else:
                dt_pagination = "false"

            # Get the data table
            dt, totalrows, ids = resource.datatable(fields=list_fields,
                                                    start=start,
                                                    limit=limit,
                                                    orderby=orderby)
            displayrows = totalrows

            if dt.empty:
                empty_str = self.crud_string(tablename, "msg_list_empty")
            else:
                empty_str = self.crud_string(tablename, "msg_no_match")
            empty = DIV(empty_str, _class="empty")

            dtargs["dt_pagination"] = dt_pagination
            dtargs["dt_pageLength"] = display_length
            # @todo: fix base URL (make configurable?) to fix export options
            s3.no_formats = True
            dtargs["dt_base_url"] = r.url(method="", vars={})
            dtargs["dt_ajax_url"] = r.url(vars={"update": widget["index"]},
                                          representation="aadata")
            actions = widget_get("actions")
            if callable(actions):
                actions = actions(r, list_id)
            if actions:
                dtargs["dt_row_actions"] = actions

            datatable = dt.html(totalrows, displayrows, id=list_id, **dtargs)

            if dt.data:
                empty.update(_style="display:none")
            else:
                datatable.update(_style="display:none")
            contents = DIV(datatable, empty, _class="dt-contents")

            # Link for create-popup
            create_popup = self._create_popup(r, widget, list_id, resource,
                                              context, totalrows)

            # Card holder label and icon
            label = widget_get("label", "")
            # Activate if-required
            #if label and isinstance(label, basestring):
            if label:
                label = current.T(label)
            else:
                label = self.crud_string(tablename, "title_list")
            icon = widget_get("icon", "")
            if icon:
                icon = ICON(icon)

            _class = self._lookup_class(r, widget)

            # Render the widget
            output = DIV(
                create_popup,
                H4(icon, label, _class="profile-sub-header"),
                DIV(contents, _class="card-holder"),
                _class=_class,
            )

            return output

        elif representation == "aadata":

            # Parse datatable filter/sort query
            searchq, orderby, left = resource.datatable_filter(
                list_fields, get_vars)

            # ORDERBY fallbacks - datatable->widget->resource->default
            if not orderby:
                orderby = widget_get("orderby")
            if not orderby:
                orderby = resource.get_config("orderby")
            if not orderby:
                orderby = default_orderby()

            # DataTable filtering
            if searchq is not None:
                totalrows = resource.count()
                resource.add_filter(searchq)
            else:
                totalrows = None

            # Get the data table
            if totalrows != 0:
                dt, displayrows, ids = resource.datatable(fields=list_fields,
                                                          start=start,
                                                          limit=limit,
                                                          left=left,
                                                          orderby=orderby,
                                                          getids=False)
            else:
                dt, displayrows = None, 0

            if totalrows is None:
                totalrows = displayrows

            # Echo
            draw = int(get_vars.get("draw") or 0)

            # Representation
            if dt is not None:
                data = dt.json(totalrows, displayrows, list_id, draw, **dtargs)
            else:
                data = '{"recordsTotal":%s,' \
                       '"recordsFiltered":0,' \
                       '"dataTable_id":"%s",' \
                       '"draw":%s,' \
                       '"data":[]}' % (totalrows, list_id, draw)

            return data

        else:
            # Really raise an exception here?
            r.error(415, current.ERROR.BAD_FORMAT)
Beispiel #26
0
    def merge(self, r, **attr):
        """
            Merge form for two records

            @param r: the S3Request
            @param **attr: the controller attributes for the request

            @note: this method can always only be POSTed, and requires
                   both "selected" and "mode" in post_vars, as well as
                   the duplicate bookmarks list in session.s3
        """

        T = current.T
        session = current.session
        response = current.response

        output = dict()
        tablename = self.tablename

        # Get the duplicate bookmarks
        s3 = session.s3
        DEDUPLICATE = self.DEDUPLICATE
        if DEDUPLICATE in s3:
            bookmarks = s3[DEDUPLICATE]
            if tablename in bookmarks:
                record_ids = bookmarks[tablename]

        # Process the post variables
        post_vars = r.post_vars
        mode = post_vars.get("mode")
        selected = post_vars.get("selected", "")
        selected = selected.split(",")
        if mode == "Inclusive":
            ids = selected
        elif mode == "Exclusive":
            ids = [i for i in record_ids if i not in selected]
        else:
            # Error
            ids = []
        if len(ids) != 2:
            r.error(501,
                    T("Please select exactly two records"),
                    next=r.url(id=0, vars={}))

        # Get the selected records
        table = self.table
        query = (table._id == ids[0]) | (table._id == ids[1])
        orderby = table.created_on if "created_on" in table else None
        rows = current.db(query).select(orderby=orderby, limitby=(0, 2))
        if len(rows) != 2:
            r.error(404, current.ERROR.BAD_RECORD, next=r.url(id=0, vars={}))
        original = rows[0]
        duplicate = rows[1]

        # Prepare form construction
        formfields = [f for f in table if f.readable or f.writable]

        ORIGINAL, DUPLICATE, KEEP = self.ORIGINAL, self.DUPLICATE, self.KEEP
        keep_o = KEEP.o in post_vars and post_vars[KEEP.o]
        keep_d = KEEP.d in post_vars and post_vars[KEEP.d]

        trs = []
        init_requires = self.init_requires
        index = 1
        num_fields = len(formfields)

        for f in formfields:

            # Render the widgets
            oid = "%s_%s" % (ORIGINAL, f.name)
            did = "%s_%s" % (DUPLICATE, f.name)
            sid = "swap_%s" % f.name
            init_requires(f, original[f], duplicate[f])
            if keep_o or not any((keep_o, keep_d)):
                owidget = self.widget(f,
                                      original[f],
                                      _name=oid,
                                      _id=oid,
                                      _tabindex=index)
            else:
                try:
                    owidget = s3_represent_value(f, value=original[f])
                except:
                    owidget = s3_str(original[f])
            if keep_d or not any((keep_o, keep_d)):
                dwidget = self.widget(f, duplicate[f], _name=did, _id=did)
            else:
                try:
                    dwidget = s3_represent_value(f, value=duplicate[f])
                except:
                    dwidget = s3_str(duplicate[f])

            # Swap button
            if not any((keep_o, keep_d)):
                swap = INPUT(_value="<-->",
                             _class="swap-button",
                             _id=sid,
                             _type="button",
                             _tabindex=index + num_fields)
            else:
                swap = DIV(_class="swap-button")

            if owidget is None or dwidget is None:
                continue

            # Render label row
            label = f.label
            trs.append(
                TR(TD(label, _class="w2p_fl"), TD(), TD(label,
                                                        _class="w2p_fl")))

            # Append widget row
            trs.append(
                TR(TD(owidget, _class="mwidget"), TD(swap),
                   TD(dwidget, _class="mwidget")))

            index = index + 1
        # Show created_on/created_by for each record
        if "created_on" in table:
            original_date = original.created_on
            duplicate_date = duplicate.created_on
            if "created_by" in table:
                represent = table.created_by.represent
                original_author = represent(original.created_by)
                duplicate_author = represent(duplicate.created_by)
                created = T("Created on %s by %s")
                original_created = created % (original_date, original_author)
                duplicate_created = created % (duplicate_date,
                                               duplicate_author)
            else:
                created = T("Created on %s")
                original_created = created % original_date
                duplicate_created = created % duplicate_date
        else:
            original_created = ""
            duplicate_created = ""

        # Page title and subtitle
        output["title"] = T("Merge records")
        #output["subtitle"] = self.crud_string(tablename, "title_list")

        # Submit buttons
        if keep_o or not any((keep_o, keep_d)):
            submit_original = INPUT(_value=T("Keep Original"),
                                    _type="submit",
                                    _name=KEEP.o,
                                    _id=KEEP.o)
        else:
            submit_original = ""

        if keep_d or not any((keep_o, keep_d)):
            submit_duplicate = INPUT(_value=T("Keep Duplicate"),
                                     _type="submit",
                                     _name=KEEP.d,
                                     _id=KEEP.d)
        else:
            submit_duplicate = ""

        # Build the form
        form = FORM(
            TABLE(
                THEAD(
                    TR(
                        TH(H3(T("Original"))),
                        TH(),
                        TH(H3(T("Duplicate"))),
                    ),
                    TR(
                        TD(original_created),
                        TD(),
                        TD(duplicate_created),
                        _class="authorinfo",
                    ),
                ),
                TBODY(trs),
                TFOOT(TR(
                    TD(submit_original),
                    TD(),
                    TD(submit_duplicate),
                ), ),
            ),
            # Append mode and selected - required to get back here!
            hidden={
                "mode": "Inclusive",
                "selected": ",".join(ids),
            })

        output["form"] = form

        # Add RESET and CANCEL options
        output["reset"] = FORM(
            INPUT(_value=T("Reset"),
                  _type="submit",
                  _name="reset",
                  _id="form-reset"),
            A(T("Cancel"), _href=r.url(id=0, vars={}), _class="action-lnk"),
            hidden={
                "mode": mode,
                "selected": ",".join(ids),
            },
        )

        # Process the merge form
        formname = "merge_%s_%s_%s" % (tablename, original[table._id],
                                       duplicate[table._id])
        if form.accepts(
                post_vars,
                session,
                formname=formname,
                onvalidation=lambda form: self.onvalidation(tablename, form),
                keepvalues=False,
                hideerror=False):

            s3db = current.s3db

            if form.vars[KEEP.d]:
                prefix = "%s_" % DUPLICATE
                original, duplicate = duplicate, original
            else:
                prefix = "%s_" % ORIGINAL

            data = Storage()
            for key in form.vars:
                if key.startswith(prefix):
                    fname = key.split("_", 1)[1]
                    data[fname] = form.vars[key]

            search = False
            resource = s3db.resource(tablename)
            try:
                resource.merge(original[table._id],
                               duplicate[table._id],
                               update=data)
            except current.auth.permission.error:
                r.unauthorized()
            except KeyError:
                r.error(404, current.ERROR.BAD_RECORD)
            except Exception:
                import sys
                r.error(424,
                        T("Could not merge records. (Internal Error: %s)") %
                        sys.exc_info()[1],
                        next=r.url())
            else:
                # Cleanup bookmark list
                if mode == "Inclusive":
                    bookmarks[tablename] = [
                        i for i in record_ids if i not in ids
                    ]
                    if not bookmarks[tablename]:
                        del bookmarks[tablename]
                        search = True
                elif mode == "Exclusive":
                    bookmarks[tablename].extend(ids)
                    if not selected:
                        search = True
                # Confirmation message
                # @todo: Having the link to the merged record in the confirmation
                # message would be nice, but it's currently not clickable there :/
                #result = A(T("Open the merged record"),
                #_href=r.url(method="read",
                #id=original[table._id],
                #vars={}))
                response.confirmation = T("Records merged successfully.")

            # Go back to bookmark list
            if search:
                self.next = r.url(method="", id=0, vars={})
            else:
                self.next = r.url(id=0, vars={})

        # View
        response.view = self._view(r, "merge.html")

        return output
Beispiel #27
0
    def organizer(self, r, **attr):
        """
            Render the organizer view (HTML method)

            @param r: the S3Request instance
            @param attr: controller attributes

            @returns: dict of values for the view
        """

        output = {}

        resource = self.resource
        get_config = resource.get_config

        # Parse resource configuration
        config = self.parse_config(resource)
        start = config["start"]
        end = config["end"]

        widget_id = "organizer"

        # Filter Defaults
        hide_filter = self.hide_filter
        filter_widgets = get_config("filter_widgets", None)

        show_filter_form = False
        default_filters = None

        if filter_widgets and not hide_filter:

            # Drop all filter widgets for start/end fields
            # (so they don't clash with the organizer's own filters)
            fw = []
            prefix_selector = self.prefix_selector
            for filter_widget in filter_widgets:
                if not filter_widget:
                    continue
                filter_field = filter_widget.field
                if isinstance(filter_field, basestring):
                    filter_field = prefix_selector(resource, filter_field)
                if start and start.selector == filter_field or \
                   end and end.selector == filter_field:
                    continue
                fw.append(filter_widget)
            filter_widgets = fw

            if filter_widgets:
                show_filter_form = True
                # Apply filter defaults (before rendering the data!)
                from s3filter import S3FilterForm
                default_filters = S3FilterForm.apply_filter_defaults(
                    r, resource)

        # Filter Form
        if show_filter_form:

            get_vars = r.get_vars

            # Where to retrieve filtered data from
            filter_submit_url = attr.get("filter_submit_url")
            if not filter_submit_url:
                get_vars_ = self._remove_filters(get_vars)
                filter_submit_url = r.url(vars=get_vars_)

            # Where to retrieve updated filter options from:
            filter_ajax_url = attr.get("filter_ajax_url")
            if filter_ajax_url is None:
                filter_ajax_url = r.url(
                    method="filter",
                    vars={},
                    representation="options",
                )

            filter_clear = get_config(
                "filter_clear",
                current.deployment_settings.get_ui_filter_clear())
            filter_formstyle = get_config("filter_formstyle", None)
            filter_submit = get_config("filter_submit", True)
            filter_form = S3FilterForm(filter_widgets,
                                       clear=filter_clear,
                                       formstyle=filter_formstyle,
                                       submit=filter_submit,
                                       ajax=True,
                                       url=filter_submit_url,
                                       ajaxurl=filter_ajax_url,
                                       _class="filter-form",
                                       _id="%s-filter-form" % widget_id)
            fresource = current.s3db.resource(
                resource.tablename)  # Use a clean resource
            alias = resource.alias if r.component else None
            output["list_filter_form"] = filter_form.html(fresource,
                                                          get_vars,
                                                          target=widget_id,
                                                          alias=alias)
        else:
            # Render as empty string to avoid the exception in the view
            output["list_filter_form"] = ""

        # Page Title
        crud_string = self.crud_string
        if r.representation != "iframe":
            if r.component:
                title = crud_string(r.tablename, "title_display")
            else:
                title = crud_string(self.tablename, "title_list")
            output["title"] = title

        # Configure Resource
        permitted = self._permitted
        resource_config = {"ajaxURL": r.url(representation="json"),
                           "useTime": config.get("use_time"),
                           "baseURL": r.url(method=""),
                           "labelCreate": s3_str(crud_string(self.tablename, "label_create")),
                           "insertable": resource.get_config("insertable", True) and \
                                         permitted("create"),
                           "editable": resource.get_config("editable", True) and \
                                       permitted("update"),
                           "startEditable": start.field and start.field.writable,
                           "durationEditable": end and end.field and end.field.writable,
                           "deletable": resource.get_config("deletable", True) and \
                                        permitted("delete"),
                           # Forced reload on update, e.g. if onaccept changes
                           # other data that are visible in the organizer
                           "reloadOnUpdate": config.get("reload_on_update", False),
                           }

        # Start and End Field
        resource_config["start"] = start.selector if start else None
        resource_config["end"] = end.selector if end else None

        # Description Labels
        labels = []
        for rfield in config["description"]:
            label = rfield.label
            if label is not None:
                label = s3_str(label)
            labels.append((rfield.colname, label))
        resource_config["columns"] = labels

        # Colors
        color = config.get("color")
        if color:
            resource_config["color"] = color.colname
            resource_config["colors"] = config.get("colors")

        # Generate form key
        formkey = uuid.uuid4().get_hex()

        # Store form key in session
        session = current.session
        keyname = "_formkey[%s]" % self.formname(r)
        session[keyname] = session.get(keyname, [])[-9:] + [formkey]

        # Instantiate Organizer Widget
        widget = S3OrganizerWidget([resource_config])
        output["organizer"] = widget.html(
            widget_id=widget_id,
            formkey=formkey,
        )

        # View
        current.response.view = self._view(r, "organize.html")

        return output
Beispiel #28
0
    def get_json_data(self, r, **attr):
        """
            Extract the resource data and return them as JSON (Ajax method)

            @param r: the S3Request instance
            @param attr: controller attributes

            TODO correct documentation!
            @returns: JSON string containing an array of items, format:
                      [{"id": the record ID,
                        "title": the record title,
                        "start": start date as ISO8601 string,
                        "end": end date as ISO8601 string (if resource has end dates),
                        "description": array of item values to render a description,
                        TODO:
                        "editable": item date/duration can be changed (true|false),
                        "deletable": item can be deleted (true|false),
                        },
                       ...
                       ]
        """

        db = current.db
        auth = current.auth

        resource = self.resource
        table = resource.table
        id_col = str(resource._id)

        config = self.parse_config(resource)

        # Determine fields to load
        fields = [resource._id.name]

        start_rfield = config["start"]
        fields.append(start_rfield)

        end_rfield = config["end"]
        if end_rfield:
            fields.append(end_rfield)

        represent = config["title"]
        if hasattr(represent, "selector"):
            title_field = represent.colname
            fields.append(represent)
        else:
            title_field = None

        description = config["description"]
        if description:
            fields.extend(description)
            columns = [rfield.colname for rfield in description]
        else:
            columns = None

        color = config["color"]
        if color:
            fields.append(color)

        # Add date filter
        start, end = self.parse_interval(r.get_vars.get("$interval"))
        if start and end:
            from s3query import FS
            start_fs = FS(start_rfield.selector)
            if not end_rfield:
                query = (start_fs >= start) & (start_fs < end)
            else:
                end_fs = FS(end_rfield.selector)
                query = (start_fs < end) & (end_fs >= start) | \
                        (start_fs >= start) & (start_fs < end) & (end_fs == None)
            resource.add_filter(query)
        else:
            r.error(400, "Invalid interval parameter")

        # Extract the records
        data = resource.select(
            fields,
            limit=None,
            raw_data=True,
            represent=True,
        )
        rows = data.rows

        # Bulk-represent the records
        record_ids = [row._row[id_col] for row in rows]
        if hasattr(represent, "bulk"):
            representations = represent.bulk(record_ids)
        else:
            representations = None

        # Determine which records can be updated/deleted
        query = table.id.belongs(record_ids)

        q = query & auth.s3_accessible_query("update", table)
        accessible_rows = db(q).select(
            table._id,
            limitby=(0, len(record_ids)),
        )
        editable = set(row[id_col] for row in accessible_rows)

        q = query & auth.s3_accessible_query("delete", table)
        accessible_rows = db(q).select(
            table._id,
            limitby=(0, len(record_ids)),
        )
        deletable = set(row[id_col] for row in accessible_rows)

        # Encode the items
        items = []
        for row in rows:

            raw = row._row
            record_id = raw[id_col]

            # Get the start date
            if start_rfield:
                start_date = self.isoformat(raw[start_rfield.colname])
            else:
                start_date = None
            if start_date is None:
                # Undated item => skip
                continue

            # Construct item title
            if title_field:
                title = row[title_field]
            elif representations:
                title = representations.get(record_id)
            elif callable(represent):
                title = represent(record_id)
            else:
                # Fallback: record ID
                title = row[id_col]

            # Build the item
            item = {
                "id": record_id,
                "t": s3_str(title),
                "s": start_date,
                "pe": 1 if record_id in editable else 0,
                "pd": 1 if record_id in deletable else 0,
            }

            if end_rfield:
                end_date = self.isoformat(raw[end_rfield.colname])
                item["e"] = end_date

            if columns:
                data = []
                for colname in columns:
                    value = row[colname]
                    if value is not None:
                        value = s3_str(value)
                    data.append(value)
                item["d"] = data

            if color:
                item["c"] = raw[color.colname]

            items.append(item)

        return json.dumps({"c": columns, "r": items})
Beispiel #29
0
    def __init__(self):
        """
            Constructor
        """

        T = current.T
        s3db = current.s3db
        settings = current.deployment_settings

        formlist = []

        forms = settings.get_mobile_forms()
        if forms:
            keys = set()
            for item in forms:

                options = {}
                if isinstance(item, (tuple, list)):

                    if len(item) == 2:
                        title, tablename = item
                        if isinstance(tablename, dict):
                            tablename, options = title, tablename
                            title = None
                    elif len(item) == 3:
                        title, tablename, options = item
                    else:
                        continue
                else:
                    title, tablename = None, item

                # Make sure table exists
                table = s3db.table(tablename)
                if not table:
                    current.log.warning("Mobile forms: non-existent resource %s" % tablename)
                    continue

                # Determine controller and function
                c, f = tablename.split("_", 1)
                c = options.get("c") or c
                f = options.get("f") or f

                # Only expose if target module is enabled
                if not settings.has_module(c):
                    continue

                # Determine the form name
                name = options.get("name")
                if not name:
                    name = "%s_%s" % (c, f)

                # Stringify URL query vars
                url_vars = options.get("vars")
                if url_vars:
                    items = []
                    for k in url_vars:
                        v = s3_str(url_vars[k])
                        url_vars[k] = v
                        items.append("%s=%s" % (k, v))
                    query = "&".join(sorted(items))
                else:
                    query = ""

                # Deduplicate by target URL
                key = (c, f, query)
                if key in keys:
                    continue
                keys.add(key)

                # Determine form title
                if title is None:
                    title = " ".join(w.capitalize() for w in f.split("_"))
                if isinstance(title, basestring):
                    title = T(title)

                # Append to form list
                url = {"c": c, "f": f}
                if url_vars:
                    url["v"] = url_vars
                formlist.append({"n": name,
                                 "l": s3_str(title),
                                 "t": tablename,
                                 "r": url,
                                 })

        self.formlist = formlist
Beispiel #30
0
    def _datatable(self, r, widget, **attr):
        """
            Generate a data table.

            @param r: the S3Request instance
            @param widget: the widget definition as dict
            @param attr: controller attributes for the request

            @todo: fix export formats
        """

        widget_get = widget.get

        # Parse context
        context = widget_get("context")
        tablename = widget_get("tablename")
        resource, context = self._resolve_context(r, tablename, context)

        # List fields
        list_fields = widget_get("list_fields")
        if not list_fields:
            # @ToDo: Set the parent so that the fkey gets removed from the list_fields
            #resource.parent = s3db.resource("")
            list_fields = resource.list_fields()

        # Widget filter option
        widget_filter = widget_get("filter")
        if widget_filter:
            resource.add_filter(widget_filter)

        # Use the widget-index to create a unique ID
        list_id = "profile-list-%s-%s" % (tablename, widget["index"])

        # Default ORDERBY
        # - first field actually in this table
        def default_orderby():
            for f in list_fields:
                selector = f[1] if isinstance(f, tuple) else f
                if selector == "id":
                    continue
                rfield = resource.resolve_selector(selector)
                if rfield.field:
                    return rfield.field
            return None

        # Pagination
        representation = r.representation
        get_vars = self.request.get_vars
        if representation == "aadata":
            start = get_vars.get("displayStart", None)
            limit = get_vars.get("pageLength", 0)
        else:
            start = get_vars.get("start", None)
            limit = get_vars.get("limit", 0)
        if limit:
            if limit.lower() == "none":
                limit = None
            else:
                try:
                    start = int(start)
                    limit = int(limit)
                except (ValueError, TypeError):
                    start = None
                    limit = 0 # use default
        else:
            # Use defaults
            start = None

        dtargs = attr.get("dtargs", {})

        if r.interactive:
            s3 = current.response.s3

            # How many records per page?
            if s3.dataTable_pageLength:
                display_length = s3.dataTable_pageLength
            else:
                display_length = widget.get("pagesize", 10)
            dtargs["dt_lengthMenu"] = [[10, 25, 50, -1],
                                       [10, 25, 50, s3_str(current.T("All"))]
                                       ]

            # ORDERBY fallbacks: widget->resource->default
            orderby = widget_get("orderby")
            if not orderby:
                orderby = resource.get_config("orderby")
            if not orderby:
                orderby = default_orderby()

            # Server-side pagination?
            if not s3.no_sspag:
                dt_pagination = "true"
                if not limit and display_length is not None:
                    limit = 2 * display_length
                else:
                    limit = None
            else:
                dt_pagination = "false"

            # Get the data table
            dt, totalrows, ids = resource.datatable(fields=list_fields,
                                                    start=start,
                                                    limit=limit,
                                                    orderby=orderby)
            displayrows = totalrows

            if dt.empty:
                empty_str = self.crud_string(tablename,
                                             "msg_list_empty")
            else:
                empty_str = self.crud_string(tablename,
                                             "msg_no_match")
            empty = DIV(empty_str, _class="empty")

            dtargs["dt_pagination"] = dt_pagination
            dtargs["dt_pageLength"] = display_length
            # @todo: fix base URL (make configurable?) to fix export options
            s3.no_formats = True
            dtargs["dt_base_url"] = r.url(method="", vars={})
            dtargs["dt_ajax_url"] = r.url(vars={"update": widget["index"]},
                                          representation="aadata")
            actions = widget_get("actions")
            if callable(actions):
                actions = actions(r, list_id)
            if actions:
                dtargs["dt_row_actions"] = actions

            datatable = dt.html(totalrows,
                                displayrows,
                                id=list_id,
                                **dtargs)

            if dt.data:
                empty.update(_style="display:none")
            else:
                datatable.update(_style="display:none")
            contents = DIV(datatable, empty, _class="dt-contents")

            # Link for create-popup
            create_popup = self._create_popup(r,
                                              widget,
                                              list_id,
                                              resource,
                                              context,
                                              totalrows)

            # Card holder label and icon
            label = widget_get("label", "")
            # Activate if-required
            #if label and isinstance(label, basestring):
            if label:
                label = current.T(label)
            else:
                label = self.crud_string(tablename, "title_list")
            icon = widget_get("icon", "")
            if icon:
                icon = ICON(icon)

            _class = self._lookup_class(r, widget)

            # Render the widget
            output = DIV(create_popup,
                         H4(icon, label,
                            _class="profile-sub-header"),
                         DIV(contents,
                             _class="card-holder"),
                         _class=_class,
                         )

            return output

        elif representation == "aadata":

            # Parse datatable filter/sort query
            searchq, orderby, left = resource.datatable_filter(list_fields,
                                                               get_vars)

            # ORDERBY fallbacks - datatable->widget->resource->default
            if not orderby:
                orderby = widget_get("orderby")
            if not orderby:
                orderby = resource.get_config("orderby")
            if not orderby:
                orderby = default_orderby()

            # DataTable filtering
            if searchq is not None:
                totalrows = resource.count()
                resource.add_filter(searchq)
            else:
                totalrows = None

            # Get the data table
            if totalrows != 0:
                dt, displayrows, ids = resource.datatable(fields=list_fields,
                                                          start=start,
                                                          limit=limit,
                                                          left=left,
                                                          orderby=orderby,
                                                          getids=False)
            else:
                dt, displayrows = None, 0

            if totalrows is None:
                totalrows = displayrows

            # Echo
            draw = int(get_vars.get("draw") or 0)

            # Representation
            if dt is not None:
                data = dt.json(totalrows,
                               displayrows,
                               list_id,
                               draw,
                               **dtargs)
            else:
                data = '{"recordsTotal":%s,' \
                       '"recordsFiltered":0,' \
                       '"dataTable_id":"%s",' \
                       '"draw":%s,' \
                       '"data":[]}' % (totalrows, list_id, draw)

            return data

        else:
            # Really raise an exception here?
            r.error(415, current.ERROR.BAD_FORMAT)
Beispiel #31
0
    def send(cls, r, resource):
        """
            Method to retrieve updates for a subscription, render the
            notification message and send it - responds to POST?format=msg
            requests to the respective resource.

            @param r: the S3Request
            @param resource: the S3Resource
        """

        _debug = current.log.debug
        _debug("S3Notifications.send()")

        json_message = current.xml.json_message

        # Read subscription data
        source = r.body
        source.seek(0)
        data = source.read()
        subscription = json.loads(data)

        #_debug("Notify PE #%s by %s on %s of %s since %s" % \
        #           (subscription["pe_id"],
        #            str(subscription["method"]),
        #            str(subscription["notify_on"]),
        #            subscription["resource"],
        #            subscription["last_check_time"],
        #            ))

        # Check notification settings
        notify_on = subscription["notify_on"]
        methods = subscription["method"]
        if not notify_on or not methods:
            return json_message(message="No notifications configured "
                                "for this subscription")

        # Authorization (pe_id must not be None)
        pe_id = subscription["pe_id"]

        if not pe_id:
            r.unauthorised()

        # Fields to extract
        fields = resource.list_fields(key="notify_fields")
        if "created_on" not in fields:
            fields.append("created_on")

        # Extract the data
        data = resource.select(fields, represent=True, raw_data=True)
        rows = data["rows"]

        # How many records do we have?
        numrows = len(rows)
        if not numrows:
            return json_message(message="No records found")

        #_debug("%s rows:" % numrows)

        # Prepare meta-data
        get_config = resource.get_config
        settings = current.deployment_settings

        page_url = subscription["page_url"]

        crud_strings = current.response.s3.crud_strings.get(resource.tablename)
        if crud_strings:
            resource_name = crud_strings.title_list
        else:
            resource_name = string.capwords(resource.name, "_")

        last_check_time = s3_decode_iso_datetime(
            subscription["last_check_time"])

        email_format = subscription["email_format"]
        if not email_format:
            email_format = settings.get_msg_notify_email_format()

        filter_query = subscription.get("filter_query")

        meta_data = {
            "systemname": settings.get_system_name(),
            "systemname_short": settings.get_system_name_short(),
            "resource": resource_name,
            "page_url": page_url,
            "notify_on": notify_on,
            "last_check_time": last_check_time,
            "filter_query": filter_query,
            "total_rows": numrows,
        }

        # Render contents for the message template(s)
        renderer = get_config("notify_renderer")
        if not renderer:
            renderer = settings.get_msg_notify_renderer()
        if not renderer:
            renderer = cls._render

        contents = {}
        if email_format == "html" and "EMAIL" in methods:
            contents["html"] = renderer(resource, data, meta_data, "html")
            contents["default"] = contents["html"]
        if email_format != "html" or "EMAIL" not in methods or len(
                methods) > 1:
            contents["text"] = renderer(resource, data, meta_data, "text")
            contents["default"] = contents["text"]

        # Subject line
        subject = get_config("notify_subject")
        if not subject:
            subject = settings.get_msg_notify_subject()
        if callable(subject):
            subject = subject(resource, data, meta_data)

        from string import Template
        subject = Template(subject).safe_substitute(S="%(systemname)s",
                                                    s="%(systemname_short)s",
                                                    r="%(resource)s")
        subject = subject % meta_data

        # Attachment
        attachment = subscription.get("attachment", False)
        document_ids = None
        if attachment:
            attachment_fnc = settings.get_msg_notify_attachment()
            if attachment_fnc:
                document_ids = attachment_fnc(resource, data, meta_data)

        # **data for send_by_pe_id function in s3msg
        send_data = {}
        send_data_fnc = settings.get_msg_notify_send_data()
        if callable(send_data_fnc):
            send_data = send_data_fnc(resource, data, meta_data)

        # Helper function to find templates from a priority list
        join = lambda *f: os.path.join(current.request.folder, *f)

        def get_template(path, filenames):
            for fn in filenames:
                filepath = join(path, fn)
                if os.path.exists(filepath):
                    try:
                        return open(filepath, "rb")
                    except:
                        pass
            return None

        # Render and send the message(s)
        themes = settings.get_template()
        prefix = resource.get_config("notify_template", "notify")

        send = current.msg.send_by_pe_id

        success = False
        errors = []

        for method in methods:

            error = None

            # Get the message template
            template = None
            filenames = ["%s_%s.html" % (prefix, method.lower())]
            if method == "EMAIL" and email_format:
                filenames.insert(0,
                                 "%s_email_%s.html" % (prefix, email_format))
            if themes != "default":
                location = settings.get_template_location()
                if not isinstance(themes, (tuple, list)):
                    themes = (themes, )
                for theme in themes[::-1]:
                    path = join(location, "templates", theme, "views", "msg")
                    template = get_template(path, filenames)
                    if template is not None:
                        break
            if template is None:
                path = join("views", "msg")
                template = get_template(path, filenames)
            if template is None:
                template = StringIO(
                    s3_str(current.T("New updates are available.")))

            # Select contents format
            if method == "EMAIL" and email_format == "html":
                output = contents["html"]
            else:
                output = contents["text"]

            # Render the message
            try:
                message = current.response.render(template, output)
            except:
                exc_info = sys.exc_info()[:2]
                error = ("%s: %s" % (exc_info[0].__name__, exc_info[1]))
                errors.append(error)
                continue

            if not message:
                continue

            # Send the message
            #_debug("Sending message per %s" % method)
            #_debug(message)
            try:
                sent = send(
                    pe_id,
                    # RFC 2822
                    subject=s3_truncate(subject, 78),
                    message=message,
                    contact_method=method,
                    system_generated=True,
                    document_ids=document_ids,
                    **send_data)
            except:
                exc_info = sys.exc_info()[:2]
                error = ("%s: %s" % (exc_info[0].__name__, exc_info[1]))
                sent = False

            if sent:
                # Successful if at least one notification went out
                success = True
            else:
                if not error:
                    error = current.session.error
                    if isinstance(error, list):
                        error = "/".join(error)
                if error:
                    errors.append(error)

        # Done
        if errors:
            message = ", ".join(errors)
        else:
            message = "Success"
        return json_message(success=success,
                            statuscode=200 if success else 403,
                            message=message)
Beispiel #32
0
    def merge(self, r, **attr):
        """
            Merge form for two records

            @param r: the S3Request
            @param **attr: the controller attributes for the request

            @note: this method can always only be POSTed, and requires
                   both "selected" and "mode" in post_vars, as well as
                   the duplicate bookmarks list in session.s3
        """

        T = current.T
        session = current.session
        response = current.response

        output = dict()
        tablename = self.tablename

        # Get the duplicate bookmarks
        s3 = session.s3
        DEDUPLICATE = self.DEDUPLICATE
        if DEDUPLICATE in s3:
            bookmarks = s3[DEDUPLICATE]
            if tablename in bookmarks:
                record_ids = bookmarks[tablename]

        # Process the post variables
        post_vars = r.post_vars
        mode = post_vars.get("mode")
        selected = post_vars.get("selected", "")
        selected = selected.split(",")
        if mode == "Inclusive":
            ids = selected
        elif mode == "Exclusive":
            ids = [i for i in record_ids if i not in selected]
        else:
            # Error
            ids = []
        if len(ids) != 2:
            r.error(501, T("Please select exactly two records"),
                    next = r.url(id=0, vars={}))

        # Get the selected records
        table = self.table
        query = (table._id == ids[0]) | (table._id == ids[1])
        orderby = table.created_on if "created_on" in table else None
        rows = current.db(query).select(orderby=orderby,
                                        limitby=(0, 2))
        if len(rows) != 2:
            r.error(404, current.ERROR.BAD_RECORD, next = r.url(id=0, vars={}))
        original = rows[0]
        duplicate = rows[1]

        # Prepare form construction
        formfields = [f for f in table if f.readable or f.writable]

        ORIGINAL, DUPLICATE, KEEP = self.ORIGINAL, self.DUPLICATE, self.KEEP
        keep_o = KEEP.o in post_vars and post_vars[KEEP.o]
        keep_d = KEEP.d in post_vars and post_vars[KEEP.d]

        trs = []
        init_requires = self.init_requires
        index = 1
        num_fields = len(formfields)

        for f in formfields:

            # Render the widgets
            oid = "%s_%s" % (ORIGINAL, f.name)
            did = "%s_%s" % (DUPLICATE, f.name)
            sid = "swap_%s" % f.name
            init_requires(f, original[f], duplicate[f])
            if keep_o or not any((keep_o, keep_d)):
                owidget = self.widget(f, original[f], _name=oid, _id=oid, _tabindex=index)
            else:
                try:
                    owidget = s3_represent_value(f, value=original[f])
                except:
                    owidget = s3_str(original[f])
            if keep_d or not any((keep_o, keep_d)):
                dwidget = self.widget(f, duplicate[f], _name=did, _id=did)
            else:
                try:
                    dwidget = s3_represent_value(f, value=duplicate[f])
                except:
                    dwidget = s3_str(duplicate[f])

            # Swap button
            if not any((keep_o, keep_d)):
                swap = INPUT(_value="<-->",
                             _class="swap-button",
                             _id=sid,
                             _type="button",
                             _tabindex = index+num_fields)
            else:
                swap = DIV(_class="swap-button")

            if owidget is None or dwidget is None:
                continue

            # Render label row
            label = f.label
            trs.append(TR(TD(label, _class="w2p_fl"),
                          TD(),
                          TD(label, _class="w2p_fl")))

            # Append widget row
            trs.append(TR(TD(owidget, _class="mwidget"),
                          TD(swap),
                          TD(dwidget, _class="mwidget")))

            index = index + 1
        # Show created_on/created_by for each record
        if "created_on" in table:
            original_date = original.created_on
            duplicate_date = duplicate.created_on
            if "created_by" in table:
                represent = table.created_by.represent
                original_author = represent(original.created_by)
                duplicate_author = represent(duplicate.created_by)
                created = T("Created on %s by %s")
                original_created = created % (original_date, original_author)
                duplicate_created = created % (duplicate_date, duplicate_author)
            else:
                created = T("Created on %s")
                original_created = created % original_date
                duplicate_created = created % duplicate_date
        else:
            original_created = ""
            duplicate_created = ""

        # Page title and subtitle
        output["title"] = T("Merge records")
        #output["subtitle"] = self.crud_string(tablename, "title_list")

        # Submit buttons
        if keep_o or not any((keep_o, keep_d)):
            submit_original = INPUT(_value=T("Keep Original"),
                                    _type="submit", _name=KEEP.o, _id=KEEP.o)
        else:
            submit_original = ""

        if keep_d or not any((keep_o, keep_d)):
            submit_duplicate = INPUT(_value=T("Keep Duplicate"),
                                     _type="submit", _name=KEEP.d, _id=KEEP.d)
        else:
            submit_duplicate = ""

        # Build the form
        form = FORM(TABLE(
                        THEAD(
                            TR(TH(H3(T("Original"))),
                               TH(),
                               TH(H3(T("Duplicate"))),
                            ),
                            TR(TD(original_created),
                               TD(),
                               TD(duplicate_created),
                               _class="authorinfo",
                            ),
                        ),
                        TBODY(trs),
                        TFOOT(
                            TR(TD(submit_original),
                               TD(),
                               TD(submit_duplicate),
                            ),
                        ),
                    ),
                    # Append mode and selected - required to get back here!
                    hidden = {
                        "mode": "Inclusive",
                        "selected": ",".join(ids),
                    }
                )

        output["form"] = form

        # Add RESET and CANCEL options
        output["reset"] = FORM(INPUT(_value=T("Reset"),
                                     _type="submit",
                                     _name="reset", _id="form-reset"),
                               A(T("Cancel"), _href=r.url(id=0, vars={}), _class="action-lnk"),
                               hidden = {"mode": mode,
                                         "selected": ",".join(ids),
                                         },
                               )

        # Process the merge form
        formname = "merge_%s_%s_%s" % (tablename,
                                       original[table._id],
                                       duplicate[table._id])
        if form.accepts(post_vars, session,
                        formname=formname,
                        onvalidation=lambda form: self.onvalidation(tablename, form),
                        keepvalues=False,
                        hideerror=False):

            s3db = current.s3db

            if form.vars[KEEP.d]:
                prefix = "%s_" % DUPLICATE
                original, duplicate = duplicate, original
            else:
                prefix = "%s_" % ORIGINAL

            data = Storage()
            for key in form.vars:
                if key.startswith(prefix):
                    fname = key.split("_", 1)[1]
                    data[fname] = form.vars[key]

            search = False
            resource = s3db.resource(tablename)
            try:
                resource.merge(original[table._id],
                               duplicate[table._id],
                               update=data)
            except current.auth.permission.error:
                r.unauthorized()
            except KeyError:
                r.error(404, current.ERROR.BAD_RECORD)
            except Exception:
                import sys
                r.error(424,
                        T("Could not merge records. (Internal Error: %s)") %
                            sys.exc_info()[1],
                        next=r.url())
            else:
                # Cleanup bookmark list
                if mode == "Inclusive":
                    bookmarks[tablename] = [i for i in record_ids if i not in ids]
                    if not bookmarks[tablename]:
                        del bookmarks[tablename]
                        search = True
                elif mode == "Exclusive":
                    bookmarks[tablename].extend(ids)
                    if not selected:
                        search = True
                # Confirmation message
                # @todo: Having the link to the merged record in the confirmation
                # message would be nice, but it's currently not clickable there :/
                #result = A(T("Open the merged record"),
                        #_href=r.url(method="read",
                                    #id=original[table._id],
                                    #vars={}))
                response.confirmation = T("Records merged successfully.")

            # Go back to bookmark list
            if search:
                self.next = r.url(method="", id=0, vars={})
            else:
                self.next = r.url(id=0, vars={})

        # View
        response.view = self._view(r, "merge.html")

        return output
Beispiel #33
0
    def htmlConfig(html, id, orderby, rfields=None, cache=None, **attr):
        """
            Method to wrap the html for a dataTable in a form, add the export formats
            and the config details required by dataTables

            @param html: The html table
            @param id: The id of the table
            @param orderby: the sort details see http://datatables.net/reference/option/order
            @param rfields: The list of resource fields
            @param attr: dictionary of attributes which can be passed in
                   dt_lengthMenu: The menu options for the number of records to be shown
                   dt_pageLength : The default number of records that will be shown
                   dt_dom : The Datatable DOM initialisation variable, describing
                            the order in which elements are displayed.
                            See http://datatables.net/ref for more details.
                   dt_pagination : Is pagination enabled, dafault 'true'
                   dt_pagingType : How the pagination buttons are displayed
                   dt_searching: Enable or disable filtering of data.
                   dt_ajax_url: The URL to be used for the Ajax call
                   dt_action_col: The column where the action buttons will be placed
                   dt_bulk_actions: list of labels for the bulk actions.
                   dt_bulk_col: The column in which the checkboxes will appear,
                                by default it will be the column immediately
                                before the first data item
                   dt_group: The column(s) that is(are) used to group the data
                   dt_group_totals: The number of record in each group.
                                    This will be displayed in parenthesis
                                    after the group title.
                   dt_group_titles: The titles to be used for each group.
                                    These are a list of lists with the inner list
                                    consisting of two values, the repr from the
                                    db and the label to display. This can be more than
                                    the actual number of groups (giving an empty group).
                   dt_group_space: Insert a space between the group heading and the next group
                   dt_bulk_selected: A list of selected items
                   dt_actions: dictionary of actions
                   dt_styles: dictionary of styles to be applied to a list of ids
                              for example:
                              {"warning" : [1,3,6,7,9],
                               "alert" : [2,10,13]}
                   dt_text_maximum_len: The maximum length of text before it is condensed
                   dt_text_condense_len: The length displayed text is condensed down to
                   dt_shrink_groups: If set then the rows within a group will be hidden
                                     two types are supported, 'individual' and 'accordion'
                   dt_group_types: The type of indicator for groups that can be 'shrunk'
                                   Permitted valies are: 'icon' (the default) 'text' and 'none'
                   dt_base_url: base URL to construct export format URLs, resource
                                default URL without any URL method or query part

            @global current.response.s3.actions used to get the RowActions
        """

        from gluon.serializers import json as jsons

        s3 = current.response.s3
        settings = current.deployment_settings

        dataTableID = s3.dataTableID
        if not dataTableID or not isinstance(dataTableID, list):
            dataTableID = s3.dataTableID = [id]
        elif id not in dataTableID:
            dataTableID.append(id)

        # The configuration parameter from the server to the client will be
        # sent in a json object stored in an hidden input field. This object
        # will then be parsed by s3.dataTable.js and the values used.
        config = Storage()
        config.id = id
        _aget = attr.get
        config.dom = _aget("dt_dom", settings.get_ui_datatables_dom())
        config.lengthMenu = _aget(
            "dt_lengthMenu",
            [[25, 50, -1], [25, 50, s3_str(current.T("All"))]])
        config.pageLength = _aget("dt_pageLength", s3.ROWSPERPAGE)
        config.pagination = _aget("dt_pagination", "true")
        config.pagingType = _aget("dt_pagingType",
                                  settings.get_ui_datatables_pagingType())
        config.searching = _aget("dt_searching", "true")

        ajaxUrl = _aget("dt_ajax_url", None)
        if not ajaxUrl:
            request = current.request
            url = URL(
                c=request.controller,
                f=request.function,
                args=request.args,
                vars=request.get_vars,
            )
            ajaxUrl = s3_set_extension(url, "aadata")
        config.ajaxUrl = ajaxUrl

        config.rowStyles = _aget("dt_styles", [])

        rowActions = _aget("dt_row_actions", s3.actions)
        if rowActions:
            config.rowActions = rowActions
        else:
            config.rowActions = []
        bulkActions = _aget("dt_bulk_actions", None)
        if bulkActions and not isinstance(bulkActions, list):
            bulkActions = [bulkActions]
        config.bulkActions = bulkActions
        config.bulkCol = bulkCol = _aget("dt_bulk_col", 0)
        action_col = _aget("dt_action_col", 0)
        if bulkActions and bulkCol <= action_col:
            action_col += 1
        config.actionCol = action_col

        group_list = _aget("dt_group", [])
        if not isinstance(group_list, list):
            group_list = [group_list]
        dt_group = []
        for group in group_list:
            if bulkActions and bulkCol <= group:
                group += 1
            if action_col >= group:
                group -= 1
            dt_group.append([group, "asc"])
        config.group = dt_group
        config.groupTotals = _aget("dt_group_totals", [])
        config.groupTitles = _aget("dt_group_titles", [])
        config.groupSpacing = _aget("dt_group_space", "false")
        for order in orderby:
            if bulkActions:
                if bulkCol <= order[0]:
                    order[0] += 1
            if action_col > 0 and action_col >= order[0]:
                order[0] -= 1
        config.order = orderby
        config.textMaxLength = _aget("dt_text_maximum_len", 80)
        config.textShrinkLength = _aget("dt_text_condense_len", 75)
        config.shrinkGroupedRows = _aget("dt_shrink_groups", "false")
        config.groupIcon = _aget("dt_group_types", [])

        # Wrap the table in a form and add some data in hidden fields
        form = FORM(_class="dt-wrapper")
        if not s3.no_formats:
            # @todo: move export-format update into drawCallback()
            # @todo: poor UX with onclick-JS, better to render real
            #        links which can be bookmarked, and then update them
            #        in drawCallback()
            permalink = _aget("dt_permalink", None)
            base_url = _aget("dt_base_url", None)
            export_formats = S3DataTable.export_formats(rfields,
                                                        permalink=permalink,
                                                        base_url=base_url)
            # Nb These can be moved around in initComplete()
            form.append(export_formats)

        form.append(html)

        # Add the configuration details for this dataTable
        form.append(
            INPUT(_type="hidden",
                  _id="%s_configurations" % id,
                  _name="config",
                  _value=jsons(config)))

        # If we have a cache set up then pass it in
        if cache:
            form.append(
                INPUT(_type="hidden",
                      _id="%s_dataTable_cache" % id,
                      _name="cache",
                      _value=jsons(cache)))

        # If we have bulk actions then add the hidden fields
        if bulkActions:
            form.append(
                INPUT(_type="hidden",
                      _id="%s_dataTable_bulkMode" % id,
                      _name="mode",
                      _value="Inclusive"))
            bulk_selected = _aget("dt_bulk_selected", "")
            if isinstance(bulk_selected, list):
                bulk_selected = ",".join(bulk_selected)
            form.append(
                INPUT(_type="hidden",
                      _id="%s_dataTable_bulkSelection" % id,
                      _name="selected",
                      _value="[%s]" % bulk_selected))
            form.append(
                INPUT(_type="hidden",
                      _id="%s_dataTable_filterURL" % id,
                      _class="dataTable_filterURL",
                      _name="filterURL",
                      _value="%s" % config.ajaxUrl))

        # Set callback?
        initComplete = settings.get_ui_datatables_initComplete()
        if initComplete:
            # Processed in views/dataTables.html
            s3.dataTable_initComplete = initComplete

        return form
Beispiel #34
0
    def send(cls, r, resource):
        """
            Method to retrieve updates for a subscription, render the
            notification message and send it - responds to POST?format=msg
            requests to the respective resource.

            @param r: the S3Request
            @param resource: the S3Resource
        """

        _debug = current.log.debug
        _debug("S3Notifications.send()")

        json_message = current.xml.json_message

        # Read subscription data
        source = r.body
        source.seek(0)
        data = source.read()
        subscription = json.loads(data)

        #_debug("Notify PE #%s by %s on %s of %s since %s" % \
        #           (subscription["pe_id"],
        #            str(subscription["method"]),
        #            str(subscription["notify_on"]),
        #            subscription["resource"],
        #            subscription["last_check_time"],
        #            ))

        # Check notification settings
        notify_on = subscription["notify_on"]
        methods = subscription["method"]
        if not notify_on or not methods:
            return json_message(message="No notifications configured "
                                        "for this subscription")

        # Authorization (pe_id must not be None)
        pe_id = subscription["pe_id"]

        if not pe_id:
            r.unauthorised()

        # Fields to extract
        fields = resource.list_fields(key="notify_fields")
        if "created_on" not in fields:
            fields.append("created_on")

        # Extract the data
        data = resource.select(fields,
                               represent=True,
                               raw_data=True)
        rows = data["rows"]

        # How many records do we have?
        numrows = len(rows)
        if not numrows:
            return json_message(message="No records found")

        #_debug("%s rows:" % numrows)

        # Prepare meta-data
        get_config = resource.get_config
        settings = current.deployment_settings

        page_url = subscription["page_url"]

        crud_strings = current.response.s3.crud_strings.get(resource.tablename)
        if crud_strings:
            resource_name = crud_strings.title_list
        else:
            resource_name = string.capwords(resource.name, "_")

        last_check_time = s3_decode_iso_datetime(subscription["last_check_time"])

        email_format = subscription["email_format"]
        if not email_format:
            email_format = settings.get_msg_notify_email_format()

        filter_query = subscription.get("filter_query")

        meta_data = {"systemname": settings.get_system_name(),
                     "systemname_short": settings.get_system_name_short(),
                     "resource": resource_name,
                     "page_url": page_url,
                     "notify_on": notify_on,
                     "last_check_time": last_check_time,
                     "filter_query": filter_query,
                     "total_rows": numrows,
                     }

        # Render contents for the message template(s)
        renderer = get_config("notify_renderer")
        if not renderer:
            renderer = settings.get_msg_notify_renderer()
        if not renderer:
            renderer = cls._render

        contents = {}
        if email_format == "html" and "EMAIL" in methods:
            contents["html"] = renderer(resource, data, meta_data, "html")
            contents["default"] = contents["html"]
        if email_format != "html" or "EMAIL" not in methods or len(methods) > 1:
            contents["text"] = renderer(resource, data, meta_data, "text")
            contents["default"] = contents["text"]

        # Subject line
        subject = get_config("notify_subject")
        if not subject:
            subject = settings.get_msg_notify_subject()
        if callable(subject):
            subject = subject(resource, data, meta_data)

        from string import Template
        subject = Template(subject).safe_substitute(S="%(systemname)s",
                                                    s="%(systemname_short)s",
                                                    r="%(resource)s")
        subject = subject % meta_data

        # Attachment
        attachment = subscription.get("attachment", False)
        document_ids = None
        if attachment:
            attachment_fnc = settings.get_msg_notify_attachment()
            if attachment_fnc:
                document_ids = attachment_fnc(resource, data, meta_data)

        # Helper function to find templates from a priority list
        join = lambda *f: os.path.join(current.request.folder, *f)
        def get_template(path, filenames):
            for fn in filenames:
                filepath = join(path, fn)
                if os.path.exists(filepath):
                    try:
                        return open(filepath, "rb")
                    except:
                        pass
            return None

        # Render and send the message(s)
        themes = settings.get_template()
        prefix = resource.get_config("notify_template", "notify")

        send = current.msg.send_by_pe_id

        success = False
        errors = []

        for method in methods:

            error = None

            # Get the message template
            template = None
            filenames = ["%s_%s.html" % (prefix, method.lower())]
            if method == "EMAIL" and email_format:
                filenames.insert(0, "%s_email_%s.html" % (prefix, email_format))
            if themes != "default":
                location = settings.get_template_location()
                if not isinstance(themes, (tuple, list)):
                    themes = (themes,)
                for theme in themes[::-1]:
                    path = join(location, "templates", theme, "views", "msg")
                    template = get_template(path, filenames)
                    if template is not None:
                        break
            if template is None:
                path = join("views", "msg")
                template = get_template(path, filenames)
            if template is None:
                template = StringIO(s3_str(current.T("New updates are available.")))

            # Select contents format
            if method == "EMAIL" and email_format == "html":
                output = contents["html"]
            else:
                output = contents["text"]

            # Render the message
            try:
                message = current.response.render(template, output)
            except:
                exc_info = sys.exc_info()[:2]
                error = ("%s: %s" % (exc_info[0].__name__, exc_info[1]))
                errors.append(error)
                continue

            if not message:
                continue

            # Send the message
            #_debug("Sending message per %s" % method)
            #_debug(message)
            try:
                sent = send(pe_id,
                            subject=s3_truncate(subject, 78),
                            message=message,
                            contact_method=method,
                            system_generated=True,
                            document_ids=document_ids)
            except:
                exc_info = sys.exc_info()[:2]
                error = ("%s: %s" % (exc_info[0].__name__, exc_info[1]))
                sent = False

            if sent:
                # Successful if at least one notification went out
                success = True
            else:
                if not error:
                    error = current.session.error
                    if isinstance(error, list):
                        error = "/".join(error)
                if error:
                    errors.append(error)

        # Done
        if errors:
            message = ", ".join(errors)
        else:
            message = "Success"
        return json_message(success=success,
                            statuscode=200 if success else 403,
                            message=message)