Пример #1
0
    def __init__(self, report, show_totals=True, **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Table header --------------------------------------------------------
        #

        # Layer titles
        labels = []
        get_mname = S3Report.mname
        for field_name, method in layers:
            label = get_label(rfields, field_name, tablename, "fact")
            mname = get_mname(method)
            if not labels:
                m = method == "list" and get_mname("count") or mname
                layer_label = "%s (%s)" % (label, m)
            labels.append("%s (%s)" % (label, mname))
        layers_title = TH(" / ".join(labels))

        # Columns field title
        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Rows field title
        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")

        headers = TR(rows_title)
        add_header = headers.append

        # Column headers
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Row totals header
        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))

        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------
        #

        tbody = TBODY()
        add_row = tbody.append

        # lookup table for cell list values
        cell_lookup_table = {} # {{}, {}}

        cells = report.cell
        rvals = report.row

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = "-"
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        add_value(", ".join(l))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    # hold the references
                    layer_ids = []
                    # get previous lookup values for this layer
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        rfield = rfields[f]
                        field = rfield.field
                        colname = rfield.colname
                        has_fk = field is not None and s3_has_foreign_key(field)
                        for id in cell.records:
                            # cell.records == [#, #, #]

                            record = report.records[id]
                            try:
                                fvalue = record[colname]
                            except AttributeError:
                                fvalue = None

                            if fvalue is not None:
                                if has_fk:
                                    if not isinstance(fvalue, list):
                                        fvalue = [fvalue]
                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk not in layer_ids:
                                            layer_ids.append(fk)
                                            layer_values[fk] = str(field.represent(fk))
                                else:
                                    if id not in layer_ids:
                                        layer_ids.append(id)
                                        layer_values[id] = s3_unicode(represent(f, fvalue))

                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                vals = " / ".join(vals)

                if any(cell_ids):
                    cell_attr = {
                        "_data-records": cell_ids
                    }
                    vals = (A(_class="report-cell-zoom"), vals)
                else:
                    cell_attr = {}

                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer --------------------------------------------------------
        #
        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")

        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals
        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))

        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------
        #
        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data ----------------------------------------------------------
        #
        drows = dcols = None
        BY = T("by")
        top = self._top
        if rows and row_titles and row_totals:
            drows = top(zip(row_titles, row_totals))
        if cols and col_titles and col_totals:
            dcols = top(zip(col_titles, col_totals))
        row_label = "%s %s" % (BY, str(row_label))
        if col_label:
            col_label = "%s %s" % (BY, str(col_label))
        layer_label=str(layer_label)

        json_data = json.dumps(dict(rows=drows,
                                    cols=dcols,
                                    data=report.compact(10, represent=True),
                                    row_label=row_label,
                                    col_label=col_label,
                                    layer_label=layer_label,
                                    cell_lookup_table=cell_lookup_table
                                   ))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #2
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)
Пример #3
0
    def __init__(self,
                 report,
                 show_totals=True,
                 url=None,
                 filter_query=None,
                 **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Layer titles --------------------------------------------------------

        # Get custom labels from report options
        layer_labels = Storage()
        report_options = resource.get_config("report_options", None)
        if report_options and "fact" in report_options:
            layer_opts = report_options["fact"]
            for item in layer_opts:
                if isinstance(item, (tuple, list)) and len(item) == 3:
                    if not "." in item[0].split("$")[0]:
                        item = ("%s.%s" % (resource.alias, item[0]),
                                item[1],
                                item[2])
                    layer_labels[(item[0], item[1])] = item[2]
                    
        labels = []
        get_mname = S3Report.mname

        for layer in layers:
            if layer in layer_labels:
                # Custom label
                label = layer_labels[layer]
                if not labels:
                    layer_label = label
                labels.append(s3_unicode(label))
            else:
                # Construct label from field-label and method
                label = get_label(rfields, layer[0], tablename, "fact")
                mname = get_mname(layer[1])
                if not labels:
                    m = layer[1] == "list" and get_mname("count") or mname
                    layer_label = "%s (%s)" % (label, m)
                labels.append("%s (%s)" % (label, mname))

        layers_title = TH(" / ".join(labels))

        # Columns field title -------------------------------------------------

        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Rows field title ----------------------------------------------------

        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")
        headers = TR(rows_title)

        # Column headers ------------------------------------------------------

        add_header = headers.append
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Row totals header ---------------------------------------------------

        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))
        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------

        tbody = TBODY()
        add_row = tbody.append

        # lookup table for cell list values
        cell_lookup_table = {} # {{}, {}}

        cells = report.cell
        rvals = report.row

        cell_vals = Storage()

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = ["-"]
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        #add_value(", ".join(l))
                        add_value(UL([LI(v) for v in l]))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    # hold the references
                    layer_ids = []
                    # get previous lookup values for this layer
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        rfield = rfields[f]
                        field = rfield.field
                        colname = rfield.colname
                        has_fk = field is not None and s3_has_foreign_key(field)
                        for id in cell.records:
                            # cell.records == [#, #, #]

                            record = report.records[id]
                            try:
                                fvalue = record[colname]
                            except AttributeError:
                                fvalue = None

                            if fvalue is not None:
                                if has_fk:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk is not None and fk not in layer_ids:
                                            layer_ids.append(int(fk))
                                            layer_values[fk] = s3_unicode(field.represent(fk))
                                else:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    for val in fvalue:
                                        if val is not None:
                                            if val not in cell_vals:
                                                next_id = len(cell_vals)
                                                cell_vals[val] = next_id
                                                layer_ids.append(next_id)
                                                layer_values[next_id] = s3_unicode(represent(f, val))
                                            else:
                                                prev_id = cell_vals[val]
                                                if prev_id not in layer_ids:
                                                    layer_ids.append(prev_id)

                                    #if id is not None and id not in layer_ids:
                                        #layer_ids.append(int(id))
                                        #layer_values[id] = s3_unicode(represent(f, fvalue))

                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                # @todo: with multiple layers - show the first, hide the rest
                #        + render layer selector in the layer title corner to
                #        + switch between layers
                #        OR: give every layer a title row (probably better method)
                vals = [DIV(v, _class="report-cell-value") for v in vals]

                if any(cell_ids):
                    cell_attr = {"_data-records": cell_ids}
                    vals.append(DIV(_class="report-cell-zoom"))
                else:
                    cell_attr = {}

                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer --------------------------------------------------------

        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")
        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals -------------------------------------------------------

        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total ---------------------------------------------------------

        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))
        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------

        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data ----------------------------------------------------------

        layer_label = s3_unicode(layer_label)
        BY = T("by")
        row_label = "%s %s" % (BY, s3_unicode(row_label))
        if col_label:
            col_label = "%s %s" % (BY, s3_unicode(col_label))
        if filter_query and hasattr(filter_query, "serialize_url"):
            filter_vars = filter_query.serialize_url(resource=report.resource)
        else:
            filter_vars = {}
        hide_opts = current.deployment_settings.get_ui_hide_report_options()
        json_data = json.dumps(dict(t=layer_label,
                                    x=col_label,
                                    y=row_label,
                                    r=report.rows,
                                    c=report.cols,
                                    d=report.compact(n=50, represent=True),
                                    u=url,
                                    f=filter_vars,
                                    h=hide_opts,
                                    cell_lookup_table=cell_lookup_table))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #4
