Exemple #1
0
def set_priority_js():
    """ Output json for priority field """

    wptable = s3db.cap_warning_priority

    rows = db(wptable).select(
        wptable.name,
        wptable.urgency,
        wptable.severity,
        wptable.certainty,
        wptable.color_code,
        orderby=wptable.name,
    )

    from gluon.serializers import json as jsons
    from s3 import s3_str
    p_settings = [(s3_str(T(r.name)), r.urgency, r.severity, r.certainty, r.color_code)\
                 for r in rows]

    priority_conf = '''S3.cap_priorities=%s''' % jsons(p_settings)
    js_global = s3.js_global
    if not priority_conf in js_global:
        js_global.append(priority_conf)

    return
Exemple #2
0
    def html(self, start=None, limit=None, pagesize=None, ajaxurl=None):
        """
            Render list data as HTML (nested DIVs)

            @param start: index of the first item (in this page)
            @param limit: (actual) number of items (in this page)
            @param pagesize: maximum number of items per page
            @param ajaxurl: the URL to Ajax-update the datalist
        """

        T = current.T
        resource = self.resource
        list_fields = self.list_fields
        rfields = resource.resolve_selectors(list_fields)[0]

        listid = self.listid
        render = self.layout

        records = self.records
        if records is not None:
            items = [
                DIV(T("Total Records: %(numrows)s") % {"numrows": self.total},
                    _class="dl-header",
                    _id="%s-header" % listid)
            ]
            _start = self.start
            for i in xrange(len(records)):
                _class = (i + _start) % 2 and "even" or "odd"
                item = render(listid,
                              resource,
                              rfields,
                              records[i],
                              _class=_class)
                # Class "dl-item" is required for pagination:
                if hasattr(item, "add_class"):
                    item.add_class("dl-item")
                items.append(item)
        else:
            # template
            raise NotImplementedError

        dl = DIV(items, _class="dl", _id=listid)

        dl_data = {
            "startindex": start,
            "maxitems": limit,
            "totalitems": self.total,
            "pagesize": pagesize,
            "ajaxurl": ajaxurl
        }
        from gluon.serializers import json as jsons
        dl_data = jsons(dl_data)
        dl.append(
            DIV(FORM(
                INPUT(_type="hidden", _class="dl-pagination", _value=dl_data)),
                A(T("more..."), _href=ajaxurl, _class="dl-pagination"),
                _class="dl-navigation"))

        return dl
Exemple #3
0
def priority_get():
    
    try:
        event_type_id = request.args[0]
    except:
        result = current.xml.json_message(False, 400, "No Event Type provided!")
    else:
        try:
            event_type_id = int(event_type_id)
        except:
            result = current.xml.json_message(False, 400, "Invalid Event Type!")
        else:
            # Get Event Name for Event ID 
            etable = s3db.event_event_type
            item = db(etable.id == event_type_id).select(etable.name,
                                                         limitby=(0, 1)
                                                         ).first()
            try:
                event_type_name = item.name
            except:
                result = current.xml.json_message(False, 400, "Event Type Not Found!")
            else:
                wptable = s3db.cap_warning_priority
                query = (wptable.event_type == event_type_name)
                      
                rows = db(query).select(wptable.id,
                                        wptable.name,
                                        orderby = wptable.id)

                from gluon.serializers import json as jsons
                if rows:
                    row_dict = [{"id": r.id, "name": T(r.name)} for r in rows] + \
                               [{"id": "", "name": T("Undefined")}]
                    result = jsons(row_dict)
                else:
                    rows = db(wptable.event_type == "others").select(wptable.id, 
                                                                     wptable.name,
                                                                     orderby = wptable.id)
            
                    row_dict = [{"id": r.id, "name": T(r.name)} for r in rows] + \
                               [{"id": "", "name": T("Undefined")}]
                    result = jsons(row_dict)
    finally:
        response.headers["Content-Type"] = "application/json"
        return result
Exemple #4
0
    def html(self,
             start=None,
             limit=None,
             pagesize=None,
             ajaxurl=None):
        """
            Render list data as HTML (nested DIVs)
        """

        T = current.T
        resource = self.resource
        list_fields = self.list_fields
        rfields = resource.resolve_selectors(list_fields)[0]

        listid = self.listid
        render = self.layout
        
        records = self.records
        if records is not None:
            items = [
                DIV(T("Total Records: %(numrows)s") % {"numrows": self.total},
                    _class="dl-header",
                    _id="%s-header" % listid)
            ]
            _start = self.start
            for i in xrange(len(records)):
                _class = (i + _start) % 2 and "even" or "odd"
                item = render(listid, resource, rfields, records[i], _class=_class)
                # Class "dl-item" is required for pagination:
                if hasattr(item, "add_class"):
                    item.add_class("dl-item")
                items.append(item)
        else:
            # template
            raise NotImplementedError

        dl = DIV(items, _class = "dl", _id = listid)

        dl_data = {"startindex": start,
                   "maxitems": limit,
                   "totalitems": self.total,
                   "pagesize": pagesize,
                   "ajaxurl": ajaxurl
                   }
        from gluon.serializers import json as jsons
        dl_data = jsons(dl_data)
        dl.append(DIV(
                    FORM(
                        INPUT(_type="hidden",
                              _class="dl-pagination",
                              _value=dl_data)),
                    A(T("more..."),
                      _href=ajaxurl,
                      _class="dl-pagination"),
                    _class="dl-navigation"))

        return dl
