Creating and using reports

Reports are used to define a view into your data. At a basic level, reports are used to process a data set, format the data, and return tabular results. But reports can define many options, including:

  • The name of the report
  • The category of the report
  • How long to cache the report
  • What source data to pull
  • How to manipulate the source data
  • How the output should be formatted
  • What options are given to the user to filter the data
  • The default sorting

The next section describes in detail how to write and interact with your own reports. You will also want to check out Data Sources for details on how to pull data; Column formatters for more on formatting report output; and User-input widgets for details on accepting user filtering options.

Report objects

class blingalytics.base.Report(cache, merge=False)

To write a report, you subclass this base Report class and define your own attributes on it. The standard list of available attributes include:

category (optional)
A string representing the category this report belongs to. The category is used by the get_reports_by_category function to group like reports together.
display_name (optional)
The title of the report as a string. If not specified, this will be generated automatically based on the class name.
code_name (optional)
The code name of the report as a string. The code name is used to identify this report, for example as part of a key name when caching the report’s data. If not specified, this will be generated automatically based on the class name.
cache_time (optional)
The number of seconds this report should remain valid in the cache. If not specified, defaults to 1800 (30 minutes).
keys
If your report has just one key, this should be a two-tuple: the name of the key column as a string; and the desired key range class or instance. If you want a report with compound keys, you can specify them as a list of these two-tuples. This is described in detail in Key range source.
columns
This should be a list of two-tuples describing your report’s columns. The first item of each two-tuple is the name of the column as a string. The second item is a column class or instance. The column definitions do all the heavy lifting of pulling data from sources, manipulating it, and formatting the output. For more details, see Data Sources.
filters (optional)
A list of two-tuples describing the filters and widgets to present to your users. The first item of each two-tuple is the name of the filter as a string. The second item is a filter instance. The types of filters you can use are specific to the sources you’re using; see the relevant source’s documentation in Data Sources. Filters will also generally specify a widget for collecting the user input; for more, see User-input widgets.
default_sort (optional)
A two-tuple representing the column and direction that the report should be sorted by default. The first item is the name of the column to sort on, as a string. The second is the sort direction, as either 'asc' or 'desc'. If not specified, this defaults to sorting by the first column, descending.

Various sources used by the report may allow or require other attributes to be specified. This will be specified in the documentation for that source.

Here is a relatively simple example of a report definition:

from blingalytics import base, formats, widgets
from blingalytics.sources import database, derived, key_range

class RevenueReport(base.Report):
    display_name = 'Company Revenue'
    code_name = 'company_revenue_report'
    category = 'business'
    cache_time = 60 * 60 * 3 # three hours

    database_entity = 'project.models.reporting.RevenueModel'
    keys = ('product_id', key_range.SourceKeyRange)
    columns = [
        ('product_id', database.GroupBy('product_id',
            format=formats.Integer(label='ID', grouping=False), footer=False)),
        ('product_name', database.Lookup('project.models.products.Product',
            'name', 'product_id', format=formats.String)),
        ('revenue', database.Sum('purchase_revenue', format=formats.Bling)),
        ('_cost_of_revenue', database.First('product_cost')),
        ('gross_margin', derived.Value(
            lambda row: (row['revenue'] - row['_cost_of_revenue']) * \
            Decimal('100.00') / row['revenue'], format=formats.Bling)),
    ]
    filters = [
        ('delivered', database.QueryFilter(
            lambda entity: entity.is_delivered == True)),
        ('online_only', database.QueryFilter(
            lambda entity, user_input: entity.is_online_purchase == user_input,
            widget=widgets.Checkbox(label='Online Purchase'))),
    ]
    default_sort = ('gross_margin', 'desc')

Once you have defined your report subclass, you instantiate it by passing in a cache instance. This tells the report how and where to cache its processed data. For more, see Cache stores. Once you have a report instance, you use the following methods to run it, manipulate it, and retrieve its data.

classmethod render_widgets()

Returns a list of this report’s widgets, rendered to HTML.

classmethod get_widgets()

Returns a list of this report’s widget instances. Calling the widgets’ render() method is left to you.

