Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #6903 - Preserve admin changelist filters after saving or delet…

…ing an object
  • Loading branch information...
commit c86a9b63984f6692d478f6f70e3c78de4ec41814 1 parent 2c4fe76
Loic Bistuer authored June 19, 2013
107  django/contrib/admin/options.py
@@ -13,6 +13,7 @@
13 13
     model_format_dict, NestedObjects, lookup_needs_distinct)
14 14
 from django.contrib.admin import validation
15 15
 from django.contrib.admin.templatetags.admin_static import static
  16
+from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
16 17
 from django.contrib import messages
17 18
 from django.views.decorators.csrf import csrf_protect
18 19
 from django.core.exceptions import PermissionDenied, ValidationError, FieldError
@@ -33,6 +34,7 @@
33 34
 from django.utils.safestring import mark_safe
34 35
 from django.utils import six
35 36
 from django.utils.deprecation import RenameMethodsBase
  37
+from django.utils.http import urlencode
36 38
 from django.utils.text import capfirst, get_text_list
37 39
 from django.utils.translation import ugettext as _
38 40
 from django.utils.translation import ungettext
@@ -393,6 +395,7 @@ class ModelAdmin(BaseModelAdmin):
393 395
     save_as = False
394 396
     save_on_top = False
395 397
     paginator = Paginator
  398
+    preserve_filters = True
396 399
     inlines = []
397 400
 
398 401
     # Custom templates (designed to be over-ridden in subclasses)
@@ -755,6 +758,27 @@ def get_list_filter(self, request):
755 758
         """
756 759
         return self.list_filter
757 760
 
  761
+    def get_preserved_filters(self, request):
  762
+        """
  763
+        Returns the preserved filters querystring.
  764
+        """
  765
+
  766
+        # FIXME: We can remove that getattr as soon as #20619 is fixed.
  767
+        match = getattr(request, 'resolver_match', None)
  768
+
  769
+        if self.preserve_filters and match:
  770
+            opts = self.model._meta
  771
+            current_url = '%s:%s' % (match.namespace, match.url_name)
  772
+            changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
  773
+            if current_url == changelist_url:
  774
+                preserved_filters = request.GET.urlencode()
  775
+            else:
  776
+                preserved_filters = request.GET.get('_changelist_filters')
  777
+
  778
+            if preserved_filters:
  779
+                return urlencode({'_changelist_filters': preserved_filters})
  780
+        return ''
  781
+
758 782
     def construct_change_message(self, request, form, formsets):
759 783
         """
760 784
         Construct a change message from a changed object.
@@ -846,6 +870,8 @@ def save_related(self, request, form, formsets, change):
846 870
     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
847 871
         opts = self.model._meta
848 872
         app_label = opts.app_label
  873
+        preserved_filters = self.get_preserved_filters(request)
  874