Exemple #5
0
    def aadata(self, totalrows, displayrows, id, draw, flist, stringify=True, action_col=None, **attr):
        """
            Method to render the data into a json object

            @param totalrows: The total rows in the unfiltered query.
            @param displayrows: The total rows in the filtered query.
            @param id: The id of the table for which this ajax call will
                       respond to.
            @param draw: An unaltered copy of draw sent from the client used
                          by dataTables as a draw count.
            @param flist: The list of fields
            @param attr: dictionary of attributes which can be passed in
                   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_totals: The number of record in each group.
                                    This will be displayed in parenthesis
                                    after the group title.
        """

        data = self.data
        if not flist:
            flist = self.colnames
        start = self.start
        end = self.end
        if action_col is None:
            action_col = attr.get("dt_action_col", 0)
        structure = {}
        aadata = []
        for i in xrange(start, end):
            row = data[i]
            details = []
            for field in flist:
                if field == "BULK":
                    details.append(
                        "<INPUT id='select%s' type='checkbox' class='bulkcheckbox'>" % row[flist[action_col]]
                    )
                else:
                    details.append(s3_unicode(row[field]))
            aadata.append(details)
        structure["dataTable_id"] = id
        structure["dataTable_filter"] = self.filterString
        structure["dataTable_groupTotals"] = attr.get("dt_group_totals", [])
        structure["dataTable_sort"] = self.orderby
        structure["data"] = aadata
        structure["recordsTotal"] = totalrows
        structure["recordsFiltered"] = displayrows
        structure["draw"] = draw
        if stringify:
            from gluon.serializers import json as jsons

            return jsons(structure)
        else:
            return structure
Exemple #6
0
def set_priority_js():
    """ Output json for priority field """

    wptable = s3db.cap_warning_priority

    rows = db(wptable).select(
        wptable.name, wptable.urgency, wptable.severity, wptable.certainty, wptable.color_code, orderby=wptable.name
    )

    from gluon.serializers import json as jsons

    p_settings = [(T(r.name), r.urgency, r.severity, r.certainty, r.color_code) for r in rows]

    priority_conf = """S3.cap_priorities=%s""" % jsons(p_settings)
    js_global = s3.js_global
    if not priority_conf in js_global:
        js_global.append(priority_conf)

    return
Exemple #7
0
    def configure(self, dashboard, context):
        """
            Controller for the widget configuration dialog

            @param dashboard: the calling S3Dashboard instance
            @param context: the S3DashboardContext

            @return: output dict for the view
        """

        response = current.response
        s3 = response.s3

        # Get the form fields from the widget class
        prototype = self.widget
        formfields = prototype.configure(self)

        # The current configuration as formdata
        formdata = dict(self.config)
        formdata["id"] = 0

        # Form buttons
        T = current.T
        submit_btn = INPUT(
            _class="tiny primary button submit-btn",
            _name="submit",
            _type="submit",
            _value=T("Submit"),
        )
        buttons = [submit_btn]

        # Construct the form
        settings = s3.crud
        formstyle = settings.formstyle
        form = SQLFORM.factory(*formfields,
                               record=formdata,
                               showid=False,
                               formstyle=formstyle,
                               table_name="config",
                               upload=s3.download_url,
                               separator="",
                               submit_button=settings.submit_button,
                               buttons=buttons)

        # Process the form
        formname = "config/%s" % self.agent_id
        if form.accepts(
                context.post_vars,
                current.session,
                onvalidation=prototype.validate_config,
                formname=formname,
                keepvalues=False,
                hideerror=False,
        ):

            # Get an updated config dict from the widget
            widget_config = self.config
            widget_id = widget_config.get("widget_id")

            new_config = prototype.accept_config(widget_config, form)

            # Pass new config to client via s3.popup_data
            popup_data = {"c": new_config}

            # Save the new config and add the new version key to the popup_data
            if dashboard:
                version = dashboard.config.save(context,
                                                {widget_id: new_config})
                if version:
                    popup_data["v"] = version

            # Using JSON serializer rather than raw json.dumps to catch T()'s
            from gluon.serializers import json as jsons
            s3.popup_data = jsons(popup_data)

            # Send a confirmation so the popup gets closed
            # (layout.html diverts to layout_popup.html with
            # "popup" request format + response.confirmation)
            response.confirmation = T("Configuration updated")

        # Set view (@todo: implement specific view?)
        response.view = "popup.html"

        return {# Context needed for layout.html to determine
                # the representation format:
                "r": context,
                "form": form,
                }
