diff options
| author | Marijke Luttekes <mail@marijkeluttekes.dev> | 2024-05-21 22:09:26 -0300 |
|---|---|---|
| committer | nessita <124304+nessita@users.noreply.github.com> | 2024-05-22 00:13:55 -0300 |
| commit | 01ed59f753139afb514170ee7f7384c155ecbc2d (patch) | |
| tree | 4b2a50dfa6abc08d1eb0f439f8078e7c1df1b8d0 /tests/admin_inlines | |
| parent | 9c5fe93349bd4339c41d057b87046e5d28be6f77 (diff) | |
Refs #35189 -- Improved admin fieldset's accessibility by setting aria-labelledby.
Before this change, HTML <fieldset> elements in the admin site did not
have an associated label to describe them. This commit defines a unique
HTML id for the heading labeling a fieldset, and sets its
aria-labelledby property to link the heading with the fieldset.
Diffstat (limited to 'tests/admin_inlines')
| -rw-r--r-- | tests/admin_inlines/admin.py | 54 | ||||
| -rw-r--r-- | tests/admin_inlines/models.py | 21 | ||||
| -rw-r--r-- | tests/admin_inlines/tests.py | 420 |
3 files changed, 451 insertions, 44 deletions
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index 0ec56d71b3..3cdaee22df 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -40,6 +40,8 @@ from .models import ( OutfitItem, ParentModelWithCustomPk, Person, + Photo, + Photographer, Poll, Profile, ProfileCollection, @@ -98,6 +100,57 @@ class AuthorAdmin(admin.ModelAdmin): ] +class PhotoInlineMixin: + model = Photo + extra = 2 + fieldsets = [ + (None, {"fields": ["image", "title"]}), + ( + "Details", + {"fields": ["description", "creation_date"], "classes": ["collapse"]}, + ), + ( + "Details", # Fieldset name intentionally duplicated + {"fields": ["update_date", "updated_by"]}, + ), + ] + + +class PhotoTabularInline(PhotoInlineMixin, admin.TabularInline): + pass + + +class PhotoStackedExtra2Inline(PhotoInlineMixin, admin.StackedInline): + pass + + +class PhotoStackedExtra3Inline(PhotoInlineMixin, admin.StackedInline): + extra = 3 + + +class PhotoStackedCollapsibleInline(PhotoInlineMixin, admin.StackedInline): + fieldsets = [] + classes = ["collapse"] + + +class PhotographerAdmin(admin.ModelAdmin): + fieldsets = [ + (None, {"fields": ["firstname", "fullname"]}), + ("Advanced options", {"fields": ["nationality", "residency"]}), + ( + "Advanced options", # Fieldset name intentionally duplicated + {"fields": ["siblings", "children"], "classes": ["collapse"]}, + ), + ] + inlines = [ + PhotoTabularInline, + PhotoTabularInline, + PhotoStackedExtra2Inline, + PhotoStackedExtra3Inline, + PhotoStackedCollapsibleInline, + ] + + class InnerInline(admin.StackedInline): model = Inner can_delete = False @@ -454,6 +507,7 @@ site.register(Teacher, TeacherAdmin) site.register(Chapter, inlines=[FootNoteNonEditableInlineCustomForm]) site.register(OutfitItem, inlines=[WeaknessInlineCustomForm]) site.register(Person, inlines=[AuthorTabularInline, FashonistaStackedInline]) +site.register(Photographer, PhotographerAdmin) site.register(Course, ClassAdminStackedHorizontal) site.register(CourseProxy, ClassAdminStackedVertical) site.register(CourseProxy1, ClassAdminTabularVertical) diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index eca5bae422..5a85556a55 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -180,6 +180,27 @@ class ShoppingWeakness(models.Model): item = models.ForeignKey(OutfitItem, models.CASCADE) +# Models for #35189 + + +class Photographer(Person): + fullname = models.CharField(max_length=100) + nationality = models.CharField(max_length=100) + residency = models.CharField(max_length=100) + siblings = models.IntegerField() + children = models.IntegerField() + + +class Photo(models.Model): + photographer = models.ForeignKey(Photographer, on_delete=models.CASCADE) + image = models.CharField(max_length=100) + title = models.CharField(max_length=100) + description = models.TextField() + creation_date = models.DateField() + update_date = models.DateField() + updated_by = models.CharField(max_length=100) + + # Models for #13510 diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 9533cc9af3..7b38619cc0 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -117,7 +117,14 @@ class TestInline(TestDataMixin, TestCase): "Autogenerated many-to-many inlines are displayed correctly (#13407)" response = self.client.get(reverse("admin:admin_inlines_author_add")) # The heading for the m2m inline block uses the right text - self.assertContains(response, "<h2>Author-book relationships</h2>") + self.assertContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) # The "add another" label is correct self.assertContains(response, "Add another Author-book relationship") # The '+' is dropped from the autogenerated form prefix (Author_books+) @@ -737,13 +744,35 @@ class TestInline(TestDataMixin, TestCase): def test_inlines_plural_heading_foreign_key(self): response = self.client.get(reverse("admin:admin_inlines_holder4_add")) - self.assertContains(response, "<h2>Inner4 stackeds</h2>", html=True) - self.assertContains(response, "<h2>Inner4 tabulars</h2>", html=True) + self.assertContains( + response, + ( + '<h2 id="inner4stacked_set-heading" class="inline-heading">' + "Inner4 stackeds</h2>" + ), + html=True, + ) + self.assertContains( + response, + ( + '<h2 id="inner4tabular_set-heading" class="inline-heading">' + "Inner4 tabulars</h2>" + ), + html=True, + ) def test_inlines_singular_heading_one_to_one(self): response = self.client.get(reverse("admin:admin_inlines_person_add")) - self.assertContains(response, "<h2>Author</h2>", html=True) # Tabular. - self.assertContains(response, "<h2>Fashionista</h2>", html=True) # Stacked. + self.assertContains( + response, + '<h2 id="author-heading" class="inline-heading">Author</h2>', + html=True, + ) # Tabular. + self.assertContains( + response, + '<h2 id="fashionista-heading" class="inline-heading">Fashionista</h2>', + html=True, + ) # Stacked. def test_inlines_based_on_model_state(self): parent = ShowInlineParent.objects.create(show_inlines=False) @@ -914,28 +943,50 @@ class TestInlinePermissions(TestCase): def test_inline_add_m2m_noperm(self): response = self.client.get(reverse("admin:admin_inlines_author_add")) # No change permission on books, so no inline - self.assertNotContains(response, "<h2>Author-book relationships</h2>") + self.assertNotContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertNotContains(response, "Add another Author-Book Relationship") self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') def test_inline_add_fk_noperm(self): response = self.client.get(reverse("admin:admin_inlines_holder2_add")) # No permissions on Inner2s, so no inline - self.assertNotContains(response, "<h2>Inner2s</h2>") + self.assertNotContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) self.assertNotContains(response, "Add another Inner2") self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"') def test_inline_change_m2m_noperm(self): response = self.client.get(self.author_change_url) # No change permission on books, so no inline - self.assertNotContains(response, "<h2>Author-book relationships</h2>") + self.assertNotContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertNotContains(response, "Add another Author-Book Relationship") self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') def test_inline_change_fk_noperm(self): response = self.client.get(self.holder_change_url) # No permissions on Inner2s, so no inline - self.assertNotContains(response, "<h2>Inner2s</h2>") + self.assertNotContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) self.assertNotContains(response, "Add another Inner2") self.assertNotContains(response, 'id="id_inner2_set-TOTAL_FORMS"') @@ -959,7 +1010,14 @@ class TestInlinePermissions(TestCase): self.assertIs( response.context["inline_admin_formset"].has_delete_permission, False ) - self.assertContains(response, "<h2>Author-book relationships</h2>") + self.assertContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertContains( response, '<input type="hidden" name="Author_books-TOTAL_FORMS" value="0" ' @@ -975,7 +1033,14 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(reverse("admin:admin_inlines_author_add")) # No change permission on Books, so no inline - self.assertNotContains(response, "<h2>Author-book relationships</h2>") + self.assertNotContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertNotContains(response, "Add another Author-Book Relationship") self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') @@ -986,7 +1051,11 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(reverse("admin:admin_inlines_holder2_add")) # Add permission on inner2s, so we get the inline - self.assertContains(response, "<h2>Inner2s</h2>") + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) self.assertContains(response, "Add another Inner2") self.assertContains( response, @@ -1002,7 +1071,14 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.author_change_url) # No change permission on books, so no inline - self.assertNotContains(response, "<h2>Author-book relationships</h2>") + self.assertNotContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertNotContains(response, "Add another Author-Book Relationship") self.assertNotContains(response, 'id="id_Author_books-TOTAL_FORMS"') self.assertNotContains(response, 'id="id_Author_books-0-DELETE"') @@ -1026,7 +1102,14 @@ class TestInlinePermissions(TestCase): self.assertIs( response.context["inline_admin_formset"].has_delete_permission, False ) - self.assertContains(response, "<h2>Author-book relationships</h2>") + self.assertContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertContains( response, '<input type="hidden" name="Author_books-TOTAL_FORMS" value="1" ' @@ -1059,7 +1142,14 @@ class TestInlinePermissions(TestCase): self.assertIs( response.context["inline_admin_formset"].has_delete_permission, True ) - self.assertContains(response, "<h2>Author-book relationships</h2>") + self.assertContains( + response, + ( + '<h2 id="Author_books-heading" class="inline-heading">' + "Author-book relationships</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Author-book relationship") self.assertContains( response, @@ -1082,7 +1172,11 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # Add permission on inner2s, so we can add but not modify existing - self.assertContains(response, "<h2>Inner2s</h2>") + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) self.assertContains(response, "Add another Inner2") # 3 extra forms only, not the existing instance form self.assertContains( @@ -1105,7 +1199,16 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # Change permission on inner2s, so we can change existing but not add new - self.assertContains(response, "<h2>Inner2s</h2>", count=2) + self.assertContains( + response, + '<h2 id="inner2_set-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) # Just the one form for existing instances self.assertContains( response, @@ -1148,7 +1251,11 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # Add/change perm, so we can add new and change existing - self.assertContains(response, "<h2>Inner2s</h2>") + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) # One form for existing instance and three extra for new self.assertContains( response, @@ -1174,7 +1281,11 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # Change/delete perm on inner2s, so we can change/delete existing - self.assertContains(response, "<h2>Inner2s</h2>") + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) # One form for existing instance only, no new self.assertContains( response, @@ -1205,7 +1316,16 @@ class TestInlinePermissions(TestCase): self.user.user_permissions.add(permission) response = self.client.get(self.holder_change_url) # All perms on inner2s, so we can add/change/delete - self.assertContains(response, "<h2>Inner2s</h2>", count=2) + self.assertContains( + response, + '<h2 id="inner2_set-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) + self.assertContains( + response, + '<h2 id="inner2_set-2-heading" class="inline-heading">Inner2s</h2>', + html=True, + ) # One form for existing instance only, three for new self.assertContains( response, @@ -1367,22 +1487,69 @@ class TestVerboseNameInlineForms(TestDataMixin, TestCase): response = modeladmin.changeform_view(request) self.assertNotContains(response, "Add another Profile") # Non-verbose model. - self.assertContains(response, "<h2>Non-verbose childss</h2>") + self.assertContains( + response, + ( + '<h2 id="profile_set-heading" class="inline-heading">' + "Non-verbose childss</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Non-verbose child") - self.assertNotContains(response, "<h2>Profiles</h2>") + self.assertNotContains( + response, + '<h2 id="profile_set-heading" class="inline-heading">Profiles</h2>', + html=True, + ) # Model with verbose name. - self.assertContains(response, "<h2>Childs with verbose names</h2>") + self.assertContains( + response, + ( + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Childs with verbose names</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Childs with verbose name") - self.assertNotContains(response, "<h2>Model with verbose name onlys</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Model with verbose name onlys</h2>", + html=True, + ) self.assertNotContains(response, "Add another Model with verbose name only") # Model with verbose name plural. - self.assertContains(response, "<h2>Childs with verbose name plurals</h2>") + self.assertContains( + response, + ( + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Childs with verbose name plurals</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Childs with verbose name plural") - self.assertNotContains(response, "<h2>Model with verbose name plural only</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Model with verbose name plural only</h2>", + html=True, + ) # Model with both verbose names. - self.assertContains(response, "<h2>Childs with both verbose namess</h2>") + self.assertContains( + response, + ( + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Childs with both verbose namess</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Childs with both verbose names") - self.assertNotContains(response, "<h2>Model with both - plural name</h2>") + self.assertNotContains( + response, + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Model with both - plural name</h2>", + html=True, + ) self.assertNotContains(response, "Add another Model with both - name") def test_verbose_name_plural_inline(self): @@ -1415,21 +1582,68 @@ class TestVerboseNameInlineForms(TestDataMixin, TestCase): request.user = self.superuser response = modeladmin.changeform_view(request) # Non-verbose model. - self.assertContains(response, "<h2>Non-verbose childs</h2>") + self.assertContains( + response, + ( + '<h2 id="profile_set-heading" class="inline-heading">' + "Non-verbose childs</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Profile") - self.assertNotContains(response, "<h2>Profiles</h2>") + self.assertNotContains( + response, + '<h2 id="profile_set-heading" class="inline-heading">Profiles</h2>', + html=True, + ) # Model with verbose name. - self.assertContains(response, "<h2>Childs with verbose name</h2>") + self.assertContains( + response, + ( + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Childs with verbose name</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Model with verbose name only") - self.assertNotContains(response, "<h2>Model with verbose name onlys</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Model with verbose name onlys</h2>", + html=True, + ) # Model with verbose name plural. - self.assertContains(response, "<h2>Childs with verbose name plural</h2>") + self.assertContains( + response, + ( + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Childs with verbose name plural</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Profile") - self.assertNotContains(response, "<h2>Model with verbose name plural only</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Model with verbose name plural only</h2>", + html=True, + ) # Model with both verbose names. - self.assertContains(response, "<h2>Childs with both verbose names</h2>") + self.assertContains( + response, + ( + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Childs with both verbose names</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Model with both - name") - self.assertNotContains(response, "<h2>Model with both - plural name</h2>") + self.assertNotContains( + response, + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Model with both - plural name</h2>", + html=True, + ) def test_both_verbose_names_inline(self): class NonVerboseProfileInline(TabularInline): @@ -1466,31 +1680,149 @@ class TestVerboseNameInlineForms(TestDataMixin, TestCase): response = modeladmin.changeform_view(request) self.assertNotContains(response, "Add another Profile") # Non-verbose model. - self.assertContains(response, "<h2>Non-verbose childs - plural name</h2>") + self.assertContains( + response, + ( + '<h2 id="profile_set-heading" class="inline-heading">' + "Non-verbose childs - plural name</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Non-verbose childs - name") - self.assertNotContains(response, "<h2>Profiles</h2>") + self.assertNotContains( + response, + '<h2 id="profile_set-heading" class="inline-heading">Profiles</h2>', + html=True, + ) # Model with verbose name. - self.assertContains(response, "<h2>Childs with verbose name - plural name</h2>") + self.assertContains( + response, + ( + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Childs with verbose name - plural name</h2>" + ), + html=True, + ) self.assertContains(response, "Add another Childs with verbose name - name") - self.assertNotContains(response, "<h2>Model with verbose name onlys</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenameprofile_set-heading" class="inline-heading">' + "Model with verbose name onlys</h2>", + html=True, + ) # Model with verbose name plural. self.assertContains( response, - "<h2>Childs with verbose name plural - plural name</h2>", + ( + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Childs with verbose name plural - plural name</h2>" + ), + html=True, ) self.assertContains( response, "Add another Childs with verbose name plural - name", ) - self.assertNotContains(response, "<h2>Model with verbose name plural only</h2>") + self.assertNotContains( + response, + '<h2 id="verbosenamepluralprofile_set-heading" class="inline-heading">' + "Model with verbose name plural only</h2>", + html=True, + ) # Model with both verbose names. - self.assertContains(response, "<h2>Childs with both - plural name</h2>") + self.assertContains( + response, + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Childs with both - plural name</h2>", + html=True, + ) self.assertContains(response, "Add another Childs with both - name") - self.assertNotContains(response, "<h2>Model with both - plural name</h2>") + self.assertNotContains( + response, + '<h2 id="bothverbosenameprofile_set-heading" class="inline-heading">' + "Model with both - plural name</h2>", + html=True, + ) self.assertNotContains(response, "Add another Model with both - name") @override_settings(ROOT_URLCONF="admin_inlines.urls") +class TestInlineWithFieldsets(TestDataMixin, TestCase): + def setUp(self): + self.client.force_login(self.superuser) + + def test_inline_headings(self): + response = self.client.get(reverse("admin:admin_inlines_photographer_add")) + # Page main title. + self.assertContains(response, "<h1>Add photographer</h1>", html=True) + + # Headings for the toplevel fieldsets. The first one has no name. + self.assertContains(response, '<fieldset class="module aligned ">') + # The second and third have the same "Advanced options" name, but the + # second one has the "collapse" class. + for x, classes in ((1, ""), (2, "collapse")): + heading_id = f"fieldset-0-advanced-options-{x}-heading" + with self.subTest(heading_id=heading_id): + self.assertContains( + response, + f'<fieldset class="module aligned {classes}" ' + f'aria-labelledby="{heading_id}">', + ) + self.assertContains( + response, + f'<h2 id="{heading_id}" class="fieldset-heading">' + "Advanced options</h2>", + ) + self.assertContains(response, f'id="{heading_id}"', count=1) + + # Headings and subheadings for all the inlines. + for inline_admin_formset in response.context["inline_admin_formsets"]: + prefix = inline_admin_formset.formset.prefix + heading_id = f"{prefix}-heading" + formset_heading = ( + f'<h2 id="{heading_id}" class="inline-heading">Photos</h2>' + ) + self.assertContains(response, formset_heading, html=True) + self.assertContains(response, f'id="{heading_id}"', count=1) + + # If this is a TabularInline, do not make further asserts since + # fieldsets are not shown as such in this table layout. + if "tabular" in inline_admin_formset.opts.template: + continue + + # Headings for every formset (the amount depends on `extra`). + for y, inline_admin_form in enumerate(inline_admin_formset): + y_plus_one = y + 1 + form_heading = ( + f'<h3><b>Photo:</b> <span class="inline_label">#{y_plus_one}</span>' + "</h3>" + ) + self.assertContains(response, form_heading, html=True) + + # Every fieldset defined for an inline's form. + for z, fieldset in enumerate(inline_admin_form): + if fieldset.name: + heading_id = f"{prefix}-{y}-details-{z}-heading" + self.assertContains( + response, + f'<fieldset class="module aligned {fieldset.classes}" ' + f'aria-labelledby="{heading_id}">', + ) + fieldset_heading = ( + f'<h4 id="{heading_id}" class="fieldset-heading">' + f"Details</h4>" + ) + self.assertContains(response, fieldset_heading) + self.assertContains(response, f'id="{heading_id}"', count=1) + + else: + fieldset_html = ( + f'<fieldset class="module aligned {fieldset.classes}">' + ) + self.assertContains(response, fieldset_html) + + +@override_settings(ROOT_URLCONF="admin_inlines.urls") class SeleniumTests(AdminSeleniumTestCase): available_apps = ["admin_inlines"] + AdminSeleniumTestCase.available_apps |
