Explorar el Código

Fixed #27475 -- Fixed NonExistentTimeError crash in ModelAdmin.date_hierarchy.

Anton Samarchyan hace 8 años
padre
commit
e88d2dfcf4

+ 4 - 7
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': [{

+ 2 - 1
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)

+ 6 - 0
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()

+ 24 - 9
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, '<a href="/my-site-url/">View site</a>')
 
+    @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',