0
    def __init__(self,
                 report,
                 show_totals=True,
                 url=None,
                 filter_query=None,
                 **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param show_totals: show totals for rows and columns
            @param url: link cells to this base-URL
            @param filter_query: use this S3ResourceQuery with the base-URL
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Layer titles:

        # Get custom labels from report options
        layer_labels = Storage()
        report_options = resource.get_config("report_options", None)
        if report_options and "fact" in report_options:
            layer_opts = report_options["fact"]
            for item in layer_opts:
                if isinstance(item, (tuple, list)) and len(item) == 3:
                    if not "." in item[0].split("$")[0]:
                        item = ("%s.%s" % (resource.alias, item[0]), item[1],
                                item[2])
                    layer_labels[(item[0], item[1])] = item[2]

        labels = []
        get_mname = S3Report.mname

        for layer in layers:
            if layer in layer_labels:
                # Custom label
                label = layer_labels[layer]
                if not labels:
                    layer_label = label
                labels.append(s3_unicode(label))
            else:
                # Construct label from field-label and method
                label = get_label(rfields, layer[0], tablename, "fact")
                mname = get_mname(layer[1])
                if not labels:
                    m = layer[1] == "list" and get_mname("count") or mname
                    layer_label = "%s (%s)" % (label, m)
                labels.append("%s (%s)" % (label, mname))

        layers_title = TH(" / ".join(labels))

        # Columns field title
        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Sort dimensions:

        cells = report.cell

        def sortdim(dim, items):
            """ Sort a dimension """

            rfield = rfields[dim]
            if not rfield:
                return
            ftype = rfield.ftype
            sortby = "value"
            if ftype == "integer":
                requires = rfield.requires
                if isinstance(requires, (tuple, list)):
                    requires = requires[0]
                if isinstance(requires, IS_EMPTY_OR):
                    requires = requires.other
                if isinstance(requires, IS_IN_SET):
                    sortby = "text"
            elif ftype[:9] == "reference":
                sortby = "text"
            items.sort(key=lambda item: item[0][sortby])

        # Sort rows
        rvals = report.row
        rows_list = []
        for i in xrange(numrows):
            row = rvals[i]
            # Add representation value of the row header
            row["text"] = represent(rows, row.value)
            rows_list.append((row, cells[i]))
        sortdim(rows, rows_list)

        # Sort columns
        cvals = report.col
        cols_list = []
        for j in xrange(numcols):
            column = cvals[j]
            column["text"] = represent(cols, column.value)
            cols_list.append((column, j))
        sortdim(cols, cols_list)

        # Build the column headers:

        # Header for the row-titles column
        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")
        headers = TR(rows_title)

        add_header = headers.append

        # Headers for the cell columns
        for j in xrange(numcols):
            v = cols_list[j][0].text
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Header for the row-totals column
        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))

        thead = THEAD(titles, headers)

        # Render the table body:

        tbody = TBODY()
        add_row = tbody.append

        # Lookup table for cell list values
        cell_lookup_table = {}  # {{}, {}}
        cell_vals = Storage()

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rows_list[i][0]
            v = row["text"]
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            row_cells = rows_list[i][1]

            # Result cells
            for j in xrange(numcols):

                cell_idx = cols_list[j][1]
                cell = row_cells[cell_idx]

                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = ["-"]
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        #add_value(", ".join(l))
                        add_value(UL([LI(v) for v in l]))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    layer_ids = []
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        rfield = rfields[f]
                        field = rfield.field
                        colname = rfield.colname
                        has_fk = field is not None and s3_has_foreign_key(
                            field)
                        for id in cell.records:

                            record = report.records[id]
                            try:
                                fvalue = record[colname]
                            except AttributeError:
                                fvalue = None

                            if fvalue is not None:
                                if has_fk:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk is not None and fk not in layer_ids:
                                            layer_ids.append(int(fk))
                                            layer_values[fk] = s3_unicode(
                                                field.represent(fk))
                                else:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    for val in fvalue:
                                        if val is not None:
                                            if val not in cell_vals:
                                                next_id = len(cell_vals)
                                                cell_vals[val] = next_id
                                                layer_ids.append(next_id)
                                                layer_values[
                                                    next_id] = s3_unicode(
                                                        represent(f, val))
                                            else:
                                                prev_id = cell_vals[val]
                                                if prev_id not in layer_ids:
                                                    layer_ids.append(prev_id)

                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                vals = [DIV(v, _class="report-cell-value") for v in vals]
                if any(cell_ids):
                    cell_attr = {"_data-records": cell_ids}
                    vals.append(DIV(_class="report-cell-zoom"))
                else:
                    cell_attr = {}
                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer:

        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")
        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals
        for j in xrange(numcols):
            cell_idx = cols_list[j][1]
            col = report.col[cell_idx]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))
        tfoot = TFOOT(col_total)

        # Wrap up:

        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data:

        layer_label = s3_unicode(layer_label)
        BY = T("by")
        row_label = "%s %s" % (BY, s3_unicode(row_label))
        if col_label:
            col_label = "%s %s" % (BY, s3_unicode(col_label))
        if filter_query and hasattr(filter_query, "serialize_url"):
            filter_vars = filter_query.serialize_url(resource=report.resource)
        else:
            filter_vars = {}
        hide_opts = current.deployment_settings.get_ui_hide_report_options()
        json_data = json.dumps(
            dict(t=layer_label,
                 x=col_label,
                 y=row_label,
                 r=report.rows,
                 c=report.cols,
                 d=report.compact(n=50, represent=True),
                 u=url,
                 f=filter_vars,
                 h=hide_opts,
                 cell_lookup_table=cell_lookup_table))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #5
