summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVytis Banaitis <vytis.banaitis@gmail.com>2016-05-05 20:52:54 +0300
committerTim Graham <timograham@gmail.com>2016-06-04 12:14:02 -0400
commit2f9c4e2b6fab3ce08cfbebff79d758196250790c (patch)
tree550e125782fed8aed39589882c426b9178f78d4e
parent89ca1128840a2ae011109cbf38ea5ee9549f1533 (diff)
Fixed #19963 -- Added support for date_hierarchy across relations.
-rw-r--r--django/contrib/admin/checks.py16
-rw-r--r--django/contrib/admin/templatetags/admin_list.py5
-rw-r--r--docs/ref/checks.txt2
-rw-r--r--docs/ref/contrib/admin/index.txt9
-rw-r--r--docs/releases/1.11.txt2
-rw-r--r--tests/admin_views/admin.py2
-rw-r--r--tests/admin_views/models.py1
-rw-r--r--tests/admin_views/tests.py20
-rw-r--r--tests/modeladmin/tests.py23
9 files changed, 66 insertions, 14 deletions
diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py
index 87788c8f30..6d338281a7 100644
--- a/django/contrib/admin/checks.py
+++ b/django/contrib/admin/checks.py
@@ -840,12 +840,16 @@ class ModelAdminChecks(BaseModelAdminChecks):
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')
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index c8ebccfd25..63b428ec68 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -5,7 +5,8 @@ import warnings
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,
@@ -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
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 03bdba97db..ac96fbf412 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -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``.
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index ac696656c2..6984589ee2 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -213,10 +213,19 @@ 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'
+
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.
+ .. versionchanged:: 1.11
+
+ The ability to reference fields on related models was added.
+
.. note::
``date_hierarchy`` uses :meth:`QuerySet.datetimes()
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index ae3f3af438..918a6157f1 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -50,7 +50,7 @@ Minor features
:mod:`django.contrib.admin`
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* :attr:`.ModelAdmin.date_hierarchy` can now reference fields across relations.
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index 31f41515bb..2146fa66f2 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -973,7 +973,7 @@ site.register(Pizza, PizzaAdmin)
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)
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index 301955efd8..b6ec1b6c31 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -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
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 281084a8a1..6455b210b2 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -5331,6 +5331,26 @@ class DateHierarchyTests(TestCase):
self.assert_non_localized_year(response, 2003)
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')
class AdminCustomSaveRelatedTests(TestCase):
diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py
index 990ed9ab32..40ea025baf 100644
--- a/tests/modeladmin/tests.py
+++ b/tests/modeladmin/tests.py
@@ -1175,9 +1175,10 @@ class DateHierarchyCheckTests(CheckTestCase):
self.assertIsInvalid(
ValidationTestModelAdmin, ValidationTestModel,
- ("The value of 'date_hierarchy' refers to 'non_existent_field', which "
- "is not an attribute of 'modeladmin.ValidationTestModel'."),
- 'admin.E127')
+ "The value of 'date_hierarchy' refers to 'non_existent_field', which "
+ "does not refer to a Field.",
+ 'admin.E127'
+ )
def test_invalid_field_type(self):
class ValidationTestModelAdmin(ModelAdmin):
@@ -1194,6 +1195,22 @@ class DateHierarchyCheckTests(CheckTestCase):
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):