Ticket #6903: 6903-r9904.diff

File 6903-r9904.diff, 14.7 KB (added by ramiro, 7 years ago)

New patch, including Jacob's changes and suggestions. Thanks Alex Gaynor for his help with the tests

  • django/contrib/admin/options.py

    diff -r 1c267332e8fc django/contrib/admin/options.py
    a b  
    1212from django.shortcuts import get_object_or_404, render_to_response
    1313from django.utils.functional import update_wrapper
    1414from django.utils.html import escape
     15from django.utils.http import urlquote
    1516from django.utils.safestring import mark_safe
    1617from django.utils.functional import curry
    1718from django.utils.text import capfirst, get_text_list
     
    2627# returns the <ul> class for a given radio_admin field
    2728get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
    2829
     30# GET parameter for the URL to return to after change/add views. This lets the
     31# admin save the state of the changelist page through the change/add page.
     32RETURN_GET_PARAM = '_return_to'
     33
    2934class IncorrectLookupParameters(Exception):
    3035    pass
    3136
     
    452457        pk_value = obj._get_pk_val()
    453458       
    454459        msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
    455         # Here, we distinguish between different save types by checking for
     460
     461        # Below, we distinguish between different save types by checking for
    456462        # the presence of keys in request.POST.
     463
     464        # The user clicked "save and continue editing". Redirect to admin URL
     465        # for this object, possibly in popup mode if needed.
    457466        if request.POST.has_key("_continue"):
    458467            self.message_user(request, msg + ' ' + _("You may edit it again below."))
    459468            if request.POST.has_key("_popup"):
    460469                post_url_continue += "?_popup=1"
    461470            return HttpResponseRedirect(post_url_continue % pk_value)
    462        
     471
     472        # This was a popup and the user clicked the simple "save" button.
     473        # Return a little script which populates the calling page with the
     474        # saved key.
    463475        if request.POST.has_key("_popup"):
    464476            return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
    465477                # escape() calls force_unicode.
    466478                (escape(pk_value), escape(obj)))
     479
     480        # The user clicked "save and add another", so redirect back to the bare
     481        # "add" page, which will be request.path.
    467482        elif request.POST.has_key("_addanother"):
    468483            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
    469484            return HttpResponseRedirect(request.path)
     485
     486        # Just a regular "save" click. IF we've been pased an explicit return
     487        # URL in GET, go there. Otherwise, redirect to the plain changelist.
     488        # If the user doesn't have changelist permission, just redirect back
     489        # to the main admin index.
    470490        else:
    471491            self.message_user(request, msg)
    472            
    473             # Figure out where to redirect. If the user has change permission,
    474             # redirect to the change-list page for this object. Otherwise,
    475             # redirect to the admin index.
    476             if self.has_change_permission(request, None):
     492            if RETURN_GET_PARAM in request.GET:
     493                post_url = request.GET[RETURN_GET_PARAM]
     494            elif self.has_change_permission(request, None):
    477495                post_url = '../'
    478496            else:
    479497                post_url = '../../../'
     
    487506        pk_value = obj._get_pk_val()
    488507       
    489508        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
     509
     510        # Similar redirecting logic to response_add, above.
     511
     512        # "Save and continue editing"
    490513        if request.POST.has_key("_continue"):
    491514            self.message_user(request, msg + ' ' + _("You may edit it again below."))
    492515            if request.REQUEST.has_key('_popup'):
    493516                return HttpResponseRedirect(request.path + "?_popup=1")
    494517            else:
    495518                return HttpResponseRedirect(request.path)
     519
     520        # "Save as a new object"
    496521        elif request.POST.has_key("_saveasnew"):
    497522            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
    498523            self.message_user(request, msg)
    499524            return HttpResponseRedirect("../%s/" % pk_value)
     525
     526        # "Save and add another"
    500527        elif request.POST.has_key("_addanother"):
    501528            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
    502529            return HttpResponseRedirect("../add/")
     530
     531        # "Save"
    503532        else:
    504533            self.message_user(request, msg)
    505             return HttpResponseRedirect("../")
    506    
     534            post_url = request.GET.get(RETURN_GET_PARAM, '../')
     535            return HttpResponseRedirect(post_url)
     536
    507537    def add_view(self, request, form_url='', extra_context=None):
    508         "The 'add' admin view for this model."
     538        """
     539        The 'add' admin view for this model.
     540        """
    509541        model = self.model
    510542        opts = model._meta
    511543       
     
    514546       
    515547        ModelForm = self.get_form(request)
    516548        formsets = []
     549        return_to = request.GET.get(RETURN_GET_PARAM, None)
    517550        if request.method == 'POST':
    518551            form = ModelForm(request.POST, request.FILES)
    519552            if form.is_valid():
     
    550584            for FormSet in self.get_formsets(request):
    551585                formset = FormSet(instance=self.model())
    552586                formsets.append(formset)
    553        
     587
     588        if return_to:
     589            form_url = form_url + '?%s=%s' % (RETURN_GET_PARAM, urlquote(return_to))
     590
    554591        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
    555592        media = self.media + adminForm.media
    556593       
     
    573610            'app_label': opts.app_label,
    574611        }
    575612        context.update(extra_context or {})
    576         return self.render_change_form(request, context, add=True)
     613        return self.render_change_form(request, context, form_url=form_url, add=True)
    577614    add_view = transaction.commit_on_success(add_view)
    578615   
    579616    def change_view(self, request, object_id, extra_context=None):
     
    600637       
    601638        ModelForm = self.get_form(request, obj)
    602639        formsets = []
     640        return_to = request.GET.get(RETURN_GET_PARAM, None)
    603641        if request.method == 'POST':
    604642            form = ModelForm(request.POST, request.FILES, instance=obj)
    605643            if form.is_valid():
     
    628667            for FormSet in self.get_formsets(request, obj):
    629668                formset = FormSet(instance=obj)
    630669                formsets.append(formset)
    631        
     670
     671        if return_to:
     672            form_url = '?%s=%s' % (RETURN_GET_PARAM, urlquote(return_to))
     673        else:
     674            form_url = ''
     675
    632676        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
    633677        media = self.media + adminForm.media
    634678       
     
    652696            'app_label': opts.app_label,
    653697        }
    654698        context.update(extra_context or {})
    655         return self.render_change_form(request, context, change=True, obj=obj)
     699        return self.render_change_form(request, context, change=True, form_url=form_url, obj=obj)
    656700    change_view = transaction.commit_on_success(change_view)
    657701   
    658702    def changelist_view(self, request, extra_context=None):
  • django/contrib/admin/views/main.py

    diff -r 1c267332e8fc django/contrib/admin/views/main.py
    a b  
    11from django.contrib.admin.filterspecs import FilterSpec
    2 from django.contrib.admin.options import IncorrectLookupParameters
     2from django.contrib.admin.options import IncorrectLookupParameters, RETURN_GET_PARAM
    33from django.contrib.admin.util import quote
    44from django.core.paginator import Paginator, InvalidPage
    55from django.db import models
    66from django.db.models.query import QuerySet
    77from django.utils.encoding import force_unicode, smart_str
    88from django.utils.translation import ugettext
    9 from django.utils.http import urlencode
     9from django.utils.http import urlencode, urlquote
    1010import operator
    1111
    1212try:
     
    6969        self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
    7070        self.filter_specs, self.has_filters = self.get_filters(request)
    7171        self.pk_attname = self.lookup_opts.pk.attname
     72        self.this_clist_url = request.get_full_path()
    7273
    7374    def get_filters(self, request):
    7475        filter_specs = []
     
    240241        return qs
    241242
    242243    def url_for_result(self, result):
    243         return "%s/" % quote(getattr(result, self.pk_attname))
     244        return "%s/?%s=%s" % (quote(getattr(result, self.pk_attname)), RETURN_GET_PARAM, urlquote(self.this_clist_url))
  • new file tests/regressiontests/admin_views/fixtures/admin-views-things.xml

    diff -r 1c267332e8fc tests/regressiontests/admin_views/fixtures/admin-views-things.xml
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<django-objects version="1.0">
     3    <object pk="1" model="admin_views.thing">
     4        <field type="CharField" name="title">Ball</field>
     5        <field to="admin_views.color" name="color" rel="ManyToOneRel">1</field>
     6    </object>
     7    <object pk="2" model="admin_views.thing">
     8        <field type="CharField" name="title">Carrot</field>
     9        <field to="admin_views.color" name="color" rel="ManyToOneRel">2</field>
     10    </object>
     11</django-objects>
     12
  • tests/regressiontests/admin_views/tests.py

    diff -r 1c267332e8fc tests/regressiontests/admin_views/tests.py
    a b  
    77from django.contrib.admin.sites import LOGIN_FORM_KEY
    88from django.contrib.admin.util import quote
    99from django.utils.html import escape
     10from django.utils.http import urlquote
    1011
    1112# local test models
    1213from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
    1314
    1415class AdminViewBasicTest(TestCase):
    15     fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
    16    
    17     # Store the bit of the URL where the admin is registered as a class 
     16    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-things.xml']
     17
     18    # Store the bit of the URL where the admin is registered as a class
    1819    # variable. That way we can test a second AdminSite just by subclassing
    1920    # this test case and changing urlbit.
    2021    urlbit = 'admin'
     
    172173        self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)       
    173174        response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
    174175        self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
     176
     177    def testFilterPreservedChangeListLinks(self):
     178        """Link to model changeform in changelist should contain filter-preserving GET data if a filter is active"""
     179        changelist_url = '/test_admin/%s/admin_views/thing/' % self.urlbit
     180        filter_spec = '?color__id__exact=1'
     181        should_contain = """<tr class="row1"><th><a href="1/?_return_to=%s%s">Ball</a></th></tr>""" % (changelist_url, urlquote(filter_spec))
     182        # get the changelist page with one active filter
     183        response = self.client.get(changelist_url, {'color__id__exact': 1})
     184        # links in the changelist table should preserve the filter
     185        self.failUnlessEqual(response.status_code, 200)
     186        self.assertContains(response, should_contain)
     187
     188    def testFilterPreservedEditForm(self):
     189        """Model changeform post handler URL should contain filter specified when clicking the changelist link."""
     190        changelist_url = '/test_admin/%s/admin_views/thing/' % self.urlbit
     191        filter_spec = '?color__id__exact=1'
     192        edit_form_url = '%s1/?_return_to=%s%s' % (changelist_url, changelist_url, urlquote(filter_spec))
     193        should_contain = '<form enctype="multipart/form-data" action="?_return_to=%s%s" method="post" id="thing_form">' % (changelist_url, urlquote(filter_spec))
     194        response = self.client.get(edit_form_url)
     195        self.failUnlessEqual(response.status_code, 200)
     196        self.assertContains(response, should_contain)
     197
     198    def testFilterPreservedAfterEditSave(self):
     199        """Redirection target after 'Save' is clicked in model changeform should be the changelist"""
     200        changelist_url = '/test_admin/%s/admin_views/thing' % self.urlbit
     201        filter_spec = '?color__id__exact=1'
     202        action = '%s/1/?_return_to=%s/%s' % (changelist_url, changelist_url, urlquote(filter_spec))
     203        post_data = {
     204            # Model fields
     205            "title": u"Car",
     206            "color": u"1",
     207        }
     208        response = self.client.post(action, post_data)
     209        # It should redirect to the changelist
     210        self.assertRedirects(response, '%s/%s' % (changelist_url, filter_spec))
     211
     212    def testFilterIgnoredEditAddAnother(self):
     213        """Redirection target after 'Save and add another' is clicked in model changeform should be an add form"""
     214        base_url = '/test_admin/%s/admin_views/thing' % self.urlbit
     215        filter_spec = '?color__id__exact=1'
     216        post_to = '%s/1/?_return_to=%s/%s' % (base_url, base_url, urlquote(filter_spec))
     217        post_data = {
     218            # Model fields
     219            "title": u"Car",
     220            "color": u"1",
     221            # Form button used
     222            "_addanother": u""
     223        }
     224        response = self.client.post(post_to, post_data)
     225        # It should redirect to an add form
     226        #self.failUnlessEqual(response.status_code, 302)
     227        self.assertRedirects(response, '%s/%s' % (base_url, 'add/'))
    175228
    176229class CustomModelAdminTest(AdminViewBasicTest):
    177230    urlbit = "admin2"
     
    508561
    509562    def test_changelist_to_changeform_link(self):
    510563        "The link from the changelist referring to the changeform of the object should be quoted"
    511         response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
    512         should_contain = """<tr class="row1"><th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
     564        changelist_url = '/test_admin/admin/admin_views/modelwithstringprimarykey/'
     565        response = self.client.get(changelist_url)
     566        should_contain = """<tr class="row1"><th><a href="%s/?_return_to=%s">%s</a></th></tr>""" % (quote(self.pk), changelist_url, escape(self.pk))
    513567        self.assertContains(response, should_contain)
    514568
    515569    def test_recentactions_link(self):
Back to Top