+        form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url)
849 875
         context.update({
850 876
             'add': add,
851 877
             'change': change,
@@ -877,11 +903,19 @@ def response_add(self, request, obj, post_url_continue=None):
877 903
         """
878 904
         opts = obj._meta
879 905
         pk_value = obj._get_pk_val()
  906
+        preserved_filters = self.get_preserved_filters(request)
880 907
 
881 908
         msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
882 909
         # Here, we distinguish between different save types by checking for
883 910
         # the presence of keys in request.POST.
884  
-        if "_continue" in request.POST:
  911
+        if "_popup" in request.POST:
  912
+            return HttpResponse(
  913
+                '<!DOCTYPE html><html><head><title></title></head><body>'
  914
+                '<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
  915
+                # escape() calls force_text.
  916
+                (escape(pk_value), escapejs(obj)))
  917
+
  918
+        elif "_continue" in request.POST:
885 919
             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
886 920
             self.message_user(request, msg, messages.SUCCESS)
887 921
             if post_url_continue is None:
@@ -889,20 +923,16 @@ def response_add(self, request, obj, post_url_continue=None):
889 923
                                             (opts.app_label, opts.model_name),
890 924
                                             args=(pk_value,),
891 925
                                             current_app=self.admin_site.name)
892  
-            if "_popup" in request.POST:
893  
-                post_url_continue += "?_popup=1"
  926
+            post_url_continue = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url_continue)
894 927
             return HttpResponseRedirect(post_url_continue)
895 928
 
896  
-        if "_popup" in request.POST:
897  
-            return HttpResponse(
898  
-                '<!DOCTYPE html><html><head><title></title></head><body>'
899  
-                '<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
900  
-                # escape() calls force_text.
901  
-                (escape(pk_value), escapejs(obj)))
902 929
         elif "_addanother" in request.POST:
903 930
             msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
904 931
             self.message_user(request, msg, messages.SUCCESS)
905  
-            return HttpResponseRedirect(request.path)
  932
+            redirect_url = request.path
  933
+            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
  934
+            return HttpResponseRedirect(redirect_url)
  935
+
906 936
         else:
907 937
             msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
908 938
             self.message_user(request, msg, messages.SUCCESS)
@@ -913,30 +943,36 @@ def response_change(self, request, obj):
913 943
         Determines the HttpResponse for the change_view stage.
914 944
         """
915 945
         opts = self.model._meta
916  
-
917 946
         pk_value = obj._get_pk_val()
  947
+        preserved_filters = self.get_preserved_filters(request)
918 948
 
919 949
         msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
920 950
         if "_continue" in request.POST:
921 951
             msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
922 952
             self.message_user(request, msg, messages.SUCCESS)
923  
-            if "_popup" in request.REQUEST:
924  
-                return HttpResponseRedirect(request.path + "?_popup=1")
925  
-            else:
926  
-                return HttpResponseRedirect(request.path)
  953
+            redirect_url = request.path
  954
+            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
  955
+            return HttpResponseRedirect(redirect_url)
  956
+
927 957
         elif "_saveasnew" in request.POST:
928 958
             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
929 959
             self.message_user(request, msg, messages.SUCCESS)
930  
-            return HttpResponseRedirect(reverse('admin:%s_%s_change' %
931  
-                                        (opts.app_label, opts.model_name),
932  
-                                        args=(pk_value,),
933  
-                                        current_app=self.admin_site.name))
  960
+            redirect_url = reverse('admin:%s_%s_change' %
  961
+                                   (opts.app_label, opts.model_name),
  962
+                                   args=(pk_value,),
  963
+                                   current_app=self.admin_site.name)
  964
+            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
  965
+            return HttpResponseRedirect(redirect_url)
  966
+
934 967
         elif "_addanother" in request.POST:
935 968
             msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
936 969
             self.message_user(request, msg, messages.SUCCESS)
937  
-            return HttpResponseRedirect(reverse('admin:%s_%s_add' %
938  
-                                        (opts.app_label, opts.model_name),
939  
-                                        current_app=self.admin_site.name))
  970
+            redirect_url = reverse('admin:%s_%s_add' %
  971
+                                   (opts.app_label, opts.model_name),
  972
+                                   current_app=self.admin_site.name)
  973
+            redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
  974
+            return HttpResponseRedirect(redirect_url)
  975
+
940 976
         else:
941 977
             msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
942 978
             self.message_user(request, msg, messages.SUCCESS)
@@ -952,6 +988,8 @@ def response_post_save_add(self, request, obj):
952 988
             post_url = reverse('admin:%s_%s_changelist' %
953 989
                                (opts.app_label, opts.model_name),
954 990
                                current_app=self.admin_site.name)
  991
+            preserved_filters = self.get_preserved_filters(request)
  992
+            post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
955 993
         else:
956 994
             post_url = reverse('admin:index',
957 995
                                current_app=self.admin_site.name)
@@ -963,10 +1001,13 @@ def response_post_save_change(self, request, obj):
963 1001
         when editing an existing object.
964 1002
         """
965 1003
         opts = self.model._meta
  1004
+
966 1005
         if self.has_change_permission(request, None):
967 1006
             post_url = reverse('admin:%s_%s_changelist' %
968 1007
                                (opts.app_label, opts.model_name),
969 1008
                                current_app=self.admin_site.name)
  1009
+            preserved_filters = self.get_preserved_filters(request)
  1010
+            post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
970 1011
         else:
971 1012
             post_url = reverse('admin:index',
972 1013
                                current_app=self.admin_site.name)
@@ -1122,6 +1163,7 @@ def add_view(self, request, form_url='', extra_context=None):
1122 1163
             'inline_admin_formsets': inline_admin_formsets,
1123 1164
             'errors': helpers.AdminErrorList(form, formsets),
1124 1165
             'app_label': opts.app_label,
  1166
+            'preserved_filters': self.get_preserved_filters(request),
1125 1167
         }
1126 1168
         context.update(extra_context or {})
1127 1169
         return self.render_change_form(request, context, form_url=form_url, add=True)
@@ -1214,6 +1256,7 @@ def change_view(self, request, object_id, form_url='', extra_context=None):
1214 1256
             'inline_admin_formsets': inline_admin_formsets,
1215 1257
             'errors': helpers.AdminErrorList(form, formsets),
1216 1258
             'app_label': opts.app_label,
  1259
+            'preserved_filters': self.get_preserved_filters(request),
1217 1260
         }
1218 1261
         context.update(extra_context or {})
1219 1262
         return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
@@ -1357,11 +1400,13 @@ def changelist_view(self, request, extra_context=None):
1357 1400
             'cl': cl,
1358 1401
             'media': media,
1359 1402
             'has_add_permission': self.has_add_permission(request),
  1403
+            'opts': cl.opts,
1360 1404
             'app_label': app_label,
1361 1405
             'action_form': action_form,
1362 1406
             'actions_on_top': self.actions_on_top,
1363 1407
             'actions_on_bottom': self.actions_on_bottom,
1364 1408
             'actions_selection_counter': self.actions_selection_counter,
  1409
+            'preserved_filters': self.get_preserved_filters(request),
1365 1410
         }
1366 1411
         context.update(extra_context or {})
1367 1412
 
@@ -1406,12 +1451,16 @@ def delete_view(self, request, object_id, extra_context=None):
1406 1451
                                        'obj': force_text(obj_display)},
1407 1452
                               messages.SUCCESS)
1408 1453
 
1409  
-            if not self.has_change_permission(request, None):
1410  
-                return HttpResponseRedirect(reverse('admin:index',
1411  
-                                                    current_app=self.admin_site.name))
1412  
-            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
1413  
-                                        (opts.app_label, opts.model_name),
1414  
-                                        current_app=self.admin_site.name))
  1454
+            if self.has_change_permission(request, None):
  1455
+                post_url = reverse('admin:%s_%s_changelist' %
  1456
+                                   (opts.app_label, opts.model_name),
  1457
+                                   current_app=self.admin_site.name)
  1458
+                preserved_filters = self.get_preserved_filters(request)
  1459
+                post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
  1460
+            else:
  1461
+                post_url = reverse('admin:index',
  1462
+                                   current_app=self.admin_site.name)
  1463
+            return HttpResponseRedirect(post_url)
1415 1464
 
1416 1465
         object_name = force_text(opts.verbose_name)
1417 1466
 
@@ -1429,6 +1478,7 @@ def delete_view(self, request, object_id, extra_context=None):
1429 1478
             "protected": protected,
1430 1479
             "opts": opts,
1431 1480
             "app_label": app_label,
  1481
+            'preserved_filters': self.get_preserved_filters(request),
1432 1482
         }
1433 1483
         context.update(extra_context or {})
1434 1484
 
@@ -1463,6 +1513,7 @@ def history_view(self, request, object_id, extra_context=None):
1463 1513
             'object': obj,
1464 1514
             'app_label': app_label,
1465 1515
             'opts': opts,
  1516
+            'preserved_filters': self.get_preserved_filters(request),
1466 1517
         }
1467 1518
         context.update(extra_context or {})
1468 1519
         return TemplateResponse(request, self.object_history_template or [
8  django/contrib/admin/templates/admin/change_form.html
... ...
@@ -1,6 +1,5 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-{% load i18n admin_static admin_modify %}
3  
-{% load admin_urls %}
  2
+{% load i18n admin_urls admin_static admin_modify %}
4 3
 
5 4
 {% block extrahead %}{{ block.super }}
6 5
 <script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
@@ -29,7 +28,10 @@
29 28
 {% if change %}{% if not is_popup %}
30 29
   <ul class="object-tools">
31 30
     {% block object-tools-items %}
32  
-    <li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
  31
+    <li>
  32
+        {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
  33
+        <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
  34
+    </li>
33 35
     {% 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%}
34 36
     {% endblock %}
35 37
   </ul>
6  django/contrib/admin/templates/admin/change_list.html
... ...
@@ -1,6 +1,5 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-{% load i18n admin_static admin_list %}
3  
-{% load admin_urls %}
  2
+{% load i18n admin_urls admin_static admin_list %}
4 3
 
5 4
 {% block extrastyle %}
6 5
   {{ block.super }}
@@ -54,7 +53,8 @@
54 53
         <ul class="object-tools">
55 54
           {% block object-tools-items %}
56 55
             <li>
57  
-              <a href="{% url cl.opts|admin_urlname:'add' %}{% if is_popup %}?_popup=1{% endif %}" class="addlink">
  56
+              {% url cl.opts|admin_urlname:'add' as add_url %}
  57
+              <a href="{% add_preserved_filters add_url is_popup %}" class="addlink">
58 58
                 {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
59 59
               </a>
60 60
             </li>
3  django/contrib/admin/templates/admin/delete_confirmation.html
... ...
@@ -1,6 +1,5 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-{% load i18n %}
3  
-{% load admin_urls %}
  2
+{% load i18n admin_urls %}
4 3
 
5 4
 {% block breadcrumbs %}
6 5
 <div class="breadcrumbs">
3  django/contrib/admin/templates/admin/delete_selected_confirmation.html
... ...
@@ -1,6 +1,5 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-{% load i18n l10n %}
3  
-{% load admin_urls %}
  2
+{% load i18n l10n admin_urls %}
4 3
 
5 4
 {% block breadcrumbs %}
6 5
 <div class="breadcrumbs">
3  django/contrib/admin/templates/admin/object_history.html
... ...
@@ -1,6 +1,5 @@
1 1
 {% extends "admin/base_site.html" %}
2  
-{% load i18n %}
3  
-{% load admin_urls %}
  2
+{% load i18n admin_urls %}
4 3
 
5 4
 {% block breadcrumbs %}
6 5
 <div class="breadcrumbs">
5  django/contrib/admin/templates/admin/submit_line.html
... ...
@@ -1,7 +1,10 @@
1 1
 {% load i18n admin_urls %}
2 2
 <div class="submit-row">
3 3
 {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
4  
-{% 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 %}
  4
+{% if show_delete_link %}
  5
+    {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
  6
+    <p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
  7
+{% endif %}
5 8
 {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{%endif%}
6 9
 {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
7 10
 {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
2  django/contrib/admin/templatetags/admin_list.py
@@ -2,6 +2,7 @@
2 2
 
3 3
 import datetime
4 4
 
  5
+from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
5 6
 from django.contrib.admin.util import (lookup_field, display_for_field,
6 7
     display_for_value, label_for_field)
7 8
 from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
@@ -217,6 +218,7 @@ def items_for_result(cl, result, form):
217 218
             table_tag = {True:'th', False:'td'}[first]
218 219
             first = False
219 220
             url = cl.url_for_result(result)
  221
+            url = add_preserved_filters({'preserved_filters': cl.preserved_filters, 'opts': cl.opts}, url)
220 222
             # Convert the pk to something that can be used in Javascript.
221 223
             # Problem cases are long ints (23L) and non-ASCII strings.
222 224
             if cl.to_field:
3  django/contrib/admin/templatetags/admin_modify.py
@@ -37,7 +37,8 @@ def submit_row(context):
37 37
                             not is_popup and (not save_as or context['add']),
38 38
         'show_save_and_continue': not is_popup and context['has_change_permission'],
39 39
         'is_popup': is_popup,
40  
-        'show_save': True
  40
+        'show_save': True,
  41
+        'preserved_filters': context.get('preserved_filters'),
41 42
     }
42 43
     if context.get('original') is not None:
43 44
         ctx['original'] = context['original']
42  django/contrib/admin/templatetags/admin_urls.py
... ...
@@ -1,8 +1,17 @@
  1
+from django.utils.http import urlencode
  2
+
  3
+try:
  4
+    from urllib.parse import parse_qsl, urlparse, urlunparse
  5
+except ImportError:
  6
+    from urlparse import parse_qsl, urlparse, urlunparse
  7
+
1 8
 from django import template
2 9
 from django.contrib.admin.util import quote
  10
+from django.core.urlresolvers import resolve, Resolver404
3 11
 
4 12
 register = template.Library()
5 13
 
  14
+
6 15
 @register.filter
7 16
 def admin_urlname(value, arg):
8 17
     return 'admin:%s_%s_%s' % (value.app_label, value.model_name, arg)
@@ -11,3 +20,36 @@ def admin_urlname(value, arg):
11 20
 @register.filter
12 21
 def admin_urlquote(value):
13 22
     return quote(value)
  23
+
  24
+
  25
+@register.simple_tag(takes_context=True)
  26
+def add_preserved_filters(context, url, popup=False):
  27
+    opts = context.get('opts')
  28
+    preserved_filters = context.get('preserved_filters')
  29
+
  30
+    parsed_url = list(urlparse(url))
  31
+    parsed_qs = dict(parse_qsl(parsed_url[4]))
  32
+    merged_qs = dict()
  33
+
  34
+    if opts and preserved_filters:
  35
+        preserved_filters = dict(parse_qsl(preserved_filters))
  36
+
  37
+        try:
  38
+            match = resolve(url)
  39
+        except Resolver404:
  40
+            pass
  41
+        else:
  42
+            current_url = '%s:%s' % (match.namespace, match.url_name)
  43
+            changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)
  44
+            if changelist_url == current_url and '_changelist_filters' in preserved_filters:
  45
+                preserved_filters = dict(parse_qsl(preserved_filters['_changelist_filters']))
  46
+
  47
+        merged_qs.update(preserved_filters)
  48
+
  49
+    if popup:
  50
+        merged_qs['_popup'] = 1
  51
+
  52
+    merged_qs.update(parsed_qs)
  53
+
  54
+    parsed_url[4] = urlencode(merged_qs)
  55
+    return urlunparse(parsed_url)
1  django/contrib/admin/views/main.py
@@ -59,6 +59,7 @@ def __init__(self, request, model, list_display, list_display_links,
59 59
         self.list_per_page = list_per_page
60 60
         self.list_max_show_all = list_max_show_all
61 61
         self.model_admin = model_admin
  62
+        self.preserved_filters = model_admin.get_preserved_filters(request)
62 63
 
63 64
         # Get search parameters from the query string.
64 65
         try:
8  docs/ref/contrib/admin/index.txt
@@ -870,6 +870,14 @@ subclass::
870 870
     ``prepopulated_fields`` doesn't accept ``DateTimeField``, ``ForeignKey``,
871 871
     nor ``ManyToManyField`` fields.
872 872
 
  873
+.. attribute:: ModelAdmin.preserve_filters
  874
+
  875
+    .. versionadded:: 1.6
  876
+
  877
+    The admin now preserves filters on the list view after creating, editing
  878
+    or deleting an object. You can restore the previous behavior of clearing
  879
+    filters by setting this attribute to ``False``.
  880
+
873 881
 .. attribute:: ModelAdmin.radio_fields
874 882
 
875 883
     By default, Django's admin uses a select-box interface (<select>) for
15  docs/releases/1.6.txt
@@ -325,6 +325,11 @@ Minor features
325 325
   :ref:`see the updated recommendation <raising-validation-error>` for raising
326 326
   a ``ValidationError``.
327 327
 
  328
+* :class:`~django.contrib.admin.ModelAdmin` now preserves filters on the list view
  329
+  after creating, editing or deleting an object. It's possible to restore the previous
  330
+  behavior of clearing filters by setting the
  331
+  :attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
  332
+
328 333
 Backwards incompatible changes in 1.6
329 334
 =====================================
330 335
 
@@ -634,6 +639,16 @@ will render something like:
634 639
 If you want to keep the current behavior of rendering ``label_tag`` without
635 640
 the ``label_suffix``, instantiate the form ``label_suffix=''``.
636 641
 
  642
+Admin views ``_changelist_filters`` GET parameter
  643
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  644
+
  645
+To achieve preserving and restoring list view filters, admin views now
  646
+pass around the `_changelist_filters` GET parameter. It's important that you
  647
+account for that change if you have custom admin templates or if your tests
  648
+rely on the previous URLs. If you want to revert to the original behavior you
  649
+can set the
  650
+:attr:`~django.contrib.admin.ModelAdmin.preserve_filters` attribute to ``False``.
  651
+
637 652
 Miscellaneous
638 653
 ~~~~~~~~~~~~~
639 654
 
153  tests/admin_views/tests.py
@@ -4157,3 +4157,156 @@ def test_message_extra_tags(self):
4157 4157
         self.assertContains(response,
4158 4158
                             '<li class="extra_tag info">Test tags</li>',
4159 4159
                             html=True)
  4160
+
  4161
+
  4162
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
  4163
+class AdminKeepChangeListFiltersTests(TestCase):
  4164
+    urls = "admin_views.urls"
  4165
+    fixtures = ['admin-views-users.xml']
  4166
+
  4167
+    def setUp(self):
  4168
+        self.client.login(username='super', password='secret')
  4169
+
  4170
+    def tearDown(self):
  4171
+        self.client.logout()
  4172
+
  4173
+    def get_changelist_filters_querystring(self):
  4174
+        return urlencode({
  4175
+            'is_superuser__exact': 0,
  4176
+            'is_staff__exact': 0,
  4177
+        })
  4178
+
  4179
+    def get_preserved_filters_querystring(self):
  4180
+        return urlencode({
  4181
+            '_changelist_filters': self.get_changelist_filters_querystring()
  4182
+        })
  4183
+
  4184
+    def get_sample_user_id(self):
  4185
+        return 104
  4186
+
  4187
+    def get_changelist_url(self):
  4188
+        return '%s?%s' % (
  4189
+            reverse('admin:auth_user_changelist'),
  4190
+            self.get_changelist_filters_querystring(),
  4191
+        )
  4192
+
  4193
+    def get_add_url(self):
  4194
+        return '%s?%s' % (
  4195
+            reverse('admin:auth_user_add'),
  4196
+            self.get_preserved_filters_querystring(),
  4197
+        )
  4198
+
  4199
+    def get_change_url(self, user_id=None):
  4200
+        if user_id is None:
  4201
+            user_id = self.get_sample_user_id()
  4202
+        return "%s?%s" % (
  4203
+            reverse('admin:auth_user_change', args=(user_id,)),
  4204
+            self.get_preserved_filters_querystring(),
  4205
+        )
  4206
+
  4207
+    def get_history_url(self, user_id=None):
  4208
+        if user_id is None:
  4209
+            user_id = self.get_sample_user_id()
  4210
+        return "%s?%s" % (
  4211
+            reverse('admin:auth_user_history', args=(user_id,)),
  4212
+            self.get_preserved_filters_querystring(),
  4213
+        )
  4214
+
  4215
+    def get_delete_url(self, user_id=None):
  4216
+        if user_id is None:
  4217
+            user_id = self.get_sample_user_id()
  4218
+        return "%s?%s" % (
  4219
+            reverse('admin:auth_user_delete', args=(user_id,)),
  4220
+            self.get_preserved_filters_querystring(),
  4221
+        )
  4222
+
  4223
+    def test_changelist_view(self):
  4224
+        response = self.client.get(self.get_changelist_url())
  4225
+        self.assertEqual(response.status_code, 200)
  4226
+
  4227
+        # Check the `change_view` link has the correct querystring.
  4228
+        detail_link = """<a href="%s">joepublic</a>""" % self.get_change_url()
  4229
+        self.assertContains(response, detail_link, count=1)
  4230
+
  4231
+    def test_change_view(self):
  4232
+        # Get the `change_view`.
  4233
+        response = self.client.get(self.get_change_url())
  4234
+        self.assertEqual(response.status_code, 200)
  4235
+
  4236
+        # Check the form action.
  4237
+        form_action = """<form enctype="multipart/form-data" action="?%s" method="post" id="user_form">""" % self.get_preserved_filters_querystring()
  4238
+        self.assertContains(response, form_action, count=1)
  4239
+
  4240
+        # Check the history link.
  4241
+        history_link = """<a href="%s" class="historylink">History</a>""" % self.get_history_url()
  4242
+        self.assertContains(response, history_link, count=1)
  4243
+
  4244
+        # Check the delete link.
  4245
+        delete_link = """<a href="%s" class="deletelink">Delete</a>""" % (self.get_delete_url())
  4246
+        self.assertContains(response, delete_link, count=1)
  4247
+
  4248
+        # Test redirect on "Save".
  4249
+        post_data = {
  4250
+            'username': 'joepublic',
  4251
+            'last_login_0': '2007-05-30',
  4252
+            'last_login_1': '13:20:10',
  4253
+            'date_joined_0': '2007-05-30',
  4254
+            'date_joined_1': '13:20:10',
  4255
+        }
  4256
+
  4257
+        post_data['_save'] = 1
  4258
+        response = self.client.post(self.get_change_url(), data=post_data)
  4259
+        self.assertRedirects(response, self.get_changelist_url())
  4260
+        post_data.pop('_save')
  4261
+
  4262
+        # Test redirect on "Save and continue".
  4263
+        post_data['_continue'] = 1
  4264
+        response = self.client.post(self.get_change_url(), data=post_data)
  4265
+        self.assertRedirects(response, self.get_change_url())
  4266
+        post_data.pop('_continue')
  4267
+
  4268
+        # Test redirect on "Save and add new".
  4269
+        post_data['_addanother'] = 1
  4270
+        response = self.client.post(self.get_change_url(), data=post_data)
  4271
+        self.assertRedirects(response, self.get_add_url())
  4272
+        post_data.pop('_addanother')
  4273
+
  4274
+    def test_add_view(self):
  4275
+        # Get the `add_view`.
  4276
+        response = self.client.get(self.get_add_url())
  4277
+        self.assertEqual(response.status_code, 200)
  4278
+    
  4279
+        # Check the form action.
  4280
+        form_action = """<form enctype="multipart/form-data" action="?%s" method="post" id="user_form">""" % self.get_preserved_filters_querystring()
  4281
+        self.assertContains(response, form_action, count=1)
  4282
+
  4283
+        # Test redirect on "Save".
  4284
+        post_data = {
  4285
+            'username': 'dummy',
  4286
+            'password1': 'test',
  4287
+            'password2': 'test',
  4288
+        }
  4289
+
  4290
+        post_data['_save'] = 1
  4291
+        response = self.client.post(self.get_add_url(), data=post_data)
  4292
+        self.assertRedirects(response, self.get_change_url(self.get_sample_user_id() + 1))
  4293
+        post_data.pop('_save')
  4294
+
  4295
+        # Test redirect on "Save and continue".
  4296
+        post_data['username'] = 'dummy2'
  4297
+        post_data['_continue'] = 1
  4298
+        response = self.client.post(self.get_add_url(), data=post_data)
  4299
+        self.assertRedirects(response, self.get_change_url(self.get_sample_user_id() + 2))
  4300
+        post_data.pop('_continue')
  4301
+
  4302
+        # Test redirect on "Save and add new".
  4303
+        post_data['username'] = 'dummy3'
  4304
+        post_data['_addanother'] = 1
  4305
+        response = self.client.post(self.get_add_url(), data=post_data)
  4306
+        self.assertRedirects(response, self.get_add_url())
  4307
+        post_data.pop('_addanother')
  4308
+
  4309
+    def test_delete_view(self):
  4310
+        # Test redirect on "Delete".
  4311
+        response = self.client.post(self.get_delete_url(), {'post': 'yes'})
  4312
+        self.assertRedirects(response, self.get_changelist_url())

0 notes on commit c86a9b6

Please sign in to comment.
Something went wrong with that request. Please try again.