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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)