summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Sandford <nick@sandford.id.au>2014-07-25 13:07:04 +0100
committerTim Graham <timograham@gmail.com>2014-07-31 08:07:28 -0400
commit9d9f0acd7e245c9f9c30727a003666618754c924 (patch)
tree719c97995bfa06038e3e72efbb0c106f3e73861d
parent9a922dcad1e50afc91329009e8689e5c08c2a1bf (diff)
downloaddjango-9d9f0acd7e245c9f9c30727a003666618754c924.tar.gz
Fixed #13163 -- Added ability to show change links on inline objects in admin.
Thanks DrMeers for the suggestion.
-rw-r--r--django/contrib/admin/options.py2
-rw-r--r--django/contrib/admin/sites.py6
-rw-r--r--django/contrib/admin/static/admin/css/base.css2
-rw-r--r--django/contrib/admin/templates/admin/edit_inline/stacked.html5
-rw-r--r--django/contrib/admin/templates/admin/edit_inline/tabular.html7
-rw-r--r--docs/ref/contrib/admin/index.txt7
-rw-r--r--docs/releases/1.8.txt4
-rw-r--r--tests/admin_inlines/admin.py3
-rw-r--r--tests/admin_inlines/tests.py36
-rw-r--r--tests/admin_ordering/tests.py10
-rw-r--r--tests/admin_registration/tests.py9
-rw-r--r--tests/generic_inline_admin/tests.py3
12 files changed, 81 insertions, 13 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 16be58e15d..f304be0b81 100644
--- a/django/contrib/admin/options.py
+++ b/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
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 84778849e9..666f1da1fc 100644
--- a/django/contrib/admin/sites.py
+++ b/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.
diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css
index ca9fa501ea..3042ae0d62 100644
--- a/django/contrib/admin/static/admin/css/base.css
+++ b/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;
}
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html
index 79c052c6c5..9d58146588 100644
--- a/django/contrib/admin/templates/admin/edit_inline/stacked.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html
index 4548af45dc..c1a07cf76f 100644
--- a/django/contrib/admin/templates/admin/edit_inline/tabular.html
+++ b/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 %}
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index d878058071..958551d660 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/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
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 690480a4e1..d4943fd0da 100644
--- a/docs/releases/1.8.txt
+++ b/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`
^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index b46a045052..4fc7478132 100644
--- a/tests/admin_inlines/admin.py
+++ b/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])
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 56f006c441..9ed54fd091 100644
--- a/tests/admin_inlines/tests.py
+++ b/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")
diff --git a/tests/admin_ordering/tests.py b/tests/admin_ordering/tests.py
index bc08adee12..1bd06e6e5a 100644
--- a/tests/admin_ordering/tests.py
+++ b/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)
diff --git a/tests/admin_registration/tests.py b/tests/admin_registration/tests.py
index e5947e0a93..98a509fc42 100644
--- a/tests/admin_registration/tests.py
+++ b/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):
"""
diff --git a/tests/generic_inline_admin/tests.py b/tests/generic_inline_admin/tests.py
index 99459b9257..dafd5d3334 100644
--- a/tests/generic_inline_admin/tests.py
+++ b/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)