Loading django/contrib/admin/utils.py +16 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,11 @@ from django.utils.text import capfirst from django.utils.translation import ungettext class FieldIsAForeignKeyColumnName(Exception): """A field is a foreign key attname, i.e. <FK>_id.""" pass def lookup_needs_distinct(opts, lookup_path): """ Returns True if 'distinct()' should be used to query the given lookup path. Loading Loading @@ -272,7 +277,7 @@ def lookup_field(name, obj, model_admin=None): opts = obj._meta try: f = _get_non_gfk_field(opts, name) except FieldDoesNotExist: except (FieldDoesNotExist, FieldIsAForeignKeyColumnName): # For non-field values, the value is either a method, property or # returned via a callable. if callable(name): Loading Loading @@ -310,6 +315,11 @@ def _get_non_gfk_field(opts, name): # Generic foreign keys OR reverse relations ((field.many_to_one and not field.related_model) or field.one_to_many)): raise FieldDoesNotExist() # Avoid coercing <FK>_id fields to FK if field.is_relation and hasattr(field, 'attname') and field.attname == name: raise FieldIsAForeignKeyColumnName() return field Loading Loading @@ -362,6 +372,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False): label = pretty_name(attr.__name__) else: label = pretty_name(name) except FieldIsAForeignKeyColumnName: label = pretty_name(name) attr = name if return_attr: return (label, attr) else: Loading @@ -372,7 +386,7 @@ def help_text_for_field(name, model): help_text = "" try: field = _get_non_gfk_field(model._meta, name) except FieldDoesNotExist: except (FieldDoesNotExist, FieldIsAForeignKeyColumnName): pass else: if hasattr(field, 'help_text'): Loading django/contrib/admin/views/main.py +3 −0 Original line number Diff line number Diff line Loading @@ -371,6 +371,9 @@ class ChangeList(object): pass else: if isinstance(field.remote_field, models.ManyToOneRel): # <FK>_id field names don't require a join. if field_name == field.get_attname(): continue return True return False Loading docs/releases/1.11.txt +4 −0 Original line number Diff line number Diff line Loading @@ -253,6 +253,10 @@ Miscellaneous * CSRF failures are logged to the ``django.security.csrf ``` logger instead of ``django.request``. * Using a foreign key's id (e.g. ``'field_id'``) in ``ModelAdmin.list_display`` displays the related object's ID instead of ``repr(object)``. Remove the ``_id`` suffix if you want the ``repr()``. .. _deprecated-features-1.11: Features deprecated in 1.11 Loading tests/admin_utils/tests.py +1 −0 Original line number Diff line number Diff line Loading @@ -259,6 +259,7 @@ class UtilsTests(SimpleTestCase): label_for_field(lambda x: "nothing", Article), "--" ) self.assertEqual(label_for_field('site_id', Article), 'Site id') class MockModelAdmin(object): def test_from_model(self, obj): Loading tests/admin_views/tests.py +12 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,18 @@ class AdminViewBasicTest(AdminViewBasicTestCase): self.assertContentBefore(response, 'The First Item', 'The Middle Item') self.assertContentBefore(response, 'The Middle Item', 'The Last Item') def test_has_related_field_in_list_display(self): """Joins shouldn't be performed for <FK>_id fields in list display.""" state = State.objects.create(name='Karnataka') City.objects.create(state=state, name='Bangalore') response = self.client.get(reverse('admin:admin_views_city_changelist'), {}) response.context['cl'].list_display = ['id', 'name', 'state'] self.assertEqual(response.context['cl'].has_related_field_in_list_display(), True) response.context['cl'].list_display = ['id', 'name', 'state_id'] self.assertEqual(response.context['cl'].has_related_field_in_list_display(), False) def test_limited_filter(self): """Ensure admin changelist filters do not contain objects excluded via limit_choices_to. This also tests relation-spanning filters (e.g. 'color__value'). Loading Loading
django/contrib/admin/utils.py +16 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,11 @@ from django.utils.text import capfirst from django.utils.translation import ungettext class FieldIsAForeignKeyColumnName(Exception): """A field is a foreign key attname, i.e. <FK>_id.""" pass def lookup_needs_distinct(opts, lookup_path): """ Returns True if 'distinct()' should be used to query the given lookup path. Loading Loading @@ -272,7 +277,7 @@ def lookup_field(name, obj, model_admin=None): opts = obj._meta try: f = _get_non_gfk_field(opts, name) except FieldDoesNotExist: except (FieldDoesNotExist, FieldIsAForeignKeyColumnName): # For non-field values, the value is either a method, property or # returned via a callable. if callable(name): Loading Loading @@ -310,6 +315,11 @@ def _get_non_gfk_field(opts, name): # Generic foreign keys OR reverse relations ((field.many_to_one and not field.related_model) or field.one_to_many)): raise FieldDoesNotExist() # Avoid coercing <FK>_id fields to FK if field.is_relation and hasattr(field, 'attname') and field.attname == name: raise FieldIsAForeignKeyColumnName() return field Loading Loading @@ -362,6 +372,10 @@ def label_for_field(name, model, model_admin=None, return_attr=False): label = pretty_name(attr.__name__) else: label = pretty_name(name) except FieldIsAForeignKeyColumnName: label = pretty_name(name) attr = name if return_attr: return (label, attr) else: Loading @@ -372,7 +386,7 @@ def help_text_for_field(name, model): help_text = "" try: field = _get_non_gfk_field(model._meta, name) except FieldDoesNotExist: except (FieldDoesNotExist, FieldIsAForeignKeyColumnName): pass else: if hasattr(field, 'help_text'): Loading
django/contrib/admin/views/main.py +3 −0 Original line number Diff line number Diff line Loading @@ -371,6 +371,9 @@ class ChangeList(object): pass else: if isinstance(field.remote_field, models.ManyToOneRel): # <FK>_id field names don't require a join. if field_name == field.get_attname(): continue return True return False Loading
docs/releases/1.11.txt +4 −0 Original line number Diff line number Diff line Loading @@ -253,6 +253,10 @@ Miscellaneous * CSRF failures are logged to the ``django.security.csrf ``` logger instead of ``django.request``. * Using a foreign key's id (e.g. ``'field_id'``) in ``ModelAdmin.list_display`` displays the related object's ID instead of ``repr(object)``. Remove the ``_id`` suffix if you want the ``repr()``. .. _deprecated-features-1.11: Features deprecated in 1.11 Loading
tests/admin_utils/tests.py +1 −0 Original line number Diff line number Diff line Loading @@ -259,6 +259,7 @@ class UtilsTests(SimpleTestCase): label_for_field(lambda x: "nothing", Article), "--" ) self.assertEqual(label_for_field('site_id', Article), 'Site id') class MockModelAdmin(object): def test_from_model(self, obj): Loading
tests/admin_views/tests.py +12 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,18 @@ class AdminViewBasicTest(AdminViewBasicTestCase): self.assertContentBefore(response, 'The First Item', 'The Middle Item') self.assertContentBefore(response, 'The Middle Item', 'The Last Item') def test_has_related_field_in_list_display(self): """Joins shouldn't be performed for <FK>_id fields in list display.""" state = State.objects.create(name='Karnataka') City.objects.create(state=state, name='Bangalore') response = self.client.get(reverse('admin:admin_views_city_changelist'), {}) response.context['cl'].list_display = ['id', 'name', 'state'] self.assertEqual(response.context['cl'].has_related_field_in_list_display(), True) response.context['cl'].list_display = ['id', 'name', 'state_id'] self.assertEqual(response.context['cl'].has_related_field_in_list_display(), False) def test_limited_filter(self): """Ensure admin changelist filters do not contain objects excluded via limit_choices_to. This also tests relation-spanning filters (e.g. 'color__value'). Loading