Fixed #11195 -- Added CSS classes to the changelist cells to allow style customizations. Thanks to akaihola, Ramiro Morales and vdboor for their work on the patch.

This commit is contained in:
Julien Phalip 2013-07-27 19:50:02 -07:00
parent 47c755327b
commit 4e0ff35146
4 changed files with 32 additions and 12 deletions

View File

@ -180,7 +180,7 @@ def items_for_result(cl, result, form):
first = True first = True
pk = cl.lookup_opts.pk.attname pk = cl.lookup_opts.pk.attname
for field_name in cl.list_display: for field_name in cl.list_display:
row_class = '' row_classes = ['field-%s' % field_name]
try: try:
f, attr, value = lookup_field(field_name, result, cl.model_admin) f, attr, value = lookup_field(field_name, result, cl.model_admin)
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -188,7 +188,7 @@ def items_for_result(cl, result, form):
else: else:
if f is None: if f is None:
if field_name == 'action_checkbox': if field_name == 'action_checkbox':
row_class = mark_safe(' class="action-checkbox"') row_classes = ['action-checkbox']
allow_tags = getattr(attr, 'allow_tags', False) allow_tags = getattr(attr, 'allow_tags', False)
boolean = getattr(attr, 'boolean', False) boolean = getattr(attr, 'boolean', False)
if boolean: if boolean:
@ -199,7 +199,7 @@ def items_for_result(cl, result, form):
if allow_tags: if allow_tags:
result_repr = mark_safe(result_repr) result_repr = mark_safe(result_repr)
if isinstance(value, (datetime.date, datetime.time)): if isinstance(value, (datetime.date, datetime.time)):
row_class = mark_safe(' class="nowrap"') row_classes.append('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)
@ -210,9 +210,10 @@ def items_for_result(cl, result, form):
else: else:
result_repr = display_for_field(value, f) result_repr = display_for_field(value, f)
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)): if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
row_class = mark_safe(' class="nowrap"') row_classes.append('nowrap')
if force_text(result_repr) == '': if force_text(result_repr) == '':
result_repr = mark_safe(' ') result_repr = mark_safe(' ')
row_class = mark_safe(' class="%s"' % ' '.join(row_classes))
# If list_display_links not defined, add the link tag to the first field # If list_display_links not defined, add the link tag to the first field
if (first and not cl.list_display_links) or field_name in cl.list_display_links: if (first and not cl.list_display_links) or field_name in cl.list_display_links:
table_tag = {True:'th', False:'td'}[first] table_tag = {True:'th', False:'td'}[first]

View File

@ -82,6 +82,9 @@ Minor features
* Buttons in :mod:`django.contrib.admin` now use the ``border-radius`` CSS * Buttons in :mod:`django.contrib.admin` now use the ``border-radius`` CSS
property for rounded corners rather than GIF background images. property for rounded corners rather than GIF background images.
* The admin changelist cells now have a ``field-<field_name>`` class in the
HTML to enable style customizations.
Backwards incompatible changes in 1.7 Backwards incompatible changes in 1.7
===================================== =====================================

View File

