diff -r 8ff680917e4e django/contrib/admin/templatetags/admin_list.py
--- a/django/contrib/admin/templatetags/admin_list.py	Mon Feb 23 20:46:32 2009 -0200
+++ b/django/contrib/admin/templatetags/admin_list.py	Mon Feb 23 23:24:33 2009 -0200
@@ -70,7 +70,7 @@
 
 def result_headers(cl):
     lookup_opts = cl.lookup_opts
-    
+
     for i, field_name in enumerate(cl.list_display):
         attr = None
         try:
@@ -97,7 +97,7 @@
                             raise AttributeError, \
                                 "'%s' model or '%s' objects have no attribute '%s'" % \
                                     (lookup_opts.object_name, cl.model_admin.__class__, field_name)
-                
+
                 try:
                     header = attr.short_description
                 except AttributeError:
@@ -205,8 +205,8 @@
                     result_repr = EMPTY_CHANGELIST_VALUE
             # Fields with choices are special: Use the representation
             # of the choice.
-            elif f.choices:
-                result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
+            elif f.flatchoices:
+                result_repr = dict(f.flatchoices).get(field_val, EMPTY_CHANGELIST_VALUE)
             else:
                 result_repr = escape(field_val)
         if force_unicode(result_repr) == '':
diff -r 8ff680917e4e tests/regressiontests/admin_views/customadmin.py
--- a/tests/regressiontests/admin_views/customadmin.py	Mon Feb 23 20:46:32 2009 -0200
+++ b/tests/regressiontests/admin_views/customadmin.py	Mon Feb 23 23:24:33 2009 -0200
@@ -10,21 +10,22 @@
 class Admin2(admin.AdminSite):
     login_template = 'custom_admin/login.html'
     index_template = 'custom_admin/index.html'
-    
+
     # A custom index view.
     def index(self, request, extra_context=None):
         return super(Admin2, self).index(request, {'foo': '*bar*'})
-    
+
     def get_urls(self):
         return patterns('',
             (r'^my_view/$', self.admin_view(self.my_view)),
         ) + super(Admin2, self).get_urls()
-    
+
     def my_view(self, request):
         return HttpResponse("Django is a magical pony!")
-    
+
 site = Admin2(name="admin2")
 
 site.register(models.Article, models.ArticleAdmin)
 site.register(models.Section, inlines=[models.ArticleInline])
 site.register(models.Thing, models.ThingAdmin)
+site.register(models.Fabric, models.FabricAdmin)
diff -r 8ff680917e4e tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/admin_views/fixtures/admin-views-fabrics.xml	Mon Feb 23 23:24:33 2009 -0200
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+  <object pk="1" model="admin_views.fabric">
+    <field type="CharField" name="surface">x</field>
+  </object>
+  <object pk="2" model="admin_views.fabric">
+    <field type="CharField" name="surface">y</field>
+  </object>
+  <object pk="3" model="admin_views.fabric">
+    <field type="CharField" name="surface">plain</field>
+  </object>
+</django-objects>
diff -r 8ff680917e4e tests/regressiontests/admin_views/models.py
--- a/tests/regressiontests/admin_views/models.py	Mon Feb 23 20:46:32 2009 -0200
+++ b/tests/regressiontests/admin_views/models.py	Mon Feb 23 23:24:33 2009 -0200
@@ -20,7 +20,7 @@
 
     def __unicode__(self):
         return self.title
-    
+
     def model_year(self):
         return self.date.year
     model_year.admin_order_field = 'date'
@@ -54,14 +54,14 @@
 
 class ChapterXtra1(models.Model):
     chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
-    xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') 
+    xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
 
     def __unicode__(self):
         return u'¿Xtra1: %s' % self.xtra
 
 class ChapterXtra2(models.Model):
     chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
-    xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') 
+    xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
 
     def __unicode__(self):
         return u'¿Xtra2: %s' % self.xtra
@@ -87,7 +87,7 @@
                 'extra_var': 'Hello!'
             }
         )
-        
+
     def modeladmin_year(self, obj):
         return obj.date.year
     modeladmin_year.admin_order_field = 'date'
@@ -121,7 +121,7 @@
 
 class Color(models.Model):
     value = models.CharField(max_length=10)
-    warm = models.BooleanField()   
+    warm = models.BooleanField()
     def __unicode__(self):
         return self.value
 
@@ -134,12 +134,28 @@
 class ThingAdmin(admin.ModelAdmin):
     list_filter = ('color',)
 