Exemple #8
0
    def html(
        self,
        start=None,
        limit=None,
        pagesize=None,
        rowsize=None,
        ajaxurl=None,
        empty=None,
        popup_url=None,
        popup_title=None,
    ):
        """
            Render list data as HTML (nested DIVs)

            @param start: index of the first item (in this page)
            @param limit: total number of available items
            @param pagesize: maximum number of items per page
            @param rowsize: number of items per row
            @param ajaxurl: the URL to Ajax-update the datalist
            @param popup_url: the URL for the modal used for the 'more'
                              button (=> we deactivate InfiniteScroll)
            @param popup_title: the title for the modal
        """

        T = current.T
        resource = self.resource
        list_fields = self.list_fields
        rfields = resource.resolve_selectors(list_fields)[0]

        list_id = self.list_id
        render = self.layout
        render_row = self.row_layout

        if not rowsize:
            rowsize = 1

        pkey = str(resource._id)

        records = self.records
        if records is not None:

            # Call prep if present
            if hasattr(render, "prep"):
                render.prep(resource, records)

            if current.response.s3.dl_no_header:
                items = []
            else:
                items = [DIV(T("Total Records: %(numrows)s") % \
                                {"numrows": self.total},
                             _class="dl-header",
                             _id="%s-header" % list_id,
                             )
                         ]

            if empty is None:
                empty = resource.crud.crud_string(resource.tablename,
                                                  "msg_no_match")
            empty = DIV(empty, _class="dl-empty")
            if self.total > 0:
                empty.update(_style="display:none")
            items.append(empty)

            row_idx = int(self.start / rowsize) + 1
            for group in self.groups(records, rowsize):
                row = []
                col_idx = 0
                for record in group:

                    if pkey in record:
                        item_id = "%s-%s" % (list_id, record[pkey])
                    else:
                        # template
                        item_id = "%s-[id]" % list_id

                    item = render(list_id, item_id, resource, rfields, record)
                    if hasattr(item, "add_class"):
                        _class = "dl-item dl-%s-cols dl-col-%s" % (rowsize,
                                                                   col_idx)
                        item.add_class(_class)
                    row.append(item)
                    col_idx += 1

                _class = "dl-row %s" % ((row_idx % 2) and "even" or "odd")
                if render_row:
                    row = render_row(list_id, resource, rowsize, row)
                    if hasattr(row, "add_class"):
                        row.add_class(_class)
                else:
                    row = DIV(row, _class=_class)

                items.append(row)
                row_idx += 1
        else:
            # template
            raise NotImplementedError

        dl = DIV(
            items,
            _class="dl",
            _id=list_id,
        )

        dl_data = {
            "startindex": start,
            "maxitems": limit,
            "totalitems": self.total,
            "pagesize": pagesize,
            "rowsize": rowsize,
            "ajaxurl": ajaxurl,
        }
        if popup_url:
            input_class = "dl-pagination"
            a_class = "s3_modal dl-more"
            #dl_data["popup_url"] = popup_url
            #dl_data["popup_title"] = popup_title
        else:
            input_class = "dl-pagination dl-scroll"
            a_class = "dl-more"
        from gluon.serializers import json as jsons
        dl_data = jsons(dl_data)
        dl.append(
            DIV(
                INPUT(
                    _type="hidden",
                    _class=input_class,
                    _value=dl_data,
                ),
                A(
                    T("more..."),
                    _href=popup_url or ajaxurl,
                    _class=a_class,
                    _title=popup_title,
                ),
                _class="dl-navigation",
            ))

        return dl
Exemple #9
0
    def aadata(self,
               totalrows,
               displayrows,
               id,
               draw,
               flist,
               stringify=True,
               action_col=None,
               **attr):
        """
            Method to render the data into a json object

            @param totalrows: The total rows in the unfiltered query.
            @param displayrows: The total rows in the filtered query.
            @param id: The id of the table for which this ajax call will
                       respond to.
            @param draw: An unaltered copy of draw sent from the client used
                          by dataTables as a draw count.
            @param flist: The list of fields
            @param attr: dictionary of attributes which can be passed in
                   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_totals: The number of record in each group.
                                    This will be displayed in parenthesis
                                    after the group title.
        """

        data = self.data
        if not flist:
            flist = self.colnames
        start = self.start
        end = self.end
        if action_col is None:
            action_col = attr.get("dt_action_col", 0)
        structure = {}
        aadata = []
        for i in xrange(start, end):
            row = data[i]
            details = []
            for field in flist:
                if field == "BULK":
                    details.append("<INPUT id='select%s' type='checkbox' class='bulkcheckbox'>" % \
                        row[flist[action_col]])
                else:
                    details.append(s3_unicode(row[field]))
            aadata.append(details)
        structure["dataTable_id"] = id
        structure["dataTable_filter"] = self.filterString
        structure["dataTable_groupTotals"] = attr.get("dt_group_totals", [])
        structure["dataTable_sort"] = self.orderby
        structure["data"] = aadata
        structure["recordsTotal"] = totalrows
        structure["recordsFiltered"] = displayrows
        structure["draw"] = draw
        if stringify:
            from gluon.serializers import json as jsons
            return jsons(structure)
        else:
            return structure
