From ff19df9c2d6ad80bb9177e8190176e9c3716705f Mon Sep 17 00:00:00 2001
From: Nick Sandford <nick@sandford.id.au>
Date: Sat, 7 Nov 2015 11:31:06 +0000
Subject: [PATCH] Fixed #19536 -- Included object-tools when
 ModelAdmin.has_add_permission() is False.

---
 .../admin/templates/admin/change_list.html    |  4 ++--
 docs/releases/1.10.txt                        |  4 ++++
 tests/admin_changelist/admin.py               |  3 +++
 tests/admin_changelist/tests.py               | 21 ++++++++++++++++---
 4 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 1ea38d8117..e0af704aa9 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -40,18 +40,18 @@
 {% block content %}
   <div id="content-main">
     {% block object-tools %}
-      {% if has_add_permission %}
         <ul class="object-tools">
           {% block object-tools-items %}
+            {% if has_add_permission %}
             <li>
               {% url cl.opts|admin_urlname:'add' as add_url %}
               <a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
                 {% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
               </a>
             </li>
+            {% endif %}
           {% endblock %}
         </ul>
-      {% endif %}
     {% endblock %}
     {% if cl.formset.errors %}
         <p class="errornote">
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index 31e15663ba..34294e901a 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -47,6 +47,10 @@ Minor features
   classes on inline fieldsets. Inlines with a ``collapse`` class will be
   initially collapsed and their header will have a small "show" link.
 
+* If a user doesn't have the add permission, the ``object-tools`` block on a
+  model's changelist will now be rendered (without the add button, of course).
+  This makes it easier to add custom tools in this case.
+
 :mod:`django.contrib.admindocs`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index 4ca5ae587a..51d183dfdb 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -22,6 +22,9 @@ class EventAdmin(admin.ModelAdmin):
     def event_date_func(self, event):
         return event.date
 
+    def has_add_permission(self, request):
+        return False
+
 site.register(Event, EventAdmin)
 
 
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 93910dc3b0..81bf3bdfc6 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -21,9 +21,10 @@ from .admin import (
     BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin,
     CustomPaginationAdmin, CustomPaginator, DynamicListDisplayChildAdmin,
     DynamicListDisplayLinksChildAdmin, DynamicListFilterChildAdmin,
-    DynamicSearchFieldsChildAdmin, EmptyValueChildAdmin, FilteredChildAdmin,
-    GroupAdmin, InvitationAdmin, NoListDisplayLinksParentAdmin, ParentAdmin,
-    QuartetAdmin, SwallowAdmin, site as custom_site,
+    DynamicSearchFieldsChildAdmin, EmptyValueChildAdmin, EventAdmin,
+    FilteredChildAdmin, GroupAdmin, InvitationAdmin,
+    NoListDisplayLinksParentAdmin, ParentAdmin, QuartetAdmin, SwallowAdmin,
+    site as custom_site,
 )
 from .models import (
     Band, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser, Event,
@@ -761,6 +762,20 @@ class ChangeListTests(TestCase):
                 list(real_page_range),
             )
 
+    def test_object_tools_displayed_no_add_permission(self):
+        """
+        When ModelAdmin.has_add_permission() returns False, the object-tools
+        block is still shown.
+        """
+        superuser = self._create_superuser('superuser')
+        m = EventAdmin(Event, custom_site)
+        request = self._mocked_authenticated_request('/event/', superuser)
+        self.assertFalse(m.has_add_permission(request))
+        response = m.changelist_view(request)
+        self.assertIn('<ul class="object-tools">', response.rendered_content)
+        # The "Add" button inside the object-tools shouldn't appear.
+        self.assertNotIn('Add', response.rendered_content)
+
 
 class AdminLogNodeTestCase(TestCase):