+class Fabric(models.Model):
+    NG_CHOICES = (
+        ('Textured', (
+                ('x', 'Horizontal'),
+                ('y', 'Vertical'),
+            )
+        ),
+        ('plain', 'Smooth'),
+    )
+    surface = models.CharField(max_length=20, choices=NG_CHOICES)
+
+class FabricAdmin(admin.ModelAdmin):
+    list_display = ('surface',)
+    list_filter = ('surface',)
+
 admin.site.register(Article, ArticleAdmin)
 admin.site.register(CustomArticle, CustomArticleAdmin)
 admin.site.register(Section, inlines=[ArticleInline])
 admin.site.register(ModelWithStringPrimaryKey)
 admin.site.register(Color)
 admin.site.register(Thing, ThingAdmin)
+admin.site.register(Fabric, FabricAdmin)
 
 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
 # That way we cover all four cases:
diff -r 8ff680917e4e tests/regressiontests/admin_views/tests.py
--- a/tests/regressiontests/admin_views/tests.py	Mon Feb 23 20:46:32 2009 -0200
+++ b/tests/regressiontests/admin_views/tests.py	Mon Feb 23 23:24:33 2009 -0200
@@ -12,19 +12,19 @@
 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
 
 class AdminViewBasicTest(TestCase):
-    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
-    
-    # Store the bit of the URL where the admin is registered as a class 
+    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
+
+    # Store the bit of the URL where the admin is registered as a class
     # variable. That way we can test a second AdminSite just by subclassing
     # this test case and changing urlbit.
     urlbit = 'admin'
-    
+
     def setUp(self):
         self.client.login(username='super', password='secret')
-    
+
     def tearDown(self):
         self.client.logout()
-    
+
     def testTrailingSlashRequired(self):
         """
         If you leave off the trailing slash, app should redirect and add it.
@@ -33,29 +33,29 @@
         self.assertRedirects(request,
             '/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
         )
-    
+
     def testBasicAddGet(self):
         """
         A smoke test to ensure GET on the add_view works.
         """
         response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
         self.failUnlessEqual(response.status_code, 200)
-    
+
     def testAddWithGETArgs(self):
         response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
         self.failUnlessEqual(response.status_code, 200)
         self.failUnless(
-            'value="My Section"' in response.content, 
+            'value="My Section"' in response.content,
             "Couldn't find an input with the right value in the response."
         )
-    
+
     def testBasicEditGet(self):
         """
         A smoke test to ensureGET on the change_view works.
         """
         response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
         self.failUnlessEqual(response.status_code, 200)
-    
+
     def testBasicAddPost(self):
         """
         A smoke test to ensure POST on add_view works.
@@ -68,7 +68,7 @@
         }
         response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
         self.failUnlessEqual(response.status_code, 302) # redirect somewhere
-    
+
     def testBasicEditPost(self):
         """
         A smoke test to ensure POST on edit_view works.
@@ -116,7 +116,7 @@
 
     def testChangeListSortingCallable(self):
         """
-        Ensure we can sort on a list_display field that is a callable 
+        Ensure we can sort on a list_display field that is a callable
         (column 2 is callable_year in ArticleAdmin)
         """
         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
@@ -126,10 +126,10 @@
             response.content.index('Middle content') < response.content.index('Newest content'),
             "Results of sorting on callable are out of order."
         )
-    
+
     def testChangeListSortingModel(self):
         """
-        Ensure we can sort on a list_display field that is a Model method 
+        Ensure we can sort on a list_display field that is a Model method
         (colunn 3 is 'model_year' in ArticleAdmin)
         """
         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
@@ -139,37 +139,51 @@
             response.content.index('Middle content') < response.content.index('Oldest content'),
             "Results of sorting on Model method are out of order."
         )
-    
+
     def testChangeListSortingModelAdmin(self):
         """
