Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed #6903: Implemented sticky admin filters #1264

Closed
wants to merge 1 commit into from

2 participants

@oinopion

Patch is mostly by julien, I only made small corrections.

@oinopion oinopion Fixed #6903: Implemented sticky admin filters
Patch is mostly by julien, I only made small corrections.
4b6829c
@timgraham
Owner

Fixed in c86a9b6 and 7d0c3b9.

@timgraham timgraham closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 12, 2013
  1. @oinopion

    Fixed #6903: Implemented sticky admin filters

    oinopion authored
    Patch is mostly by julien, I only made small corrections.
This page is out of date. Refresh to see the latest.
View
45 django/contrib/admin/options.py
@@ -14,6 +14,7 @@
from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
+from django.utils.http import is_safe_url
from django.views.decorators.csrf import csrf_protect
from django.core.exceptions import PermissionDenied, ValidationError, FieldError
from django.core.paginator import Paginator
@@ -33,6 +34,7 @@
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
+from django.utils.http import urllib_parse
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
@@ -963,10 +965,17 @@ def response_post_save_change(self, request, obj):
when editing an existing object.
"""
opts = self.model._meta
+ changelist_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.model_name),
+ current_app=self.admin_site.name)
+
+ changelist_filters = request.POST.get('_changelist_filters')
+ if changelist_filters:
+ return HttpResponseRedirect(
+ '%s?%s' % (changelist_url, changelist_filters))
+
if self.has_change_permission(request, None):
- post_url = reverse('admin:%s_%s_changelist' %
- (opts.app_label, opts.model_name),
- current_app=self.admin_site.name)
+ post_url = changelist_url
else:
post_url = reverse('admin:index',
current_app=self.admin_site.name)
@@ -1149,7 +1158,9 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
ModelForm = self.get_form(request, obj)
formsets = []
inline_instances = self.get_inline_instances(request, obj)
+ changelist_filters = None
if request.method == 'POST':
+ changelist_filters = request.POST.get('_changelist_filters')
form = ModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
form_validated = True
@@ -1188,6 +1199,15 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
queryset=inline.get_queryset(request))
formsets.append(formset)
+ referer = request.META.get('HTTP_REFERER')
+ if referer:
+ referer = urllib_parse.urlparse(referer)
+ changelist_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.model_name))
+ if is_safe_url(url=referer.geturl(), host=request.get_host()) \
+ and referer.path.startswith(changelist_url):
+ changelist_filters = referer.query
+
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
self.get_prepopulated_fields(request, obj),
self.get_readonly_fields(request, obj),
@@ -1214,6 +1234,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
'app_label': opts.app_label,
+ 'changelist_filters': changelist_filters,
}
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
@@ -1406,12 +1427,23 @@ def delete_view(self, request, object_id, extra_context=None):
'obj': force_text(obj_display)},
messages.SUCCESS)
+ changelist_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.model_name),
+ current_app=self.admin_site.name)
+
+ changelist_filters = request.POST.get('_changelist_filters')
+ if changelist_filters:
+ return HttpResponseRedirect(
+ '%s?%s' % (changelist_url, changelist_filters))
+
if not self.has_change_permission(request, None):
return HttpResponseRedirect(reverse('admin:index',
current_app=self.admin_site.name))
- return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
- (opts.app_label, opts.model_name),
- current_app=self.admin_site.name))
+ return HttpResponseRedirect(changelist_url)
+ else:
+ changelist_filters = request.GET.get('_changelist_filters')
+ if changelist_filters:
+ changelist_filters = unquote(changelist_filters)
object_name = force_text(opts.verbose_name)
@@ -1429,6 +1461,7 @@ def delete_view(self, request, object_id, extra_context=None):
"protected": protected,
"opts": opts,
"app_label": app_label,
+ "changelist_filters": changelist_filters,
}
context.update(extra_context or {})
View
1  django/contrib/admin/templates/admin/change_form.html
@@ -38,6 +38,7 @@
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
{% if save_on_top %}{% block submit_buttons_top %}{% submit_row %}{% endblock %}{% endif %}
{% if errors %}
<p class="errornote">
View
1  django/contrib/admin/templates/admin/delete_confirmation.html
@@ -36,6 +36,7 @@
<form action="" method="post">{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes" />
+ {% if changelist_filters %}<input type="hidden" name="_changelist_filters" value="{{ changelist_filters }}" />{% endif %}
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
</div>
</form>
View
9 django/contrib/admin/templates/admin/submit_line.html
@@ -1,7 +1,14 @@
{% load i18n admin_urls %}
<div class="submit-row">
{% 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="{{ delete_url }}{% if changelist_filters %}?_changelist_filters={{ changelist_filters|admin_urlquote }}{% endif %}" 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_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 %}
View
4 django/contrib/admin/templatetags/admin_modify.py
@@ -37,10 +37,12 @@ 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,
}
if context.get('original') is not None:
ctx['original'] = context['original']
+ if context.get('changelist_filters'):
+ ctx['changelist_filters'] = context['changelist_filters']
return ctx
@register.filter
View
94 tests/admin_views/tests.py
@@ -4157,3 +4157,97 @@ def test_message_extra_tags(self):
self.assertContains(response,
'<li class="extra_tag info">Test tags</li>',
html=True)
+
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class AdminChangeListRedirectionTests(TestCase):
+ urls = "admin_views.urls"
+ fixtures = ['admin-views-users']
+
+ def setUp(self):
+ Person.objects.create(name="Chris", gender=1, alive=True, age=25)
+ Person.objects.create(name="Jack", gender=2, alive=False, age=47)
+ Person.objects.create(name="Bob", gender=1, alive=True, age=36)
+
+ self.client.login(username='super', password='secret')
+
+ def get_data(self):
+ return {
+ 'name': 'Joe', 'gender': '1', 'alive': '1', 'age': '58'
+ }
+
+ def tearDown(self):
+ self.client.logout()
+
+ def get_filters_querystring(self):
+ return urlencode({
+ 'alive': 1,
+ })
+
+ def get_filtered_changelist_url(self):
+ return "%s%s?%s" % ('http://testserver', reverse('admin:admin_views_person_changelist'), self.get_filters_querystring())
+
+ def test_change_redirect(self):
+ """
+ Ensure that we're correctly redirected to the filtered changelist
+ after an existing object is saved.
+ Refs #6903.
+ """
+ person = Person.objects.get(name='Chris')
+ change_url = reverse('admin:admin_views_person_change', args=(person.pk,))
+
+ # Simulate clicking an object from a filtered changelist
+ response = self.client.get(change_url, HTTP_REFERER=self.get_filtered_changelist_url())
+ self.assertContains(response, '_changelist_filters')
+ self.assertContains(response, self.get_filters_querystring())
+
+ # Save the object
+ data = self.get_data()
+ data['_save'] = 1
+ data['_changelist_filters'] = self.get_filters_querystring()
+ response = self.client.post(change_url, data)
+
+ # Check that we return to the filtered changelist
+ self.assertRedirects(response, self.get_filtered_changelist_url())
+
+ def test_add_redirect(self):
+ """
+ Ensure that we're correctly redirected to the non-filtered changelist
+ after a new object is added.
+ Refs #6903.
+ """
+ add_url = reverse('admin:admin_views_person_add')
+
+ response = self.client.get(add_url, HTTP_REFERER=self.get_filtered_changelist_url())
+ self.assertNotContains(response, '_changelist_filters')
+ self.assertNotContains(response, self.get_filters_querystring())
+
+ # Add a new object
+ data = self.get_data()
+ data['_save'] = 1
+ data['_changelist_filters'] = self.get_filters_querystring()
+ response = self.client.post(add_url, data)
+
+ # Check that we return to the non-filtered changelist
+ self.assertRedirects(response, reverse('admin:admin_views_person_changelist'))
+
+ def test_delete_redirect(self):
+ """
+ Ensure that we're correctly redirected to the filtered changelist
+ after an existing object is deleted.
+ Refs #6903.
+ """
+ person = Person.objects.get(name='Chris')
+ delete_url = reverse('admin:admin_views_person_delete', args=(person.pk,))
+
+ response = self.client.get(delete_url + '?_changelist_filters=%s' % self.get_filters_querystring())
+ self.assertContains(response, '_changelist_filters')
+ self.assertContains(response, self.get_filters_querystring())
+
+ # Delete the object
+ data = {'post': 'yes', '_changelist_filters': self.get_filters_querystring()}
+ response = self.client.post(delete_url, data)
+
+ # Check that we return to the filtered changelist
+ self.assertRedirects(response, self.get_filtered_changelist_url())
+
Something went wrong with that request. Please try again.