From e88d2dfcf4daa2b4ee451f518085413bb3b8deeb Mon Sep 17 00:00:00 2001 From: Anton Samarchyan Date: Fri, 24 Feb 2017 10:22:25 -0500 Subject: [PATCH] Fixed #27475 -- Fixed NonExistentTimeError crash in ModelAdmin.date_hierarchy. --- .../contrib/admin/templatetags/admin_list.py | 11 +++---- tests/admin_views/admin.py | 3 +- tests/admin_views/models.py | 6 ++++ tests/admin_views/tests.py | 33 ++++++++++++++----- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 613db7d90db..6901c4cec9c 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -2,8 +2,7 @@ import datetime from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.admin.utils import ( - display_for_field, display_for_value, get_fields_from_path, - label_for_field, lookup_field, + display_for_field, display_for_value, label_for_field, lookup_field, ) from django.contrib.admin.views.main import ( ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR, @@ -335,8 +334,6 @@ def date_hierarchy(cl): """ if cl.date_hierarchy: field_name = cl.date_hierarchy - field = get_fields_from_path(cl.model, field_name)[-1] - dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates' year_field = '%s__year' % field_name month_field = '%s__month' % field_name day_field = '%s__day' % field_name @@ -370,7 +367,7 @@ def date_hierarchy(cl): } elif year_lookup and month_lookup: days = cl.queryset.filter(**{year_field: year_lookup, month_field: month_lookup}) - days = getattr(days, dates_or_datetimes)(field_name, 'day') + days = getattr(days, 'dates')(field_name, 'day') return { 'show': True, 'back': { @@ -384,7 +381,7 @@ def date_hierarchy(cl): } elif year_lookup: months = cl.queryset.filter(**{year_field: year_lookup}) - months = getattr(months, dates_or_datetimes)(field_name, 'month') + months = getattr(months, 'dates')(field_name, 'month') return { 'show': True, 'back': { @@ -397,7 +394,7 @@ def date_hierarchy(cl): } for month in months] } else: - years = getattr(cl.queryset, dates_or_datetimes)(field_name, 'year') + years = getattr(cl.queryset, 'dates')(field_name, 'year') return { 'show': True, 'choices': [{ diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 18270392663..24e6039f80c 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -22,7 +22,7 @@ from django.utils.safestring import mark_safe from .forms import MediaActionForm from .models import ( Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField, - AdminOrderedModelMethod, Album, Answer, Article, BarAccount, Book, + AdminOrderedModelMethod, Album, Answer, Answer2, Article, BarAccount, Book, Bookmark, Category, Chapter, ChapterXtra1, Child, ChildOfReferer, Choice, City, Collector, Color, Color2, ComplexSortedPerson, CoverLetter, CustomArticle, CyclicOne, CyclicTwo, DependentChild, DooHickey, EmptyModel, @@ -971,6 +971,7 @@ site.register(Topping, ToppingAdmin) site.register(Album, AlbumAdmin) site.register(Question) site.register(Answer, date_hierarchy='question__posted') +site.register(Answer2, date_hierarchy='question__expires') site.register(PrePopulatedPost, PrePopulatedPostAdmin) site.register(ComplexSortedPerson, ComplexSortedPersonAdmin) site.register(FilteredManager, CustomManagerAdmin) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 4364260ef72..ea8fc18b5c6 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -590,6 +590,7 @@ class WorkHour(models.Model): class Question(models.Model): question = models.CharField(max_length=20) posted = models.DateField(default=datetime.date.today) + expires = models.DateTimeField(null=True, blank=True) class Answer(models.Model): @@ -600,6 +601,11 @@ class Answer(models.Model): return self.answer +class Answer2(Answer): + class Meta: + proxy = True + + class Reservation(models.Model): start_date = models.DateTimeField() price = models.IntegerField() diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index e36dfa31419..a113e293776 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -5,6 +5,8 @@ import re import unittest from urllib.parse import parse_qsl, urljoin, urlparse +import pytz + from django.contrib.admin import AdminSite, ModelAdmin from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.models import ADDITION, DELETION, LogEntry @@ -42,15 +44,16 @@ from .admin import CityAdmin, site, site2 from .forms import MediaActionForm from .models import ( Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField, - AdminOrderedModelMethod, Answer, Article, BarAccount, Book, Bookmark, - Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child, Choice, - City, Collector, Color, ComplexSortedPerson, CoverLetter, CustomArticle, - CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel, ExternalSubscriber, - Fabric, FancyDoodad, FieldOverridePost, FilteredManager, FooAccount, - FoodDelivery, FunkyTag, Gallery, Grommet, Inquisition, Language, Link, - MainPrepopulated, Media, ModelWithStringPrimaryKey, OtherStory, Paper, - Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, - Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post, + AdminOrderedModelMethod, Answer, Answer2, Article, BarAccount, Book, + Bookmark, Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child, + Choice, City, Collector, Color, ComplexSortedPerson, CoverLetter, + CustomArticle, CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel, + ExternalSubscriber, Fabric, FancyDoodad, FieldOverridePost, + FilteredManager, FooAccount, FoodDelivery, FunkyTag, Gallery, Grommet, + Inquisition, Language, Link, MainPrepopulated, Media, + ModelWithStringPrimaryKey, OtherStory, Paper, Parent, + ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture, + Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post, PrePopulatedPost, Promo, Question, Recommendation, Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Restaurant, RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage, @@ -918,6 +921,18 @@ class AdminViewBasicTest(AdminViewBasicTestCase): self.assertEqual(response.context['site_url'], '/my-site-url/') self.assertContains(response, 'View site') + @override_settings(TIME_ZONE='America/Sao_Paulo', USE_TZ=True) + def test_date_hierarchy_timezone_dst(self): + # This datetime doesn't exist in this timezone due to DST. + date = pytz.timezone('America/Sao_Paulo').localize(datetime.datetime(2016, 10, 16, 15), is_dst=None) + 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=16') + self.assertContains(response, 'question__expires__month=10') + self.assertContains(response, 'question__expires__year=2016') + @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates',