diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 90370bd978..4203287123 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -186,9 +186,7 @@ class AdminReadonlyField(object): if getattr(attr, "allow_tags", False): result_repr = mark_safe(result_repr) else: - if value is None: - result_repr = EMPTY_CHANGELIST_VALUE - elif isinstance(f.rel, ManyToManyRel): + if isinstance(f.rel, ManyToManyRel) and value is not None: result_repr = ", ".join(map(six.text_type, value.all())) else: result_repr = display_for_field(value, f) diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index 40e800419e..d71c398376 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/regressiontests/admin_views/admin.py @@ -27,7 +27,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, - RelatedPrepopulated, UndeletableObject, UserMessenger, Simple) + RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice) def callable_year(dt_value): @@ -615,6 +615,12 @@ class MessageTestingAdmin(admin.ModelAdmin): self.message_user(request, "Test tags", extra_tags="extra_tag") +class ChoiceList(admin.ModelAdmin): + list_display = ['choice'] + readonly_fields = ['choice'] + fields = ['choice'] + + site = admin.AdminSite(name="admin") site.register(Article, ArticleAdmin) site.register(CustomArticle, CustomArticleAdmin) @@ -690,6 +696,7 @@ site.register(AdminOrderedCallable, AdminOrderedCallableAdmin) site.register(Color2, CustomTemplateFilterColorAdmin) site.register(Simple, AttributeErrorRaisingAdmin) site.register(UserMessenger, MessageTestingAdmin) +site.register(Choice, ChoiceList) # Register core models we need in our tests from django.contrib.auth.models import User, Group diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 6f2ddc23a2..003b6ce8b2 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -660,3 +660,7 @@ class Simple(models.Model): """ Simple model with nothing on it for use in testing """ + +class Choice(models.Model): + choice = models.CharField(max_length=1, blank=True, null=True, + choices=(('y','Yes'), ('n','No'), (None, 'No opinion'))) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 313809ec1c..62f69f6d94 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -46,7 +46,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, - Simple, UndeletableObject) + Simple, UndeletableObject, Choice) ERROR_MESSAGE = "Please enter the correct username and password \ @@ -3202,6 +3202,15 @@ class ReadonlyTest(TestCase): response = self.client.get('/test_admin/admin2/auth/user/%s/password/' % su.pk) self.assertEqual(response.status_code, 404) + def test_change_form_renders_correct_null_choice_value(self): + """ + Regression test for #17911. + """ + choice = Choice.objects.create(choice=None) + response = self.client.get('/test_admin/admin/admin_views/choice/%s/' % choice.pk) + self.assertContains(response, '

No opinion

', html=True) + self.assertNotContains(response, '

(None)

') + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class RawIdFieldsTest(TestCase):