0
    def __init__(self, report, **attributes):
        """
            Constructor

            @param report: the S3Report
            @param attributes: the HTML attributes for the table
        """

        manager = current.manager

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        lfields = report.lfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(lfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Table header --------------------------------------------------------
        #
        # @todo: make class and move into CSS:
        _style = "border:1px solid #cccccc; font-weight:bold;"

        # Layer titles
        labels = []
        get_mname = report.mname
        for field, method in layers:
            label = get_label(lfields, field, tablename, "report_fact")
            mname = get_mname(method)
            if not labels:
                m = method == "list" and get_mname("count") or mname
                layer_label = "%s (%s)" % (label, m)
            labels.append("%s (%s)" % (label, mname))
        layers_title = TD(" / ".join(labels), _style=_style)

        # Columns field title
        if cols:
            col_label = get_label(lfields, cols, tablename, "report_cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TD(col_label, _style=_style, _colspan=_colspan)

        titles = TR(layers_title, cols_title)

        # Rows field title
        row_label = get_label(lfields, rows, tablename, "report_rows")
        rows_title = TH(row_label, _style=_style)

        headers = TR(rows_title)
        add_header = headers.append

        # Column headers
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(str(v)))
            colhdr = TH(v, _style=_style)
            add_header(colhdr)

        # Row totals header
        if cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal"))

        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------
        #
        tbody = TBODY()
        add_row = tbody.append

        cells = report.cell
        rvals = report.row
        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(str(v)))
            rowhdr = TD(DIV(v))
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                add_value = vals.append
                for layer in layers:
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = "-"
                        else:
                            l = str(value)
                        add_value(", ".join(l))
                    else:
                        add_value(str(value))
                vals = " / ".join(vals)
                add_cell(TD(DIV(vals)))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if cols is not None:
                add_cell(TD(DIV(totals)))

            add_row(tr)

        # Table footer --------------------------------------------------------
        #
        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")

        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TD(TOTAL, _class="totals_header"))

        # Column totals
        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(DIV(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(DIV(grand_totals)))

        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------
        #
        append = components.append
        append(thead)
        append(tbody)
        append(tfoot)

        # Chart data ----------------------------------------------------------
        #
        drows = dcols = None
        BY = T("by")
        top = self._top
        if rows and row_titles and row_totals:
            drows = top(zip(row_titles, row_totals))
        if cols and col_titles and col_totals:
            dcols = top(zip(col_titles, col_totals))
        row_label = "%s %s" % (BY, str(row_label))
        col_label = "%s %s" % (BY, str(col_label))
        layer_label=str(layer_label)
        json_data = json.dumps(dict(rows=drows,
                                    cols=dcols,
                                    row_label=row_label,
                                    col_label=col_label,
                                    layer_label=layer_label
                                   ))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #6
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("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 (subscriber must be logged in)
        auth = current.auth
        pe_id = subscription["pe_id"]
        if not auth.s3_logged_in() or auth.user.pe_id != 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()

        from string import Template
        subject = Template(subject).safe_substitute(S="%(systemname)s",
                                                    s="%(systemname_short)s",
                                                    r="%(resource)s")
        subject = subject % 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(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

            # 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)
            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)
Пример #7
0
    def __init__(self, report, show_totals=True, **attributes):
        """
            Constructor

            @param report: the S3Report
            @param attributes: the HTML attributes for the table
        """

        manager = current.manager

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        lfields = report.lfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(lfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Table header --------------------------------------------------------
        #
        # @todo: make class and move into CSS:
        _style = "border:1px solid #cccccc; font-weight:bold;"

        # Layer titles
        labels = []
        get_mname = report.mname
        for field, method in layers:
            label = get_label(lfields, field, tablename, "report_fact")
            mname = get_mname(method)
            if not labels:
                m = method == "list" and get_mname("count") or mname
                layer_label = "%s (%s)" % (label, m)
            labels.append("%s (%s)" % (label, mname))
        layers_title = TD(" / ".join(labels), _style=_style)

        # Columns field title
        if cols:
            col_label = get_label(lfields, cols, tablename, "report_cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TD(col_label, _style=_style, _colspan=_colspan)

        titles = TR(layers_title, cols_title)

        # Rows field title
        row_label = get_label(lfields, rows, tablename, "report_rows")
        rows_title = TH(row_label, _style=_style)

        headers = TR(rows_title)
        add_header = headers.append

        # Column headers
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _style=_style)
            add_header(colhdr)

        # Row totals header
        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal"))

        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------
        #
        tbody = TBODY()
        add_row = tbody.append

        cells = report.cell
        rvals = report.row
        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(DIV(v))
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                add_value = vals.append
                for layer in layers:
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = "-"
                        else:
                            l = unicode(value)
                        add_value(", ".join(l))
                    else:
                        add_value(unicode(value))
                vals = " / ".join(vals)
                add_cell(TD(DIV(vals)))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(DIV(totals)))

            add_row(tr)

        # Table footer --------------------------------------------------------
        #
        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")

        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TD(TOTAL, _class="totals_header"))

        # Column totals
        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(DIV(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(DIV(grand_totals)))

        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------
        #
        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data ----------------------------------------------------------
        #
        drows = dcols = None
        BY = T("by")
        top = self._top
        if rows and row_titles and row_totals:
            drows = top(zip(row_titles, row_totals))
        if cols and col_titles and col_totals:
            dcols = top(zip(col_titles, col_totals))
        row_label = "%s %s" % (BY, str(row_label))
        col_label = "%s %s" % (BY, str(col_label))
        layer_label = str(layer_label)
        json_data = json.dumps(
            dict(rows=drows,
                 cols=dcols,
                 row_label=row_label,
                 col_label=col_label,
                 layer_label=layer_label))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #8
0
    def __init__(self,
                 report,
                 show_totals=True,
                 url=None,
                 filter_query=None,
                 **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param show_totals: show totals for rows and columns
            @param url: link cells to this base-URL
            @param filter_query: use this S3ResourceQuery with the base-URL
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Layer titles:

        # Get custom labels from report options
        layer_labels = Storage()
        report_options = resource.get_config("report_options", None)
        if report_options and "fact" in report_options:
            layer_opts = report_options["fact"]
            for item in layer_opts:
                if isinstance(item, (tuple, list)) and len(item) == 3:
                    if not "." in item[0].split("$")[0]:
                        item = ("%s.%s" % (resource.alias, item[0]),
                                item[1],
                                item[2])
                    layer_labels[(item[0], item[1])] = item[2]

        labels = []
        get_mname = S3Report.mname

        for layer in layers:
            if layer in layer_labels:
                # Custom label
                label = layer_labels[layer]
                if not labels:
                    layer_label = label
                labels.append(s3_unicode(label))
            else:
                # Construct label from field-label and method
                label = get_label(rfields, layer[0], tablename, "fact")
                mname = get_mname(layer[1])
                if not labels:
                    m = layer[1] == "list" and get_mname("count") or mname
                    layer_label = "%s (%s)" % (label, m)
                labels.append("%s (%s)" % (label, mname))

        layers_title = TH(" / ".join(labels))

        # Columns field title
        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Sort dimensions:

        cells = report.cell

        def sortdim(dim, items):
            """ Sort a dimension """

            rfield = rfields[dim]
            if not rfield:
                return
            ftype = rfield.ftype
            sortby = "value"
            if ftype == "integer":
                requires = rfield.requires
                if isinstance(requires, (tuple, list)):
                    requires = requires[0]
                if isinstance(requires, IS_EMPTY_OR):
                    requires = requires.other
                if isinstance(requires, IS_IN_SET):
                    sortby = "text"
            elif ftype[:9] == "reference":
                sortby = "text"
            items.sort(key=lambda item: item[0][sortby])

        # Sort rows
        rvals = report.row
        rows_list = []
        for i in xrange(numrows):
            row = rvals[i]
            # Add representation value of the row header
            row["text"] = represent(rows, row.value)
            rows_list.append((row, cells[i]))
        sortdim(rows, rows_list)

        # Sort columns
        cvals = report.col
        cols_list = []
        for j in xrange(numcols):
            column = cvals[j]
            column["text"] = represent(cols, column.value)
            cols_list.append((column, j))
        sortdim(cols, cols_list)

        # Build the column headers:

        # Header for the row-titles column
        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")
        headers = TR(rows_title)

        add_header = headers.append

        # Headers for the cell columns
        for j in xrange(numcols):
            v = cols_list[j][0].text
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Header for the row-totals column
        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))

        thead = THEAD(titles, headers)

        # Render the table body:

        tbody = TBODY()
        add_row = tbody.append

        # Lookup table for cell list values
        cell_lookup_table = {} # {{}, {}}
        cell_vals = Storage()

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rows_list[i][0]
            v = row["text"]
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            row_cells = rows_list[i][1]

            # Result cells
            for j in xrange(numcols):

                cell_idx = cols_list[j][1]
                cell = row_cells[cell_idx]

                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = ["-"]
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        #add_value(", ".join(l))
                        add_value(UL([LI(v) for v in l]))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    layer_ids = []
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        rfield = rfields[f]
                        field = rfield.field
                        colname = rfield.colname
                        has_fk = field is not None and s3_has_foreign_key(field)
                        for id in cell.records:

                            record = report.records[id]
                            try:
                                fvalue = record[colname]
                            except AttributeError:
                                fvalue = None

                            if fvalue is not None:
                                if has_fk:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk is not None and fk not in layer_ids:
                                            layer_ids.append(int(fk))
                                            layer_values[fk] = s3_unicode(field.represent(fk))
                                else:
                                    if type(fvalue) is not list:
                                        fvalue = [fvalue]
                                    for val in fvalue:
                                        if val is not None:
                                            if val not in cell_vals:
                                                next_id = len(cell_vals)
                                                cell_vals[val] = next_id
                                                layer_ids.append(next_id)
                                                layer_values[next_id] = s3_unicode(represent(f, val))
                                            else:
                                                prev_id = cell_vals[val]
                                                if prev_id not in layer_ids:
                                                    layer_ids.append(prev_id)

                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                vals = [DIV(v, _class="report-cell-value") for v in vals]
                if any(cell_ids):
                    cell_attr = {"_data-records": cell_ids}
                    vals.append(DIV(_class="report-cell-zoom"))
                else:
                    cell_attr = {}
                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer:

        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")
        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals
        for j in xrange(numcols):
            cell_idx = cols_list[j][1]
            col = report.col[cell_idx]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))
        tfoot = TFOOT(col_total)

        # Wrap up:

        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data:

        layer_label = s3_unicode(layer_label)
        BY = T("by")
        row_label = "%s %s" % (BY, s3_unicode(row_label))
        if col_label:
            col_label = "%s %s" % (BY, s3_unicode(col_label))
        if filter_query and hasattr(filter_query, "serialize_url"):
            filter_vars = filter_query.serialize_url(resource=report.resource)
        else:
            filter_vars = {}
        hide_opts = current.deployment_settings.get_ui_hide_report_options()
        json_data = json.dumps(dict(t=layer_label,
                                    x=col_label,
                                    y=row_label,
                                    r=report.rows,
                                    c=report.cols,
                                    d=report.compact(n=50, represent=True),
                                    u=url,
                                    f=filter_vars,
                                    h=hide_opts,
                                    cell_lookup_table=cell_lookup_table))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #9
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("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"]))

        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 (subscriber must be logged in)
        auth = current.auth
        pe_id = subscription["pe_id"]
        if not auth.s3_logged_in() or auth.user.pe_id != pe_id:
            r.unauthorised()

        # Last check time
        last_check_time = current.xml.decode_iso_datetime(
            subscription["last_check_time"])

        # Fields to report
        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)

        # Render and send the messages
        settings = current.deployment_settings

        join = lambda *f: os.path.join(current.request.folder, *f)
        theme = settings.get_template()
        send = current.msg.send_by_pe_id

        success = False
        errors = []

        # Email format
        email_format = subscription["email_format"]
        if not email_format:
            email_format = settings.get_msg_notification_email_format()

        # Pre-render the contents for the view
        contents = {}
        if email_format == "html" and "EMAIL" in methods:
            contents["html"] = cls._pre_render(resource, data, notify_on,
                                               last_check_time, "html")
            contents["default"] = contents["html"]
        if email_format != "html" or "EMAIL" not in methods or len(
                methods) > 1:
            contents["text"] = cls._pre_render(resource, data, notify_on,
                                               last_check_time, "text")
            contents["default"] = contents["text"]

        # Add human-readable representation of the filter query
        filter_query = subscription.get("filter_query")
        for f, c in contents.iteritems():
            c["filter_query"] = filter_query

        # Subject line
        get_config = resource.get_config
        subject = get_config("notification_subject",
                             settings.get_msg_notification_subject())
        from string import Template
        subject = Template(subject).safe_substitute(s="%(system)s",
                                                    r="%(resource)s")
        subject = subject % contents["default"]

        # Helper function to find templates from a priority list
        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

        prefix = resource.get_config("notification_prefix", "notify")
        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 theme != "default":
                path = join("private", "templates", theme, "views", "msg")
                template = get_template(path, filenames)
            if template is None:
                path = join("views", "msg")
                template = get_template(path, filenames)
            if template is None:
                template = StringIO(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

            # Send the message
            #_debug("Sending message per %s" % method)
            #_debug(message)
            try:
                sent = send(pe_id,
                            subject=s3_truncate(subject, 64),
                            message=message,
                            pr_message_method=method,
                            system_generated=True)
            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)
