From 30610719d58502233d1e916eafbbbe69409a539f Mon Sep 17 00:00:00 2001
From: Malcolm Tredinnick <malcolm.tredinnick@gmail.com>
Date: Fri, 10 Sep 2010 16:56:36 +0000
Subject: [PATCH] Adding related objects in the admin (via popup) respects user
 permissions. Patch from SmileyChris. Fixed #1035.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13708 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/contrib/admin/options.py            |  8 +++++++-
 django/contrib/admin/widgets.py            |  9 +++++++--
 tests/regressiontests/admin_views/tests.py | 22 ++++++++++++++++++++++
 3 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 3aeb9d88a0..3b6e2b794e 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -107,7 +107,13 @@ class BaseModelAdmin(object):
             # rendered output. formfield can be None if it came from a
             # OneToOneField with parent_link=True or a M2M intermediary.
             if formfield and db_field.name not in self.raw_id_fields:
-                formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
+                related_modeladmin = self.admin_site._registry.get(
+                                                            db_field.rel.to)
+                can_add_related = bool(related_modeladmin and
+                            related_modeladmin.has_add_permission(request))
+                formfield.widget = widgets.RelatedFieldWidgetWrapper(
+                            formfield.widget, db_field.rel, self.admin_site,
+                            can_add_related=can_add_related)
 
             return formfield
 
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 1d321d0620..134effc74e 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -205,13 +205,18 @@ class RelatedFieldWidgetWrapper(forms.Widget):
     This class is a wrapper to a given widget to add the add icon for the
     admin interface.
     """
-    def __init__(self, widget, rel, admin_site):
+    def __init__(self, widget, rel, admin_site, can_add_related=None):
         self.is_hidden = widget.is_hidden
         self.needs_multipart_form = widget.needs_multipart_form
         self.attrs = widget.attrs
         self.choices = widget.choices
         self.widget = widget
         self.rel = rel
+        # Backwards compatible check for whether a user can add related
+        # objects.
+        if can_add_related is None:
+            can_add_related = rel_to in self.admin_site._registry
+        self.can_add_related = can_add_related
         # so we can check if the related object is registered with this AdminSite
         self.admin_site = admin_site
 
@@ -236,7 +241,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
             related_url = '%s%s/%s/add/' % info
         self.widget.choices = self.choices
         output = [self.widget.render(name, value, *args, **kwargs)]
-        if rel_to in self.admin_site._registry: # If the related object has an admin interface:
+        if self.can_add_related:
             # TODO: "id_" is hard-coded here. This should instead use the correct
             # API to determine the ID dynamically.
             output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index f76765236e..80319f0329 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -604,6 +604,28 @@ class AdminViewPermissionsTest(TestCase):
                         'Plural error message not found in response to post with multiple errors.')
         self.client.get('/test_admin/admin/logout/')
 
+    def testConditionallyShowAddSectionLink(self):
+        """
+        The foreign key widget should only show the "add related" button if the
+        user has permission to add that related item.
+        """
+        # Set up and log in user.
+        url = '/test_admin/admin/admin_views/article/add/'
+        add_link_text = ' class="add-another"'
+        self.client.get('/test_admin/admin/')
+        self.client.post('/test_admin/admin/', self.adduser_login)
+        # The add user can't add sections yet, so they shouldn't see the "add
+        # section" link.
+        response = self.client.get(url)
+        self.assertNotContains(response, add_link_text)
+        # Allow the add user to add sections too. Now they can see the "add
+        # section" link.
+        add_user = User.objects.get(username='adduser')
+        perm = get_perm(Section, Section._meta.get_add_permission())
+        add_user.user_permissions.add(perm)
+        response = self.client.get(url)
+        self.assertContains(response, add_link_text)
+
     def testCustomModelAdminTemplates(self):
         self.client.get('/test_admin/admin/')
         self.client.post('/test_admin/admin/', self.super_login)