Skip to main content

A django package for creating simple stats from a query

Project description

https://badge.fury.io/py/django-simple-stats.svg

django-simple-stats

A django package for creating stats from a query.

installation

Install it from pip:

pip install django-simple-stats

or the latest version from git:

pip install git+https://github.com/spapas/django-simple-stats

No other installation is needed.

Usage

The only supported method is the get_stats. It expects a django query and a configuration list. Each element of the configuration list is a dictionary with the following attributes:

  • label (required): The textual description of this statistic

  • kind (required): What kind of aggregate we need. Choices are: query_aggregate_single, query_aggregate, choice_aggregate, choice_aggregate_with_null, query_aggregate_date, query_aggregate_buckets.

  • method (required): The aggregate method. Can be one of count, sum, max, min, avg.

  • field (required): The field that the aggreate will run on; use __ for joins i.e fiedld1__field2

  • what (optional): Only required for query_aggregate_date, it is eithed year, month, day

  • choices (optional): Only required for choice_aggregate and choice_aggregate_with_null, it must be a django choices list

  • buckets (optional): only required for query_aggregate_buckets. Must be a list from the biggest to the lowest value.

  • aggr_field (optional): this field is optional and can be used for query_aggregate, query_aggregate_date, choice_aggregate and choice_aggregate_with_null. It denotes a field that would run the aggregate function on.

See below for a complete example.

The response will be a list of dictionaries with the following attributes:

  • label: Same as the label in the configuration

  • value: Will have a value if you use the query_aggregate_single, else will be None

  • values: Will be empty for query_aggregate_single else will be a list of tuples. Each tuple will have two elements, (label, value)

Stat kinds

  • The query_aggregate_single will run the aggregate function on a field and return a single value. For example you can get the total number of rows of your query or the sum of all fields.

  • The query_aggregate will run the aggregate function on a field and return the list of values. You can run the aggregate function on a different field by passing aggr_field (so you can group by a field and return the sum of another field for each group). This is mainly useful for foreign keys and if you’ve got distinct values in your queries. For example count the number of rows per user. Also it is useful for booleans for example to get the number of rows that have a flag turned on and off.

  • The choice_aggregate is similar to the query_aggregate but will use a choices attribute to return better looking values. This will not return Null values

  • The choice_aggregate_with_null is the same as choice_aggregate but will return Null values (so you can add a (None, "Empty") choice to your choices)

  • The query_aggregate_date is similar to the query_aggregate but will return the aggregates on a specific date field; use what to pass year, month, day.

  • Finally, the query_aggregate_buckets is used to create buckets of values. You’ll pass the list of buckets and the query will return the results that belong in each bucket. The stats module will run individual queries with field__gte for each value. So for example if you pass [100, 50, 10] and you have a field price it will run price__gte=100, price__gte=50, price__gte=10 and return the results.

Example

from simple_stats import get_stats

STATS_CFG = cfg = [
        {
            'label': 'Total',
            'kind': 'query_aggregate_single',
            'method': 'count',
            'field': 'id',
        },
        {
            'label': 'Total price',
            'kind': 'query_aggregate_single',
            'method': 'sum',
            'field': 'price',
        },
        {
            'label': 'Per authority',
            'kind': 'query_aggregate',
            'method': 'count',
            'field': 'pilot_authority__name',
        },
        {
            'label': 'Per authority by price',
            'kind': 'query_aggregate',
            'method': 'count',
            'field': 'pilot_authority__name',
            'aggr_field': 'price',
        },
        {
            'label': 'Per status',
            'kind': 'choice_aggregate',
            'method': 'count',
            'field': 'status',
            'choices': models.STATUS_CHOICES,
        },
        {
            'label': 'Per status by price',
            'kind': 'choice_aggregate',
            'method': 'count',
            'field': 'status',
            'aggr_field': 'price',
            'choices': models.STATUS_CHOICES,
        },
        {
            'label': 'Per year',
            'kind': 'query_aggregate_date',
            'method': 'count',
            'field': 'created_on',
            'what': 'year',
        },
        {
            'label': 'Per year by price',
            'kind': 'query_aggregate_date',
            'method': 'count',
            'field': 'created_on',
            'what': 'year',
            'aggr_field': 'price',
        },
        {
            'label': 'Per price',
            'kind': 'query_aggregate_buckets',
            'method': 'count',
            'field': 'price',
            'buckets': [100_00, 50_00, 1_000, 500, 0]
        }
    ]

