diff --git a/django/contrib/admin/templates/admin/submit_line.html b/django/contrib/admin/templates/admin/submit_line.html
index 38a97a1c6a..52baed3ffa 100644
--- a/django/contrib/admin/templates/admin/submit_line.html
+++ b/django/contrib/admin/templates/admin/submit_line.html
@@ -1,7 +1,10 @@
{% load i18n admin_urls %}
{% if show_save %}
{% endif %}
-{% if show_delete_link %}
{% trans "Delete" %}
{% endif %}
+{% if show_delete_link %}
+ {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
+
{% trans "Delete" %}
+{% endif %}
{% if show_save_as_new %}
{%endif%}
{% if show_save_and_add_another %}
{% endif %}
{% if show_save_and_continue %}
{% endif %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 965352e0f5..e81b13cda4 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import datetime
+from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.admin.util import (lookup_field, display_for_field,
display_for_value, label_for_field)
from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
@@ -217,6 +218,7 @@ def items_for_result(cl, result, form):
table_tag = {True:'th', False:'td'}[first]
first = False
url = cl.url_for_result(result)
+ url = add_preserved_filters({'preserved_filters': cl.preserved_filters, 'opts': cl.opts}, url)
# Convert the pk to something that can be used in Javascript.
# Problem cases are long ints (23L) and non-ASCII strings.
if cl.to_field:
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index cecc6ed6c4..98ac1a657e 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -37,7 +37,8 @@ def submit_row(context):
not is_popup and (not save_as or context['add']),
'show_save_and_continue': not is_popup and context['has_change_permission'],
'is_popup': is_popup,
- 'show_save': True
+ 'show_save': True,
+ 'preserved_filters': context.get('preserved_filters'),
}
if context.get('original') is not None:
ctx['original'] = context['original']
diff --git a/django/contrib/admin/templatetags/admin_urls.py b/django/contrib/admin/templatetags/admin_urls.py
index bca95d92ae..bb1b16883d 100644
--- a/django/contrib/admin/templatetags/admin_urls.py
+++ b/django/contrib/admin/templatetags/admin_urls.py
@@ -1,8 +1,17 @@
+from django.utils.http import urlencode
+
+try:
+ from urllib.parse import parse_qsl, urlparse, urlunparse
+except ImportError:
+ from urlparse import parse_qsl, urlparse, urlunparse
+
from django import template
from django.contrib.admin.util import quote
+from django.core.urlresolvers import resolve, Resolver404
register = template.Library()
+
@register.filter
def admin_urlname(value, arg):
return 'admin:%s_%s_%s' % (value.app_label, value.model_name, arg)
@@ -11,3 +20,36 @@ def admin_urlname(value, arg):
@register.filter
def admin_urlquote(value):
return quote(value)
+
+
+@register.simple_tag(takes_context=True)
+def add_preserved_filters(context, url, popup=False):
+ opts = context.get('opts')
+ preserved_filters = context.get('preserved_filters')
+
+ parsed_url = list(urlparse(url))
+ parsed_qs = dict(parse_qsl(parsed_url[4]))
+ merged_qs = dict()
+
+ if opts and preserved_filters:
+ preserved_filters = dict(parse_qsl(preserved_filters))
+
+ try:
+ match = resolve(url)
+ except Resolver404:
+ pass
+ else:
+ current_url = '%s:%s' % (match.namespace, match.url_name)
+ changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
+ if changelist_url == current_url and '_changelist_filters' in preserved_filters:
+ preserved_filters = dict(parse_qsl(preserved_filters['_changelist_filters']))
+
+ merged_qs.update(preserved_filters)
+
+ if popup:
+ merged_qs['_popup'] = 1
+
+ merged_qs.update(parsed_qs)
+
+ parsed_url[4] = urlencode(merged_qs)
+ return urlunparse(parsed_url)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 8ea7e10fc0..f676706a89 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -59,6 +59,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
self.list_per_page = list_per_page
self.list_max_show_all = list_max_show_all
self.model_admin = model_admin
+ self.preserved_filters = model_admin.get_preserved_filters(request)
# Get search parameters from the query string.
try:
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 513499c0c1..90ef68837a 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -870,6 +870,14 @@ subclass::
``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``,
nor ``ManyToManyField`` fields.
+.. attribute:: ModelAdmin.preserve_filters
+
+ .. versionadded:: 1.6
+
+ The admin now preserves filters on the list view after creating, editing
+ or deleting an object. You can restore the previous behavior of clearing
+ filters by setting this attribute to ``False``.
+
.. attribute:: ModelAdmin.radio_fields
By default, Django's admin uses a select-box interface (