diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 6a614b80a0..b4fa54af6c 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1308,6 +1308,22 @@ class ModelAdmin(BaseModelAdmin): inline_admin_formsets.append(inline_admin_formset) return inline_admin_formsets + def get_changeform_initial_data(self, request): + """ + Get the initial form data. + Unless overridden, this populates from the GET params. + """ + initial = dict(request.GET.items()) + for k in initial: + try: + f = self.model._meta.get_field(k) + except models.FieldDoesNotExist: + continue + # We have to special-case M2Ms as a list of comma-separated PKs. + if isinstance(f, models.ManyToManyField): + initial[k] = initial[k].split(",") + return initial + @csrf_protect_m @transaction.atomic def changeform_view(self, request, object_id=None, form_url='', extra_context=None): @@ -1358,16 +1374,7 @@ class ModelAdmin(BaseModelAdmin): return self.response_change(request, new_object) else: if add: - # Prepare the dict of initial data from the request. - # We have to special-case M2Ms as a list of comma-separated PKs. - initial = dict(request.GET.items()) - for k in initial: - try: - f = opts.get_field(k) - except models.FieldDoesNotExist: - continue - if isinstance(f, models.ManyToManyField): - initial[k] = initial[k].split(",") + initial = self.get_changeform_initial_data(request) form = ModelForm(initial=initial) formsets, inline_instances = self._create_formsets(request, self.model()) else: diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 2c852a3ee4..f2fdd39714 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1709,6 +1709,21 @@ templates used by the :class:`ModelAdmin` views: ``obj_display`` is a string with the name of the deleted object. +.. method:: ModelAdmin.get_changeform_initial_data(request) + + .. versionadded:: 1.7 + + A hook for the initial data on admin change forms. By default, fields are + given initial values from ``GET`` parameters. For instance, + ``?name=initial_value`` will set the ``name`` field's initial value to be + ``initial_value``. + + This method should return a dictionary in the form + ``{'fieldname': 'fieldval'}``:: + + def get_changeform_initial_data(self, request): + return {'name': 'custom_initial_value'} + Other methods ~~~~~~~~~~~~~ diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index f9400032e5..305cf6da66 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -308,6 +308,10 @@ Minor features ` value by prefixing the ``admin_order_field`` value with a hyphen. +* The :meth:`ModelAdmin.get_changeform_initial_data() + ` method may be + overridden to define custom behavior for setting initial change form data. + :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index ae625b6a53..e9c6d4dc4c 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -796,6 +796,10 @@ class RestaurantAdmin(admin.ModelAdmin): inlines = [WorkerInlineAdmin] view_on_site = False + def get_changeform_initial_data(self, request): + return {'name': 'overridden_value'} + + site = admin.AdminSite(name="admin") site.register(Article, ArticleAdmin) site.register(CustomArticle, CustomArticleAdmin) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 382ef56647..5e4edf6909 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -765,6 +765,19 @@ class AdminViewFormUrlTest(TestCase): self.assertTrue('form_url' in response.context, msg='form_url not present in response.context') self.assertEqual(response.context['form_url'], 'pony') + def testInitialDataCanBeOverridden(self): + """ + Tests that the behavior for setting initial + form data can be overridden in the ModelAdmin class. + + Usually, the initial value is set via the GET params. + """ + response = self.client.get('/test_admin/%s/admin_views/restaurant/add/' % self.urlbit, {'name': 'test_value'}) + # this would be the usual behaviour + self.assertNotContains(response, 'value="test_value"') + # this is the overridden behaviour + self.assertContains(response, 'value="overridden_value"') + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class AdminJavaScriptTest(TestCase):