-        Ensure we can sort on a list_display field that is a ModelAdmin method 
+        Ensure we can sort on a list_display field that is a ModelAdmin method
         (colunn 4 is 'modeladmin_year' in ArticleAdmin)
         """
         response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
         self.failUnlessEqual(response.status_code, 200)
         self.failUnless(
-            response.content.index('Oldest content') < response.content.index('Middle content') and 
+            response.content.index('Oldest content') < response.content.index('Middle content') and
             response.content.index('Middle content') < response.content.index('Newest content'),
             "Results of sorting on ModelAdmin method are out of order."
         )
-        
+
+    def testNamedGroupFieldChoicesChangeList(self):
+        """
+        Ensures the admin changelist shows correct values in the relevant column
+        for rows corresponding to instances of a model in which a named group
+        has been used in the choices option of a field.
+        """
+        response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
+        self.failUnlessEqual(response.status_code, 200)
+        self.failUnless(
+            '<a href="1/">Horizontal</a>' in response.content and
+            '<a href="2/">Vertical</a>' in response.content,
+            "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
+        )
+
     def testLimitedFilter(self):
         """Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
         self.failUnlessEqual(response.status_code, 200)
         self.failUnless(
-            '<div id="changelist-filter">' in response.content, 
+            '<div id="changelist-filter">' in response.content,
             "Expected filter not found in changelist view."
         )
         self.failIf(
             '<a href="?color__id__exact=3">Blue</a>' in response.content,
             "Changelist filter not correctly limited by limit_choices_to."
         )
-        
+
     def testIncorrectLookupParameters(self):
         """Ensure incorrect lookup parameters are handled gracefully."""
         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
-        self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)        
+        self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
         response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
         self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
 
@@ -186,7 +200,7 @@
         request = self.client.get('/test_admin/admin2/')
         self.assertTemplateUsed(request, 'custom_admin/index.html')
         self.assert_('Hello from a custom index template *bar*' in request.content)
-    
+
     def testCustomAdminSiteView(self):
         self.client.login(username='super', password='secret')
         response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
@@ -411,7 +425,7 @@
         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
         self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
         self.failUnlessEqual(Article.objects.get(pk=1).content, '<p>edited article</p>')
-        
+
         # one error in form should produce singular error message, multiple errors plural
         change_dict['title'] = ''
         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
@@ -422,7 +436,7 @@
         post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
         self.failUnlessEqual(request.status_code, 200)
         self.failUnless('Please correct the errors below.' in post.content,
-                        'Plural error message not found in response to post with multiple errors.')        
+                        'Plural error message not found in response to post with multiple errors.')
         self.client.get('/test_admin/admin/logout/')
 
     def testCustomModelAdminTemplates(self):
@@ -523,7 +537,7 @@
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
         should_contain = """<a href="../../%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
         self.assertContains(response, should_contain)
-    
+
     def test_url_conflicts_with_add(self):
         "A model with a primary key that ends with add should be visible"
         add_model = ModelWithStringPrimaryKey(id="i have something to add")
@@ -531,7 +545,7 @@
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(add_model.pk))
         should_contain = """<h1>Change model with string primary key</h1>"""
         self.assertContains(response, should_contain)
-    
+
     def test_url_conflicts_with_delete(self):
         "A model with a primary key that ends with delete should be visible"
         delete_model = ModelWithStringPrimaryKey(id="delete")
@@ -539,7 +553,7 @@
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(delete_model.pk))
         should_contain = """<h1>Change model with string primary key</h1>"""
         self.assertContains(response, should_contain)
-    
+
     def test_url_conflicts_with_history(self):
         "A model with a primary key that ends with history should be visible"
         history_model = ModelWithStringPrimaryKey(id="history")
@@ -547,7 +561,7 @@
         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(history_model.pk))
         should_contain = """<h1>Change model with string primary key</h1>"""
         self.assertContains(response, should_contain)
-        
+
 
 class SecureViewTest(TestCase):
     fixtures = ['admin-views-users.xml']
@@ -582,28 +596,28 @@
                      LOGIN_FORM_KEY: 1,
                      'username': 'joepublic',
                      'password': 'secret'}
-    
+
     def tearDown(self):
         self.client.logout()
-    
+
     def test_secure_view_shows_login_if_not_logged_in(self):
         "Ensure that we see the login form"
         response = self.client.get('/test_admin/admin/secure-view/' )
         self.assertTemplateUsed(response, 'admin/login.html')
-    
+
     def test_secure_view_login_successfully_redirects_to_original_url(self):
         request = self.client.get('/test_admin/admin/secure-view/')
         self.failUnlessEqual(request.status_code, 200)
         query_string = "the-answer=42"
         login = self.client.post('/test_admin/admin/secure-view/', self.super_login, QUERY_STRING = query_string )
         self.assertRedirects(login, '/test_admin/admin/secure-view/?%s' % query_string)
-    
+
     def test_staff_member_required_decorator_works_as_per_admin_login(self):
         """
         Make sure only staff members can log in.
 
         Successful posts to the login page will redirect to the orignal url.
-        Unsuccessfull attempts will continue to render the login page with 
+        Unsuccessfull attempts will continue to render the login page with
         a 200 status code.
         """
         # Super User
