Skip to main content
https://badge.fury.io/py/django-qsstats-magic.svg https://img.shields.io/pypi/pyversions/django-qsstats-magic.svg https://img.shields.io/pypi/djversions/django-qsstats-magic.svg https://github.com/bashu/django-qsstats-magic/actions/workflows/test.yml/badge.svg

The goal of django-qsstats is to be a microframework to make repetitive tasks such as generating aggregate statistics of querysets over time easier. It’s probably overkill for the task at hand, but yay microframeworks!

django-qsstats-magic is a refactoring of django-qsstats app with slightly changed API, simplified internals and faster time_series implementation.

Maintained by Basil Shubin, and some great contributors.

Installation

First install the module, preferably in a virtual environment. It can be installed from PyPI:

pip install django-qsstats-magic

Usage

How many users signed up today? this month? this year?

from django.contrib.auth.models import User
import qsstats

qs = User.objects.all()
qss = qsstats.QuerySetStats(qs, 'date_joined')

print('%s new accounts today.' % qss.this_day())
print('%s new accounts this week.' % qss.this_week())
print('%s new accounts this month.' % qss.this_month())
print('%s new accounts this year.' % qss.this_year())
print('%s new accounts until now.' % qss.until_now())

This might print something like:

5 new accounts today.
11 new accounts this week.
27 new accounts this month.
377 new accounts this year.
409 new accounts until now.

Aggregating time-series data suitable for graphing

from django.contrib.auth.models import User
import datetime, qsstats

qs = User.objects.all()
qss = qsstats.QuerySetStats(qs, 'date_joined')

today = datetime.date.today()
seven_days_ago = today - datetime.timedelta(days=7)

time_series = qss.time_series(seven_days_ago, today)
print('New users in the last 7 days: %s' % [t[1] for t in time_series])

This might print something like:

New users in the last 7 days: [3, 10, 7, 4, 12, 9, 11]

Please see qsstats/tests/test_*.py for similar usage examples.

API

The QuerySetStats object

In order to provide maximum flexibility, the QuerySetStats object can be instantiated with as little or as much information as you like. All keyword arguments are optional but DateFieldMissingError and QuerySetMissingError will be raised if you try to use QuerySetStats without providing enough information.

qs

The queryset to operate on.

Default: None

date_field

The date field within the queryset to use.

Default: None

aggregate

The django aggregation instance. Can be also set when instantiating or calling one of the methods.

Default: Count('id')

operator

The default operator to use for the pivot function. Can be also set when calling pivot.

Default: 'lte'

today

The date that will be considered as today date. If today param is None QuerySetStats’ today will be datetime.date.today().

Default: None

All of the documented methods take a standard set of keyword arguments that override any information already stored within the QuerySetStats object. These keyword arguments are date_field and aggregate.

Once you have a QuerySetStats object instantiated, you can receive a single aggregate result by using the following methods:

  • for_minute

  • for_hour

  • for_day

  • for_week

  • for_month

  • for_year

    Positional arguments: dt, a datetime.datetime or datetime.date object to filter the queryset to this interval (minute, hour, day, week, month or year).

  • this_minute

  • this_hour

  • this_day

  • this_week

  • this_month

  • this_year

    Wrappers around for_<interval> that uses dateutil.relativedelta to provide aggregate information for this current interval.

QuerySetStats also provides a method for returning aggregated time-series data which may be extremely useful in plotting data:

time_series

Positional arguments: start and end, each a datetime.date or datetime.datetime object used in marking the start and stop of the time series data.

Keyword arguments: In addition to the standard date_field and aggregate keyword argument, time_series takes an optional interval keyword argument used to mark which interval to use while calculating aggregate data between start and end. This argument defaults to 'days' and can accept 'years', 'months', 'weeks', 'days', 'hours' or 'minutes'. It will raise InvalidIntervalError otherwise.

This methods returns a list of tuples. The first item in each tuple is a datetime.datetime object for the current interval. The second item is the result of the aggregate operation. For example:

[(datetime.datetime(2010, 3, 28, 0, 0), 12), (datetime.datetime(2010, 3, 29, 0, 0), 0), ...]

Formatting of date information is left as an exercise to the user and may vary depending on interval used.

until

Provide aggregate information until a given date or time, filtering the queryset using lte.

Positional arguments: dt a datetime.date or datetime.datetime object to be used for filtering the queryset since.

Keyword arguments: date_field, aggregate.

until_now

Aggregate information until now.

Positional arguments: dt a datetime.date or datetime.datetime object to be used for filtering the queryset since (using lte).

Keyword arguments: date_field, aggregate.

after

Aggregate information after a given date or time, filtering the queryset using gte.

Positional arguments: dt a datetime.date or datetime.datetime object to be used for filtering the queryset since.

Keyword arguments: date_field, aggregate.

after_now

Aggregate information after now.

Positional arguments: dt a datetime.date or datetime.datetime object to be used for filtering the queryset since (using gte).

Keyword arguments: date_field, aggregate.

pivot

Used by until, after, and until_now but potentially useful if you would like to specify your own operator instead of the defaults.

Positional arguments: dt a datetime.date or datetime.datetime object to be used for filtering the queryset since (using lte).