def my_view(request):
    qs = TestModel.objects.all()

    stats = get_stats(qs, STATS_CFG)
    return render(request, 'my_template.html', {'stats': stats})

The stats will be an array of dictionaries like the following:

[
  {'label': 'Total', 'values': [], 'value': 1216},
  {'label': 'Total price', 'values': [], 'value': 323.16},
  {'label': 'Per authority', 'values': [('Authority 1', 200), ('Authority 2', 9),   ], 'value': None},
  {'label': 'Per authority by price', 'values': [('Authority 1', 123.23), ('Authority 2', 42.12),   ], 'value': None},
  {'label': 'Per status', 'values': [('New', 200), ('Cancel', 0), 'value': None},
  {'label': 'Per status by price', 'values': [('New', 32.01), ('Cancel', 44.23), 'value': None},
  {'label': 'Per year', 'values': [(2021, 582), (2022, 634)], 'value': None}
  {'label': 'Per year by price', 'values': [(2021, 5.82), (2022, 6.34)], 'value': None}
  {'label': 'Per price', 'values': [('> 5000', 1), ('> 1000', 29), ('> 500', 86), ('> 0', 305)], 'value': None}
]

You can display this in your template using something like:

<div class='row'>
  {% for s in stats %}
  <div class='col-md-4 mb-5' style='max-height: 500px; overflow: auto;'>
      <h4>{{ s.label }}</h4>
      {% if s.values %}
          <table class='table table-condensed table-striped small table-sm'>
              {% for v in s.values %}
                  <tr>
                      <td>{{ v.0 }}</td>
                      <td>{{ v.1 }}</td>
                  </tr>
              {% endfor %}
          </table>
      {% else %}
          <b>{{ s.value }}</b>
      {% endif %}
  </div>
  {% endfor %}
</div>

Exporting the stats

You can easily export these stats in xls using the xlwt (https://pypi.org/project/xlwt/) library and this function:

import xlwt

def create_xls_resp(stats, response):
    context = self.get_context_data()
    import xlwt
    wb = xlwt.Workbook(encoding="utf-8")
    for stat in stats:
        ws = wb.add_sheet(stat["label"][:31])
        ws.write(0,0,stat["label"], xlwt.easyxf('font: name Calibri, bold on', ))
        if stat["value"]:
            ws.write(0,1,stat["value"], xlwt.easyxf('font: name Calibri, bold on', ))

        for i, val in enumerate(stat["values"], start=2):
            for j,v in enumerate(val, start=0):
                ws.write(i,j,v)
    wb.save(response)

Now you can call it like this from your view:

from django.http import HttpResponse

def my_export_view(request):
    qs = TestModel.objects.all()

    stats = get_stats(qs, STATS_CFG)
    response = HttpResponse(content_type="application/ms-excel")
    response["Content-Disposition"] = "attachment; filename=export.xls"
    create_xls_resp(response)
    return response

Changelog

  • v.0.4.0: Allow the aggregate function to run on a different field using aggr_field

  • v.0.3.1: Fix small bug with choice_aggregate_with_null

  • v.0.3.0: Add choice_aggregate_with_null and throw if stat kind is not found

  • v.0.2.1: Fix small bug with column aliases

  • v.0.2.0: Changed API; use query_aggregate_datetime for a datetime field and query_aggregate_date for a date field

  • v.0.1.0: Initial version

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-simple-stats-0.4.0.tar.gz (6.6 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page