Пример #10
0
    def __init__(self, report, show_totals=True, **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Table header --------------------------------------------------------
        #

        # Layer titles
        labels = []
        get_mname = S3Report.mname
        for field_name, method in layers:
            label = get_label(rfields, field_name, tablename, "fact")
            mname = get_mname(method)
            if not labels:
                m = method == "list" and get_mname("count") or mname
                layer_label = "%s (%s)" % (label, m)
            labels.append("%s (%s)" % (label, mname))
        layers_title = TH(" / ".join(labels))

        # Columns field title
        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Rows field title
        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")

        headers = TR(rows_title)
        add_header = headers.append

        # Column headers
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Row totals header
        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))

        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------
        #

        tbody = TBODY()
        add_row = tbody.append

        # lookup table for cell list values
        cell_lookup_table = {} # {{}, {}}

        cells = report.cell
        rvals = report.row

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = "-"
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        add_value(", ".join(l))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    # hold the references
                    layer_ids = []
                    # get previous lookup values for this layer
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        for id in cell.records:
                            # cell.records == [#, #, #]
                            field = rfields[f].field
                            record = report.records[id]

                            if field.tablename in record:
                                fvalue = record[field.tablename][field.name]
                            else:
                                fvalue = record[field.name]

                            if fvalue is not None:
                                if s3_has_foreign_key(field):
                                    if not isinstance(fvalue, list):
                                        fvalue = [fvalue]

                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk not in layer_ids:
                                            layer_ids.append(fk)
                                            layer_values[fk] = str(field.represent(fk))
                                else:
                                    if id not in layer_ids:
                                        layer_ids.append(id)
                                        layer_values[id] = s3_unicode(represent(f, fvalue))


                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                vals = " / ".join(vals)

                if any(cell_ids):
                    cell_attr = {
                        "_data-records": cell_ids
                    }
                    vals = (A(_class="report-cell-zoom"), vals)
                else:
                    cell_attr = {}

                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer --------------------------------------------------------
        #
        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")

        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals
        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total
        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))

        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------
        #
        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data ----------------------------------------------------------
        #
        drows = dcols = None
        BY = T("by")
        top = self._top
        if rows and row_titles and row_totals:
            drows = top(zip(row_titles, row_totals))
        if cols and col_titles and col_totals:
            dcols = top(zip(col_titles, col_totals))
        row_label = "%s %s" % (BY, str(row_label))
        if col_label:
            col_label = "%s %s" % (BY, str(col_label))
        layer_label=str(layer_label)

        json_data = json.dumps(dict(rows=drows,
                                    cols=dcols,
                                    row_label=row_label,
                                    col_label=col_label,
                                    layer_label=layer_label,
                                    cell_lookup_table=cell_lookup_table
                                   ))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #11
