Fixed #19963 -- Added support for date_hierarchy across relations.
This commit is contained in:
parent
89ca112884
commit
2f9c4e2b6f
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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``.
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue