瀏覽代碼

Fixed #13163 -- Added ability to show change links on inline objects in admin.

Thanks DrMeers for the suggestion.
Nick Sandford 10 年之前
父節點
當前提交
9d9f0acd7e

+ 2 - 0
django/contrib/admin/options.py

@@ -1721,6 +1721,7 @@ class InlineModelAdmin(BaseModelAdmin):
     verbose_name = None
     verbose_name_plural = None
     can_delete = True
+    show_change_link = False
 
     checks_class = InlineModelAdminChecks
 
@@ -1728,6 +1729,7 @@ class InlineModelAdmin(BaseModelAdmin):
         self.admin_site = admin_site
         self.parent_model = parent_model
         self.opts = self.model._meta
+        self.has_registered_model = admin_site.is_registered(self.model)
         super(InlineModelAdmin, self).__init__()
         if self.verbose_name is None:
             self.verbose_name = self.model._meta.verbose_name

+ 6 - 0
django/contrib/admin/sites.py

@@ -114,6 +114,12 @@ class AdminSite(object):
                 raise NotRegistered('The model %s is not registered' % model.__name__)
             del self._registry[model]
 
+    def is_registered(self, model):
+        """
+        Check if a model class is registered with this `AdminSite`.
+        """
+        return model in self._registry
+
     def add_action(self, action, name=None):
         """
         Register an action to be available globally.

+ 1 - 1
django/contrib/admin/static/admin/css/base.css

@@ -632,7 +632,7 @@ div.breadcrumbs {
     background: url(../img/icon_addlink.gif) 0 .2em no-repeat;
 }
 
-.changelink {
+.changelink, .inlinechangelink {
     padding-left: 12px;
     background: url(../img/icon_changelink.gif) 0 .2em no-repeat;
 }

+ 3 - 2
django/contrib/admin/templates/admin/edit_inline/stacked.html

@@ -1,11 +1,12 @@
-{% load i18n admin_static %}
+{% load i18n admin_urls admin_static %}
 <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
   <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
 {{ inline_admin_formset.formset.management_form }}
 {{ inline_admin_formset.formset.non_form_errors }}
 
 {% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
-  <h3><b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
+  <h3><b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>{% endif %}
+{% else %}#{{ forloop.counter }}{% endif %}</span>
       {% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %}
     {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
   </h3>

+ 5 - 2
django/contrib/admin/templates/admin/edit_inline/tabular.html

@@ -1,4 +1,4 @@
-{% load i18n admin_static admin_modify %}
+{% load i18n admin_urls admin_static admin_modify %}
 <div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
   <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
 {{ inline_admin_formset.formset.management_form }}
@@ -26,7 +26,10 @@
              id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
         <td class="original">
           {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
-          {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
+          {% if inline_admin_form.original %}
+          {{ inline_admin_form.original }}
+          {% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}<a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>{% endif %}
+          {% endif %}
           {% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %}
             </p>{% endif %}
           {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}

+ 7 - 0
docs/ref/contrib/admin/index.txt

@@ -2025,6 +2025,13 @@ The ``InlineModelAdmin`` class adds:
     Specifies whether or not inline objects can be deleted in the inline.
     Defaults to ``True``.
 
+.. attribute:: InlineModelAdmin.show_change_link
+
+    .. versionadded:: 1.8
+
+    Specifies whether or not inline objects that can be changed in the
+    admin have a link to the change form. Defaults to ``False``.
+
 .. method:: InlineModelAdmin.get_formset(request, obj=None, **kwargs)
 
     Returns a :class:`~django.forms.models.BaseInlineFormSet` class for use in

+ 4 - 0
docs/releases/1.8.txt

@@ -35,6 +35,10 @@ Minor features
   :meth:`~django.contrib.admin.ModelAdmin.has_module_permission`
   method to allow limiting access to the module on the admin index page.
 
+* :class:`~django.contrib.admin.InlineModelAdmin` now has an attribute
+  :attr:`~django.contrib.admin.InlineModelAdmin.show_change_link` that
+  supports showing a link to an inline object's change form.
+
 :mod:`django.contrib.auth`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 

+ 3 - 0
tests/admin_inlines/admin.py

@@ -90,10 +90,12 @@ class TitleInline(admin.TabularInline):
 
 class Inner4StackedInline(admin.StackedInline):
     model = Inner4Stacked
+    show_change_link = True
 
 
 class Inner4TabularInline(admin.TabularInline):
     model = Inner4Tabular
+    show_change_link = True
 
 
 class Holder4Admin(admin.ModelAdmin):
@@ -212,3 +214,4 @@ site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2In
 site.register(BinaryTree, inlines=[BinaryTreeAdmin])
 site.register(ExtraTerrestrial, inlines=[SightingInline])
 site.register(SomeParentModel, inlines=[SomeChildModelInline])
+site.register([Question, Inner4Stacked, Inner4Tabular])

+ 35 - 1
tests/admin_inlines/tests.py

@@ -13,7 +13,9 @@ from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
     OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
     ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
     Sighting, Novel, Chapter, FootNote, BinaryTree, SomeParentModel,
-    SomeChildModel)
+    SomeChildModel, Poll, Question, Inner4Stacked, Inner4Tabular, Holder4)
+
+INLINE_CHANGELINK_HTML = 'class="inlinechangelink">Change</a>'
 
 
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
@@ -311,6 +313,38 @@ class TestInline(TestCase):
             count=1
         )
 
+    def test_inlines_show_change_link_registered(self):
+        "Inlines `show_change_link` for registered models when enabled."
+        holder = Holder4.objects.create(dummy=1)
+        item1 = Inner4Stacked.objects.create(dummy=1, holder=holder)
+        item2 = Inner4Tabular.objects.create(dummy=1, holder=holder)
+        items = (
+            ('inner4stacked', item1.pk),
+            ('inner4tabular', item2.pk),
+        )
+        response = self.client.get('/admin/admin_inlines/holder4/%s/' % holder.pk)
+        self.assertTrue(response.context['inline_admin_formset'].opts.has_registered_model)
+        for model, pk in items:
+            url = '/admin/admin_inlines/%s/%s/' % (model, pk)
+            self.assertContains(response, '<a href="%s" %s' % (url, INLINE_CHANGELINK_HTML))
+
+    def test_inlines_show_change_link_unregistered(self):
+        "Inlines `show_change_link` disabled for unregistered models."
+        parent = ParentModelWithCustomPk.objects.create(my_own_pk="foo", name="Foo")
+        ChildModel1.objects.create(my_own_pk="bar", name="Bar", parent=parent)
+        ChildModel2.objects.create(my_own_pk="baz", name="Baz", parent=parent)
+        response = self.client.get('/admin/admin_inlines/parentmodelwithcustompk/foo/')
+        self.assertFalse(response.context['inline_admin_formset'].opts.has_registered_model)
+        self.assertNotContains(response, INLINE_CHANGELINK_HTML)
+
+    def test_tabular_inline_show_change_link_false_registered(self):
+        "Inlines `show_change_link` disabled by default."
+        poll = Poll.objects.create(name="New poll")
+        Question.objects.create(poll=poll)
+        response = self.client.get('/admin/admin_inlines/poll/%s/' % poll.pk)
+        self.assertTrue(response.context['inline_admin_formset'].opts.has_registered_model)
+        self.assertNotContains(response, INLINE_CHANGELINK_HTML)
+
 
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
                    ROOT_URLCONF="admin_inlines.urls")

+ 5 - 5
tests/admin_ordering/tests.py

@@ -44,7 +44,7 @@ class TestAdminOrdering(TestCase):
         The default ordering should be by name, as specified in the inner Meta
         class.
         """
