diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index dadd4acfa1..1e5fab917e 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -2037,10 +2037,13 @@ class InlineModelAdmin(BaseModelAdmin): self.opts = self.model._meta self.has_registered_model = admin_site.is_registered(self.model) super().__init__() + if self.verbose_name_plural is None: + if self.verbose_name is None: + self.verbose_name_plural = self.model._meta.verbose_name_plural + else: + self.verbose_name_plural = format_lazy('{}s', self.verbose_name) if self.verbose_name is None: self.verbose_name = self.model._meta.verbose_name - if self.verbose_name_plural is None: - self.verbose_name_plural = self.model._meta.verbose_name_plural @property def media(self): diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index fc1eab44f1..e787349d5f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2453,13 +2453,19 @@ The ``InlineModelAdmin`` class adds or customizes: .. attribute:: InlineModelAdmin.verbose_name - An override to the ``verbose_name`` found in the model's inner ``Meta`` - class. + An override to the :attr:`~django.db.models.Options.verbose_name` from the + model's inner ``Meta`` class. .. attribute:: InlineModelAdmin.verbose_name_plural - An override to the ``verbose_name_plural`` found in the model's inner - ``Meta`` class. + An override to the :attr:`~django.db.models.Options.verbose_name_plural` + from the model's inner ``Meta`` class. If this isn't given and the + :attr:`.InlineModelAdmin.verbose_name` is defined, Django will use + :attr:`.InlineModelAdmin.verbose_name` + ``'s'``. + + .. versionchanged:: 4.0 + + The fallback to :attr:`.InlineModelAdmin.verbose_name` was added. .. attribute:: InlineModelAdmin.can_delete diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 7ae566d43a..8f910ad9db 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -85,6 +85,9 @@ Minor features * The new :attr:`.ModelAdmin.search_help_text` attribute allows specifying a descriptive text for the search box. +* The :attr:`.InlineModelAdmin.verbose_name_plural` attribute now fallbacks to + the :attr:`.InlineModelAdmin.verbose_name` + ``'s'``. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 261c4f0148..c96853a7ff 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -967,6 +967,55 @@ class TestReadOnlyChangeViewInlinePermissions(TestCase): class TestVerboseNameInlineForms(TestDataMixin, TestCase): factory = RequestFactory() + def test_verbose_name_inline(self): + class NonVerboseProfileInline(TabularInline): + model = Profile + verbose_name = 'Non-verbose childs' + + class VerboseNameProfileInline(TabularInline): + model = VerboseNameProfile + verbose_name = 'Childs with verbose name' + + class VerboseNamePluralProfileInline(TabularInline): + model = VerboseNamePluralProfile + verbose_name = 'Childs with verbose name plural' + + class BothVerboseNameProfileInline(TabularInline): + model = BothVerboseNameProfile + verbose_name = 'Childs with both verbose names' + + modeladmin = ModelAdmin(ProfileCollection, admin_site) + modeladmin.inlines = [ + NonVerboseProfileInline, + VerboseNameProfileInline, + VerboseNamePluralProfileInline, + BothVerboseNameProfileInline, + ] + obj = ProfileCollection.objects.create() + url = reverse('admin:admin_inlines_profilecollection_change', args=(obj.pk,)) + request = self.factory.get(url) + request.user = self.superuser + response = modeladmin.changeform_view(request) + self.assertNotContains(response, 'Add another Profile') + # Non-verbose model. + self.assertContains(response, '

Non-verbose childss

') + self.assertContains(response, 'Add another Non-verbose child') + self.assertNotContains(response, '

Profiles

') + # Model with verbose name. + self.assertContains(response, '

Childs with verbose names

') + self.assertContains(response, 'Add another Childs with verbose name') + self.assertNotContains(response, '

Model with verbose name onlys

') + self.assertNotContains(response, 'Add another Model with verbose name only') + # Model with verbose name plural. + self.assertContains(response, '

Childs with verbose name plurals

') + self.assertContains(response, 'Add another Childs with verbose name plural') + self.assertNotContains(response, '

Model with verbose name plural only

') + # Model with both verbose names. + self.assertContains(response, '

Childs with both verbose namess

') + self.assertContains(response, 'Add another Childs with both verbose names') + self.assertNotContains(response, '

Model with both - plural name

') + self.assertNotContains(response, 'Add another Model with both - name') + def test_verbose_name_plural_inline(self): class NonVerboseProfileInline(TabularInline): model = Profile