Fixed #19963 -- Added support for date_hierarchy across relations.

This commit is contained in:
Vytis Banaitis 2016-05-05 20:52:54 +03:00 committed by Tim Graham
parent 89ca112884
commit 2f9c4e2b6f
9 changed files with 66 additions and 14 deletions

View File

@ -840,12 +840,16 @@ class ModelAdminChecks(BaseModelAdminChecks):
return [] return []
else: else:
try: try:
field = obj.model._meta.get_field(obj.date_hierarchy) field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1]
except FieldDoesNotExist: except (NotRelationField, FieldDoesNotExist):
return refer_to_missing_field( return [
option='date_hierarchy', field=obj.date_hierarchy, checks.Error(
model=obj.model, obj=obj, id='admin.E127', "The value of 'date_hierarchy' refers to '%s', which "
"does not refer to a Field." % obj.date_hierarchy,
obj=obj.__class__,
id='admin.E127',
) )
]
else: else:
if not isinstance(field, (models.DateField, models.DateTimeField)): if not isinstance(field, (models.DateField, models.DateTimeField)):
return must_be('a DateField or DateTimeField', option='date_hierarchy', obj=obj, id='admin.E128') return must_be('a DateField or DateTimeField', option='date_hierarchy', obj=obj, id='admin.E128')

View File

@ -5,7 +5,8 @@ import warnings
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,
@ -346,7 +347,7 @@ def date_hierarchy(cl):
""" """
if cl.date_hierarchy: if cl.date_hierarchy:
field_name = cl.date_hierarchy field_name = cl.date_hierarchy
field = cl.opts.get_field(field_name) field = get_fields_from_path(cl.model, field_name)[-1]
dates_or_datetimes = 'datetimes' if isinstance(field, models.DateTimeField) else 'dates' 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

View File

@ -393,7 +393,7 @@ with the admin site:
which is not editable through the admin. which is not editable through the admin.
* **admin.E126**: The value of ``search_fields`` must be a list or tuple. * **admin.E126**: The value of ``search_fields`` must be a list or tuple.
* **admin.E127**: The value of ``date_hierarchy`` refers to ``<field name>``, * **admin.E127**: The value of ``date_hierarchy`` refers to ``<field name>``,
which is not an attribute of ``<model>``. which does not refer to a Field.
* **admin.E128**: The value of ``date_hierarchy`` must be a ``DateField`` or * **admin.E128**: The value of ``date_hierarchy`` must be a ``DateField`` or
``DateTimeField``. ``DateTimeField``.

View File

@ -213,10 +213,19 @@ subclass::
date_hierarchy = 'pub_date' date_hierarchy = 'pub_date'
You can also specify a field on a related model using the ``__`` lookup,
for example::
date_hierarchy = 'author__pub_date'
This will intelligently populate itself based on available data, This will intelligently populate itself based on available data,
e.g. if all the dates are in one month, it'll show the day-level e.g. if all the dates are in one month, it'll show the day-level
drill-down only. drill-down only.
.. versionchanged:: 1.11
The ability to reference fields on related models was added.
.. note:: .. note::
``date_hierarchy`` uses :meth:`QuerySet.datetimes() ``date_hierarchy`` uses :meth:`QuerySet.datetimes()

View File

@ -50,7 +50,7 @@ Minor features
:mod:`django.contrib.admin` :mod:`django.contrib.admin`
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ... * :attr:`.ModelAdmin.date_hierarchy` can now reference fields across relations.
:mod:`django.contrib.admindocs` :mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -973,7 +973,7 @@ site.register(Pizza, PizzaAdmin)
site.register(Topping, ToppingAdmin) site.register(Topping, ToppingAdmin)
site.register(Album, AlbumAdmin) site.register(Album, AlbumAdmin)
site.register(Question) site.register(Question)
site.register(Answer) site.register(Answer, date_hierarchy='question__posted')
site.register(PrePopulatedPost, PrePopulatedPostAdmin) site.register(PrePopulatedPost, PrePopulatedPostAdmin)
site.register(ComplexSortedPerson, ComplexSortedPersonAdmin) site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
site.register(FilteredManager, CustomManagerAdmin) site.register(FilteredManager, CustomManagerAdmin)

View File

@ -621,6 +621,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)
@python_2_unicode_compatible @python_2_unicode_compatible

View File

@ -5331,6 +5331,26 @@ class DateHierarchyTests(TestCase):
self.assert_non_localized_year(response, 2003) self.assert_non_localized_year(response, 2003)
self.assert_non_localized_year(response, 2005) self.assert_non_localized_year(response, 2005)
def test_related_field(self):
questions_data = (
# (posted data, number of answers),
(datetime.date(2001, 1, 30), 0),
(datetime.date(2003, 3, 15), 1),
(datetime.date(2005, 5, 3), 2),
)
for date, answer_count in questions_data:
question = Question.objects.create(posted=date)
for i in range(answer_count):
question.answer_set.create()
response = self.client.get(reverse('admin:admin_views_answer_changelist'))
for date, answer_count in questions_data:
link = '?question__posted__year=%d"' % (date.year,)
if answer_count > 0:
self.assertContains(response, link)
else:
self.assertNotContains(response, link)
@override_settings(ROOT_URLCONF='admin_views.urls') @override_settings(ROOT_URLCONF='admin_views.urls')
class AdminCustomSaveRelatedTests(TestCase): class AdminCustomSaveRelatedTests(TestCase):

View File

@ -1175,9 +1175,10 @@ class DateHierarchyCheckTests(CheckTestCase):
self.assertIsInvalid( self.assertIsInvalid(
ValidationTestModelAdmin, ValidationTestModel, ValidationTestModelAdmin, ValidationTestModel,
("The value of 'date_hierarchy' refers to 'non_existent_field', which " "The value of 'date_hierarchy' refers to 'non_existent_field', which "
"is not an attribute of 'modeladmin.ValidationTestModel'."), "does not refer to a Field.",
'admin.E127') 'admin.E127'
)
def test_invalid_field_type(self): def test_invalid_field_type(self):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
@ -1194,6 +1195,22 @@ class DateHierarchyCheckTests(CheckTestCase):
self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel) self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
def test_related_valid_case(self):
class ValidationTestModelAdmin(ModelAdmin):
date_hierarchy = 'band__sign_date'
self.assertIsValid(ValidationTestModelAdmin, ValidationTestModel)
def test_related_invalid_field_type(self):
class ValidationTestModelAdmin(ModelAdmin):
date_hierarchy = 'band__name'
self.assertIsInvalid(
ValidationTestModelAdmin, ValidationTestModel,
"The value of 'date_hierarchy' must be a DateField or DateTimeField.",
'admin.E128'
)
class OrderingCheckTests(CheckTestCase): class OrderingCheckTests(CheckTestCase):