diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 081d001..79f82d4 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -762,21 +762,32 @@ 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, continue_url=None, add_url=None, hasperm_url=None, noperm_url=None):
         """
         Determines the HttpResponse for the add_view stage.
         """
         opts = obj._meta
         pk_value = obj._get_pk_val()
+        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_url is None:
+                continue_url = reverse('admin:%s_%s_change' % (
+                        app_label, model_name
+                    ),
+                    args=(pk_value,),
+                    current_app=site_name)
             if "_popup" in request.POST:
-                post_url_continue += "?_popup=1"
-            return HttpResponseRedirect(post_url_continue % pk_value)
+                continue_url += "?_popup=1"
+            return HttpResponseRedirect(continue_url)
 
         if "_popup" in request.POST:
             return HttpResponse(
@@ -785,72 +796,97 @@ 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_url is None:
+                add_url = reverse('admin:%s_%s_add' %
+                    (app_label, model_name),
+                    current_app=site_name)
+            return HttpResponseRedirect(add_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:
+                    url = reverse('admin:%s_%s_changelist' %
+                        (app_label, model_name),
+                        current_app=site_name)
+                else:
+                    url = hasperm_url
             else:
-                post_url = reverse('admin:index',
-                                   current_app=self.admin_site.name)
-            return HttpResponseRedirect(post_url)
+                if noperm_url is None:
+                    url = reverse('admin:index', current_app=site_name)
+                else:
+                    url = noperm_url
+            return HttpResponseRedirect(url)
 
-    def response_change(self, request, obj):
+    def response_change(self, request, obj, continue_url=None, add_url=None, hasperm_url=None, noperm_url=None):
         """
         Determines the HttpResponse for the change_view stage.
         """
         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
 
+        msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
         pk_value = obj._get_pk_val()
 
-        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)}
+        if "_continue" in request.POST or "_saveasnew" in request.POST:
+            if continue_url is None:
+                continue_url = reverse('admin:%s_%s_change' %
+                    (app_label, model_name),
+                    args=(pk_value,),
+                    current_app=site_name)
         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 "_popup" in request.POST:
+                continue_url += "?_popup=1"
+            return HttpResponseRedirect(continue_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))
+            return HttpResponseRedirect(continue_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_url is None:
+                add_url = reverse('admin:%s_%s_add' %
+                                       (app_label, model_name),
+                                       current_app=site_name)
+            return HttpResponseRedirect(add_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:
+                    url = reverse('admin:%s_%s_changelist' %
+                                   (app_label, model_name),
+                                   current_app=site_name)
+                else:
+                    url = hasperm_url
             else:
-                post_url = reverse('admin:index',
-                                   current_app=self.admin_site.name)
-            return HttpResponseRedirect(post_url)
+                if noperm_url is None:
+                    url = reverse('admin:index', current_app=site_name)
+                else:
+                    url = noperm_url
+            return HttpResponseRedirect(url)
 
     def response_action(self, request, queryset):
         """
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index ccf940d..52f8083 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -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, continue_url=None, add_url=None, hasperm_url=None, noperm_url=None):
         """
         Determines the HttpResponse for the add_view stage. It mostly defers to
         its superclass implementation but is customized because the User model
@@ -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, continue_url, add_url, hasperm_url, noperm_url)
 
 admin.site.register(Group, GroupAdmin)
 admin.site.register(User, UserAdmin)