@ -91,7 +91,7 @@ class ChangeListTests(TestCase):
context = Context({'cl': cl}) context = Context({'cl': cl})
table_output = template.render(context) table_output = template.render(context)
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">(None)</td></tr></tbody>' % link row_html = '<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th><td class="field-parent nowrap">(None)</td></tr></tbody>' % link
self.assertFalse(table_output.find(row_html) == -1, self.assertFalse(table_output.find(row_html) == -1,
'Failed to find expected row element: %s' % table_output) 'Failed to find expected row element: %s' % table_output)
@ -114,7 +114,7 @@ class ChangeListTests(TestCase):
context = Context({'cl': cl}) context = Context({'cl': cl})
table_output = template.render(context) table_output = template.render(context)
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,)) link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">Parent object</td></tr></tbody>' % link row_html = '<tbody><tr class="row1"><th class="field-name"><a href="%s">name</a></th><td class="field-parent nowrap">Parent object</td></tr></tbody>' % link
self.assertFalse(table_output.find(row_html) == -1, self.assertFalse(table_output.find(row_html) == -1,
'Failed to find expected row element: %s' % table_output) 'Failed to find expected row element: %s' % table_output)
@ -150,7 +150,7 @@ class ChangeListTests(TestCase):
# make sure that list editable fields are rendered in divs correctly # make sure that list editable fields are rendered in divs correctly
editable_name_field = '<input name="form-0-name" value="name" class="vTextField" maxlength="30" type="text" id="id_form-0-name" />' editable_name_field = '<input name="form-0-name" value="name" class="vTextField" maxlength="30" type="text" id="id_form-0-name" />'
self.assertInHTML('<td>%s</td>' % editable_name_field, table_output, msg_prefix='Failed to find "name" list_editable field') self.assertInHTML('<td class="field-name">%s</td>' % editable_name_field, table_output, msg_prefix='Failed to find "name" list_editable field')
def test_result_list_editable(self): def test_result_list_editable(self):
""" """

View File

@ -1496,7 +1496,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
response = self.client.get(prefix) response = self.client.get(prefix)
# this URL now comes through reverse(), thus url quoting and iri_to_uri encoding # this URL now comes through reverse(), thus url quoting and iri_to_uri encoding
pk_final_url = escape(iri_to_uri(urlquote(quote(self.pk)))) pk_final_url = escape(iri_to_uri(urlquote(quote(self.pk))))
should_contain = """<th><a href="%s%s/">%s</a></th>""" % (prefix, pk_final_url, escape(self.pk)) should_contain = """<th class="field-__str__"><a href="%s%s/">%s</a></th>""" % (prefix, pk_final_url, escape(self.pk))
self.assertContains(response, should_contain) self.assertContains(response, should_contain)
def test_recentactions_link(self): def test_recentactions_link(self):
@ -2151,8 +2151,8 @@ class AdminViewListEditable(TestCase):
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table. self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-1-id"', 1) self.assertContains(response, 'id="id_form-1-id"', 1)
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True) self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True)
self.assertContains(response, '<td>%d</td>' % story1.id, 1) self.assertContains(response, '<td class="field-id">%d</td>' % story1.id, 1)
self.assertContains(response, '<td>%d</td>' % story2.id, 1) self.assertContains(response, '<td class="field-id">%d</td>' % story2.id, 1)
def test_pk_hidden_fields_with_list_display_links(self): def test_pk_hidden_fields_with_list_display_links(self):
""" Similarly as test_pk_hidden_fields, but when the hidden pk fields are """ Similarly as test_pk_hidden_fields, but when the hidden pk fields are
@ -2167,8 +2167,8 @@ class AdminViewListEditable(TestCase):
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table. self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-1-id"', 1) self.assertContains(response, 'id="id_form-1-id"', 1)
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True) self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True)
self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link1, story1.id), 1) self.assertContains(response, '<th class="field-id"><a href="%s">%d</a></th>' % (link1, story1.id), 1)
self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link2, story2.id), 1) self.assertContains(response, '<th class="field-id"><a href="%s">%d</a></th>' % (link2, story2.id), 1)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -3877,6 +3877,22 @@ class CSSTest(TestCase):
self.assertContains(response, self.assertContains(response,
'<body class="app-admin_views model-section ') '<body class="app-admin_views model-section ')
def test_changelist_field_classes(self):
"""
Cells of the change list table should contain the field name in their class attribute
Refs #11195.
"""
Podcast.objects.create(name="Django Dose",
release_date=datetime.date.today())
response = self.client.get('/test_admin/admin/admin_views/podcast/')
self.assertContains(
response, '<th class="field-name">')
self.assertContains(
response, '<td class="field-release_date nowrap">')
self.assertContains(
response, '<td class="action-checkbox">')
try: try:
import docutils import docutils
except ImportError: except ImportError: