Package: django-admin-lightweight-date-hierarchy
Django Admin date_hierarchy with zero queriesNOTE: Some of the functionality provided by this extension isincluded as
part of Djangostarting at
version 2.1.The built-indate_hierarchytag 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, setdate_hierarchy_drilldown = Falseon theModelAdmin.
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.Whendate_hierarchy_drilldown = Trueor when not set the default behaviour is preserved.SupportPython>=3.7Django 3.2, 4.2, >=5QuickstartInstall django-admin-lightweight-date-hierarchy:pip install django-admin-lightweight-date-hierarchyAdd it to yourINSTALLED_APPS:INSTALLED_APPS=(...'django_admin_lightweight_date_hierarchy',...)Add the following to anyModelAdminwithdate_hierarchyto prevent the default drill-down behaviour:@admin.register(MyModel)classMyModelAdmin(admin.ModelAdmin):date_hierarchy='created'date_hierarchy_drilldown=FalseTo change the default dates generated by the template tag for any level in the hierarchy, implement a
function calledget_date_hierarchy_drilldown(self, year_lookup=None, month_lookup=None)on theModelAdmin.
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:importdatetimeimportcalendarfromdjango.utilsimporttimezonefromdjango.contribimportadmin@admin.register(MyModel)classMyModelAdmin(admin.ModelAdmin):date_hierarchy='created'date_hierarchy_drilldown=Falsedefget_date_hierarchy_drilldown(self,year_lookup,month_lookup):"""Drill-down only on past dates."""today=timezone.now().date()ifyear_lookupisNoneandmonth_lookupisNone:# Past 3 years.return(datetime.date(y,1,1)foryinrange(today.year-2,today.year+1))elifyear_lookupisnotNoneandmonth_lookupisNone:# Past months of selected year.this_month=today.replace(day=1)return(monthformonthin(datetime.date(int(year_lookup),month,1)formonthinrange(1,13))ifmonth<=this_month)elifyear_lookupisnotNoneandmonth_lookupisnotNone:# Past days of selected month.days_in_month=calendar.monthrange(year_lookup,month_lookup)[1]return(dayfordayin(datetime.date(year_lookup,month_lookup,i+1)foriinrange(days_in_month))ifday<=today)Blog PostMore about the process of developing date hierarchy drill-down in this blog postscaling django admin date hierarchy.RangeBasedDateHierarchyListFilterDjango 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
acreateddate field for November 2017, Django will execute the following query:SELECT...FROMapp_modelWHEREcreatedBETWEEN'2017-01-01 00:00:00'AND'2017-12-31 23:59:59.999999'ANDEXTRACT('month',created)=11A 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 example, 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:SELECT...FROMapp_modelWHEREcreated>='2017-11-01 00:00:00'ANDcreated<'2017-12-01 00:00:00'This is whatRangeBasedDateHierarchyListFilterdoes.To achieve the above query, add the following to your ModelAdmin:fromdjango.contribimportadminfromdjango_admin_lightweight_date_hierarchy.adminimportRangeBasedDateHierarchyListFilter@admin.register(MyModel)classMyModelAdmin(admin.ModelAdmin):date_hierarchy='created'list_filter=(RangeBasedDateHierarchyListFilter,)Blog PostMore about the motivation and the performace ofRangeBasedDateHierarchyListFilterin this blog postDjango Admin Range-Based Date Hierarchy.Running Testssource <YOURVIRTUALENV>/bin/activate
(venv) $ pip install tox
(venv) $ tox