Skip to main content

Using django admin date hierarchy queries free!

Project description

=============================
Django Admin lightweight date hierarchy
=============================

.. image:: https://badge.fury.io/py/django-admin-lightweight-date-hierarchy.svg
:target: https://badge.fury.io/py/django-admin-lightweight-date-hierarchy

.. image:: https://travis-ci.org/hakib/django-admin-lightweight-date-hierarchy.svg?branch=master
:target: https://travis-ci.org/hakib/django-admin-lightweight-date-hierarchy

.. image:: https://codecov.io/gh/hakib/django-admin-lightweight-date-hierarchy/branch/master/graph/badge.svg
:target: https://codecov.io/gh/hakib/django-admin-lightweight-date-hierarchy


Django Admin date_hierarchy with zero queries
----------------------------------------------

The built-in `date_hierarchy`_ tag performs a query to find the dates for which there is data.
On large tables this query can be very expensive.

To prevent additional queries, set ``date_hierarchy_drilldown = False`` on the ``ModelAdmin``.
When drill-down is disabled the tag will generate a default range of dates based solely
on the selected hierarchy level - without performing a query.

Default options for hierarchy levels:

- None - +-3 years from current year.
- Year - all months of the selected year.
- Month - all days of the selected month.

When ``date_hierarchy_drilldown = True`` or when not set the default behaviour is preserved.

.. _`date_hierarchy`: https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.date_hierarchy


Support
----------

Python 2.7, 3.4, 3.5, 3.6

Django 1.9, 1.10, 1.11


Quickstart
----------

Install django-admin-lightweight-date-hierarchy::

pip install django-admin-lightweight-date-hierarchy

Add it to your `INSTALLED_APPS`:

.. code-block:: python

INSTALLED_APPS = (
...
'django_admin_lightweight_date_hierarchy',
...
)

Add the following to any ``ModelAdmin`` with ``date_hierarchy`` to prevent the default drill-down behaviour:

.. code-block:: python

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
date_hierarchy = 'created'
date_hierarchy_drilldown = False


To change the default dates generated by the template tag for any level in the hierarchy, implement a
function called ``get_date_hierarchy_drilldown(self, year_lookup=None, month_lookup=None)`` on the ``ModelAdmin``.
The function receives the date hierarchy filter and is expected to return a list of dates to offer for drill-down.

For example, a custom drill-down that offers only past dates:


.. code-block:: python


import datetime
import calendar

from django.utils import timezone
from django.contrib import admin


@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
date_hierarchy = 'created'
date_hierarchy_drilldown = False

def get_date_hierarchy_drilldown(self, year_lookup, month_lookup):
"""Drill-down only on past dates."""

today = timezone.now().date()

if year_lookup is None and month_lookup is None:
# Past 3 years.
return (
datetime.date(y, 1, 1)
for y in range(today.year - 2, today.year + 1)
)

elif year_lookup is not None and month_lookup is None:
# Past months of selected year.
this_month = today.replace(day=1)
return (
month for month in (
datetime.date(int(year_lookup), month, 1)
for month in range(1, 13)
) if month <= this_month
)

elif year_lookup is not None and month_lookup is not None:
# Past days of selected month.
days_in_month = calendar.monthrange(year_lookup, month_lookup)[1]
return (
day for day in (
datetime.date(year_lookup, month_lookup, i + 1)
for i in range(days_in_month)
) if day <= today
)


RangeBasedDateHierarchyListFilter
----------------------------------------------

Django filters the queryset for a given level in the date hierarchy using a database
function to extract the relevent date part. For example, when filtering a queryset on
a `created` date field for November 2017, Django will execute the following query:

.. code-block:: sql

SELECT
...
FROM
app_model
WHERE
created BETWEEN '2017-01-01 00:00:00' AND '2017-12-31 23:59:59.999999'
AND EXTRACT('month', created) = 11

A function is opaque to the database optimizer. If you have a range-based (btree) index
on the field, using EXTRACT does not limit the range at all, and so the index is not
utilized properly which might lead to a sub optimal execution plan.

There are several approaches to tackle this issue. For exmaple, in databases that support
function based indexes the developer can add an index on the specific function to try and
improve the performace of the query. The downside to this approach is having to maintain
additional indexes for each level of the hierarchy. Additional indexes slow down insert
and update operations, and take up space.

Another approach is to simplify the condition used by Django to filter the queryset
for any given level in the hierarchy:

.. code-block:: sql

SELECT
...
FROM
app_model
WHERE
created >= '2017-11-01 00:00:00'
AND created < '2017-12-01 00:00:00'


This is what RangeBasedDateHierarchyListFilter does.

To achieve the above query, simply add the following to your ModelAdmin:

.. code-block:: python


from django.contrib import admin
from django_admin_lightweight_date_hierarchy.admin import RangeBasedDateHierarchyListFilter


@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
date_hierarchy = 'created'

list_filters = (
RangeBasedDateHierarchyListFilter,
)


Blog Post
----------

You can read more about this package in my blog post `scaling django admin date hierarchy`_.

.. _`scaling django admin date hierarchy`: https://medium.com/@hakibenita/scaling-django-admin-date-hierarchy-85c8e441dd4c


Running Tests
-------------

::

source <YOURVIRTUALENV>/bin/activate
(venv) $ pip install tox
(venv) $ tox


Credits
-------

Tools used in rendering this package:

* Cookiecutter_
* `cookiecutter-djangopackage`_

.. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage




History
-------

0.3.0 (2017-11-24)
++++++++++++++++++

* Added RangeBasedDateHierarchyListFilter.

0.2.0 (2017-10-21)
++++++++++++++++++

* Option to provide custom drill down for any level in the hierarchy.


0.1.0 (2017-09-21)
++++++++++++++++++

* First release on PyPI.

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

Built Distribution

File details

Details for the file django-admin-lightweight-date-hierarchy-0.3.0.tar.gz.

File metadata

File hashes

Hashes for django-admin-lightweight-date-hierarchy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3e368154aa775162cdbcc5153574e69282f2542152364f88a38640dbbcafe52b
MD5 1be608eef9cf6051c4a2b79d5f96325e
BLAKE2b-256 7cb1ef97eedbbb2a35d3abdf122b510f2deeb3796484f40a821003379481423a

See more details on using hashes here.

File details

Details for the file django_admin_lightweight_date_hierarchy-0.3.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_admin_lightweight_date_hierarchy-0.3.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 d9146fc50fff81d68ddebaaacba9b168f3a02afc8d8314528998c181ce50d606
MD5 5acb5016c230ac276549b6ca788d4cfd
BLAKE2b-256 c01c0fb593e4a4ed6b49a1ebab94cea3328a472a2f21371f6047877ea5fce7cd

See more details on using hashes here.

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