Fixed #19425 - Added InlineModelAdmin.get_extra hook.

Thanks dave@ for the suggestion and Rohan Jain for the patch.
This commit is contained in:
Tim Graham 2013-05-30 13:48:10 -04:00
parent 7902fd74f1
commit 36aecb12b8
6 changed files with 65 additions and 2 deletions

View File

@ -1512,6 +1512,10 @@ class InlineModelAdmin(BaseModelAdmin):
js.extend(['SelectBox.js', 'SelectFilter2.js']) js.extend(['SelectBox.js', 'SelectFilter2.js'])
return forms.Media(js=[static('admin/js/%s' % url) for url in 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): def get_formset(self, request, obj=None, **kwargs):
"""Returns a BaseInlineFormSet class for use in admin add/change views.""" """Returns a BaseInlineFormSet class for use in admin add/change views."""
if self.declared_fieldsets: if self.declared_fieldsets:
@ -1538,7 +1542,7 @@ class InlineModelAdmin(BaseModelAdmin):
"fields": fields, "fields": fields,
"exclude": exclude, "exclude": exclude,
"formfield_callback": partial(self.formfield_for_dbfield, request=request), "formfield_callback": partial(self.formfield_for_dbfield, request=request),
"extra": self.extra, "extra": self.get_extra(request, obj, **kwargs),
"max_num": self.max_num, "max_num": self.max_num,
"can_delete": can_delete, "can_delete": can_delete,
} }

View File

@ -1715,6 +1715,11 @@ The ``InlineModelAdmin`` class adds:
The dynamic link will not appear if the number of currently displayed forms 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. 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: .. _ref-contrib-admin-inline-max-num:
.. attribute:: InlineModelAdmin.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. Returns a ``BaseInlineFormSet`` class for use in admin add/change views.
See the example for :class:`ModelAdmin.get_formsets`. 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 Working with a model with two or more foreign keys to the same parent model
--------------------------------------------------------------------------- ---------------------------------------------------------------------------

View File

@ -292,6 +292,10 @@ Minor features
:meth:`~django.db.models.query.QuerySet.select_related` can be cleared using :meth:`~django.db.models.query.QuerySet.select_related` can be cleared using
``select_related(None)``. ``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 Backwards incompatible changes in 1.6
===================================== =====================================

View File

@ -129,6 +129,17 @@ class ChildModel1Inline(admin.TabularInline):
class ChildModel2Inline(admin.StackedInline): class ChildModel2Inline(admin.StackedInline):
model = ChildModel2 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 # admin for #19524
class SightingInline(admin.TabularInline): class SightingInline(admin.TabularInline):
model = Sighting model = Sighting
@ -150,4 +161,5 @@ site.register(Author, AuthorAdmin)
site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]) site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline])
site.register(ProfileCollection, inlines=[ProfileInline]) site.register(ProfileCollection, inlines=[ProfileInline])
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline]) site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
site.register(BinaryTree, inlines=[BinaryTreeAdmin])
site.register(ExtraTerrestrial, inlines=[SightingInline]) site.register(ExtraTerrestrial, inlines=[SightingInline])

View File

@ -183,6 +183,12 @@ class ChildModel2(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return '/child_model2/' 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 # Models for #19524
class LifeForm(models.Model): class LifeForm(models.Model):

View File

@ -12,7 +12,7 @@ from .admin import InnerInline, TitleInline, site
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2, ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
Sighting, Title, Novel, Chapter, FootNote) Sighting, Title, Novel, Chapter, FootNote, BinaryTree)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -193,6 +193,18 @@ class TestInline(TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1) 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 = '<input id="id_binarytree_set-TOTAL_FORMS" name="binarytree_set-TOTAL_FORMS" type="hidden" value="2" />'
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',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class TestInlineMedia(TestCase): class TestInlineMedia(TestCase):