Exemple #10
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, 'individulal' 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, 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
Exemple #11
0
    def json(self,
             resource,
             start=None,
             limit=None,
             fields=None,
             orderby=None,
             represent=False,
             tooltip=None):
        """
            Export a resource as JSON

            @param resource: the resource to export from
            @param start: index of the first record to export
            @param limit: maximum number of records to export
            @param fields: list of field selectors for fields to include in
                           the export (None for all fields)
            @param orderby: ORDERBY expression
            @param represent: whether values should be represented
            @param tooltip: additional tooltip field, either a field selector
                            or an expression "f(k,v)" where f is a function
                            name that can be looked up from s3db, and k,v are
                            field selectors for the row, f will be called with
                            a list of tuples (k,v) for each row and is expected
                            to return a dict {k:tooltip} => used by
                            filterOptionsS3 to extract onhover-tooltips for
                            Ajax-update of options
        """

        if fields is None:
            # json_fields falls back to list_fields if not-defined.
            # If that's not defined either, it falls back to all readable fields in the table
            fields = resource.list_fields("json_fields", id_column=True)

        if orderby is None:
            orderby = resource.get_config("orderby", None)

        tooltip_function = None
        if tooltip:
            if type(tooltip) is list:
                tooltip = tooltip[-1]
            import re
            match = re.match("(\w+)\((\w+),(\w+)\)", tooltip)
            if match:
                function_name, kname, vname = match.groups()
                # Try to resolve the function name
                tooltip_function = current.s3db.get(function_name)
                if tooltip_function:
                    if kname not in fields:
                        fields.append(kname)
                    if vname not in fields:
                        fields.append(vname)
            else:
                if tooltip not in fields:
                    fields.append(tooltip)

        # Get the data
        _rows = resource.select(fields,
                                start=start,
                                limit=limit,
                                orderby=orderby,
                                represent=represent).rows

        # Simplify to plain fieldnames for fields in this table
        tn = "%s." % resource.tablename
        rows = []
        rappend = rows.append
        for _row in _rows:
            row = {}
            for f in _row:
                v = _row[f]
                if tn in f:
                    f = f.split(tn, 1)[1]
                row[f] = v
            rappend(row)

        if tooltip:
            if tooltip_function:
                # Resolve key and value names against the resource
                try:
                    krfield = resource.resolve_selector(kname)
                    vrfield = resource.resolve_selector(vname)
                except (AttributeError, SyntaxError):
                    import sys
                    current.log.error(sys.exc_info()[1])
                else:
                    # Extract key and value fields from each row and
                    # build options dict for function call
                    options = []
                    items = {}
                    for row in rows:
                        try:
                            k = krfield.extract(row)
                        except KeyError:
                            break
                        try:
                            v = vrfield.extract(row)
                        except KeyError:
                            break
                        items[k] = row
                        options.append((k, v))
                    # Call tooltip rendering function
                    try:
                        tooltips = tooltip_function(options)
                    except:
                        import sys
                        current.log.error(sys.exc_info()[1])
                    else:
                        # Add tooltips as "_tooltip" to the corresponding rows
                        if isinstance(tooltips, dict):
                            from s3utils import s3_unicode
                            for k, v in tooltips.items():
                                if k in items:
                                    items[k]["_tooltip"] = s3_unicode(v)

            else:
                # Resolve the tooltip field name against the resource
                try:
                    tooltip_rfield = resource.resolve_selector(tooltip)
                except (AttributeError, SyntaxError):
                    import sys
                    current.log.error(sys.exc_info()[1])
                else:
                    # Extract the tooltip field from each row
                    # and add it as _tooltip
                    from s3utils import s3_unicode
                    for row in rows:
                        try:
                            value = tooltip_rfield.extract(row)
                        except KeyError:
                            break
                        if value:
                            row["_tooltip"] = s3_unicode(value)

        # Return as JSON
        response = current.response
        if response:
            response.headers["Content-Type"] = "application/json"

        from gluon.serializers import json as jsons
        return jsons(rows)
Exemple #12
0
def tour_builder(output):
    """
         Helper function to attach a guided tour (if required) to the output
    """

    auth = current.auth
    db = current.db
    s3db = current.s3db
    request = current.request
    s3 = current.response.s3
    T = current.T

    req_vars = request.vars
    tour_id = req_vars.tour
    # Now see if the details are on the database for this user
    tour = None
    user_id = None
    if auth.is_logged_in():
        user_id = auth.s3_logged_in_person()
        # Find out if the user has done this tour before
        utable = s3db.tour_user
        uquery = (utable.person_id == user_id) & \
                (utable.tour_config_id == tour_id)
        tour = db(uquery).select(utable.id,
                                utable.completed,
                                utable.place,
                                utable.resume,
                                limitby=(0, 1)).first()
        # If the tour has just been started (from the menu) then
        # it may be necessary to redirect to a different controller
        # @todo: does place need to be changed to controller and function?
        if not req_vars.tour_running:
            if (tour and not tour.completed and tour.place != request.controller):
                redirect("%s?tour=%s" %(tour.resume, tour_id))

    # get the details from the database
    dtable = s3db.tour_details
    dquery = (dtable.tour_config_id == tour_id) &\
            (dtable.controller == request.controller) &\
            (dtable.function == request.function)
    details = db(dquery).select(dtable.args,
                               dtable.tip_title,
                               dtable.tip_details,
                               dtable.button,
                               dtable.tip_location,
                               dtable.html_id,
                               dtable.html_class,
                               dtable.datatable_id,
                               dtable.datatable_row,
                               dtable.redirect,
                               orderby=(dtable.posn)
                               )
