diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index e778429e90..bda8d4b4cd 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -1,6 +1,7 @@ import datetime -from django.contrib.admin.util import lookup_field, display_for_field, label_for_field +from django.contrib.admin.util import (lookup_field, display_for_field, + display_for_value, label_for_field) from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE, ORDER_VAR, PAGE_VAR, SEARCH_VAR) from django.contrib.admin.templatetags.admin_static import static @@ -184,15 +185,15 @@ def items_for_result(cl, result, form): boolean = getattr(attr, 'boolean', False) if boolean: allow_tags = True - result_repr = _boolean_icon(value) - else: - result_repr = smart_unicode(value) + result_repr = display_for_value(value, boolean) # Strip HTML tags in the resulting text, except if the # function has an "allow_tags" attribute set to True. if not allow_tags: result_repr = escape(result_repr) else: result_repr = mark_safe(result_repr) + if isinstance(value, (datetime.date, datetime.time)): + row_class = ' class="nowrap"' else: if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(result, f.name) @@ -202,9 +203,7 @@ def items_for_result(cl, result, form): result_repr = escape(field_val) else: result_repr = display_for_field(value, f) - if isinstance(f, models.DateField)\ - or isinstance(f, models.TimeField)\ - or isinstance(f, models.ForeignKey): + if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)): row_class = ' class="nowrap"' if force_unicode(result_repr) == '': result_repr = mark_safe(' ') diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 61182a6d02..b8456d859f 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -1,3 +1,6 @@ +import datetime +import decimal + from django.db import models from django.db.models.sql.constants import LOOKUP_SEP from django.db.models.deletion import Collector @@ -323,7 +326,7 @@ def display_for_field(value, field): return EMPTY_CHANGELIST_VALUE elif isinstance(field, models.DateTimeField): return formats.localize(timezone.localtime(value)) - elif isinstance(field, models.DateField) or isinstance(field, models.TimeField): + elif isinstance(field, (models.DateField, models.TimeField)): return formats.localize(value) elif isinstance(field, models.DecimalField): return formats.number_format(value, field.decimal_places) @@ -333,6 +336,24 @@ def display_for_field(value, field): return smart_unicode(value) +def display_for_value(value, boolean=False): + from django.contrib.admin.templatetags.admin_list import _boolean_icon + from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE + + if boolean: + return _boolean_icon(value) + elif value is None: + return EMPTY_CHANGELIST_VALUE + elif isinstance(value, datetime.datetime): + return formats.localize(timezone.localtime(value)) + elif isinstance(value, (datetime.date, datetime.time)): + return formats.localize(value) + elif isinstance(value, (decimal.Decimal, float, int, long)): + return formats.number_format(value) + else: + return smart_unicode(value) + + class NotRelationField(Exception): pass diff --git a/tests/regressiontests/admin_changelist/admin.py b/tests/regressiontests/admin_changelist/admin.py index 80bb732eb0..9ecfbc6e12 100644 --- a/tests/regressiontests/admin_changelist/admin.py +++ b/tests/regressiontests/admin_changelist/admin.py @@ -3,8 +3,8 @@ from __future__ import absolute_import from django.contrib import admin from django.core.paginator import Paginator -from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet, - Membership, ChordsMusician, ChordsBand, Invitation, Swallow) +from .models import (Event, Child, Parent, Genre, Band, Musician, Group, + Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow) site = admin.AdminSite(name="admin") @@ -15,6 +15,15 @@ class CustomPaginator(Paginator): allow_empty_first_page=allow_empty_first_page) +class EventAdmin(admin.ModelAdmin): + list_display = ['event_date_func'] + + def event_date_func(self, event): + return event.date + +site.register(Event, EventAdmin) + + class ParentAdmin(admin.ModelAdmin): list_filter = ['child__name'] search_fields = ['child__name'] diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py index 1c615eab19..dcc343bb6a 100644 --- a/tests/regressiontests/admin_changelist/models.py +++ b/tests/regressiontests/admin_changelist/models.py @@ -1,5 +1,7 @@ from django.db import models +class Event(models.Model): + date = models.DateField() class Parent(models.Model): name = models.CharField(max_length=128) diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index f4cdbde4fb..62166ce174 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/regressiontests/admin_changelist/tests.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import datetime + from django.contrib import admin from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR @@ -7,14 +9,15 @@ from django.contrib.auth.models import User from django.template import Context, Template from django.test import TestCase from django.test.client import RequestFactory +from django.utils import formats from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin, GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin, DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin, FilteredChildAdmin, CustomPaginator, site as custom_site, SwallowAdmin) -from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet, - Membership, ChordsMusician, ChordsBand, Invitation, Swallow, +from .models import (Event, Child, Parent, Genre, Band, Musician, Group, + Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow, UnorderedObject, OrderedObject) @@ -325,6 +328,19 @@ class ChangeListTests(TestCase): self.assertEqual(cl.paginator.count, 30) self.assertEqual(cl.paginator.page_range, [1, 2, 3]) + def test_computed_list_display_localization(self): + """ + Regression test for #13196: output of functions should be localized + in the changelist. + """ + User.objects.create_superuser( + username='super', email='super@localhost', password='secret') + self.client.login(username='super', password='secret') + event = Event.objects.create(date=datetime.date.today()) + response = self.client.get('/admin/admin_changelist/event/') + self.assertContains(response, formats.localize(event.date)) + self.assertNotContains(response, unicode(event.date)) + def test_dynamic_list_display(self): """ Regression tests for #14206: dynamic list_display support. @@ -519,4 +535,4 @@ class ChangeListTests(TestCase): OrderedObjectAdmin.ordering = ['-id', 'bool'] check_results_order() OrderedObjectAdmin.ordering = ['id', 'bool'] - check_results_order(ascending=True) \ No newline at end of file + check_results_order(ascending=True)