-        ma = ModelAdmin(Band, None)
+        ma = ModelAdmin(Band, admin.site)
         names = [b.name for b in ma.get_queryset(request)]
         self.assertListEqual(['Aerosmith', 'Radiohead', 'Van Halen'], names)
 
@@ -55,7 +55,7 @@ class TestAdminOrdering(TestCase):
         """
         class BandAdmin(ModelAdmin):
             ordering = ('rank',)  # default ordering is ('name',)
-        ma = BandAdmin(Band, None)
+        ma = BandAdmin(Band, admin.site)
         names = [b.name for b in ma.get_queryset(request)]
         self.assertListEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names)
 
@@ -67,7 +67,7 @@ class TestAdminOrdering(TestCase):
         other_user = User.objects.create(username='other')
         request = self.request_factory.get('/')
         request.user = super_user
-        ma = DynOrderingBandAdmin(Band, None)
+        ma = DynOrderingBandAdmin(Band, admin.site)
         names = [b.name for b in ma.get_queryset(request)]
         self.assertListEqual(['Radiohead', 'Van Halen', 'Aerosmith'], names)
         request.user = other_user
@@ -94,7 +94,7 @@ class TestInlineModelAdminOrdering(TestCase):
         The default ordering should be by name, as specified in the inner Meta
         class.
         """
-        inline = SongInlineDefaultOrdering(self.band, None)
+        inline = SongInlineDefaultOrdering(self.band, admin.site)
         names = [s.name for s in inline.get_queryset(request)]
         self.assertListEqual(['Dude (Looks Like a Lady)', 'Jaded', 'Pink'], names)
 
@@ -102,7 +102,7 @@ class TestInlineModelAdminOrdering(TestCase):
         """
         Let's check with ordering set to something different than the default.
         """
-        inline = SongInlineNewOrdering(self.band, None)
+        inline = SongInlineNewOrdering(self.band, admin.site)
         names = [s.name for s in inline.get_queryset(request)]
         self.assertListEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names)
 

+ 9 - 0
tests/admin_registration/tests.py

@@ -70,6 +70,15 @@ class TestRegistration(TestCase):
         """
         self.assertRaises(ImproperlyConfigured, self.site.register, Location)
 
+    def test_is_registered_model(self):
+        "Checks for registered models should return true."
+        self.site.register(Person)
+        self.assertTrue(self.site.is_registered(Person))
+
+    def test_is_registered_not_registered_model(self):
+        "Checks for unregistered models should return false."
+        self.assertFalse(self.site.is_registered(Person))
+
 
 class TestRegistrationDecorator(TestCase):
     """

+ 1 - 2
tests/generic_inline_admin/tests.py

@@ -256,8 +256,7 @@ class GenericInlineAdminWithUniqueTogetherTest(TestCase):
 class NoInlineDeletionTest(TestCase):
 
     def test_no_deletion(self):
-        fake_site = object()
-        inline = MediaPermanentInline(EpisodePermanent, fake_site)
+        inline = MediaPermanentInline(EpisodePermanent, admin_site)
         fake_request = object()
         formset = inline.get_formset(fake_request)
         self.assertFalse(formset.can_delete)