diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6b0982eab8c..2324080ba14 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1946,6 +1946,20 @@ class ModelAdmin(BaseModelAdmin): "admin/object_history.html" ], context) + def get_formset_kwargs(self, request, obj, inline, prefix): + formset_params = { + 'instance': obj, + 'prefix': prefix, + 'queryset': inline.get_queryset(request), + } + if request.method == 'POST': + formset_params.update({ + 'data': request.POST.copy(), + 'files': request.FILES, + 'save_as_new': '_saveasnew' in request.POST + }) + return formset_params + def _create_formsets(self, request, obj, change): "Helper function to generate formsets for add/change_view." formsets = [] @@ -1959,17 +1973,7 @@ class ModelAdmin(BaseModelAdmin): prefixes[prefix] = prefixes.get(prefix, 0) + 1 if prefixes[prefix] != 1 or not prefix: prefix = "%s-%s" % (prefix, prefixes[prefix]) - formset_params = { - 'instance': obj, - 'prefix': prefix, - 'queryset': inline.get_queryset(request), - } - if request.method == 'POST': - formset_params.update({ - 'data': request.POST.copy(), - 'files': request.FILES, - 'save_as_new': '_saveasnew' in request.POST - }) + formset_params = self.get_formset_kwargs(request, obj, inline, prefix) formset = FormSet(**formset_params) def user_deleted_form(request, obj, formset, index): diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 1fd5d0f60a2..5b3493c858e 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2088,6 +2088,22 @@ templates used by the :class:`ModelAdmin` views: ``obj_id`` is the serialized identifier used to retrieve the object to be deleted. +.. method:: ModelAdmin.get_formset_kwargs(request, obj, inline, prefix) + + .. versionadded:: 4.0 + + A hook for customizing the keyword arguments passed to the constructor of a + formset. For example, to pass ``request`` to formset forms:: + + class MyModelAdmin(admin.ModelAdmin): + def get_formset_kwargs(self, request, obj, inline, prefix): + return { + **super().get_formset_kwargs(request, obj, inline, prefix), + 'form_kwargs': {'request': request}, + } + + You can also used it to set ``initial`` for formset forms. + .. method:: ModelAdmin.get_changeform_initial_data(request) A hook for the initial data on admin change forms. By default, fields are diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index 1339b88f247..f1a25d0d927 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -37,6 +37,9 @@ Minor features * The ``admin/base.html`` template now has a new block ``header`` which contains the admin site header. +* The new :meth:`.ModelAdmin.get_formset_kwargs` method allows customizing the + keyword arguments passed to the constructor of a formset. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 925da719821..92dca62035d 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -951,6 +951,12 @@ class CityAdmin(admin.ModelAdmin): inlines = [RestaurantInlineAdmin] view_on_site = True + def get_formset_kwargs(self, request, obj, inline, prefix): + return { + **super().get_formset_kwargs(request, obj, inline, prefix), + 'form_kwargs': {'initial': {'name': 'overridden_name'}}, + } + class WorkerAdmin(admin.ModelAdmin): def view_on_site(self, obj): diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 8cb3fda9668..94ddf2be34d 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1117,6 +1117,10 @@ class AdminViewBasicTest(AdminViewBasicTestCase): self.assertContains(response, '<h1>View article</h1>') self.assertContains(response, '<h2>Article 2</h2>') + def test_formset_kwargs_can_be_overridden(self): + response = self.client.get(reverse('admin:admin_views_city_add')) + self.assertContains(response, 'overridden_name') + @override_settings(TEMPLATES=[{ 'BACKEND': 'django.template.backends.django.DjangoTemplates',