#        tour_filename = os.path.join(request.folder,
#                                     "private",
#                                     "tour",
#                                     tour_name)
#        tour_file = open (tour_filename, "rb")
#        # now open the details of the guided_tour into a dictionary
#        import csv
#        tour_details = csv.DictReader(tour_file, skipinitialspace=True)
    # load the list of tour items in the html
    joyride_OL = OL(_id="joyrideID_1")
    pre_step_data = []
    post_step_data = []
    post_ride_data = []
    last_row = None
    last_used = None
    req_args = request.args
    cnt = -1
    for row in details:
        if row.args:
            args = row.args.split(",")
        else:
            args = []
        # if the page has a nested login form then "login" will be added to
        # the req_args list so it needs to be added to the args list as well
        if "login" in req_args:
            if "login" not in args:
                args.append("login")
        # The following will capture the actual id used for the req_arg
        # Example org/organisation/10, where 10 is the id from the database
        posn = 0
        for arg in args:
            if arg == "dt_id":
                args[posn] = req_args[posn]
            posn += 1
        # Now check that the tour url matches the current url
        if (args == req_args):
            cnt += 1 # number of records used in this part of the tour
            if row.datatable_id:
                dt_id = row.datatable_id
#                    cols = []
#                    if "DataTable_columns" in row:
#                        cols = row["DataTable_columns"].split(",")
                row_num = 0
                if row.datatable_row:
                    row_num = row.datatable_row
                # Now set this up for the pre-processor hook in joyride
                pre_step_data.append([cnt, dt_id, row_num])
            if row.redirect:
                redirect_row = row.redirect.split(",")
                if len(redirect_row) >= 3:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              args=redirect_row[2:],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    if "dt_id" in redirect_row[2]:
                        post_step_data.append([cnt, url, dt_id, row_num])
                elif len(redirect_row) == 2:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    post_step_data.append([cnt, url])
                else:
                    url = URL(c=redirect_row[0],vars={"tour_running":True,
                                                  "tour":tour_id})
                    post_step_data.append([cnt, url])
            extra = {}
            if row.html_id:
                extra["_data-id"] = row.html_id
            elif row.html_class:
                extra["_data-class"] = row.html_class
            if row.button:
                extra["_data-button"] = row.button
            else:
                extra["_data-button"] = "Next"
            if row.tip_location:
                extra["_data-options"] = "tipLocation:%s" % row.tip_location.lower()
            else:
                extra["_data-options"] = "tipLocation:right"
            joyride_OL.append(LI(H2(T(row.tip_title)),
                                 P(T(row.tip_details)),
                                    **extra
                                 )
                              )
            last_used = row
        last_row = row
    # The following redirect will be triggered if the user has moved away
    # from the tour, such as by clicking on a tab. However if a tab
    # is part of the tour we are unable to determine if they have moved
    # away or just visiting as part of the tour and so it will continue.
    if len(joyride_OL) == 0:
        del request.vars.tour
        redirect(URL(args=req_args,
                     vars=request.vars))
    if (user_id != None) and (last_row == last_used):
        # set up an AJAX call to record that the tour has been completed
        post_ride_data = [cnt, tour_id]
    joyride_div = DIV(joyride_OL,
                      _class="hidden")
    # Add the javascript configuration data
    from gluon.serializers import json as jsons
    if pre_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="prestep_data",
                                 _name="prestep_data",
                                 _value=jsons(pre_step_data))
                           )
    if post_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="poststep_data",
                                 _name="poststep_data",
                                 _value=jsons(post_step_data))
                           )
    if post_ride_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="postride_data",
                                 _name="postride_data",
                                 _value=jsons(post_ride_data))
                           )
    # Now add the details to the tour_user table
    if user_id != None:
        if tour == None:
            # this user has never done this tour before so create a new record
            utable.insert(person_id = user_id,
                          tour_config_id = tour_id,
                          place = request.controller,
                          resume = request.url)
        else:
            # the user has done some of this tour so update the record
            db(uquery).update(place = request.controller,
                             resume = request.url,
                             completed = False)

    output["joyride_div"] = joyride_div
    if s3.debug:
        appname = request.application
        s3.scripts.append("/%s/static/scripts/jquery.joyride.js" % appname)
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.js" % appname)
        s3.stylesheets.append("plugins/joyride.min.css")
    else:
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.min.js" % request.application)
        s3.stylesheets.append("plugins/joyride.css")
    return output
