Commit 80bcbecd authored by Anton Baklanov's avatar Anton Baklanov Committed by Tim Graham
Browse files

Fixed #19361 -- Added link to object's change form in admin's post-save message.

Thanks Roel Kramer for tests.
parent 1e7da99e
Loading
Loading
Loading
Loading
+51 −15
Original line number Diff line number Diff line
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)

+3 −0
Original line number Diff line number Diff line
@@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

+51 −19
Original line number Diff line number Diff line
@@ -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 &amp; 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 &quot;Candidate, Best&quot; 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 &quot;ShortMessage object&quot; 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 &quot;Urgent telegram&quot; 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 &quot;Paper object&quot; 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 &quot;John Doe II&quot; 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 '
                '&quot;ShortMessage_Deferred_timestamp object&quot; 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 &quot;Telegram without typo&quot; 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 &quot;Paper_Deferred_author object&quot; 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):