diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index a36c8c13c8..030e6b682c 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -327,10 +327,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 193b64505c..b2da23f08e 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',) @@ -763,7 +767,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 1accf46819..6483fc8717 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 2b4ed7b3f5..aa5f9ecebc 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -50,7 +50,8 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount, OtherStory, ComplexSortedPerson, PluggableSearchPerson, Parent, Child, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject, - Simple, UndeletableObject, UnchangeableObject, Choice, ShortMessage, Telegram) + Simple, UndeletableObject, UnchangeableObject, Choice, ShortMessage, + Telegram, Pizza, Topping) from .admin import site, site2 @@ -3638,6 +3639,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):