Exemple #13
0
def tour_builder(output):
    """
         Helper function to attach a guided tour (if required) to the output
    """

    auth = current.auth
    db = current.db
    s3db = current.s3db
    request = current.request
    s3 = current.response.s3
    T = current.T

    req_vars = request.vars
    tour_id = req_vars.tour
    # Now see if the details are on the database for this user
    tour = None
    user_id = None
    if auth.is_logged_in():
        user_id = auth.s3_logged_in_person()
        # Find out if the user has done this tour before
        utable = s3db.tour_user
        uquery = (utable.person_id == user_id) & \
                (utable.tour_config_id == tour_id)
        tour = db(uquery).select(utable.id,
                                utable.completed,
                                utable.place,
                                utable.resume,
                                limitby=(0, 1)).first()
        # If the tour has just been started (from the menu) then
        # it may be necessary to redirect to a different controller
        # @todo: does place need to be changed to controller and function?
        if not req_vars.tour_running:
            if (tour and not tour.completed and tour.place != request.controller):
                redirect("%s?tour=%s" %(tour.resume, tour_id))

    # get the details from the database
    dtable = s3db.tour_details
    dquery = (dtable.tour_config_id == tour_id) &\
            (dtable.controller == request.controller) &\
            (dtable.function == request.function)
    details = db(dquery).select(dtable.args,
                               dtable.tip_title,
                               dtable.tip_details,
                               dtable.button,
                               dtable.tip_location,
                               dtable.html_id,
                               dtable.html_class,
                               dtable.datatable_id,
                               dtable.datatable_row,
                               dtable.redirect,
                               orderby=(dtable.posn)
                               )
#        tour_filename = os.path.join(request.folder,
#                                     "private",
#                                     "tour",
#                                     tour_name)
#        tour_file = open (tour_filename, "rb")
#        # now open the details of the guided_tour into a dictionary
#        import csv
#        tour_details = csv.DictReader(tour_file, skipinitialspace=True)
    # load the list of tour items in the html
    joyride_OL = OL(_id="joyrideID_1")
    pre_step_data = []
    post_step_data = []
    post_ride_data = []
    last_row = None
    last_used = None
    req_args = request.args
    cnt = -1
    for row in details:
        if row.args:
            args = row.args.split(",")
        else:
            args = []
        # if the page has a nested login form then "login" will be added to
        # the req_args list so it needs to be added to the args list as well
        if "login" in req_args:
            if "login" not in args:
                args.append("login")
        # The following will capture the actual id used for the req_arg
        # Example org/organisation/10, where 10 is the id from the database
        posn = 0
        for arg in args:
            if arg == "dt_id":
                args[posn] = req_args[posn]
            posn += 1
        # Now check that the tour url matches the current url
        if (args == req_args):
            cnt += 1 # number of records used in this part of the tour
            if row.datatable_id:
                dt_id = row.datatable_id
#                    cols = []
#                    if "DataTable_columns" in row:
#                        cols = row["DataTable_columns"].split(",")
                row_num = 0
                if row.datatable_row:
                    row_num = row.datatable_row
                # Now set this up for the pre-processor hook in joyride
                pre_step_data.append([cnt, dt_id, row_num])
            if row.redirect:
                redirect_row = row.redirect.split(",")
                if len(redirect_row) >= 3:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              args=redirect_row[2:],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    if "dt_id" in redirect_row[2]:
                        post_step_data.append([cnt, url, dt_id, row_num])
                elif len(redirect_row) == 2:
                    url = URL(c=redirect_row[0],
                              f=redirect_row[1],
                              vars={"tour_running":True,
                                    "tour":tour_id}
                              )
                    post_step_data.append([cnt, url])
                else:
                    url = URL(c=redirect_row[0],vars={"tour_running":True,
                                                  "tour":tour_id})
                    post_step_data.append([cnt, url])
            extra = {}
            if row.html_id:
                extra["_data-id"] = row.html_id
            elif row.html_class:
                extra["_data-class"] = row.html_class
            if row.button:
                extra["_data-button"] = row.button
            else:
                extra["_data-button"] = "Next"
            if row.tip_location:
                extra["_data-options"] = "tipLocation:%s" % row.tip_location.lower()
            else:
                extra["_data-options"] = "tipLocation:right"
            joyride_OL.append(LI(H2(T(row.tip_title)),
                                 P(T(row.tip_details)),
                                    **extra
                                 )
                              )
            last_used = row
        last_row = row
    # The following redirect will be triggered if the user has moved away
    # from the tour, such as by clicking on a tab. However if a tab
    # is part of the tour we are unable to determine if they have moved
    # away or just visiting as part of the tour and so it will continue.
    if len(joyride_OL) == 0:
        del request.vars.tour
        redirect(URL(args=req_args,
                     vars=request.vars))
    if (user_id != None) and (last_row == last_used):
        # set up an AJAX call to record that the tour has been completed
        post_ride_data = [cnt, tour_id]
    joyride_div = DIV(joyride_OL,
                      _class="hidden")
    # Add the javascript configuration data
    from gluon.serializers import json as jsons
    if pre_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="prestep_data",
                                 _name="prestep_data",
                                 _value=jsons(pre_step_data))
                           )
    if post_step_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="poststep_data",
                                 _name="poststep_data",
                                 _value=jsons(post_step_data))
                           )
    if post_ride_data:
        joyride_div.append(INPUT(_type="hidden",
                                 _id="postride_data",
                                 _name="postride_data",
                                 _value=jsons(post_ride_data))
                           )
    # Now add the details to the tour_user table
    if user_id != None:
        if tour == None:
            # this user has never done this tour before so create a new record
            utable.insert(person_id = user_id,
                          tour_config_id = tour_id,
                          place = request.controller,
                          resume = request.url)
        else:
            # the user has done some of this tour so update the record
            db(uquery).update(place = request.controller,
                             resume = request.url,
                             completed = False)

    output["joyride_div"] = joyride_div
    if s3.debug:
        appname = request.application
        s3.scripts.append("/%s/static/scripts/jquery.joyride.js" % appname)
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.js" % appname)
        s3.stylesheets.append("plugins/joyride.min.css")
    else:
        s3.scripts.append("/%s/static/scripts/S3/s3.guidedtour.min.js" % request.application)
        s3.stylesheets.append("plugins/joyride.css")
    return output
