mirror of https://github.com/django/django.git
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:
parent
1e7da99ea6
commit
80bcbecd4a
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import operator
|
import operator
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
@ -38,8 +40,8 @@ from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
from django.utils.html import escape, escapejs
|
from django.utils.html import escape, escapejs, format_html
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode, urlquote
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst, get_text_list
|
from django.utils.text import capfirst, get_text_list
|
||||||
from django.utils.translation import string_concat, ugettext as _, ungettext
|
from django.utils.translation import string_concat, ugettext as _, ungettext
|
||||||
|
@ -1058,7 +1060,20 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
pk_value = obj._get_pk_val()
|
pk_value = obj._get_pk_val()
|
||||||
preserved_filters = self.get_preserved_filters(request)
|
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
|
# Here, we distinguish between different save types by checking for
|
||||||
# the presence of keys in request.POST.
|
# the presence of keys in request.POST.
|
||||||
|
|
||||||
|
@ -1075,13 +1090,13 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
})
|
})
|
||||||
|
|
||||||
elif "_continue" in request.POST:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
if post_url_continue is None:
|
if post_url_continue is None:
|
||||||
post_url_continue = reverse('admin:%s_%s_change' %
|
post_url_continue = obj_url
|
||||||
(opts.app_label, opts.model_name),
|
|
||||||
args=(quote(pk_value),),
|
|
||||||
current_app=self.admin_site.name)
|
|
||||||
post_url_continue = add_preserved_filters(
|
post_url_continue = add_preserved_filters(
|
||||||
{'preserved_filters': preserved_filters, 'opts': opts},
|
{'preserved_filters': preserved_filters, 'opts': opts},
|
||||||
post_url_continue
|
post_url_continue
|
||||||
|
@ -1089,14 +1104,20 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
return HttpResponseRedirect(post_url_continue)
|
return HttpResponseRedirect(post_url_continue)
|
||||||
|
|
||||||
elif "_addanother" in request.POST:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
redirect_url = request.path
|
redirect_url = request.path
|
||||||
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
else:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
return self.response_post_save_add(request, obj)
|
return self.response_post_save_add(request, obj)
|
||||||
|
|
||||||
|
@ -1122,16 +1143,25 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
pk_value = obj._get_pk_val()
|
pk_value = obj._get_pk_val()
|
||||||
preserved_filters = self.get_preserved_filters(request)
|
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:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
redirect_url = request.path
|
redirect_url = request.path
|
||||||
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
elif "_saveasnew" in request.POST:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
redirect_url = reverse('admin:%s_%s_change' %
|
redirect_url = reverse('admin:%s_%s_change' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
|
@ -1141,7 +1171,10 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
elif "_addanother" in request.POST:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
redirect_url = reverse('admin:%s_%s_add' %
|
redirect_url = reverse('admin:%s_%s_add' %
|
||||||
(opts.app_label, opts.model_name),
|
(opts.app_label, opts.model_name),
|
||||||
|
@ -1150,7 +1183,10 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
return HttpResponseRedirect(redirect_url)
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
|
||||||
else:
|
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)
|
self.message_user(request, msg, messages.SUCCESS)
|
||||||
return self.response_post_save_change(request, obj)
|
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
|
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 ``/``.
|
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`
|
:mod:`django.contrib.admindocs`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -2025,6 +2025,28 @@ class AdminViewPermissionsTest(TestCase):
|
||||||
self.assertNotContains(response, 'admin_views')
|
self.assertNotContains(response, 'admin_views')
|
||||||
self.assertNotContains(response, 'Articles')
|
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'],
|
@override_settings(PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'],
|
||||||
ROOT_URLCONF="admin_views.urls")
|
ROOT_URLCONF="admin_views.urls")
|
||||||
|
@ -3801,10 +3823,12 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(CoverLetter.objects.count(), 1)
|
self.assertEqual(CoverLetter.objects.count(), 1)
|
||||||
# Message should contain non-ugly model verbose name
|
# Message should contain non-ugly model verbose name
|
||||||
|
pk = CoverLetter.objects.all()[0].pk
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The cover letter "Candidate, Best" was added successfully.</li>',
|
'<li class="success">The cover letter "<a href="%s">'
|
||||||
html=True
|
'Candidate, Best</a>" was added successfully.</li>' %
|
||||||
|
reverse('admin:admin_views_coverletter_change', args=(pk,)), html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# model has no __unicode__ method
|
# model has no __unicode__ method
|
||||||
|
@ -3819,10 +3843,12 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(ShortMessage.objects.count(), 1)
|
self.assertEqual(ShortMessage.objects.count(), 1)
|
||||||
# Message should contain non-ugly model verbose name
|
# Message should contain non-ugly model verbose name
|
||||||
|
pk = ShortMessage.objects.all()[0].pk
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The short message "ShortMessage object" was added successfully.</li>',
|
'<li class="success">The short message "<a href="%s">'
|
||||||
html=True
|
'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):
|
def test_add_model_modeladmin_only_qs(self):
|
||||||
|
@ -3840,10 +3866,12 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(Telegram.objects.count(), 1)
|
self.assertEqual(Telegram.objects.count(), 1)
|
||||||
# Message should contain non-ugly model verbose name
|
# Message should contain non-ugly model verbose name
|
||||||
|
pk = Telegram.objects.all()[0].pk
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The telegram "Urgent telegram" was added successfully.</li>',
|
'<li class="success">The telegram "<a href="%s">'
|
||||||
html=True
|
'Urgent telegram</a>" was added successfully.</li>' %
|
||||||
|
reverse('admin:admin_views_telegram_change', args=(pk,)), html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# model has no __unicode__ method
|
# model has no __unicode__ method
|
||||||
|
@ -3858,10 +3886,12 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(Paper.objects.count(), 1)
|
self.assertEqual(Paper.objects.count(), 1)
|
||||||
# Message should contain non-ugly model verbose name
|
# Message should contain non-ugly model verbose name
|
||||||
|
pk = Paper.objects.all()[0].pk
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The paper "Paper object" was added successfully.</li>',
|
'<li class="success">The paper "<a href="%s">'
|
||||||
html=True
|
'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):
|
def test_edit_model_modeladmin_defer_qs(self):
|
||||||
|
@ -3885,8 +3915,9 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
# representation is set by model's __unicode__()
|
# representation is set by model's __unicode__()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The cover letter "John Doe II" was changed successfully.</li>',
|
'<li class="success">The cover letter "<a href="%s">'
|
||||||
html=True
|
'John Doe II</a>" was changed successfully.</li>' %
|
||||||
|
reverse('admin:admin_views_coverletter_change', args=(cl.pk,)), html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# model has no __unicode__ method
|
# model has no __unicode__ method
|
||||||
|
@ -3906,11 +3937,10 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
# Message should contain non-ugly model verbose name. The ugly(!)
|
# Message should contain non-ugly model verbose name. The ugly(!)
|
||||||
# instance representation is set by six.text_type()
|
# instance representation is set by six.text_type()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response, (
|
response,
|
||||||
'<li class="success">The short message '
|
'<li class="success">The short message "<a href="%s">'
|
||||||
'"ShortMessage_Deferred_timestamp object" was '
|
'ShortMessage_Deferred_timestamp object</a>" was changed successfully.</li>' %
|
||||||
'changed successfully.</li>'
|
reverse('admin:admin_views_shortmessage_change', args=(sm.pk,)), html=True
|
||||||
), html=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_edit_model_modeladmin_only_qs(self):
|
def test_edit_model_modeladmin_only_qs(self):
|
||||||
|
@ -3934,8 +3964,9 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
# representation is set by model's __unicode__()
|
# representation is set by model's __unicode__()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The telegram "Telegram without typo" was changed successfully.</li>',
|
'<li class="success">The telegram "<a href="%s">'
|
||||||
html=True
|
'Telegram without typo</a>" was changed successfully.</li>' %
|
||||||
|
reverse('admin:admin_views_telegram_change', args=(t.pk,)), html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# model has no __unicode__ method
|
# model has no __unicode__ method
|
||||||
|
@ -3956,8 +3987,9 @@ class AdminCustomQuerysetTest(TestCase):
|
||||||
# instance representation is set by six.text_type()
|
# instance representation is set by six.text_type()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
'<li class="success">The paper "Paper_Deferred_author object" was changed successfully.</li>',
|
'<li class="success">The paper "<a href="%s">'
|
||||||
html=True
|
'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):
|
def test_history_view_custom_qs(self):
|
||||||
|
|
Loading…
Reference in New Issue