Fixed #13196 -- Formatting in admin changelists.

Handled values returned by functions more like field values.
In particular, localized dates, times and datetimes properly,
and converted datetimes to the current timezone.
This commit is contained in:
Aymeric Augustin 2012-04-29 11:51:12 +02:00
parent 7c27d1561e
commit 905bd7fb44
5 changed files with 60 additions and 13 deletions

View File

@ -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(' ')

View File

@ -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

View File

@ -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']

View File

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

View File

@ -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)
check_results_order(ascending=True)