Exemple #14
0
    def configure(self, dashboard, context):
        """
            Controller for the widget configuration dialog

            @param dashboard: the calling S3Dashboard instance
            @param context: the S3DashboardContext

            @return: output dict for the view
        """

        response = current.response
        s3 = response.s3

        # Get the form fields from the widget class
        prototype = self.widget
        formfields = prototype.configure(self)

        # The current configuration as formdata
        formdata = dict(self.config)
        formdata["id"] = 0

        # Form buttons
        T = current.T
        submit_btn = INPUT(_class = "tiny primary button submit-btn",
                           _name = "submit",
                           _type = "submit",
                           _value = T("Submit"),
                           )
        buttons = [submit_btn]

        # Construct the form
        settings = s3.crud
        formstyle = settings.formstyle
        form = SQLFORM.factory(*formfields,
                               record = formdata,
                               showid = False,
                               formstyle = formstyle,
                               table_name = "config",
                               upload = s3.download_url,
                               separator = "",
                               submit_button = settings.submit_button,
                               buttons = buttons)

        # Process the form
        formname = "config/%s" % self.agent_id
        if form.accepts(context.post_vars,
                        current.session,
                        onvalidation = prototype.validate_config,
                        formname = formname,
                        keepvalues = False,
                        hideerror = False,
                        ):

            # Get an updated config dict from the widget
            widget_config = self.config
            widget_id = widget_config.get("widget_id")

            new_config = prototype.accept_config(widget_config, form)

            # Pass new config to client via s3.popup_data
            popup_data = {"c": new_config}

            # Save the new config and add the new version key to the popup_data
            if dashboard:
                version = dashboard.config.save(context, {widget_id: new_config})
                if version:
                    popup_data["v"] = version

            # Using JSON serializer rather than raw json.dumps to catch T()'s
            from gluon.serializers import json as jsons
            s3.popup_data = jsons(popup_data)

            # Send a confirmation so the popup gets closed
            # (layout.html diverts to layout_popup.html with
            # "popup" request format + response.confirmation)
            response.confirmation = T("Configuration updated")

        # Set view (@todo: implement specific view?)
        response.view = "popup.html"

        return {# Context needed for layout.html to determine
                # the representation format:
                "r": context,
                "form": form,
                }
Exemple #15
0
    def json(self, resource,
             start=None,
             limit=None,
             fields=None,
             orderby=None,
             represent=False,
             tooltip=None):
        """
            Export a resource as JSON

            @param resource: the resource to export from
            @param start: index of the first record to export
            @param limit: maximum number of records to export
            @param fields: list of field selectors for fields to include in
                           the export (None for all fields)
            @param orderby: ORDERBY expression
            @param represent: whether values should be represented
            @param tooltip: additional tooltip field, either a field selector
                            or an expression "f(k,v)" where f is a function
                            name that can be looked up from s3db, and k,v are
                            field selectors for the row, f will be called with
                            a list of tuples (k,v) for each row and is expected
                            to return a dict {k:tooltip} => used by
                            filterOptionsS3 to extract onhover-tooltips for
                            Ajax-update of options
        """

        if fields is None:
            # json_fields falls back to list_fields if not-defined.
            # If that's not defined either, it falls back to all readable fields in the table
            fields = resource.list_fields("json_fields", id_column=True)

        if orderby is None:
            orderby = resource.get_config("orderby", None)

        tooltip_function = None
        if tooltip:
            if type(tooltip) is list:
                tooltip = tooltip[-1]
            import re
            match = re.match("(\w+)\((\w+),(\w+)\)", tooltip)
            if match:
                function_name, kname, vname = match.groups()
                # Try to resolve the function name
                tooltip_function = current.s3db.get(function_name)
                if tooltip_function:
                    if kname not in fields:
                        fields.append(kname)
                    if vname not in fields:
                        fields.append(vname)
            else:
                if tooltip not in fields:
                    fields.append(tooltip)

        # Get the data
        _rows = resource.select(fields,
                                start=start,
                                limit=limit,
                                orderby=orderby,
                                represent=represent).rows

        # Simplify to plain fieldnames for fields in this table
        tn = "%s." % resource.tablename
        rows = []
        rappend = rows.append
        for _row in _rows:
            row = {}
            for f in _row:
                v = _row[f]
                if tn in f:
                    f = f.split(tn, 1)[1]
                row[f] = v
            rappend(row)

        if tooltip:
            if tooltip_function:
                # Resolve key and value names against the resource
                try:
                    krfield = resource.resolve_selector(kname)
                    vrfield = resource.resolve_selector(vname)
                except (AttributeError, SyntaxError):
                    import sys
                    current.log.error(sys.exc_info()[1])
                else:
                    # Extract key and value fields from each row and
                    # build options dict for function call
                    options = []
                    items = {}
                    for row in rows:
                        try:
                            k = krfield.extract(row)
                        except KeyError:
                            break
                        try:
                            v = vrfield.extract(row)
                        except KeyError:
                            break
                        items[k] = row
                        options.append((k, v))
                    # Call tooltip rendering function
                    try:
                        tooltips = tooltip_function(options)
                    except:
                        import sys
                        current.log.error(sys.exc_info()[1])
                    else:
                        # Add tooltips as "_tooltip" to the corresponding rows
                        if isinstance(tooltips, dict):
                            from s3utils import s3_unicode
                            for k, v in tooltips.items():
                                if k in items:
                                    items[k]["_tooltip"] = s3_unicode(v)

            else:
                # Resolve the tooltip field name against the resource
                try:
                    tooltip_rfield = resource.resolve_selector(tooltip)
                except (AttributeError, SyntaxError):
                    import sys
                    current.log.error(sys.exc_info()[1])
                else:
                    # Extract the tooltip field from each row
                    # and add it as _tooltip
                    from s3utils import s3_unicode
                    for row in rows:
                        try:
                            value = tooltip_rfield.extract(row)
                        except KeyError:
                            break
                        if value:
                            row["_tooltip"] = s3_unicode(value)

        # Return as JSON
        response = current.response
        if response:
            response.headers["Content-Type"] = "application/json"

        from gluon.serializers import json as jsons
        return jsons(rows)
