From 45bef6706ae7436b5c2c503018b21ff70b2e6728 Mon Sep 17 00:00:00 2001 From: Teresa Partida Date: Thu, 11 Feb 2021 10:27:33 +0100 Subject: [PATCH] Fixed #30231 -- Fixed admin filter horizontal/vertical verbose_name generation. Co-authored-by: David Smith --- .../contrib/admin/static/admin/js/inlines.js | 12 ++--- tests/admin_inlines/admin.py | 52 +++++++++++++++++-- tests/admin_inlines/models.py | 32 ++++++++++++ tests/admin_inlines/tests.py | 34 ++++++++++++ 4 files changed, 119 insertions(+), 11 deletions(-) diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index 82ec027237..d9a9032c45 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -218,12 +218,10 @@ // instantiate a new SelectFilter instance for it. if (typeof SelectFilter !== 'undefined') { $('.selectfilter').each(function(index, value) { - const namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length - 1], false); + SelectFilter.init(value.id, this.dataset.fieldName, false); }); $('.selectfilterstacked').each(function(index, value) { - const namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length - 1], true); + SelectFilter.init(value.id, this.dataset.fieldName, true); }); } }; @@ -283,12 +281,10 @@ // If any SelectFilter widgets were added, instantiate a new instance. if (typeof SelectFilter !== "undefined") { $(".selectfilter").each(function(index, value) { - const namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length - 1], false); + SelectFilter.init(value.id, this.dataset.fieldName, false); }); $(".selectfilterstacked").each(function(index, value) { - const namearr = value.name.split('-'); - SelectFilter.init(value.id, namearr[namearr.length - 1], true); + SelectFilter.init(value.id, this.dataset.fieldName, true); }); } }; diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index 50266308e1..9171c7102a 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -5,9 +5,10 @@ from django.db import models from .models import ( Author, BinaryTree, CapoFamiglia, Chapter, Child, ChildModel1, ChildModel2, - Consigliere, EditablePKBook, ExtraTerrestrial, Fashionista, FootNote, - Holder, Holder2, Holder3, Holder4, Holder5, Inner, Inner2, Inner3, - Inner4Stacked, Inner4Tabular, Inner5Stacked, Inner5Tabular, NonAutoPKBook, + Class, Consigliere, Course, CourseProxy, CourseProxy1, CourseProxy2, + EditablePKBook, ExtraTerrestrial, Fashionista, FootNote, Holder, Holder2, + Holder3, Holder4, Holder5, Inner, Inner2, Inner3, Inner4Stacked, + Inner4Tabular, Inner5Stacked, Inner5Tabular, NonAutoPKBook, NonAutoPKBookChild, Novel, NovelReadonlyChapter, OutfitItem, ParentModelWithCustomPk, Person, Poll, Profile, ProfileCollection, Question, ReadOnlyInline, ShoppingWeakness, Sighting, SomeChildModel, @@ -300,6 +301,47 @@ class FashonistaStackedInline(admin.StackedInline): model = Fashionista +# Admin for #30231 +class ClassStackedHorizontal(admin.StackedInline): + model = Class + extra = 1 + filter_horizontal = ['person'] + + +class ClassAdminStackedHorizontal(admin.ModelAdmin): + inlines = [ClassStackedHorizontal] + + +class ClassTabularHorizontal(admin.TabularInline): + model = Class + extra = 1 + filter_horizontal = ['person'] + + +class ClassAdminTabularHorizontal(admin.ModelAdmin): + inlines = [ClassTabularHorizontal] + + +class ClassTabularVertical(admin.TabularInline): + model = Class + extra = 1 + filter_vertical = ['person'] + + +class ClassAdminTabularVertical(admin.ModelAdmin): + inlines = [ClassTabularVertical] + + +class ClassStackedVertical(admin.StackedInline): + model = Class + extra = 1 + filter_vertical = ['person'] + + +class ClassAdminStackedVertical(admin.ModelAdmin): + inlines = [ClassStackedVertical] + + site.register(TitleCollection, inlines=[TitleInline]) # Test bug #12561 and #12778 # only ModelAdmin media @@ -327,3 +369,7 @@ site.register(Teacher, TeacherAdmin) site.register(Chapter, inlines=[FootNoteNonEditableInlineCustomForm]) site.register(OutfitItem, inlines=[WeaknessInlineCustomForm]) site.register(Person, inlines=[AuthorTabularInline, FashonistaStackedInline]) +site.register(Course, ClassAdminStackedHorizontal) +site.register(CourseProxy, ClassAdminStackedVertical) +site.register(CourseProxy1, ClassAdminTabularVertical) +site.register(CourseProxy2, ClassAdminTabularHorizontal) diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index 241eceebb2..a0a9093a5b 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -294,6 +294,38 @@ class SomeChildModel(models.Model): parent = models.ForeignKey(SomeParentModel, models.CASCADE) readonly_field = models.CharField(max_length=1) + +# Models for #30231 +class Course(models.Model): + name = models.CharField(max_length=128) + + def __str__(self): + return self.name + + +class Class(models.Model): + person = models.ManyToManyField(Person, verbose_name='attendant') + course = models.ForeignKey(Course, on_delete=models.CASCADE) + + +class CourseProxy(Course): + + class Meta: + proxy = True + + +class CourseProxy1(Course): + + class Meta: + proxy = True + + +class CourseProxy2(Course): + + class Meta: + proxy = True + + # Other models diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 7af6972585..8286f10906 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1356,3 +1356,37 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertEqual( len(self.selenium.find_elements_by_css_selector(tabular_inline_formset_selector)), 1 ) + + def test_inlines_verbose_name(self): + """ + The item added by the "Add another XXX" link must use the correct + verbose_name in the inline form. + """ + self.admin_login(username='super', password='secret') + # Each combination of horizontal/vertical fiter with stacked/tabular + # inlines. + tests = [ + 'admin:admin_inlines_course_add', + 'admin:admin_inlines_courseproxy_add', + 'admin:admin_inlines_courseproxy1_add', + 'admin:admin_inlines_courseproxy2_add', + ] + css_selector = '.dynamic-class_set#class_set-%s h2' + + for url_name in tests: + with self.subTest(url=url_name): + self.selenium.get(self.live_server_url + reverse(url_name)) + # First inline shows the verbose_name. + available, chosen = self.selenium.find_elements_by_css_selector(css_selector % 0) + self.assertEqual(available.text, 'AVAILABLE ATTENDANT') + self.assertEqual(chosen.text, 'CHOSEN ATTENDANT') + # Added inline should also have the correct verbose_name. + self.selenium.find_element_by_link_text('Add another Class').click() + available, chosen = self.selenium.find_elements_by_css_selector(css_selector % 1) + self.assertEqual(available.text, 'AVAILABLE ATTENDANT') + self.assertEqual(chosen.text, 'CHOSEN ATTENDANT') + # Third inline should also have the correct verbose_name. + self.selenium.find_element_by_link_text('Add another Class').click() + available, chosen = self.selenium.find_elements_by_css_selector(css_selector % 2) + self.assertEqual(available.text, 'AVAILABLE ATTENDANT') + self.assertEqual(chosen.text, 'CHOSEN ATTENDANT')