mirror of https://github.com/django/django.git
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:
parent
7c27d1561e
commit
905bd7fb44
|
@ -1,6 +1,7 @@
|
||||||
import datetime
|
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,
|
from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
|
||||||
ORDER_VAR, PAGE_VAR, SEARCH_VAR)
|
ORDER_VAR, PAGE_VAR, SEARCH_VAR)
|
||||||
from django.contrib.admin.templatetags.admin_static import static
|
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)
|
boolean = getattr(attr, 'boolean', False)
|
||||||
if boolean:
|
if boolean:
|
||||||
allow_tags = True
|
allow_tags = True
|
||||||
result_repr = _boolean_icon(value)
|
result_repr = display_for_value(value, boolean)
|
||||||
else:
|
|
||||||
result_repr = smart_unicode(value)
|
|
||||||
# Strip HTML tags in the resulting text, except if the
|
# Strip HTML tags in the resulting text, except if the
|
||||||
# function has an "allow_tags" attribute set to True.
|
# function has an "allow_tags" attribute set to True.
|
||||||
if not allow_tags:
|
if not allow_tags:
|
||||||
result_repr = escape(result_repr)
|
result_repr = escape(result_repr)
|
||||||
else:
|
else:
|
||||||
result_repr = mark_safe(result_repr)
|
result_repr = mark_safe(result_repr)
|
||||||
|
if isinstance(value, (datetime.date, datetime.time)):
|
||||||
|
row_class = ' class="nowrap"'
|
||||||
else:
|
else:
|
||||||
if isinstance(f.rel, models.ManyToOneRel):
|
if isinstance(f.rel, models.ManyToOneRel):
|
||||||
field_val = getattr(result, f.name)
|
field_val = getattr(result, f.name)
|
||||||
|
@ -202,9 +203,7 @@ def items_for_result(cl, result, form):
|
||||||
result_repr = escape(field_val)
|
result_repr = escape(field_val)
|
||||||
else:
|
else:
|
||||||
result_repr = display_for_field(value, f)
|
result_repr = display_for_field(value, f)
|
||||||
if isinstance(f, models.DateField)\
|
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
|
||||||
or isinstance(f, models.TimeField)\
|
|
||||||
or isinstance(f, models.ForeignKey):
|
|
||||||
row_class = ' class="nowrap"'
|
row_class = ' class="nowrap"'
|
||||||
if force_unicode(result_repr) == '':
|
if force_unicode(result_repr) == '':
|
||||||
result_repr = mark_safe(' ')
|
result_repr = mark_safe(' ')
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.sql.constants import LOOKUP_SEP
|
from django.db.models.sql.constants import LOOKUP_SEP
|
||||||
from django.db.models.deletion import Collector
|
from django.db.models.deletion import Collector
|
||||||
|
@ -323,7 +326,7 @@ def display_for_field(value, field):
|
||||||
return EMPTY_CHANGELIST_VALUE
|
return EMPTY_CHANGELIST_VALUE
|
||||||
elif isinstance(field, models.DateTimeField):
|
elif isinstance(field, models.DateTimeField):
|
||||||
return formats.localize(timezone.localtime(value))
|
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)
|
return formats.localize(value)
|
||||||
elif isinstance(field, models.DecimalField):
|
elif isinstance(field, models.DecimalField):
|
||||||
return formats.number_format(value, field.decimal_places)
|
return formats.number_format(value, field.decimal_places)
|
||||||
|
@ -333,6 +336,24 @@ def display_for_field(value, field):
|
||||||
return smart_unicode(value)
|
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):
|
class NotRelationField(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ from __future__ import absolute_import
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
|
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
|
||||||
Membership, ChordsMusician, ChordsBand, Invitation, Swallow)
|
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow)
|
||||||
|
|
||||||
|
|
||||||
site = admin.AdminSite(name="admin")
|
site = admin.AdminSite(name="admin")
|
||||||
|
@ -15,6 +15,15 @@ class CustomPaginator(Paginator):
|
||||||
allow_empty_first_page=allow_empty_first_page)
|
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):
|
class ParentAdmin(admin.ModelAdmin):
|
||||||
list_filter = ['child__name']
|
list_filter = ['child__name']
|
||||||
search_fields = ['child__name']
|
search_fields = ['child__name']
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
class Event(models.Model):
|
||||||
|
date = models.DateField()
|
||||||
|
|
||||||
class Parent(models.Model):
|
class Parent(models.Model):
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.options import IncorrectLookupParameters
|
from django.contrib.admin.options import IncorrectLookupParameters
|
||||||
from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR
|
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.template import Context, Template
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.utils import formats
|
||||||
|
|
||||||
from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
|
from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
|
||||||
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
|
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
|
||||||
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
|
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
|
||||||
FilteredChildAdmin, CustomPaginator, site as custom_site,
|
FilteredChildAdmin, CustomPaginator, site as custom_site,
|
||||||
SwallowAdmin)
|
SwallowAdmin)
|
||||||
from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
|
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
|
||||||
Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
|
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
|
||||||
UnorderedObject, OrderedObject)
|
UnorderedObject, OrderedObject)
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,6 +328,19 @@ class ChangeListTests(TestCase):
|
||||||
self.assertEqual(cl.paginator.count, 30)
|
self.assertEqual(cl.paginator.count, 30)
|
||||||
self.assertEqual(cl.paginator.page_range, [1, 2, 3])
|
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):
|
def test_dynamic_list_display(self):
|
||||||
"""
|
"""
|
||||||
Regression tests for #14206: dynamic list_display support.
|
Regression tests for #14206: dynamic list_display support.
|
||||||
|
|
Loading…
Reference in New Issue