Fixed #6903 - Preserve admin changelist filters after saving or deleting an object
This commit is contained in:
parent
2c4fe761a0
commit
c86a9b6398
|
@ -13,6 +13,7 @@ from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_o
|
||||||
model_format_dict, NestedObjects, lookup_needs_distinct)
|
model_format_dict, NestedObjects, lookup_needs_distinct)
|
||||||
from django.contrib.admin import validation
|
from django.contrib.admin import validation
|
||||||
from django.contrib.admin.templatetags.admin_static import static
|
from django.contrib.admin.templatetags.admin_static import static
|
||||||
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError, FieldError
|
from django.core.exceptions import PermissionDenied, ValidationError, FieldError
|
||||||
|
@ -33,6 +34,7 @@ from django.utils.html import escape, escapejs
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.deprecation import RenameMethodsBase
|
from django.utils.deprecation import RenameMethodsBase
|
||||||
|
from django.utils.http import urlencode
|
||||||
from django.utils.text import capfirst, get_text_list
|
from django.utils.text import capfirst, get_text_list
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ungettext
|
from django.utils.translation import ungettext
|
||||||
|
@ -393,6 +395,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
save_as = False
|
save_as = False
|
||||||
save_on_top = False
|
save_on_top = False
|
||||||
paginator = Paginator
|
paginator = Paginator
|
||||||
|
preserve_filters = True
|
||||||
inlines = []
|
inlines = []
|
||||||
|
|
||||||
# Custom templates (designed to be over-ridden in subclasses)
|
# Custom templates (designed to be over-ridden in subclasses)
|
||||||
|
@ -755,6 +758,27 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"""
|
"""
|
||||||
return self.list_filter
|
return self.list_filter
|
||||||
|
|
||||||
|
def get_preserved_filters(self, request):
|
||||||
|
"""
|
||||||
|
Returns the preserved filters querystring.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME: We can remove that getattr as soon as #20619 is fixed.
|
||||||
|
match = getattr(request, 'resolver_match', None)
|
||||||
|
|
||||||
|
if self.preserve_filters and match:
|
||||||
|
opts = self.model._meta
|
||||||
|
current_url = '%s:%s' % (match.namespace, match.url_name)
|
||||||
|
changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
|
||||||
|
if current_url == changelist_url:
|
||||||
|
preserved_filters = request.GET.urlencode()
|
||||||
|
else:
|
||||||
|
preserved_filters = request.GET.get('_changelist_filters')
|
||||||
|
|
||||||
|
if preserved_filters:
|
||||||
|
return urlencode({'_changelist_filters': preserved_filters})
|
||||||
|
return ''
|
||||||
|
|
||||||
def construct_change_message(self, request, form, formsets):
|
def construct_change_message(self, request, form, formsets):
|
||||||
"""
|
"""
|
||||||
Construct a change message from a changed object.
|
Construct a change message from a changed object.
|
||||||
|
@ -846,6 +870,8 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url)
|
||||||
context.update({
|
context.update({
|
||||||
'add': add,
|
'add': add,
|
||||||
'change': change,
|
'change': change,
|
||||||
|
@ -877,11 +903,19 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"""
|
"""
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
pk_value = obj._get_pk_val()
|
pk_value = obj._get_pk_val()
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
|
||||||
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
||||||
# Here, we distinguish between different save types by checking for
|
# Here, we distinguish between different save types by checking for
|
||||||
# the presence of keys in request.POST.
|
# the presence of keys in request.POST.
|
||||||
if "_continue" in request.POST:
|
if "_popup" in request.POST:
|
||||||
|
return HttpResponse(
|
||||||
|
'<!DOCTYPE html><html><head><title></title></head><body>'
|
||||||
|
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
|
||||||
|
# escape() calls force_text.
|
||||||
|
(escape(pk_value), escapejs(obj)))
|
||||||
|
|
||||||
|
elif "_continue" in request.POST:
|
||||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
if post_url_continue is None:
|
if post_url_continue is None:
|
||||||
|
@ -889,20 +923,16 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
args=(pk_value,),
|
args=(pk_value,),
|
||||||
current_app=self.admin_site.name)
|
current_app=self.admin_site.name)
|
||||||
if "_popup" in request.POST:
|
post_url_continue = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url_continue)
|
||||||
post_url_continue += "?_popup=1"
|
|
||||||
return HttpResponseRedirect(post_url_continue)
|
return HttpResponseRedirect(post_url_continue)
|
||||||
|
|
||||||
if "_popup" in request.POST:
|
|
||||||
return HttpResponse(
|
|
||||||
'<!DOCTYPE html><html><head><title></title></head><body>'
|
|
||||||
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
|
|
||||||
# escape() calls force_text.
|
|
||||||
(escape(pk_value), escapejs(obj)))
|
|
||||||
elif "_addanother" in request.POST:
|
elif "_addanother" in request.POST:
|
||||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
return HttpResponseRedirect(request.path)
|
redirect_url = request.path
|
||||||
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
|
@ -913,30 +943,36 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
Determines the HttpResponse for the change_view stage.
|
Determines the HttpResponse for the change_view stage.
|
||||||
"""
|
"""
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
|
|
||||||
pk_value = obj._get_pk_val()
|
pk_value = obj._get_pk_val()
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
|
||||||
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
||||||
if "_continue" in request.POST:
|
if "_continue" in request.POST:
|
||||||
msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
if "_popup" in request.REQUEST:
|
redirect_url = request.path
|
||||||
return HttpResponseRedirect(request.path + "?_popup=1")
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
else:
|
return HttpResponseRedirect(redirect_url)
|
||||||
return HttpResponseRedirect(request.path)
|
|
||||||
elif "_saveasnew" in request.POST:
|
elif "_saveasnew" in request.POST:
|
||||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
return HttpResponseRedirect(reverse('admin:%s_%s_change' %
|
redirect_url = reverse('admin:%s_%s_change' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
args=(pk_value,),
|
args=(pk_value,),
|
||||||
current_app=self.admin_site.name))
|
current_app=self.admin_site.name)
|
||||||
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
elif "_addanother" in request.POST:
|
elif "_addanother" in request.POST:
|
||||||
msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
return HttpResponseRedirect(reverse('admin:%s_%s_add' %
|
redirect_url = reverse('admin:%s_%s_add' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
current_app=self.admin_site.name))
|
current_app=self.admin_site.name)
|
||||||
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
|
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
|
||||||
self.message_user(request, msg, messages.SUCCESS)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
|
@ -952,6 +988,8 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
post_url = reverse('admin:%s_%s_changelist' %
|
post_url = reverse('admin:%s_%s_changelist' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
current_app=self.admin_site.name)
|
current_app=self.admin_site.name)
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
|
||||||
else:
|
else:
|
||||||
post_url = reverse('admin:index',
|
post_url = reverse('admin:index',
|
||||||
current_app=self.admin_site.name)
|
current_app=self.admin_site.name)
|
||||||
|
@ -963,10 +1001,13 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
when editing an existing object.
|
when editing an existing object.
|
||||||
"""
|
"""
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
|
|
||||||
if self.has_change_permission(request, None):
|
if self.has_change_permission(request, None):
|
||||||
post_url = reverse('admin:%s_%s_changelist' %
|
post_url = reverse('admin:%s_%s_changelist' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
current_app=self.admin_site.name)
|
current_app=self.admin_site.name)
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
|
||||||
else:
|
else:
|
||||||
post_url = reverse('admin:index',
|
post_url = reverse('admin:index',
|
||||||
current_app=self.admin_site.name)
|
current_app=self.admin_site.name)
|
||||||
|
@ -1122,6 +1163,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'inline_admin_formsets': inline_admin_formsets,
|
'inline_admin_formsets': inline_admin_formsets,
|
||||||
'errors': helpers.AdminErrorList(form, formsets),
|
'errors': helpers.AdminErrorList(form, formsets),
|
||||||
'app_label': opts.app_label,
|
'app_label': opts.app_label,
|
||||||
|
'preserved_filters': self.get_preserved_filters(request),
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return self.render_change_form(request, context, form_url=form_url, add=True)
|
return self.render_change_form(request, context, form_url=form_url, add=True)
|
||||||
|
@ -1214,6 +1256,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'inline_admin_formsets': inline_admin_formsets,
|
'inline_admin_formsets': inline_admin_formsets,
|
||||||
'errors': helpers.AdminErrorList(form, formsets),
|
'errors': helpers.AdminErrorList(form, formsets),
|
||||||
'app_label': opts.app_label,
|
'app_label': opts.app_label,
|
||||||
|
'preserved_filters': self.get_preserved_filters(request),
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
|
return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
|
||||||
|
@ -1357,11 +1400,13 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'cl': cl,
|
'cl': cl,
|
||||||
'media': media,
|
'media': media,
|
||||||
'has_add_permission': self.has_add_permission(request),
|
'has_add_permission': self.has_add_permission(request),
|
||||||
|
'opts': cl.opts,
|
||||||
'app_label': app_label,
|
'app_label': app_label,
|
||||||
'action_form': action_form,
|
'action_form': action_form,
|
||||||
'actions_on_top': self.actions_on_top,
|
'actions_on_top': self.actions_on_top,
|
||||||
'actions_on_bottom': self.actions_on_bottom,
|
'actions_on_bottom': self.actions_on_bottom,
|
||||||
'actions_selection_counter': self.actions_selection_counter,
|
'actions_selection_counter': self.actions_selection_counter,
|
||||||
|
'preserved_filters': self.get_preserved_filters(request),
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
|
|
||||||
|
@ -1406,12 +1451,16 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'obj': force_text(obj_display)},
|
'obj': force_text(obj_display)},
|
||||||
messages.SUCCESS)
|
messages.SUCCESS)
|
||||||
|
|
||||||
if not self.has_change_permission(request, None):
|
if self.has_change_permission(request, None):
|
||||||
return HttpResponseRedirect(reverse('admin:index',
|
post_url = reverse('admin:%s_%s_changelist' %
|
||||||
current_app=self.admin_site.name))
|
|
||||||
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
|
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
current_app=self.admin_site.name))
|
current_app=self.admin_site.name)
|
||||||
|
preserved_filters = self.get_preserved_filters(request)
|
||||||
|
post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
|
||||||
|
else:
|
||||||
|
post_url = reverse('admin:index',
|
||||||
|
current_app=self.admin_site.name)
|
||||||
|
return HttpResponseRedirect(post_url)
|
||||||
|
|
||||||
object_name = force_text(opts.verbose_name)
|
object_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
@ -1429,6 +1478,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"protected": protected,
|
"protected": protected,
|
||||||
"opts": opts,
|
"opts": opts,
|
||||||
"app_label": app_label,
|
"app_label": app_label,
|
||||||
|
'preserved_filters': self.get_preserved_filters(request),
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
|
|
||||||
|
@ -1463,6 +1513,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'app_label': app_label,
|
'app_label': app_label,
|
||||||
'opts': opts,
|
'opts': opts,
|
||||||
|
'preserved_filters': self.get_preserved_filters(request),
|
||||||
}
|
}
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return TemplateResponse(request, self.object_history_template or [
|
return TemplateResponse(request, self.object_history_template or [
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n admin_static admin_modify %}
|
{% load i18n admin_urls admin_static admin_modify %}
|
||||||
{% load admin_urls %}
|
|
||||||
|
|
||||||
{% block extrahead %}{{ block.super }}
|
{% block extrahead %}{{ block.super }}
|
||||||
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
|
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
|
||||||
|
@ -29,7 +28,10 @@
|
||||||
{% if change %}{% if not is_popup %}
|
{% if change %}{% if not is_popup %}
|
||||||
<ul class="object-tools">
|
<ul class="object-tools">
|
||||||
{% block object-tools-items %}
|
{% block object-tools-items %}
|
||||||
<li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
|
<li>
|
||||||
|
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
|
||||||
|
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
|
||||||
|
</li>
|
||||||
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n admin_static admin_list %}
|
{% load i18n admin_urls admin_static admin_list %}
|
||||||
{% load admin_urls %}
|
|
||||||
|
|
||||||
{% block extrastyle %}
|
{% block extrastyle %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
@ -54,7 +53,8 @@
|
||||||
<ul class="object-tools">
|
<ul class="object-tools">
|
||||||
{% block object-tools-items %}
|
{% block object-tools-items %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% endif %}" class="addlink">
|
{% url cl.opts|admin_urlname:'add' as add_url %}
|
||||||
|
<a href="{% add_preserved_filters add_url is_popup %}" class="addlink">
|
||||||
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
|
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n %}
|
{% load i18n admin_urls %}
|
||||||
{% load admin_urls %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n l10n %}
|
{% load i18n l10n admin_urls %}
|
||||||
{% load admin_urls %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n %}
|
{% load i18n admin_urls %}
|
||||||
{% load admin_urls %}
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
{% load i18n admin_urls %}
|
{% load i18n admin_urls %}
|
||||||
<div class="submit-row">
|
<div class="submit-row">
|
||||||
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
|
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
|
||||||
{% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
|
{% if show_delete_link %}
|
||||||
|
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
|
||||||
|
<p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
|
||||||
|
{% endif %}
|
||||||
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{%endif%}
|
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{%endif%}
|
||||||
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
|
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
|
||||||
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
|
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
from django.contrib.admin.util import (lookup_field, display_for_field,
|
from django.contrib.admin.util import (lookup_field, display_for_field,
|
||||||
display_for_value, label_for_field)
|
display_for_value, label_for_field)
|
||||||
from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
|
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]
|
table_tag = {True:'th', False:'td'}[first]
|
||||||
first = False
|
first = False
|
||||||
url = cl.url_for_result(result)
|
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.
|
# Convert the pk to something that can be used in Javascript.
|
||||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||||
if cl.to_field:
|
if cl.to_field:
|
||||||
|
|
|
@ -37,7 +37,8 @@ def submit_row(context):
|
||||||
not is_popup and (not save_as or context['add']),
|
not is_popup and (not save_as or context['add']),
|
||||||
'show_save_and_continue': not is_popup and context['has_change_permission'],
|
'show_save_and_continue': not is_popup and context['has_change_permission'],
|
||||||
'is_popup': is_popup,
|
'is_popup': is_popup,
|
||||||
'show_save': True
|
'show_save': True,
|
||||||
|
'preserved_filters': context.get('preserved_filters'),
|
||||||
}
|
}
|
||||||
if context.get('original') is not None:
|
if context.get('original') is not None:
|
||||||
ctx['original'] = context['original']
|
ctx['original'] = context['original']
|
||||||
|
|
|
@ -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 import template
|
||||||
from django.contrib.admin.util import quote
|
from django.contrib.admin.util import quote
|
||||||
|
from django.core.urlresolvers import resolve, Resolver404
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def admin_urlname(value, arg):
|
def admin_urlname(value, arg):
|
||||||
return 'admin:%s_%s_%s' % (value.app_label, value.model_name, arg)
|
return 'admin:%s_%s_%s' % (value.app_label, value.model_name, arg)
|
||||||
|
@ -11,3 +20,36 @@ def admin_urlname(value, arg):
|
||||||
@register.filter
|
@register.filter
|
||||||
def admin_urlquote(value):
|
def admin_urlquote(value):
|
||||||
return quote(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)
|
||||||
|
|
|
@ -59,6 +59,7 @@ class ChangeList(six.with_metaclass(RenameChangeListMethods)):
|
||||||
self.list_per_page = list_per_page
|
self.list_per_page = list_per_page
|
||||||
self.list_max_show_all = list_max_show_all
|
self.list_max_show_all = list_max_show_all
|
||||||
self.model_admin = model_admin
|
self.model_admin = model_admin
|
||||||
|
self.preserved_filters = model_admin.get_preserved_filters(request)
|
||||||
|
|
||||||
# Get search parameters from the query string.
|
# Get search parameters from the query string.
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -870,6 +870,14 @@ subclass::
|
||||||
``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``,
|
``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``,
|
||||||
nor ``ManyToManyField`` fields.
|
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
|
.. attribute:: ModelAdmin.radio_fields
|
||||||
|
|
||||||
By default, Django's admin uses a select-box interface (<select>) for
|
By default, Django's admin uses a select-box interface (<select>) for
|
||||||
|
|
|
@ -325,6 +325,11 @@ Minor features
|
||||||
:ref:`see the updated recommendation <raising-validation-error>` for raising
|
:ref:`see the updated recommendation <raising-validation-error>` for raising
|
||||||
a ``ValidationError``.
|
a ``ValidationError``.
|
||||||
|
|
||||||
|
* :class:`~django.contrib.admin.ModelAdmin` now preserves filters on the list view
|
||||||
|
after creating, editing or deleting an object. It's possible to restore the previous
|
||||||
|
behavior of clearing filters by setting the
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.6
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
@ -634,6 +639,16 @@ will render something like:
|
||||||
If you want to keep the current behavior of rendering ``label_tag`` without
|
If you want to keep the current behavior of rendering ``label_tag`` without
|
||||||
the ``label_suffix``, instantiate the form ``label_suffix=''``.
|
the ``label_suffix``, instantiate the form ``label_suffix=''``.
|
||||||
|
|
||||||
|
Admin views ``_changelist_filters`` GET parameter
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To achieve preserving and restoring list view filters, admin views now
|
||||||
|
pass around the `_changelist_filters` GET parameter. It's important that you
|
||||||
|
account for that change if you have custom admin templates or if your tests
|
||||||
|
rely on the previous URLs. If you want to revert to the original behavior you
|
||||||
|
can set the
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -4157,3 +4157,156 @@ class AdminUserMessageTest(TestCase):
|
||||||
self.assertContains(response,
|
self.assertContains(response,
|
||||||
'<li class="extra_tag info">Test tags</li>',
|
'<li class="extra_tag info">Test tags</li>',
|
||||||
html=True)
|
html=True)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
|
class AdminKeepChangeListFiltersTests(TestCase):
|
||||||
|
urls = "admin_views.urls"
|
||||||
|
fixtures = ['admin-views-users.xml']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client.login(username='super', password='secret')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def get_changelist_filters_querystring(self):
|
||||||
|
return urlencode({
|
||||||
|
'is_superuser__exact': 0,
|
||||||
|
'is_staff__exact': 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_preserved_filters_querystring(self):
|
||||||
|
return urlencode({
|
||||||
|
'_changelist_filters': self.get_changelist_filters_querystring()
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_sample_user_id(self):
|
||||||
|
return 104
|
||||||
|
|
||||||
|
def get_changelist_url(self):
|
||||||
|
return '%s?%s' % (
|
||||||
|
reverse('admin:auth_user_changelist'),
|
||||||
|
self.get_changelist_filters_querystring(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_add_url(self):
|
||||||
|
return '%s?%s' % (
|
||||||
|
reverse('admin:auth_user_add'),
|
||||||
|
self.get_preserved_filters_querystring(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_change_url(self, user_id=None):
|
||||||
|
if user_id is None:
|
||||||
|
user_id = self.get_sample_user_id()
|
||||||
|
return "%s?%s" % (
|
||||||
|
reverse('admin:auth_user_change', args=(user_id,)),
|
||||||
|
self.get_preserved_filters_querystring(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_history_url(self, user_id=None):
|
||||||
|
if user_id is None:
|
||||||
|
user_id = self.get_sample_user_id()
|
||||||
|
return "%s?%s" % (
|
||||||
|
reverse('admin:auth_user_history', args=(user_id,)),
|
||||||
|
self.get_preserved_filters_querystring(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_delete_url(self, user_id=None):
|
||||||
|
if user_id is None:
|
||||||
|
user_id = self.get_sample_user_id()
|
||||||
|
return "%s?%s" % (
|
||||||
|
reverse('admin:auth_user_delete', args=(user_id,)),
|
||||||
|
self.get_preserved_filters_querystring(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_changelist_view(self):
|
||||||
|
response = self.client.get(self.get_changelist_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Check the `change_view` link has the correct querystring.
|
||||||
|
detail_link = """<a href="%s">joepublic</a>""" % self.get_change_url()
|
||||||
|
self.assertContains(response, detail_link, count=1)
|
||||||
|
|
||||||
|
def test_change_view(self):
|
||||||
|
# Get the `change_view`.
|
||||||
|
response = self.client.get(self.get_change_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Check the form action.
|
||||||
|
form_action = """<form enctype="multipart/form-data" action="?%s" method="post" id="user_form">""" % self.get_preserved_filters_querystring()
|
||||||
|
self.assertContains(response, form_action, count=1)
|
||||||
|
|
||||||
|
# Check the history link.
|
||||||
|
history_link = """<a href="%s" class="historylink">History</a>""" % self.get_history_url()
|
||||||
|
self.assertContains(response, history_link, count=1)
|
||||||
|
|
||||||
|
# Check the delete link.
|
||||||
|
delete_link = """<a href="%s" class="deletelink">Delete</a>""" % (self.get_delete_url())
|
||||||
|
self.assertContains(response, delete_link, count=1)
|
||||||
|
|
||||||
|
# Test redirect on "Save".
|
||||||
|
post_data = {
|
||||||
|
'username': 'joepublic',
|
||||||
|
'last_login_0': '2007-05-30',
|
||||||
|
'last_login_1': '13:20:10',
|
||||||
|
'date_joined_0': '2007-05-30',
|
||||||
|
'date_joined_1': '13:20:10',
|
||||||
|
}
|
||||||
|
|
||||||
|
post_data['_save'] = 1
|
||||||
|
response = self.client.post(self.get_change_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_changelist_url())
|
||||||
|
post_data.pop('_save')
|
||||||
|
|
||||||
|
# Test redirect on "Save and continue".
|
||||||
|
post_data['_continue'] = 1
|
||||||
|
response = self.client.post(self.get_change_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_change_url())
|
||||||
|
post_data.pop('_continue')
|
||||||
|
|
||||||
|
# Test redirect on "Save and add new".
|
||||||
|
post_data['_addanother'] = 1
|
||||||
|
response = self.client.post(self.get_change_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_add_url())
|
||||||
|
post_data.pop('_addanother')
|
||||||
|
|
||||||
|
def test_add_view(self):
|
||||||
|
# Get the `add_view`.
|
||||||
|
response = self.client.get(self.get_add_url())
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Check the form action.
|
||||||
|
form_action = """<form enctype="multipart/form-data" action="?%s" method="post" id="user_form">""" % self.get_preserved_filters_querystring()
|
||||||
|
self.assertContains(response, form_action, count=1)
|
||||||
|
|
||||||
|
# Test redirect on "Save".
|
||||||
|
post_data = {
|
||||||
|
'username': 'dummy',
|
||||||
|
'password1': 'test',
|
||||||
|
'password2': 'test',
|
||||||
|
}
|
||||||
|
|
||||||
|
post_data['_save'] = 1
|
||||||
|
response = self.client.post(self.get_add_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_change_url(self.get_sample_user_id() + 1))
|
||||||
|
post_data.pop('_save')
|
||||||
|
|
||||||
|
# Test redirect on "Save and continue".
|
||||||
|
post_data['username'] = 'dummy2'
|
||||||
|
post_data['_continue'] = 1
|
||||||
|
response = self.client.post(self.get_add_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_change_url(self.get_sample_user_id() + 2))
|
||||||
|
post_data.pop('_continue')
|
||||||
|
|
||||||
|
# Test redirect on "Save and add new".
|
||||||
|
post_data['username'] = 'dummy3'
|
||||||
|
post_data['_addanother'] = 1
|
||||||
|
response = self.client.post(self.get_add_url(), data=post_data)
|
||||||
|
self.assertRedirects(response, self.get_add_url())
|
||||||
|
post_data.pop('_addanother')
|
||||||
|
|
||||||
|
def test_delete_view(self):
|
||||||
|
# Test redirect on "Delete".
|
||||||
|
response = self.client.post(self.get_delete_url(), {'post': 'yes'})
|
||||||
|
self.assertRedirects(response, self.get_changelist_url())
|
||||||
|
|
Loading…
Reference in New Issue