Fixed #29724 -- Fixed timezone handling in ModelAdmin.date_hierarchy queries.
Thanks Alexander Holmbäck for the initial patch.
This commit is contained in:
parent
e43c34d10d
commit
55cdf6c52d
|
@ -2,7 +2,8 @@ import datetime
|
||||||
|
|
||||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
from django.contrib.admin.utils import (
|
from django.contrib.admin.utils import (
|
||||||
display_for_field, display_for_value, label_for_field, lookup_field,
|
display_for_field, display_for_value, get_fields_from_path,
|
||||||
|
label_for_field, lookup_field,
|
||||||
)
|
)
|
||||||
from django.contrib.admin.views.main import (
|
from django.contrib.admin.views.main import (
|
||||||
ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
|
ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
|
||||||
|
@ -13,7 +14,7 @@ from django.template import Library
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.urls import NoReverseMatch
|
from django.urls import NoReverseMatch
|
||||||
from django.utils import formats
|
from django.utils import formats, timezone
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
@ -359,6 +360,13 @@ def date_hierarchy(cl):
|
||||||
"""
|
"""
|
||||||
if cl.date_hierarchy:
|
if cl.date_hierarchy:
|
||||||
field_name = cl.date_hierarchy
|
field_name = cl.date_hierarchy
|
||||||
|
field = get_fields_from_path(cl.model, field_name)[-1]
|
||||||
|
if isinstance(field, models.DateTimeField):
|
||||||
|
dates_or_datetimes = 'datetimes'
|
||||||
|
qs_kwargs = {'is_dst': True}
|
||||||
|
else:
|
||||||
|
dates_or_datetimes = 'dates'
|
||||||
|
qs_kwargs = {}
|
||||||
year_field = '%s__year' % field_name
|
year_field = '%s__year' % field_name
|
||||||
month_field = '%s__month' % field_name
|
month_field = '%s__month' % field_name
|
||||||
day_field = '%s__day' % field_name
|
day_field = '%s__day' % field_name
|
||||||
|
@ -374,6 +382,11 @@ def date_hierarchy(cl):
|
||||||
# select appropriate start level
|
# select appropriate start level
|
||||||
date_range = cl.queryset.aggregate(first=models.Min(field_name),
|
date_range = cl.queryset.aggregate(first=models.Min(field_name),
|
||||||
last=models.Max(field_name))
|
last=models.Max(field_name))
|
||||||
|
if dates_or_datetimes == 'datetimes':
|
||||||
|
date_range = {
|
||||||
|
k: timezone.localtime(v) if timezone.is_aware(v) else v
|
||||||
|
for k, v in date_range.items()
|
||||||
|
}
|
||||||
if date_range['first'] and date_range['last']:
|
if date_range['first'] and date_range['last']:
|
||||||
if date_range['first'].year == date_range['last'].year:
|
if date_range['first'].year == date_range['last'].year:
|
||||||
year_lookup = date_range['first'].year
|
year_lookup = date_range['first'].year
|
||||||
|
@ -391,7 +404,7 @@ def date_hierarchy(cl):
|
||||||
'choices': [{'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))}]
|
'choices': [{'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))}]
|
||||||
}
|
}
|
||||||
elif year_lookup and month_lookup:
|
elif year_lookup and month_lookup:
|
||||||
days = getattr(cl.queryset, 'dates')(field_name, 'day')
|
days = getattr(cl.queryset, dates_or_datetimes)(field_name, 'day', **qs_kwargs)
|
||||||
return {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'back': {
|
'back': {
|
||||||
|
@ -404,7 +417,7 @@ def date_hierarchy(cl):
|
||||||
} for day in days]
|
} for day in days]
|
||||||
}
|
}
|
||||||
elif year_lookup:
|
elif year_lookup:
|
||||||
months = getattr(cl.queryset, 'dates')(field_name, 'month')
|
months = getattr(cl.queryset, dates_or_datetimes)(field_name, 'month', **qs_kwargs)
|
||||||
return {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'back': {
|
'back': {
|
||||||
|
@ -417,7 +430,7 @@ def date_hierarchy(cl):
|
||||||
} for month in months]
|
} for month in months]
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
years = getattr(cl.queryset, 'dates')(field_name, 'year')
|
years = getattr(cl.queryset, dates_or_datetimes)(field_name, 'year', **qs_kwargs)
|
||||||
return {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'back': None,
|
'back': None,
|
||||||
|
|
|
@ -981,6 +981,18 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||||
self.assertContains(response, 'question__expires__month=10')
|
self.assertContains(response, 'question__expires__month=10')
|
||||||
self.assertContains(response, 'question__expires__year=2016')
|
self.assertContains(response, 'question__expires__year=2016')
|
||||||
|
|
||||||
|
@override_settings(TIME_ZONE='America/Los_Angeles', USE_TZ=True)
|
||||||
|
def test_date_hierarchy_local_date_differ_from_utc(self):
|
||||||
|
# This datetime is 2017-01-01 in UTC.
|
||||||
|
date = pytz.timezone('America/Los_Angeles').localize(datetime.datetime(2016, 12, 31, 16))
|
||||||
|
q = Question.objects.create(question='Why?', expires=date)
|
||||||
|
Answer2.objects.create(question=q, answer='Because.')
|
||||||
|
response = self.client.get(reverse('admin:admin_views_answer2_changelist'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'question__expires__day=31')
|
||||||
|
self.assertContains(response, 'question__expires__month=12')
|
||||||
|
self.assertContains(response, 'question__expires__year=2016')
|
||||||
|
|
||||||
def test_sortable_by_columns_subset(self):
|
def test_sortable_by_columns_subset(self):
|
||||||
expected_sortable_fields = ('date', 'callable_year')
|
expected_sortable_fields = ('date', 'callable_year')
|
||||||
expected_not_sortable_fields = (
|
expected_not_sortable_fields = (
|
||||||
|
|
Loading…
Reference in New Issue