0
    def __init__(self,
                 report,
                 show_totals=True,
                 url=None,
                 filter_query=None,
                 **attributes):
        """
            Constructor

            @param report: the S3Pivottable instance
            @param attributes: the HTML attributes for the table
        """

        T = current.T
        TOTAL = T("Total")

        TABLE.__init__(self, **attributes)
        components = self.components = []
        self.json_data = None

        layers = report.layers
        resource = report.resource
        tablename = resource.tablename

        cols = report.cols
        rows = report.rows
        numcols = report.numcols
        numrows = report.numrows
        rfields = report.rfields

        get_label = self._get_label
        get_total = self._totals
        represent = lambda f, v, d="": \
                    self._represent(rfields, f, v, default=d)

        layer_label = None
        col_titles = []
        add_col_title = col_titles.append
        col_totals = []
        add_col_total = col_totals.append
        row_titles = []
        add_row_title = row_titles.append
        row_totals = []
        add_row_total = row_totals.append

        # Layer titles --------------------------------------------------------

        labels = []
        get_mname = S3Report.mname
        for field_name, method in layers:
            # @todo: get the layer label from the report options
            label = get_label(rfields, field_name, tablename, "fact")
            mname = get_mname(method)
            if not labels:
                m = method == "list" and get_mname("count") or mname
                layer_label = "%s (%s)" % (label, m)
            labels.append("%s (%s)" % (label, mname))
        layers_title = TH(" / ".join(labels))

        # Columns field title -------------------------------------------------

        if cols:
            col_label = get_label(rfields, cols, tablename, "cols")
            _colspan = numcols + 1
        else:
            col_label = ""
            _colspan = numcols
        cols_title = TH(col_label, _colspan=_colspan, _scope="col")

        titles = TR(layers_title, cols_title)

        # Rows field title ----------------------------------------------------

        row_label = get_label(rfields, rows, tablename, "rows")
        rows_title = TH(row_label, _scope="col")
        headers = TR(rows_title)

        # Column headers ------------------------------------------------------

        add_header = headers.append
        values = report.col
        for i in xrange(numcols):
            value = values[i].value
            v = represent(cols, value)
            add_col_title(s3_truncate(unicode(v)))
            colhdr = TH(v, _scope="col")
            add_header(colhdr)

        # Row totals header ---------------------------------------------------

        if show_totals and cols is not None:
            add_header(TH(TOTAL, _class="totals_header rtotal", _scope="col"))
        thead = THEAD(titles, headers)

        # Table body ----------------------------------------------------------

        tbody = TBODY()
        add_row = tbody.append

        # lookup table for cell list values
        cell_lookup_table = {}  # {{}, {}}

        cells = report.cell
        rvals = report.row

        for i in xrange(numrows):

            # Initialize row
            _class = i % 2 and "odd" or "even"
            tr = TR(_class=_class)
            add_cell = tr.append

            # Row header
            row = rvals[i]
            v = represent(rows, row.value)
            add_row_title(s3_truncate(unicode(v)))
            rowhdr = TD(v)
            add_cell(rowhdr)

            # Result cells
            for j in xrange(numcols):
                cell = cells[i][j]
                vals = []
                cell_ids = []
                add_value = vals.append
                for layer_idx, layer in enumerate(layers):
                    f, m = layer
                    value = cell[layer]
                    if m == "list":
                        if isinstance(value, list):
                            l = [represent(f, v, d="-") for v in value]
                        elif value is None:
                            l = ["-"]
                        else:
                            if type(value) in (int, float):
                                l = IS_NUMBER.represent(value)
                            else:
                                l = unicode(value)
                        #add_value(", ".join(l))
                        add_value(UL([LI(v) for v in l]))
                    else:
                        if type(value) in (int, float):
                            add_value(IS_NUMBER.represent(value))
                        else:
                            add_value(unicode(value))

                    # hold the references
                    layer_ids = []
                    # get previous lookup values for this layer
                    layer_values = cell_lookup_table.get(layer_idx, {})

                    if m == "count":
                        rfield = rfields[f]
                        field = rfield.field
                        colname = rfield.colname
                        has_fk = field is not None and s3_has_foreign_key(
                            field)
                        for id in cell.records:
                            # cell.records == [#, #, #]

                            record = report.records[id]
                            try:
                                fvalue = record[colname]
                            except AttributeError:
                                fvalue = None

                            if fvalue is not None:
                                if has_fk:
                                    if not isinstance(fvalue, list):
                                        fvalue = [fvalue]
                                    # list of foreign keys
                                    for fk in fvalue:
                                        if fk not in layer_ids:
                                            layer_ids.append(fk)
                                            layer_values[fk] = str(
                                                field.represent(fk))
                                else:
                                    if id not in layer_ids:
                                        layer_ids.append(id)
                                        layer_values[id] = s3_unicode(
                                            represent(f, fvalue))

                    cell_ids.append(layer_ids)
                    cell_lookup_table[layer_idx] = layer_values

                # @todo: with multiple layers - show the first, hide the rest
                #        + render layer selector in the layer title corner to
                #        + switch between layers
                #        OR: give every layer a title row (probably better method)
                vals = DIV([DIV(v) for v in vals])

                if any(cell_ids):
                    cell_attr = {"_data-records": cell_ids}
                    vals = (A(_class="report-cell-zoom"), vals)
                else:
                    cell_attr = {}

                add_cell(TD(vals, **cell_attr))

            # Row total
            totals = get_total(row, layers, append=add_row_total)
            if show_totals and cols is not None:
                add_cell(TD(totals))

            add_row(tr)

        # Table footer --------------------------------------------------------

        i = numrows
        _class = i % 2 and "odd" or "even"
        _class = "%s %s" % (_class, "totals_row")
        col_total = TR(_class=_class)
        add_total = col_total.append
        add_total(TH(TOTAL, _class="totals_header", _scope="row"))

        # Column totals -------------------------------------------------------

        for j in xrange(numcols):
            col = report.col[j]
            totals = get_total(col, layers, append=add_col_total)
            add_total(TD(IS_NUMBER.represent(totals)))

        # Grand total ---------------------------------------------------------

        if cols is not None:
            grand_totals = get_total(report.totals, layers)
            add_total(TD(grand_totals))
        tfoot = TFOOT(col_total)

        # Wrap up -------------------------------------------------------------

        append = components.append
        append(thead)
        append(tbody)
        if show_totals:
            append(tfoot)

        # Chart data ----------------------------------------------------------

        layer_label = s3_unicode(layer_label)
        BY = T("by")
        row_label = "%s %s" % (BY, s3_unicode(row_label))
        if col_label:
            col_label = "%s %s" % (BY, s3_unicode(col_label))
        if filter_query and hasattr(filter_query, "serialize_url"):
            filter_vars = filter_query.serialize_url(resource=report.resource)
        else:
            filter_vars = {}
        json_data = json.dumps(
            dict(t=layer_label,
                 x=col_label,
                 y=row_label,
                 r=report.rows,
                 c=report.cols,
                 d=report.compact(n=50, represent=True),
                 u=url,
                 f=filter_vars,
                 cell_lookup_table=cell_lookup_table))
        self.report_data = Storage(row_label=row_label,
                                   col_label=col_label,
                                   layer_label=layer_label,
                                   json_data=json_data)
