Loading django/contrib/admin/options.py +104 −42 Original line number Diff line number Diff line from functools import update_wrapper, partial import warnings from django import forms from django.conf import settings from django.forms.formsets import all_valid Loading @@ -6,7 +8,7 @@ from django.forms.models import (modelform_factory, modelformset_factory, inlineformset_factory, BaseInlineFormSet) from django.contrib.contenttypes.models import ContentType from django.contrib.admin import widgets, helpers from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict from django.contrib.admin.templatetags.admin_static import static from django.contrib import messages from django.views.decorators.csrf import csrf_protect Loading Loading @@ -763,21 +765,49 @@ class ModelAdmin(BaseModelAdmin): "admin/change_form.html" ], context, current_app=self.admin_site.name) def response_add(self, request, obj, post_url_continue='../%s/'): def response_add(self, request, obj, post_url_continue='../%s/', continue_editing_url=None, add_another_url=None, hasperm_url=None, noperm_url=None): """ Determines the HttpResponse for the add_view stage. """ :param request: HttpRequest instance. :param obj: Object just added. :param post_url_continue: Deprecated/undocumented. :param continue_editing_url: URL where user will be redirected after pressing 'Save and continue editing'. :param add_another_url: URL where user will be redirected after pressing 'Save and add another'. :param hasperm_url: URL to redirect after a successful object creation when the user has change permissions. :param noperm_url: URL to redirect after a successful object creation when the user has no change permissions. """ if post_url_continue != '../%s/': warnings.warn("The undocumented 'post_url_continue' argument to " "ModelAdmin.response_add() is deprecated, use the new " "*_url arguments instead.", DeprecationWarning, stacklevel=2) opts = obj._meta pk_value = obj._get_pk_val() pk_value = obj.pk app_label = opts.app_label model_name = opts.module_name site_name = self.admin_site.name msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. if "_continue" in request.POST: self.message_user(request, msg + ' ' + _("You may edit it again below.")) msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) if continue_editing_url is None: continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(continue_editing_url, args=(quote(pk_value),), current_app=site_name) if "_popup" in request.POST: post_url_continue += "?_popup=1" return HttpResponseRedirect(post_url_continue % pk_value) url += "?_popup=1" return HttpResponseRedirect(url) if "_popup" in request.POST: return HttpResponse( Loading @@ -786,72 +816,104 @@ class ModelAdmin(BaseModelAdmin): # escape() calls force_text. (escape(pk_value), escapejs(obj))) elif "_addanother" in request.POST: self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name))) return HttpResponseRedirect(request.path) msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict self.message_user(request, msg) if add_another_url is None: add_another_url = 'admin:%s_%s_add' % (app_label, model_name) url = reverse(add_another_url, current_app=site_name) return HttpResponseRedirect(url) else: msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict self.message_user(request, msg) # Figure out where to redirect. If the user has change permission, # redirect to the change-list page for this object. Otherwise, # redirect to the admin index. if self.has_change_permission(request, None): post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.module_name), current_app=self.admin_site.name) if hasperm_url is None: hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name) url = reverse(hasperm_url, current_app=site_name) else: post_url = reverse('admin:index', current_app=self.admin_site.name) return HttpResponseRedirect(post_url) if noperm_url is None: noperm_url = 'admin:index' url = reverse(noperm_url, current_app=site_name) return HttpResponseRedirect(url) def response_change(self, request, obj): def response_change(self, request, obj, continue_editing_url=None, save_as_new_url=None, add_another_url=None, hasperm_url=None, noperm_url=None): """ Determines the HttpResponse for the change_view stage. :param request: HttpRequest instance. :param obj: Object just modified. :param continue_editing_url: URL where user will be redirected after pressing 'Save and continue editing'. :param save_as_new_url: URL where user will be redirected after pressing 'Save as new' (when applicable). :param add_another_url: URL where user will be redirected after pressing 'Save and add another'. :param hasperm_url: URL to redirect after a successful object edition when the user has change permissions. :param noperm_url: URL to redirect after a successful object edition when the user has no change permissions. """ opts = obj._meta app_label = opts.app_label model_name = opts.module_name site_name = self.admin_site.name verbose_name = opts.verbose_name # Handle proxy models automatically created by .only() or .defer(). # Refs #14529 verbose_name = opts.verbose_name module_name = opts.module_name if obj._deferred: opts_ = opts.proxy_for_model._meta verbose_name = opts_.verbose_name module_name = opts_.module_name model_name = opts_.module_name pk_value = obj._get_pk_val() msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)} msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)} if "_continue" in request.POST: self.message_user(request, msg + ' ' + _("You may edit it again below.")) if "_popup" in request.REQUEST: return HttpResponseRedirect(request.path + "?_popup=1") else: return HttpResponseRedirect(request.path) msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) if continue_editing_url is None: continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(continue_editing_url, args=(quote(obj.pk),), current_app=site_name) if "_popup" in request.POST: url += "?_popup=1" return HttpResponseRedirect(url) elif "_saveasnew" in request.POST: msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj} msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) return HttpResponseRedirect(reverse('admin:%s_%s_change' % (opts.app_label, module_name), args=(pk_value,), current_app=self.admin_site.name)) if save_as_new_url is None: save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(save_as_new_url, args=(quote(obj.pk),), current_app=site_name) return HttpResponseRedirect(url) elif "_addanother" in request.POST: self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name))) return HttpResponseRedirect(reverse('admin:%s_%s_add' % (opts.app_label, module_name), current_app=self.admin_site.name)) msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict self.message_user(request, msg) if add_another_url is None: add_another_url = 'admin:%s_%s_add' % (app_label, model_name) url = reverse(add_another_url, current_app=site_name) return HttpResponseRedirect(url) else: msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict self.message_user(request, msg) # Figure out where to redirect. If the user has change permission, # redirect to the change-list page for this object. Otherwise, # redirect to the admin index. if self.has_change_permission(request, None): post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, module_name), current_app=self.admin_site.name) if hasperm_url is None: hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name) url = reverse(hasperm_url, current_app=site_name) else: post_url = reverse('admin:index', current_app=self.admin_site.name) return HttpResponseRedirect(post_url) if noperm_url is None: noperm_url = 'admin:index' url = reverse(noperm_url, current_app=site_name) return HttpResponseRedirect(url) def response_action(self, request, queryset): """ Loading django/contrib/auth/admin.py +2 −3 Original line number Diff line number Diff line Loading @@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin): 'admin/auth/user/change_password.html' ], context, current_app=self.admin_site.name) def response_add(self, request, obj, post_url_continue='../%s/'): def response_add(self, request, obj, **kwargs): """ Determines the HttpResponse for the add_view stage. It mostly defers to its superclass implementation but is customized because the User model Loading @@ -166,8 +166,7 @@ class UserAdmin(admin.ModelAdmin): # * We are adding a user in a popup if '_addanother' not in request.POST and '_popup' not in request.POST: request.POST['_continue'] = 1 return super(UserAdmin, self).response_add(request, obj, post_url_continue) return super(UserAdmin, self).response_add(request, obj, **kwargs) admin.site.register(Group, GroupAdmin) admin.site.register(User, UserAdmin) tests/regressiontests/admin_custom_urls/models.py +37 −0 Original line number Diff line number Diff line Loading @@ -50,3 +50,40 @@ class ActionAdmin(admin.ModelAdmin): admin.site.register(Action, ActionAdmin) class Person(models.Model): nick = models.CharField(max_length=20) class PersonAdmin(admin.ModelAdmin): """A custom ModelAdmin that customizes the deprecated post_url_continue argument to response_add()""" def response_add(self, request, obj, post_url_continue='../%s/continue/', continue_url=None, add_url=None, hasperm_url=None, noperm_url=None): return super(PersonAdmin, self).response_add(request, obj, post_url_continue, continue_url, add_url, hasperm_url, noperm_url) admin.site.register(Person, PersonAdmin) class City(models.Model): name = models.CharField(max_length=20) class CityAdmin(admin.ModelAdmin): """A custom ModelAdmin that redirects to the changelist when the user presses the 'Save and add another' button when adding a model instance.""" def response_add(self, request, obj, add_another_url='admin:admin_custom_urls_city_changelist', **kwargs): return super(CityAdmin, self).response_add(request, obj, add_another_url=add_another_url, **kwargs) admin.site.register(City, CityAdmin) tests/regressiontests/admin_custom_urls/tests.py +45 −1 Original line number Diff line number Diff line from __future__ import absolute_import, unicode_literals import warnings from django.contrib.admin.util import quote from django.core.urlresolvers import reverse from django.template.response import TemplateResponse from django.test import TestCase from django.test.utils import override_settings from .models import Action from .models import Action, Person, City @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) Loading Loading @@ -81,3 +83,45 @@ class AdminCustomUrlsTest(TestCase): self.assertEqual(response.status_code, 200) self.assertContains(response, 'Change action') self.assertContains(response, 'value="path/to/html/document.html"') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class CustomUrlsWorkflowTests(TestCase): fixtures = ['users.json'] def setUp(self): self.client.login(username='super', password='secret') def tearDown(self): self.client.logout() def test_old_argument_deprecation(self): """Test reporting of post_url_continue deprecation.""" post_data = { 'nick': 'johndoe', } cnt = Person.objects.count() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") response = self.client.post(reverse('admin:admin_custom_urls_person_add'), post_data) self.assertEqual(response.status_code, 302) self.assertEqual(Person.objects.count(), cnt + 1) # We should get a DeprecationWarning self.assertEqual(len(w), 1) self.assertTrue(isinstance(w[0].message, DeprecationWarning)) def test_custom_add_another_redirect(self): """Test customizability of post-object-creation redirect URL.""" post_data = { 'name': 'Rome', '_addanother': '1', } cnt = City.objects.count() with warnings.catch_warnings(record=True) as w: # POST to the view whose post-object-creation redir URL argument we # are customizing (object creation) response = self.client.post(reverse('admin:admin_custom_urls_city_add'), post_data) self.assertEqual(City.objects.count(), cnt + 1) # Check that it redirected to the URL we set self.assertRedirects(response, reverse('admin:admin_custom_urls_city_changelist')) self.assertEqual(len(w), 0) # We should get no DeprecationWarning Loading
django/contrib/admin/options.py +104 −42 Original line number Diff line number Diff line from functools import update_wrapper, partial import warnings from django import forms from django.conf import settings from django.forms.formsets import all_valid Loading @@ -6,7 +8,7 @@ from django.forms.models import (modelform_factory, modelformset_factory, inlineformset_factory, BaseInlineFormSet) from django.contrib.contenttypes.models import ContentType from django.contrib.admin import widgets, helpers from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict from django.contrib.admin.templatetags.admin_static import static from django.contrib import messages from django.views.decorators.csrf import csrf_protect Loading Loading @@ -763,21 +765,49 @@ class ModelAdmin(BaseModelAdmin): "admin/change_form.html" ], context, current_app=self.admin_site.name) def response_add(self, request, obj, post_url_continue='../%s/'): def response_add(self, request, obj, post_url_continue='../%s/', continue_editing_url=None, add_another_url=None, hasperm_url=None, noperm_url=None): """ Determines the HttpResponse for the add_view stage. """ :param request: HttpRequest instance. :param obj: Object just added. :param post_url_continue: Deprecated/undocumented. :param continue_editing_url: URL where user will be redirected after pressing 'Save and continue editing'. :param add_another_url: URL where user will be redirected after pressing 'Save and add another'. :param hasperm_url: URL to redirect after a successful object creation when the user has change permissions. :param noperm_url: URL to redirect after a successful object creation when the user has no change permissions. """ if post_url_continue != '../%s/': warnings.warn("The undocumented 'post_url_continue' argument to " "ModelAdmin.response_add() is deprecated, use the new " "*_url arguments instead.", DeprecationWarning, stacklevel=2) opts = obj._meta pk_value = obj._get_pk_val() pk_value = obj.pk app_label = opts.app_label model_name = opts.module_name site_name = self.admin_site.name msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. if "_continue" in request.POST: self.message_user(request, msg + ' ' + _("You may edit it again below.")) msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) if continue_editing_url is None: continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(continue_editing_url, args=(quote(pk_value),), current_app=site_name) if "_popup" in request.POST: post_url_continue += "?_popup=1" return HttpResponseRedirect(post_url_continue % pk_value) url += "?_popup=1" return HttpResponseRedirect(url) if "_popup" in request.POST: return HttpResponse( Loading @@ -786,72 +816,104 @@ class ModelAdmin(BaseModelAdmin): # escape() calls force_text. (escape(pk_value), escapejs(obj))) elif "_addanother" in request.POST: self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name))) return HttpResponseRedirect(request.path) msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict self.message_user(request, msg) if add_another_url is None: add_another_url = 'admin:%s_%s_add' % (app_label, model_name) url = reverse(add_another_url, current_app=site_name) return HttpResponseRedirect(url) else: msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict self.message_user(request, msg) # Figure out where to redirect. If the user has change permission, # redirect to the change-list page for this object. Otherwise, # redirect to the admin index. if self.has_change_permission(request, None): post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.module_name), current_app=self.admin_site.name) if hasperm_url is None: hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name) url = reverse(hasperm_url, current_app=site_name) else: post_url = reverse('admin:index', current_app=self.admin_site.name) return HttpResponseRedirect(post_url) if noperm_url is None: noperm_url = 'admin:index' url = reverse(noperm_url, current_app=site_name) return HttpResponseRedirect(url) def response_change(self, request, obj): def response_change(self, request, obj, continue_editing_url=None, save_as_new_url=None, add_another_url=None, hasperm_url=None, noperm_url=None): """ Determines the HttpResponse for the change_view stage. :param request: HttpRequest instance. :param obj: Object just modified. :param continue_editing_url: URL where user will be redirected after pressing 'Save and continue editing'. :param save_as_new_url: URL where user will be redirected after pressing 'Save as new' (when applicable). :param add_another_url: URL where user will be redirected after pressing 'Save and add another'. :param hasperm_url: URL to redirect after a successful object edition when the user has change permissions. :param noperm_url: URL to redirect after a successful object edition when the user has no change permissions. """ opts = obj._meta app_label = opts.app_label model_name = opts.module_name site_name = self.admin_site.name verbose_name = opts.verbose_name # Handle proxy models automatically created by .only() or .defer(). # Refs #14529 verbose_name = opts.verbose_name module_name = opts.module_name if obj._deferred: opts_ = opts.proxy_for_model._meta verbose_name = opts_.verbose_name module_name = opts_.module_name model_name = opts_.module_name pk_value = obj._get_pk_val() msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)} msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)} if "_continue" in request.POST: self.message_user(request, msg + ' ' + _("You may edit it again below.")) if "_popup" in request.REQUEST: return HttpResponseRedirect(request.path + "?_popup=1") else: return HttpResponseRedirect(request.path) msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) if continue_editing_url is None: continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(continue_editing_url, args=(quote(obj.pk),), current_app=site_name) if "_popup" in request.POST: url += "?_popup=1" return HttpResponseRedirect(url) elif "_saveasnew" in request.POST: msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj} msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict self.message_user(request, msg) return HttpResponseRedirect(reverse('admin:%s_%s_change' % (opts.app_label, module_name), args=(pk_value,), current_app=self.admin_site.name)) if save_as_new_url is None: save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name) url = reverse(save_as_new_url, args=(quote(obj.pk),), current_app=site_name) return HttpResponseRedirect(url) elif "_addanother" in request.POST: self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name))) return HttpResponseRedirect(reverse('admin:%s_%s_add' % (opts.app_label, module_name), current_app=self.admin_site.name)) msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict self.message_user(request, msg) if add_another_url is None: add_another_url = 'admin:%s_%s_add' % (app_label, model_name) url = reverse(add_another_url, current_app=site_name) return HttpResponseRedirect(url) else: msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict self.message_user(request, msg) # Figure out where to redirect. If the user has change permission, # redirect to the change-list page for this object. Otherwise, # redirect to the admin index. if self.has_change_permission(request, None): post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, module_name), current_app=self.admin_site.name) if hasperm_url is None: hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name) url = reverse(hasperm_url, current_app=site_name) else: post_url = reverse('admin:index', current_app=self.admin_site.name) return HttpResponseRedirect(post_url) if noperm_url is None: noperm_url = 'admin:index' url = reverse(noperm_url, current_app=site_name) return HttpResponseRedirect(url) def response_action(self, request, queryset): """ Loading
django/contrib/auth/admin.py +2 −3 Original line number Diff line number Diff line Loading @@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin): 'admin/auth/user/change_password.html' ], context, current_app=self.admin_site.name) def response_add(self, request, obj, post_url_continue='../%s/'): def response_add(self, request, obj, **kwargs): """ Determines the HttpResponse for the add_view stage. It mostly defers to its superclass implementation but is customized because the User model Loading @@ -166,8 +166,7 @@ class UserAdmin(admin.ModelAdmin): # * We are adding a user in a popup if '_addanother' not in request.POST and '_popup' not in request.POST: request.POST['_continue'] = 1 return super(UserAdmin, self).response_add(request, obj, post_url_continue) return super(UserAdmin, self).response_add(request, obj, **kwargs) admin.site.register(Group, GroupAdmin) admin.site.register(User, UserAdmin)
tests/regressiontests/admin_custom_urls/models.py +37 −0 Original line number Diff line number Diff line Loading @@ -50,3 +50,40 @@ class ActionAdmin(admin.ModelAdmin): admin.site.register(Action, ActionAdmin) class Person(models.Model): nick = models.CharField(max_length=20) class PersonAdmin(admin.ModelAdmin): """A custom ModelAdmin that customizes the deprecated post_url_continue argument to response_add()""" def response_add(self, request, obj, post_url_continue='../%s/continue/', continue_url=None, add_url=None, hasperm_url=None, noperm_url=None): return super(PersonAdmin, self).response_add(request, obj, post_url_continue, continue_url, add_url, hasperm_url, noperm_url) admin.site.register(Person, PersonAdmin) class City(models.Model): name = models.CharField(max_length=20) class CityAdmin(admin.ModelAdmin): """A custom ModelAdmin that redirects to the changelist when the user presses the 'Save and add another' button when adding a model instance.""" def response_add(self, request, obj, add_another_url='admin:admin_custom_urls_city_changelist', **kwargs): return super(CityAdmin, self).response_add(request, obj, add_another_url=add_another_url, **kwargs) admin.site.register(City, CityAdmin)
tests/regressiontests/admin_custom_urls/tests.py +45 −1 Original line number Diff line number Diff line from __future__ import absolute_import, unicode_literals import warnings from django.contrib.admin.util import quote from django.core.urlresolvers import reverse from django.template.response import TemplateResponse from django.test import TestCase from django.test.utils import override_settings from .models import Action from .models import Action, Person, City @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) Loading Loading @@ -81,3 +83,45 @@ class AdminCustomUrlsTest(TestCase): self.assertEqual(response.status_code, 200) self.assertContains(response, 'Change action') self.assertContains(response, 'value="path/to/html/document.html"') @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) class CustomUrlsWorkflowTests(TestCase): fixtures = ['users.json'] def setUp(self): self.client.login(username='super', password='secret') def tearDown(self): self.client.logout() def test_old_argument_deprecation(self): """Test reporting of post_url_continue deprecation.""" post_data = { 'nick': 'johndoe', } cnt = Person.objects.count() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") response = self.client.post(reverse('admin:admin_custom_urls_person_add'), post_data) self.assertEqual(response.status_code, 302) self.assertEqual(Person.objects.count(), cnt + 1) # We should get a DeprecationWarning self.assertEqual(len(w), 1) self.assertTrue(isinstance(w[0].message, DeprecationWarning)) def test_custom_add_another_redirect(self): """Test customizability of post-object-creation redirect URL.""" post_data = { 'name': 'Rome', '_addanother': '1', } cnt = City.objects.count() with warnings.catch_warnings(record=True) as w: # POST to the view whose post-object-creation redir URL argument we # are customizing (object creation) response = self.client.post(reverse('admin:admin_custom_urls_city_add'), post_data) self.assertEqual(City.objects.count(), cnt + 1) # Check that it redirected to the URL we set self.assertRedirects(response, reverse('admin:admin_custom_urls_city_changelist')) self.assertEqual(len(w), 0) # We should get no DeprecationWarning