diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index b31756d762..962d8f7f1d 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -320,10 +320,15 @@ def label_for_field(name, model, model_admin=None, return_attr=False): def help_text_for_field(name, model): + help_text = "" try: - help_text = model._meta.get_field_by_name(name)[0].help_text + field_data = model._meta.get_field_by_name(name) except models.FieldDoesNotExist: - help_text = "" + pass + else: + field = field_data[0] + if not isinstance(field, RelatedObject): + help_text = field.help_text return smart_text(help_text) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 039abb819b..b31c5b59e4 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -450,6 +450,10 @@ class GadgetAdmin(admin.ModelAdmin): return CustomChangeList +class ToppingAdmin(admin.ModelAdmin): + readonly_fields = ('pizzas',) + + class PizzaAdmin(admin.ModelAdmin): readonly_fields = ('toppings',) @@ -755,7 +759,7 @@ site.register(Book, inlines=[ChapterInline]) site.register(Promo) site.register(ChapterXtra1, ChapterXtra1Admin) site.register(Pizza, PizzaAdmin) -site.register(Topping) +site.register(Topping, ToppingAdmin) site.register(Album, AlbumAdmin) site.register(Question) site.register(Answer) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 9a7f0ff30e..f92b3a1a24 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -495,7 +495,7 @@ class Topping(models.Model): class Pizza(models.Model): name = models.CharField(max_length=20) - toppings = models.ManyToManyField('Topping') + toppings = models.ManyToManyField('Topping', related_name='pizzas') class Album(models.Model): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 3c276199ff..28dea38cfd 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -49,7 +49,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, OtherStory, ComplexSortedPerson, PluggableSearchPerson, Parent, Child, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, - Simple, UndeletableObject, Choice, ShortMessage, Telegram) + Simple, UndeletableObject, Choice, ShortMessage, Telegram, Pizza, Topping) from .admin import site, site2 @@ -3559,6 +3559,17 @@ class ReadonlyTest(TestCase): self.assertContains(response, '

No opinion

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

(None)

') + def test_readonly_backwards_ref(self): + """ + Regression test for #16433 - backwards references for related objects + broke if the related field is read-only due to the help_text attribute + """ + topping = Topping.objects.create(name='Salami') + pizza = Pizza.objects.create(name='Americano') + pizza.toppings.add(topping) + response = self.client.get('/test_admin/admin/admin_views/topping/add/') + self.assertEqual(response.status_code, 200) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class RawIdFieldsTest(TestCase):