Пример #12
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("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"]))

        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 (subscriber must be logged in)
        auth = current.auth
        pe_id = subscription["pe_id"]
        if not auth.s3_logged_in() or auth.user.pe_id != pe_id:
            r.unauthorised()

        # Last check time
        last_check_time = current.xml.decode_iso_datetime(
                                subscription["last_check_time"])

        # Fields to report
        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)

        # Render and send the messages
        settings = current.deployment_settings
        
        join = lambda *f: os.path.join(current.request.folder, *f)
        theme = settings.get_template()
        send = current.msg.send_by_pe_id

        success = False
        errors = []

        # Email format
        email_format = subscription["email_format"]
        if not email_format:
            email_format = settings.get_msg_notification_email_format()

        # Pre-render the contents for the view
        contents = {}
        if email_format == "html" and "EMAIL" in methods:
            contents["html"] = cls._pre_render(resource,
                                               data,
                                               notify_on,
                                               last_check_time,
                                               "html")
            contents["default"] = contents["html"]
        if email_format != "html" or "EMAIL" not in methods or len(methods) > 1:
            contents["text"] = cls._pre_render(resource,
                                               data,
                                               notify_on,
                                               last_check_time,
                                               "text")
            contents["default"] = contents["text"]
        
        # Add human-readable representation of the filter query
        filter_query = subscription.get("filter_query")
        for f, c in contents.iteritems():
            c["filter_query"] = filter_query

        # Subject line
        get_config = resource.get_config
        subject = get_config("notification_subject",
                             settings.get_msg_notification_subject())
        from string import Template
        subject = Template(subject).safe_substitute(s="%(system)s",
                                                    r="%(resource)s")
        subject = subject % contents["default"]

        # Helper function to find templates from a priority list
        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
            
        prefix = resource.get_config("notification_prefix", "notify")
        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 theme != "default":
                path = join("private", "templates", theme, "views", "msg")
                template = get_template(path, filenames)
            if template is None:
                path = join("views", "msg")
                template = get_template(path, filenames)
            if template is None:
                template = StringIO(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

            # Send the message
            #_debug("Sending message per %s" % method)
            #_debug(message)
            try:
                sent = send(pe_id,
                            subject=s3_truncate(subject, 64),
                            message=message,
                            pr_message_method=method,
                            system_generated=True)
            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)