diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 7373837..487fc4e 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -13,7 +13,7 @@ from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_o
 from django.contrib.admin.templatetags.admin_static import static
 from django.contrib import messages
 from django.views.decorators.csrf import csrf_protect
-from django.core.exceptions import PermissionDenied, ValidationError, FieldError
+from django.core.exceptions import PermissionDenied, ValidationError, FieldError, ImproperlyConfigured
 from django.core.paginator import Paginator
 from django.core.urlresolvers import reverse
 from django.db import models, transaction, router
@@ -1467,6 +1467,17 @@ 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):
+        """Get the number of extra inline forms needed"""
+        if not isinstance(self.extra, int):
+            raise ImproperlyConfigured("'%s.extra' should be a integer."
+                                       % self.__class__.__name__)
+        return self.extra
+
+    def get_max_num(self, request, obj=None, **kwargs):
+        """Get the max number of extra inline forms needed"""
+        return self.max_num
+
     def get_formset(self, request, obj=None, **kwargs):
         """Returns a BaseInlineFormSet class for use in admin add/change views."""
         if self.declared_fieldsets:
@@ -1493,8 +1504,8 @@ class InlineModelAdmin(BaseModelAdmin):
             "fields": fields,
             "exclude": exclude,
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
-            "extra": self.extra,
-            "max_num": self.max_num,
+            "extra": self.get_extra(request, obj, *kwargs),
+            "max_num": self.get_max_num(request, obj, *kwargs),
             "can_delete": can_delete,
         }
 
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 8d65f96..06b5055 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -199,11 +199,6 @@ def validate_inline(cls, parent, parent_model):
 
     fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
 
-    # extra = 3
-    if not isinstance(cls.extra, int):
-        raise ImproperlyConfigured("'%s.extra' should be a integer."
-                % cls.__name__)
-
     # max_num = None
     max_num = getattr(cls, 'max_num', None)
     if max_num is not None and not isinstance(max_num, int):
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 67e498e..35a33d9 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1629,6 +1629,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
+
+    Also see :meth:`InlineModelAdmin.get_extra` if you need to customize the
+    behavior for this.
+
     .. _ref-contrib-admin-inline-max-num:
 
 .. attribute:: InlineModelAdmin.max_num
@@ -1676,6 +1681,50 @@ 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 needed. By default, returns the
+    :attr:`InlineModelAdmin.extra` attribute.
+
+    Here, you can programmatically determine the number of extra inline forms
+    which should be shown on the interface. This may be based on the object
+    passed as the keyword argument ``obj``. For example::
+
+        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
+
+
+.. method:: InlineModelAdmin.get_max_num(self, request, obj=None, **kwargs)
+
+    .. versionadded:: 1.6
+
+
+    Returns the maximum number of extra inline forms needed. By default, returns the
+    :attr:`InlineModelAdmin.max_num` attribute.
+
+    Here, you can programmatically determine the maximum number of extra inline
+    forms which should be shown on the interface. This may be based on the object
+    passed as the keyword argument ``obj``. For example::
+
+        class BinaryTreeAdmin(admin.TabularInline):
+            model = BinaryTree
+
+            def get_max_num(self, request, obj=None, **kwargs):
+                max_num = 10
+                if obj.parent:
+                    return max_num - 5
+                return max_num
+
+
 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 0eab854..ca34914 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -184,6 +184,20 @@ Minor features
 
 * The jQuery library embedded in the admin has been upgraded to version 1.9.1.
 
+* ``InlineModelAdmin`` can override ``get_extra`` method to programmatically
+  determine the number of extra inline forms.
+
+* :class:`~django.contrib.admin.InlineModelAdmin` can override
+  :meth:`~django.contrib.admin.InlineModelAdmin.get_extra` method
+  to programmatically determine the number of extra inline forms.
+
+* ``InlineModelAdmin`` can override ``get_max_num`` method to programmatically
+  determine the maximum number of extra inline forms.
+
+* :class:`~django.contrib.admin.InlineModelAdmin` can override
+  :meth:`~django.contrib.admin.InlineModelAdmin.get_max_num` method
+  to programmatically determine the maximum number of extra inline forms.
+
 * Syndication feeds (:mod:`django.contrib.syndication`) can now pass extra
   context through to feed templates using a new `Feed.get_context_data()`
   callback.
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 44671d0..2f88248 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -129,6 +129,22 @@ class ChildModel1Inline(admin.TabularInline):
 class ChildModel2Inline(admin.StackedInline):
     model = ChildModel2
 
+# admin for #19425 and #18388
+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
+
+    def get_max_num(self, request, obj=None, **kwargs):
+        max_num = 3
+        if obj:
+            return max_num - obj.binarytree_set.count()
+        return max_num
+
 # admin for #19524
 class SightingInline(admin.TabularInline):
     model = Sighting
@@ -150,4 +166,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 82c1c3f..d4ba0ab 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 714a2f1..1b18181 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,20 @@ 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(name="Tree Head")
+        bt_head.save()
+        bt_child = BinaryTree(name="First Child", parent=bt_head)
+        bt_child.save()
+
+        # The total number of forms will remain the same in any 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',))
 class TestInlineMedia(TestCase):