Keyword arguments: operator, date_field, aggregate.

Raises InvalidOperatorError if the operator provided is not one of 'lt', 'lte', gt or gte.

Testing

The test suite uses pytest and pytest-django, and runs entirely against an in-memory SQLite database - no external database setup is required:

$ uv run --group dev pytest

For testing against the full matrix of supported Python and Django versions, install tox (pip install tox) and run tox from the source checkout:

$ tox

Difference from django-qsstats

  1. Faster time_series method using 1 sql query (currently works for MySQL and PostgreSQL, with a fallback to the old method for other DB backends).

  2. Single aggregate parameter instead of aggregate_field and aggregate_class. Default value is always Count('id') and can’t be specified in settings.py. QUERYSETSTATS_DEFAULT_OPERATOR option is also unsupported now.

  3. Support for minute and hour aggregates.

  4. start_date and end_date arguments are renamed to start and end because of 3.

  5. Internals are changed.

I don’t know if original author (Matt Croydon) would like my changes so I renamed a project for now. If the changes will be merged then django-qsstats-magic will become obsolete.

Contributing

If you’ve found a bug, implemented a feature or customized the template and think it is useful then please consider contributing. Patches, pull requests or just suggestions are welcome!

Credits

django-qsstats-magic was originally started by Mikhail Korobov who has now unfortunately abandoned the project.

License

django-qsstats-magic is released under the BSD license.

Changes

2.0.0 (2026-09-01)

  • Backwards incompatible: removed the deprecated InvalidInterval, InvalidOperator, DateFieldMissing and QuerySetMissing exception aliases (deprecated in 1.1.1). Use InvalidIntervalError, InvalidOperatorError, DateFieldMissingError and QuerySetMissingError instead.

  • Backwards incompatible: time_series() no longer transparently falls back to a separate, slower query engine on ValueError. That fallback’s only trigger was a Python-level field-type check that’s now handled upfront (see below), so it was dead, unreachable code - along with the bugs in it, including incorrect and sometimes missing buckets for interval='weeks' (and other intervals) when start/end weren’t aligned to interval boundaries. If your database genuinely can’t perform timezone-aware truncation, that error now surfaces directly instead of being silently retried with a misleading warning.

  • Fixed pivot() (and until/until_now/after/after_now) raising AttributeError instead of falling back to the documented default operator ('lte') when called without one; QuerySetStats had silently lost its operator constructor argument.

  • Fixed TypeError in time_series() when an aggregate (e.g. Sum, Avg) returned None for an interval instead of the default Count’s 0.

  • Fixed time_series() mislabeling buckets for multi-unit intervals (e.g. interval='2days'): each bucket’s timestamp drifted forward by num - 1 units from its true start, even though the aggregated values themselves were already correct.

  • Fixed the time_series() query unnecessarily falling back to a slower code path when date_field is a plain DateField rather than a DateTimeField.

  • Fixed __getattr__ raising a bare, message-less AttributeError for unknown attributes instead of a normal, informative one.

  • Added type hints throughout qsstats.QuerySetStats and qsstats.utils; the package is now mypy-clean.

  • Added __all__ to qsstats documenting its public API.

  • Dropped the unused six dependency.

1.1.1 (2026-08-31)

  • Renamed InvalidInterval, InvalidOperator, DateFieldMissing and QuerySetMissing to InvalidIntervalError, InvalidOperatorError, DateFieldMissingError and QuerySetMissingError. The old names still work but now raise a DeprecationWarning and will be removed in a future release.

  • Dropped the qsstats.compat module; django.utils.timezone.now() is used directly.

  • Now requires Python 3.10+ and Django 5.2+.

  • Migrated packaging to pyproject.toml, switched the test suite to pytest, and added pre-commit hooks and GitHub Actions CI/release workflows.

Release files for django-qsstats-magic 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-qsstats-magic 2.0.0
File Size Uploaded
django_qsstats_magic-2.0.0.tar.gz 13.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-qsstats-magic 2.0.0
File Interpreter ABI Platform
django_qsstats_magic-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.7 kB

Release files / django_qsstats_magic-2.0.0.tar.gz

Download URL django_qsstats_magic-2.0.0.tar.gz
Size 13.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e75d462a35e2e200178895704505d8a99d8e497cbb41371de1cbeff19c6138a3
BLAKE2b-256 checksum
How to use checksums
3672f4cbec3cb17158f5772b4d497eda6f44a8962e453fec7cfdf894d71f3ba4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / django_qsstats_magic-2.0.0-py3-none-any.whl

Download URL django_qsstats_magic-2.0.0-py3-none-any.whl
Size 13.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cd484f807cfe572fb8b3894a0e43a04d3be5a6d2ffb0f44a8c38318c119f5350
BLAKE2b-256 checksum
How to use checksums
f90988f81171601c94a5da7d9b076bba8a36997ae3cd2539f387de606874d7fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.0.0 This release

2 release files

1.1.1

2 release files

1.1.0

1 release file

1.0.0

1 release file

0.7.2

1 release file

0.7.1

1 release file

0.7

1 release file

0.6.2

1 release file

0.6.1

1 release file

0.6

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page