Exemple #16
0
    def html(self,
             start=None,
             limit=None,
             pagesize=None,
             rowsize=None,
             ajaxurl=None,
             empty=None,
             popup_url=None,
             popup_title=None,
             ):
        """
            Render list data as HTML (nested DIVs)

            @param start: index of the first item (in this page)
            @param limit: total number of available items
            @param pagesize: maximum number of items per page
            @param rowsize: number of items per row
            @param ajaxurl: the URL to Ajax-update the datalist
            @param empty: message to display if the list is empty
            @param popup_url: the URL for the modal used for the 'more'
                              button (=> we deactivate InfiniteScroll)
            @param popup_title: the title for the modal
        """

        T = current.T
        resource = self.resource
        list_fields = self.list_fields
        rfields = resource.resolve_selectors(list_fields)[0]

        list_id = self.list_id
        render = self.layout
        render_row = self.row_layout

        if not rowsize:
            rowsize = 1

        pkey = str(resource._id)

        records = self.records
        if records is not None:

            # Call prep if present
            if hasattr(render, "prep"):
                render.prep(resource, records)

            if current.response.s3.dl_no_header:
                items = []
            else:
                items = [DIV(T("Total Records: %(numrows)s") % \
                                {"numrows": self.total},
                             _class="dl-header",
                             _id="%s-header" % list_id,
                             )
                         ]

            if empty is None:
                empty = resource.crud.crud_string(resource.tablename,
                                                  "msg_no_match")
            empty = DIV(empty, _class="dl-empty")
            if self.total > 0:
                empty.update(_style="display:none")
            items.append(empty)

            row_idx = int(self.start / rowsize) + 1
            for group in self.groups(records, rowsize):
                row = []
                col_idx = 0
                for record in group:

                    if pkey in record:
                        item_id = "%s-%s" % (list_id, record[pkey])
                    else:
                        # template
                        item_id = "%s-[id]" % list_id

                    item = render(list_id,
                                  item_id,
                                  resource,
                                  rfields,
                                  record)
                    if hasattr(item, "add_class"):
                        _class = "dl-item dl-%s-cols dl-col-%s" % (rowsize, col_idx)
                        item.add_class(_class)
                    row.append(item)
                    col_idx += 1

                _class = "dl-row %s" % ((row_idx % 2) and "even" or "odd")
                if render_row:
                    row = render_row(list_id,
                                     resource,
                                     rowsize,
                                     row)
                    if hasattr(row, "add_class"):
                        row.add_class(_class)
                else:
                    row = DIV(row, _class=_class)

                items.append(row)
                row_idx += 1
        else:
            # template
            raise NotImplementedError

        dl = DIV(items,
                 _class="dl",
                 _id=list_id,
                 )

        dl_data = {"startindex": start,
                   "maxitems": limit,
                   "totalitems": self.total,
                   "pagesize": pagesize,
                   "rowsize": rowsize,
                   "ajaxurl": ajaxurl,
                   }
        if popup_url:
            input_class = "dl-pagination"
            a_class = "s3_modal dl-more"
            #dl_data["popup_url"] = popup_url
            #dl_data["popup_title"] = popup_title
        else:
            input_class = "dl-pagination dl-scroll"
            a_class = "dl-more"
        from gluon.serializers import json as jsons
        dl_data = jsons(dl_data)
        dl.append(DIV(INPUT(_type="hidden",
                            _class=input_class,
                            _value=dl_data,
                            ),
                      A(T("more..."),
                        _href = popup_url or ajaxurl,
                        _class = a_class,
                        _title = popup_title,
                        ),
                      _class="dl-navigation",
                      ))

        return dl
Exemple #17
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