From 36aecb12b850aeed173a8e524cacb3444f807785 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 30 May 2013 13:48:10 -0400 Subject: [PATCH] Fixed #19425 - Added InlineModelAdmin.get_extra hook. Thanks dave@ for the suggestion and Rohan Jain for the patch. --- django/contrib/admin/options.py | 6 +++++- docs/ref/contrib/admin/index.txt | 25 +++++++++++++++++++++++++ docs/releases/1.6.txt | 4 ++++ tests/admin_inlines/admin.py | 12 ++++++++++++ tests/admin_inlines/models.py | 6 ++++++ tests/admin_inlines/tests.py | 14 +++++++++++++- 6 files changed, 65 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6202646988..a0a82f32ff 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1512,6 +1512,10 @@ class InlineModelAdmin(BaseModelAdmin): js.extend(['SelectBox.js', 'SelectFilter2.js']) return forms.Media(js=[static('admin/js/%s' % url) for url in js]) + def get_extra(self, request, obj=None, **kwargs): + """Hook for customizing the number of extra inline forms.""" + return self.extra + def get_formset(self, request, obj=None, **kwargs): """Returns a BaseInlineFormSet class for use in admin add/change views.""" if self.declared_fieldsets: @@ -1538,7 +1542,7 @@ class InlineModelAdmin(BaseModelAdmin): "fields": fields, "exclude": exclude, "formfield_callback": partial(self.formfield_for_dbfield, request=request), - "extra": self.extra, + "extra": self.get_extra(request, obj, **kwargs), "max_num": self.max_num, "can_delete": can_delete, } diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 0aec62f7b9..73ea74adc0 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1715,6 +1715,11 @@ The ``InlineModelAdmin`` class adds: The dynamic link will not appear if the number of currently displayed forms exceeds ``max_num``, or if the user does not have JavaScript enabled. + .. versionadded:: 1.6 + + :meth:`InlineModelAdmin.get_extra` also allows you to customize the number + of extra forms. + .. _ref-contrib-admin-inline-max-num: .. attribute:: InlineModelAdmin.max_num @@ -1762,6 +1767,26 @@ The ``InlineModelAdmin`` class adds: Returns a ``BaseInlineFormSet`` class for use in admin add/change views. See the example for :class:`ModelAdmin.get_formsets`. +.. method:: InlineModelAdmin.get_extra(self, request, obj=None, **kwargs) + + .. versionadded:: 1.6 + + Returns the number of extra inline forms to use. By default, returns the + :attr:`InlineModelAdmin.extra` attribute. + + Override this method to programmatically determine the number of extra + inline forms. For example, this may be based on the model instance + (passed as the keyword argument ``obj``):: + + class BinaryTreeAdmin(admin.TabularInline): + model = BinaryTree + + def get_extra(self, request, obj=None, **kwargs): + extra = 2 + if obj: + return extra - obj.binarytree_set.count() + return extra + Working with a model with two or more foreign keys to the same parent model --------------------------------------------------------------------------- diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index c8e6044a06..2afa9a32cd 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -292,6 +292,10 @@ Minor features :meth:`~django.db.models.query.QuerySet.select_related` can be cleared using ``select_related(None)``. +* The :meth:`~django.contrib.admin.InlineModelAdmin.get_extra` method on + :class:`~django.contrib.admin.InlineModelAdmin` may be overridden to + customize the number of extra inline forms. + Backwards incompatible changes in 1.6 ===================================== diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index 44671d0ac4..2bc9dc5234 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -129,6 +129,17 @@ class ChildModel1Inline(admin.TabularInline): class ChildModel2Inline(admin.StackedInline): model = ChildModel2 +# admin for #19425 +class BinaryTreeAdmin(admin.TabularInline): + model = BinaryTree + + def get_extra(self, request, obj=None, **kwargs): + extra = 2 + if obj: + return extra - obj.binarytree_set.count() + + return extra + # admin for #19524 class SightingInline(admin.TabularInline): model = Sighting @@ -150,4 +161,5 @@ site.register(Author, AuthorAdmin) site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]) site.register(ProfileCollection, inlines=[ProfileInline]) site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline]) +site.register(BinaryTree, inlines=[BinaryTreeAdmin]) site.register(ExtraTerrestrial, inlines=[SightingInline]) diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index 82c1c3f078..d4ba0ab6bc 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -183,6 +183,12 @@ class ChildModel2(models.Model): def get_absolute_url(self): return '/child_model2/' + +# Models for #19425 +class BinaryTree(models.Model): + name = models.CharField(max_length=100) + parent = models.ForeignKey('self', null=True, blank=True) + # Models for #19524 class LifeForm(models.Model): diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 714a2f1c61..23c7f95bfe 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -12,7 +12,7 @@ from .admin import InnerInline, TitleInline, site from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2, - Sighting, Title, Novel, Chapter, FootNote) + Sighting, Title, Novel, Chapter, FootNote, BinaryTree) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @@ -193,6 +193,18 @@ class TestInline(TestCase): self.assertEqual(response.status_code, 302) self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1) + def test_custom_get_extra_form(self): + bt_head = BinaryTree.objects.create(name="Tree Head") + bt_child = BinaryTree.objects.create(name="First Child", parent=bt_head) + + # The total number of forms will remain the same in either case + total_forms_hidden = '' + response = self.client.get('/admin/admin_inlines/binarytree/add/') + self.assertContains(response, total_forms_hidden) + + response = self.client.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id) + self.assertContains(response, total_forms_hidden) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class TestInlineMedia(TestCase):