clean_user_inputs(**kwargs)

Set user inputs on the report, returning a list of any validation errors that occurred.

The user input should be passed in as keyword arguments the same as they are returned in a GET or POST from the widgets’ HTML. The widgets will be cleaned, converting to the appropriate Python objects.

If there were errors, they are returned as a list of strings. If not, returns None and stores the user inputs on the report. Note that this effectively changes the unique_id property of the report.

If your report has user input widgets, this should be called before run_report; if the report has no widgets, you don’t need to call this at all.

run_report()

Processes the report data and stores it in cache.

Depending on the size of the data and the processing going on, this call can be time-consuming. If you are deploying this as part of a web application, it’s recommended that you perform this step outside of the request-response cycle.

is_report_started()

If run_report() is currently running, this returns True; otherwise, False.

is_report_finished()

If run_report() has completed and there is a current cached copy of this report, returns True; otherwise, False.

kill_cache(full=False)

By default, this removes or invalidates (depending on the cache store being used) this cached report. If there are other cached versions of this report (with different user inputs) they are left unchanged.

If you pass in full=True, this will instead perform a full report-wide cache invalidation. This means any version of this report in cache, regardless of user inputs, will be wiped.

report_header()

Returns the header data for this report, which describes the columns and how to display them.

The header info is returned as a list of dicts, one for each column, in order. Certain column types may add other relevant info, but by default, the header dicts will contain the following:

  • label: The human-readable label for the column.
  • alignment: Either 'left' or 'right' for the preferred text alignment for the column.
  • hidden: If True, the column is meant for internal use only and should not be displayed to the user; if False, it should be shown.
  • sortable: If True, this column can be sorted on.

The first column returned by a Blingalytics report is always a hidden column specifying the row’s cache ID. The first column header is for this internal ID.

report_rows(selected_rows=None, sort=None, limit=None, offset=0, format='html')

Returns the requested rows for the report from cache. There are a number of arguments you can provide to limit, order and format the rows, all of which are optional:

  • selected_rows: If you want to limit your results to a subset of the report’s rows, you can provide them as a list of internal cache row IDs (the ID of a row is always returned in the row’s first column). Defaults to None, which does not limit the results.
  • sort: This is a two-tuple to specify the sorting on the table, in the same format as the default_sort attribute on reports. That is, the first element should be the label of the column and the second should be either 'asc' or 'desc'. Defaults to the sorting specified in the report’s default_sort attribute.
  • limit: The number of rows to return. Defaults to None, which does not limit the results.
  • offset: The number of rows offset at which to start returning rows. Defaults to 0.
  • format: The type of formatting to use when processing the output. The built-in options are 'html' or 'csv'. Defaults to 'html'. This is discussed in more detail in Column formatters.

The rows are returned as a list of lists of values. The first column returned by Blingalytics for any report is always a hidden column specifying the row’s internal cache ID.

Returns the computed footer row for the report. There is one argument to control the formatting of the output:

  • format: The type of formatting to use when processing the output. The built-in options are 'html' or 'csv'. Defaults to 'html'. This is discussed in more detail in Column formatters.

The footer row is returned as a list, including the data for any hidden columns. The first column returned by Blingalytics for any report is reserved for the row’s internal cache ID, so the first item returned for the footer will always be None.

report_timestamp()

Returns the timestamp when the report instance was originally computed and cached, as a datetime object.

report_row_count()

Returns the total number of rows in this report instance.

Utility functions

The blingalytics module provides a few utility methods that are useful for retrieving your reports.

Note

The report registration method is done using a metaclass set on the base.Report class. This means that the modules where you’ve defined your report classes have to be imported before the methods below will know they exist. You can import them when your code initializes, or right before you call the utility functions, or whatever — just be sure to do it.

blingalytics.get_report_by_code_name(code_name)

Returns the report class with the given code_name, or None if not found.

blingalytics.get_reports_by_category()

Returns all known reports, organized by category. The result is returned as a dict of category strings to lists of report classes.

Table Of Contents

Previous topic

Sweet walkthrough

Next topic

Data Sources

This Page