Ticket #6903: 6903-r9904.diff
File 6903-r9904.diff, 14.7 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/options.py
diff -r 1c267332e8fc django/contrib/admin/options.py
a b 12 12 from django.shortcuts import get_object_or_404, render_to_response 13 13 from django.utils.functional import update_wrapper 14 14 from django.utils.html import escape 15 from django.utils.http import urlquote 15 16 from django.utils.safestring import mark_safe 16 17 from django.utils.functional import curry 17 18 from django.utils.text import capfirst, get_text_list … … 26 27 # returns the <ul> class for a given radio_admin field 27 28 get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') 28 29 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. 32 RETURN_GET_PARAM = '_return_to' 33 29 34 class IncorrectLookupParameters(Exception): 30 35 pass 31 36 … … 452 457 pk_value = obj._get_pk_val() 453 458 454 459 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 456 462 # 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. 457 466 if request.POST.has_key("_continue"): 458 467 self.message_user(request, msg + ' ' + _("You may edit it again below.")) 459 468 if request.POST.has_key("_popup"): 460 469 post_url_continue += "?_popup=1" 461 470 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. 463 475 if request.POST.has_key("_popup"): 464 476 return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ 465 477 # escape() calls force_unicode. 466 478 (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. 467 482 elif request.POST.has_key("_addanother"): 468 483 self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) 469 484 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. 470 490 else: 471 491 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): 477 495 post_url = '../' 478 496 else: 479 497 post_url = '../../../' … … 487 506 pk_value = obj._get_pk_val() 488 507 489 508 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" 490 513 if request.POST.has_key("_continue"): 491 514 self.message_user(request, msg + ' ' + _("You may edit it again below.")) 492 515 if request.REQUEST.has_key('_popup'): 493 516 return HttpResponseRedirect(request.path + "?_popup=1") 494 517 else: 495 518 return HttpResponseRedirect(request.path) 519 520 # "Save as a new object" 496 521 elif request.POST.has_key("_saveasnew"): 497 522 msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj} 498 523 self.message_user(request, msg) 499 524 return HttpResponseRedirect("../%s/" % pk_value) 525 526 # "Save and add another" 500 527 elif request.POST.has_key("_addanother"): 501 528 self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) 502 529 return HttpResponseRedirect("../add/") 530 531 # "Save" 503 532 else: 504 533 self.message_user(request, msg) 505 return HttpResponseRedirect("../") 506 534 post_url = request.GET.get(RETURN_GET_PARAM, '../') 535 return HttpResponseRedirect(post_url) 536 507 537 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 """ 509 541 model = self.model 510 542 opts = model._meta 511 543 … … 514 546 515 547 ModelForm = self.get_form(request) 516 548 formsets = [] 549 return_to = request.GET.get(RETURN_GET_PARAM, None) 517 550 if request.method == 'POST': 518 551 form = ModelForm(request.POST, request.FILES) 519 552 if form.is_valid(): … … 550 584 for FormSet in self.get_formsets(request): 551 585 formset = FormSet(instance=self.model()) 552 586 formsets.append(formset) 553 587 588 if return_to: 589 form_url = form_url + '?%s=%s' % (RETURN_GET_PARAM, urlquote(return_to)) 590 554 591 adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) 555 592 media = self.media + adminForm.media 556 593 … … 573 610 'app_label': opts.app_label, 574 611 } 575 612 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) 577 614 add_view = transaction.commit_on_success(add_view) 578 615 579 616 def change_view(self, request, object_id, extra_context=None): … … 600 637 601 638 ModelForm = self.get_form(request, obj) 602 639 formsets = [] 640 return_to = request.GET.get(RETURN_GET_PARAM, None) 603 641 if request.method == 'POST': 604 642 form = ModelForm(request.POST, request.FILES, instance=obj) 605 643 if form.is_valid(): … … 628 667 for FormSet in self.get_formsets(request, obj): 629 668 formset = FormSet(instance=obj) 630 669 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 632 676 adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) 633 677 media = self.media + adminForm.media 634 678 … … 652 696 'app_label': opts.app_label, 653 697 } 654 698 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) 656 700 change_view = transaction.commit_on_success(change_view) 657 701 658 702 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 1 1 from django.contrib.admin.filterspecs import FilterSpec 2 from django.contrib.admin.options import IncorrectLookupParameters 2 from django.contrib.admin.options import IncorrectLookupParameters, RETURN_GET_PARAM 3 3 from django.contrib.admin.util import quote 4 4 from django.core.paginator import Paginator, InvalidPage 5 5 from django.db import models 6 6 from django.db.models.query import QuerySet 7 7 from django.utils.encoding import force_unicode, smart_str 8 8 from django.utils.translation import ugettext 9 from django.utils.http import urlencode 9 from django.utils.http import urlencode, urlquote 10 10 import operator 11 11 12 12 try: … … 69 69 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)) 70 70 self.filter_specs, self.has_filters = self.get_filters(request) 71 71 self.pk_attname = self.lookup_opts.pk.attname 72 self.this_clist_url = request.get_full_path() 72 73 73 74 def get_filters(self, request): 74 75 filter_specs = [] … … 240 241 return qs 241 242 242 243 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 7 7 from django.contrib.admin.sites import LOGIN_FORM_KEY 8 8 from django.contrib.admin.util import quote 9 9 from django.utils.html import escape 10 from django.utils.http import urlquote 10 11 11 12 # local test models 12 13 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey 13 14 14 15 class 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 18 19 # variable. That way we can test a second AdminSite just by subclassing 19 20 # this test case and changing urlbit. 20 21 urlbit = 'admin' … … 172 173 self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit) 173 174 response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'}) 174 175 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/')) 175 228 176 229 class CustomModelAdminTest(AdminViewBasicTest): 177 230 urlbit = "admin2" … … 508 561 509 562 def test_changelist_to_changeform_link(self): 510 563 "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)) 513 567 self.assertContains(response, should_contain) 514 568 515 569 def test_recentactions_link(self):