Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 11 additions & 6 deletions django/contrib/admin/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,12 +840,17 @@ def _check_date_hierarchy(self, obj):
return []
else:
try:
field = obj.model._meta.get_field(obj.date_hierarchy)
except FieldDoesNotExist:
return refer_to_missing_field(
option='date_hierarchy', field=obj.date_hierarchy,
model=obj.model, obj=obj, id='admin.E127',
)
field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1]
except (NotRelationField, FieldDoesNotExist):
return [
checks.Error(
"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:
if not isinstance(field, (models.DateField, models.DateTimeField)):
return must_be('a DateField or DateTimeField', option='date_hierarchy', obj=obj, id='admin.E128')
Expand Down
5 changes: 3 additions & 2 deletions django/contrib/admin/templatetags/admin_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
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 (
ALL_VAR, ORDER_VAR, PAGE_VAR, SEARCH_VAR,
Expand Down Expand Up @@ -346,7 +347,7 @@ def date_hierarchy(cl):
"""
if 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'
year_field = '%s__year' % field_name
month_field = '%s__month' % field_name
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ with the admin site:
which is not editable through the admin.
* **admin.E126**: The value of ``search_fields`` must be a list or tuple.
* **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
``DateTimeField``.

Expand Down
8 changes: 8 additions & 0 deletions docs/ref/contrib/admin/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ subclass::

date_hierarchy = 'pub_date'

You can also specify a field on a related model using the ``__`` lookup, for example::

date_hierarchy = 'author__pub_date'

.. versionchanged:: 1.11

Ability to reference fields on related models was added.

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
drill-down only.
Expand Down
2 changes: 1 addition & 1 deletion docs/releases/1.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Minor features
:mod:`django.contrib.admin`
~~~~~~~~~~~~~~~~~~~~~~~~~~~

* ...
* ``ModelAdmin.date_hierarchy`` can now reference fields across relations.

:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion tests/admin_views/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ def get_formsets_with_inlines(self, request, obj=None):
site.register(Topping, ToppingAdmin)
site.register(Album, AlbumAdmin)
site.register(Question)
site.register(Answer)
site.register(Answer, date_hierarchy='question__posted')
site.register(PrePopulatedPost, PrePopulatedPostAdmin)
site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
site.register(FilteredManager, CustomManagerAdmin)
Expand Down
1 change: 1 addition & 0 deletions tests/admin_views/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ class WorkHour(models.Model):

class Question(models.Model):
question = models.CharField(max_length=20)
posted = models.DateField(default=datetime.date.today)


@python_2_unicode_compatible
Expand Down
22 changes: 22 additions & 0 deletions tests/admin_views/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5331,6 +5331,28 @@ def test_multiple_years(self):
self.assert_non_localized_year(response, 2003)
self.assert_non_localized_year(response, 2005)

def test_related(self):
"""
Ensure that date hierarchy works over relations. Refs #xxxxx.
"""
QUESTIONS = (
(datetime.date(2001, 1, 30), 0),
(datetime.date(2003, 3, 15), 1),
(datetime.date(2005, 5, 3), 2),
)
for date, answer_count in QUESTIONS:
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:
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')
class AdminCustomSaveRelatedTests(TestCase):
Expand Down
17 changes: 16 additions & 1 deletion tests/modeladmin/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,7 @@ class ValidationTestModelAdmin(ModelAdmin):
self.assertIsInvalid(
ValidationTestModelAdmin, ValidationTestModel,
("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')

def test_invalid_field_type(self):
Expand All @@ -1194,6 +1194,21 @@ class ValidationTestModelAdmin(ModelAdmin):

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):

Expand Down