mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #19361 -- Added link to object's change form in admin's post-save message.
Thanks Roel Kramer for tests.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							1e7da99ea6
						
					
				
				
					commit
					80bcbecd4a
				
			| @@ -1,3 +1,5 @@ | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import copy | ||||
| import operator | ||||
| from collections import OrderedDict | ||||
| @@ -38,8 +40,8 @@ from django.template.response import SimpleTemplateResponse, TemplateResponse | ||||
| from django.utils import six | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.utils.encoding import force_text, python_2_unicode_compatible | ||||
| from django.utils.html import escape, escapejs | ||||
| from django.utils.http import urlencode | ||||
| from django.utils.html import escape, escapejs, format_html | ||||
| from django.utils.http import urlencode, urlquote | ||||
| from django.utils.safestring import mark_safe | ||||
| from django.utils.text import capfirst, get_text_list | ||||
| from django.utils.translation import string_concat, ugettext as _, ungettext | ||||
| @@ -1058,7 +1060,20 @@ class ModelAdmin(BaseModelAdmin): | ||||
|         opts = obj._meta | ||||
|         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)} | ||||
|         obj_url = reverse( | ||||
|             'admin:%s_%s_change' % (opts.app_label, opts.model_name), | ||||
|             args=(quote(pk_value),), | ||||
|             current_app=self.admin_site.name, | ||||
|         ) | ||||
|         # Add a link to the object's change form if the user can edit the obj. | ||||
|         if self.has_change_permission(request, obj): | ||||
|             obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj) | ||||
|         else: | ||||
|             obj_repr = force_text(obj) | ||||
|         msg_dict = { | ||||
|             'name': force_text(opts.verbose_name), | ||||
|             'obj': obj_repr, | ||||
|         } | ||||
|         # Here, we distinguish between different save types by checking for | ||||
|         # the presence of keys in request.POST. | ||||
|  | ||||
| @@ -1075,13 +1090,13 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             }) | ||||
|  | ||||
|         elif "_continue" in request.POST: | ||||
|             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was added successfully. You may edit it again below.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             if post_url_continue is None: | ||||
|                 post_url_continue = reverse('admin:%s_%s_change' % | ||||
|                                             (opts.app_label, opts.model_name), | ||||
|                                             args=(quote(pk_value),), | ||||
|                                             current_app=self.admin_site.name) | ||||
|                 post_url_continue = obj_url | ||||
|             post_url_continue = add_preserved_filters( | ||||
|                 {'preserved_filters': preserved_filters, 'opts': opts}, | ||||
|                 post_url_continue | ||||
| @@ -1089,14 +1104,20 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             return HttpResponseRedirect(post_url_continue) | ||||
|  | ||||
|         elif "_addanother" in request.POST: | ||||
|             msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was added successfully. You may add another {name} below.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = request.path | ||||
|             redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) | ||||
|             return HttpResponseRedirect(redirect_url) | ||||
|  | ||||
|         else: | ||||
|             msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was added successfully.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             return self.response_post_save_add(request, obj) | ||||
|  | ||||
| @@ -1122,16 +1143,25 @@ class ModelAdmin(BaseModelAdmin): | ||||
|         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': format_html('<a href="{}">{}</a>', urlquote(request.path), obj), | ||||
|         } | ||||
|         if "_continue" in request.POST: | ||||
|             msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was changed successfully. You may edit it again below.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = request.path | ||||
|             redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) | ||||
|             return HttpResponseRedirect(redirect_url) | ||||
|  | ||||
|         elif "_saveasnew" in request.POST: | ||||
|             msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was added successfully. You may edit it again below.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = reverse('admin:%s_%s_change' % | ||||
|                                    (opts.app_label, opts.model_name), | ||||
| @@ -1141,7 +1171,10 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             return HttpResponseRedirect(redirect_url) | ||||
|  | ||||
|         elif "_addanother" in request.POST: | ||||
|             msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was changed successfully. You may add another {name} below.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             redirect_url = reverse('admin:%s_%s_add' % | ||||
|                                    (opts.app_label, opts.model_name), | ||||
| @@ -1150,7 +1183,10 @@ class ModelAdmin(BaseModelAdmin): | ||||
|             return HttpResponseRedirect(redirect_url) | ||||
|  | ||||
|         else: | ||||
|             msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict | ||||
|             msg = format_html( | ||||
|                 _('The {name} "{obj}" was changed successfully.'), | ||||
|                 **msg_dict | ||||
|             ) | ||||
|             self.message_user(request, msg, messages.SUCCESS) | ||||
|             return self.response_post_save_change(request, obj) | ||||
|  | ||||
|   | ||||
| @@ -36,6 +36,9 @@ Minor features | ||||
|   link <django.contrib.admin.AdminSite.site_url>` at the top of each admin page | ||||
|   will now point to ``request.META['SCRIPT_NAME']`` if set, instead of ``/``. | ||||
|  | ||||
| * The success message that appears after adding or editing an object now | ||||
|   contains a link to the object's change form. | ||||
|  | ||||
| :mod:`django.contrib.admindocs` | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
|   | ||||
| @@ -2025,6 +2025,28 @@ class AdminViewPermissionsTest(TestCase): | ||||
|         self.assertNotContains(response, 'admin_views') | ||||
|         self.assertNotContains(response, 'Articles') | ||||
|  | ||||
|     def test_post_save_message_no_forbidden_links_visible(self): | ||||
|         """ | ||||
|         Post-save message shouldn't contain a link to the change form if the | ||||
|         user doen't have the change permission. | ||||
|         """ | ||||
|         login = self.client.post(reverse('admin:login'), self.adduser_login) | ||||
|         self.assertRedirects(login, self.index_url) | ||||
|         # Emulate Article creation for user with add-only permission. | ||||
|         post_data = { | ||||
|             "title": "Fun & games", | ||||
|             "content": "Some content", | ||||
|             "date_0": "2015-10-31", | ||||
|             "date_1": "16:35:00", | ||||
|             "_save": "Save", | ||||
|         } | ||||
|         response = self.client.post(reverse('admin:admin_views_article_add'), post_data, follow=True) | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The article "Fun & games" was added successfully.</li>', | ||||
|             html=True | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'], | ||||
|     ROOT_URLCONF="admin_views.urls") | ||||
| @@ -3801,10 +3823,12 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(CoverLetter.objects.count(), 1) | ||||
|         # Message should contain non-ugly model verbose name | ||||
|         pk = CoverLetter.objects.all()[0].pk | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The cover letter "Candidate, Best" was added successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The cover letter "<a href="%s">' | ||||
|             'Candidate, Best</a>" was added successfully.</li>' % | ||||
|             reverse('admin:admin_views_coverletter_change', args=(pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|         # model has no __unicode__ method | ||||
| @@ -3819,10 +3843,12 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(ShortMessage.objects.count(), 1) | ||||
|         # Message should contain non-ugly model verbose name | ||||
|         pk = ShortMessage.objects.all()[0].pk | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The short message "ShortMessage object" was added successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The short message "<a href="%s">' | ||||
|             'ShortMessage object</a>" was added successfully.</li>' % | ||||
|             reverse('admin:admin_views_shortmessage_change', args=(pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|     def test_add_model_modeladmin_only_qs(self): | ||||
| @@ -3840,10 +3866,12 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(Telegram.objects.count(), 1) | ||||
|         # Message should contain non-ugly model verbose name | ||||
|         pk = Telegram.objects.all()[0].pk | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The telegram "Urgent telegram" was added successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The telegram "<a href="%s">' | ||||
|             'Urgent telegram</a>" was added successfully.</li>' % | ||||
|             reverse('admin:admin_views_telegram_change', args=(pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|         # model has no __unicode__ method | ||||
| @@ -3858,10 +3886,12 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertEqual(Paper.objects.count(), 1) | ||||
|         # Message should contain non-ugly model verbose name | ||||
|         pk = Paper.objects.all()[0].pk | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The paper "Paper object" was added successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The paper "<a href="%s">' | ||||
|             'Paper object</a>" was added successfully.</li>' % | ||||
|             reverse('admin:admin_views_paper_change', args=(pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|     def test_edit_model_modeladmin_defer_qs(self): | ||||
| @@ -3885,8 +3915,9 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         # representation is set by model's __unicode__() | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The cover letter "John Doe II" was changed successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The cover letter "<a href="%s">' | ||||
|             'John Doe II</a>" was changed successfully.</li>' % | ||||
|             reverse('admin:admin_views_coverletter_change', args=(cl.pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|         # model has no __unicode__ method | ||||
| @@ -3906,11 +3937,10 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         # Message should contain non-ugly model verbose name. The ugly(!) | ||||
|         # instance representation is set by six.text_type() | ||||
|         self.assertContains( | ||||
|             response, ( | ||||
|                 '<li class="success">The short message ' | ||||
|                 '"ShortMessage_Deferred_timestamp object" was ' | ||||
|                 'changed successfully.</li>' | ||||
|             ), html=True | ||||
|             response, | ||||
|             '<li class="success">The short message "<a href="%s">' | ||||
|             'ShortMessage_Deferred_timestamp object</a>" was changed successfully.</li>' % | ||||
|             reverse('admin:admin_views_shortmessage_change', args=(sm.pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|     def test_edit_model_modeladmin_only_qs(self): | ||||
| @@ -3934,8 +3964,9 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         # representation is set by model's __unicode__() | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The telegram "Telegram without typo" was changed successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The telegram "<a href="%s">' | ||||
|             'Telegram without typo</a>" was changed successfully.</li>' % | ||||
|             reverse('admin:admin_views_telegram_change', args=(t.pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|         # model has no __unicode__ method | ||||
| @@ -3956,8 +3987,9 @@ class AdminCustomQuerysetTest(TestCase): | ||||
|         # instance representation is set by six.text_type() | ||||
|         self.assertContains( | ||||
|             response, | ||||
|             '<li class="success">The paper "Paper_Deferred_author object" was changed successfully.</li>', | ||||
|             html=True | ||||
|             '<li class="success">The paper "<a href="%s">' | ||||
|             'Paper_Deferred_author object</a>" was changed successfully.</li>' % | ||||
|             reverse('admin:admin_views_paper_change', args=(p.pk,)), html=True | ||||
|         ) | ||||
|  | ||||
|     def test_history_view_custom_qs(self): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user