Fixed #27475 -- Fixed NonExistentTimeError crash in ModelAdmin.date_hierarchy.
This commit is contained in:
parent
8346680e1c
commit
e88d2dfcf4
|
@ -2,8 +2,7 @@ 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, get_fields_from_path,
|
display_for_field, display_for_value, label_for_field, lookup_field,
|
||||||
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,
|
||||||
|
@ -335,8 +334,6 @@ 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]
|
|
||||||
dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates'
|
|
||||||
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
|
||||||
|
@ -370,7 +367,7 @@ def date_hierarchy(cl):
|
||||||
}
|
}
|
||||||
elif year_lookup and month_lookup:
|
elif year_lookup and month_lookup:
|
||||||
days = cl.queryset.filter(**{year_field: year_lookup, month_field: 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 {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'back': {
|
'back': {
|
||||||
|
@ -384,7 +381,7 @@ def date_hierarchy(cl):
|
||||||
}
|
}
|
||||||
elif year_lookup:
|
elif year_lookup:
|
||||||
months = cl.queryset.filter(**{year_field: 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 {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'back': {
|
'back': {
|
||||||
|
@ -397,7 +394,7 @@ def date_hierarchy(cl):
|
||||||
} for month in months]
|
} for month in months]
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
years = getattr(cl.queryset, dates_or_datetimes)(field_name, 'year')
|
years = getattr(cl.queryset, 'dates')(field_name, 'year')
|
||||||
return {
|
return {
|
||||||
'show': True,
|
'show': True,
|
||||||
'choices': [{
|
'choices': [{
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.utils.safestring import mark_safe
|
||||||
from .forms import MediaActionForm
|
from .forms import MediaActionForm
|
||||||
from .models import (
|
from .models import (
|
||||||
Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
|
Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
|
||||||
AdminOrderedModelMethod, Album, Answer, Article, BarAccount, Book,
|
AdminOrderedModelMethod, Album, Answer, Answer2, Article, BarAccount, Book,
|
||||||
Bookmark, Category, Chapter, ChapterXtra1, Child, ChildOfReferer, Choice,
|
Bookmark, Category, Chapter, ChapterXtra1, Child, ChildOfReferer, Choice,
|
||||||
City, Collector, Color, Color2, ComplexSortedPerson, CoverLetter,
|
City, Collector, Color, Color2, ComplexSortedPerson, CoverLetter,
|
||||||
CustomArticle, CyclicOne, CyclicTwo, DependentChild, DooHickey, EmptyModel,
|
CustomArticle, CyclicOne, CyclicTwo, DependentChild, DooHickey, EmptyModel,
|
||||||
|
@ -971,6 +971,7 @@ site.register(Topping, ToppingAdmin)
|
||||||
site.register(Album, AlbumAdmin)
|
site.register(Album, AlbumAdmin)
|
||||||
site.register(Question)
|
site.register(Question)
|
||||||
site.register(Answer, date_hierarchy='question__posted')
|
site.register(Answer, date_hierarchy='question__posted')
|
||||||
|
site.register(Answer2, date_hierarchy='question__expires')
|
||||||
site.register(PrePopulatedPost, PrePopulatedPostAdmin)
|
site.register(PrePopulatedPost, PrePopulatedPostAdmin)
|
||||||
site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
|
site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
|
||||||
site.register(FilteredManager, CustomManagerAdmin)
|
site.register(FilteredManager, CustomManagerAdmin)
|
||||||
|
|
|
@ -590,6 +590,7 @@ class WorkHour(models.Model):
|
||||||
class Question(models.Model):
|
class Question(models.Model):
|
||||||
question = models.CharField(max_length=20)
|
question = models.CharField(max_length=20)
|
||||||
posted = models.DateField(default=datetime.date.today)
|
posted = models.DateField(default=datetime.date.today)
|
||||||
|
expires = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class Answer(models.Model):
|
class Answer(models.Model):
|
||||||
|
@ -600,6 +601,11 @@ class Answer(models.Model):
|
||||||
return self.answer
|
return self.answer
|
||||||
|
|
||||||
|
|
||||||
|
class Answer2(Answer):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
class Reservation(models.Model):
|
class Reservation(models.Model):
|
||||||
start_date = models.DateTimeField()
|
start_date = models.DateTimeField()
|
||||||
price = models.IntegerField()
|
price = models.IntegerField()
|
||||||
|
|
|
@ -5,6 +5,8 @@ import re
|
||||||
import unittest
|
import unittest
|
||||||
from urllib.parse import parse_qsl, urljoin, urlparse
|
from urllib.parse import parse_qsl, urljoin, urlparse
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
from django.contrib.admin import AdminSite, ModelAdmin
|
from django.contrib.admin import AdminSite, ModelAdmin
|
||||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||||
from django.contrib.admin.models import ADDITION, DELETION, LogEntry
|
from django.contrib.admin.models import ADDITION, DELETION, LogEntry
|
||||||
|
@ -42,15 +44,16 @@ from .admin import CityAdmin, site, site2
|
||||||
from .forms import MediaActionForm
|
from .forms import MediaActionForm
|
||||||
from .models import (
|
from .models import (
|
||||||
Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
|
Actor, AdminOrderedAdminMethod, AdminOrderedCallable, AdminOrderedField,
|
||||||
AdminOrderedModelMethod, Answer, Article, BarAccount, Book, Bookmark,
|
AdminOrderedModelMethod, Answer, Answer2, Article, BarAccount, Book,
|
||||||
Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child, Choice,
|
Bookmark, Category, Chapter, ChapterXtra1, ChapterXtra2, Character, Child,
|
||||||
City, Collector, Color, ComplexSortedPerson, CoverLetter, CustomArticle,
|
Choice, City, Collector, Color, ComplexSortedPerson, CoverLetter,
|
||||||
CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel, ExternalSubscriber,
|
CustomArticle, CyclicOne, CyclicTwo, DooHickey, Employee, EmptyModel,
|
||||||
Fabric, FancyDoodad, FieldOverridePost, FilteredManager, FooAccount,
|
ExternalSubscriber, Fabric, FancyDoodad, FieldOverridePost,
|
||||||
FoodDelivery, FunkyTag, Gallery, Grommet, Inquisition, Language, Link,
|
FilteredManager, FooAccount, FoodDelivery, FunkyTag, Gallery, Grommet,
|
||||||
MainPrepopulated, Media, ModelWithStringPrimaryKey, OtherStory, Paper,
|
Inquisition, Language, Link, MainPrepopulated, Media,
|
||||||
Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona,
|
ModelWithStringPrimaryKey, OtherStory, Paper, Parent,
|
||||||
Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
|
ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture,
|
||||||
|
Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post,
|
||||||
PrePopulatedPost, Promo, Question, Recommendation, Recommender,
|
PrePopulatedPost, Promo, Question, Recommendation, Recommender,
|
||||||
RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Restaurant,
|
RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Restaurant,
|
||||||
RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage,
|
RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage,
|
||||||
|
@ -918,6 +921,18 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
|
||||||
self.assertEqual(response.context['site_url'], '/my-site-url/')
|
self.assertEqual(response.context['site_url'], '/my-site-url/')
|
||||||
self.assertContains(response, '<a href="/my-site-url/">View site</a>')
|
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=[{
|
@override_settings(TEMPLATES=[{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
|
Loading…
Reference in New Issue