diff --git a/AUTHORS b/AUTHORS
index 6faa171..48433b6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -357,6 +357,7 @@ answer newbie questions, and generally made Django that much better:
     michael.mcewan@gmail.com
     Paul McLanahan <paul@mclanahan.net>
     Tobias McNulty <http://www.caktusgroup.com/blog>
+    Simon Meers <http://simonmeers.com/>
     Zain Memon
     Christian Metts
     michal@plovarna.cz
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 04a3492..0af2375 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -1,6 +1,7 @@
 from django import forms
 from django.contrib.admin.util import (flatten_fieldsets, lookup_field,
-    display_for_field, label_for_field, help_text_for_field)
+    display_for_field, label_for_field, help_text_for_field,
+    get_related_object, get_changelink_url)
 from django.contrib.admin.templatetags.admin_static import static
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
@@ -189,6 +190,17 @@ class AdminReadonlyField(object):
                     result_repr = display_for_field(value, f)
         return conditional_escape(result_repr)
 
+    def change_url(self):
+        """ Return admin change URL for field if applicable. """
+        field, original, model_admin = \
+            self.field['field'], self.form.instance, self.model_admin
+        try:
+            f, attr, value = lookup_field(field, original, model_admin)
+        except (AttributeError, ValueError, ObjectDoesNotExist):
+            return None
+        return get_changelink_url(value, self.model_admin.admin_site)
+
+
 class InlineAdminFormSet(object):
     """
     A wrapper around an inline formset for use in the admin system.
@@ -254,6 +266,10 @@ class InlineAdminForm(AdminForm):
         if original is not None:
             self.original_content_type_id = ContentType.objects.get_for_model(original).pk
         self.show_url = original and hasattr(original, 'get_absolute_url')
+        if original is not None and model_admin is not None:
+            self.admin_url = get_changelink_url(original, model_admin.admin_site)
+        else:
+            self.admin_url = None
         super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
             readonly_fields, model_admin)
 
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html
index 476e261..cf6723b 100644
--- a/django/contrib/admin/templates/admin/edit_inline/stacked.html
+++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html
@@ -6,6 +6,7 @@
 
 {% for inline_admin_form in inline_admin_formset %}<div class="inline-related{% 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|title }}:</b>&nbsp;<span class="inline_label">{% if inline_admin_form.original %}{{ inline_admin_form.original }}{% else %}#{{ forloop.counter }}{% endif %}</span>
+    {% if inline_admin_form.admin_url %}<a href="{{ inline_admin_form.admin_url }}" class="inline-changelink changelink">{% trans "edit separately" %}</a>&nbsp;&nbsp;{% endif %}
     {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% 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 71b097e..6741749 100644
--- a/django/contrib/admin/templates/admin/edit_inline/tabular.html
+++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html
@@ -27,6 +27,8 @@
         <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.admin_url %}<a href="{{ inline_admin_form.admin_url }}" class="inline-changelink changelink">{% trans "edit separately" %}</a>&nbsp;&nbsp;{% endif %}
+
           {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
             </p>{% endif %}
           {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
@@ -46,7 +48,7 @@
             {% for field in line %}
               <td class="{{ field.field.name }}">
               {% if field.is_readonly %}
-                  <p>{{ field.contents }}</p>
+                <p>{{ field.contents }}{% if field.change_url %} <a href="{{ field.change_url }}" class="readonly-changelink changelink">{% trans "edit" %}</a>{% endif %}</p>
               {% else %}
                   {{ field.field.errors.as_ul }}
                   {{ field.field }}
diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html
index 6363aee..8720999 100644
--- a/django/contrib/admin/templates/admin/includes/fieldset.html
+++ b/django/contrib/admin/templates/admin/includes/fieldset.html
@@ -14,7 +14,7 @@
                     {% else %}
                         {{ field.label_tag }}
                         {% if field.is_readonly %}
-                            <p>{{ field.contents }}</p>
+                            <p>{{ field.contents }}{% if field.change_url %} <a href="{{ field.change_url }}" class="readonly-changelink changelink">Edit</a>{% endif %}</p>
                         {% else %}
                             {{ field.field }}
                         {% endif %}
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index 7204a12..2434c4f 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -8,8 +8,8 @@ from django.utils.html import escape
 from django.utils.safestring import mark_safe
 from django.utils.text import capfirst
 from django.utils.encoding import force_unicode, smart_unicode, smart_str
-from django.utils.translation import ungettext
-from django.core.urlresolvers import reverse
+from django.utils.translation import ungettext, ugettext
+from django.core.urlresolvers import reverse, NoReverseMatch
 
 
 def quote(s):
@@ -393,3 +393,41 @@ def get_limit_choices_to_from_path(model, path):
         return limit_choices_to # already a Q
     else:
         return models.Q(**limit_choices_to) # convert dict to Q
+
+def get_related_object(rel, value):
+    try:
+        return rel.to.objects.get(pk=value)
+    except:
+        return None
+
+def get_changelink_url(obj, admin_site):
+    """ If obj is registered in admin_site, return URL for change view. """
+    if admin_site is None:
+        # probably a ManyToManyRawIdWidget
+        return None
+    if obj.__class__ in admin_site._registry:
+        try:
+            view_name = '%s:%s_%s_change' % (
+                admin_site.name,
+                obj._meta.app_label,
+                obj._meta.object_name.lower())
+            return reverse(view_name, None, (quote(obj._get_pk_val()),))
+        except NoReverseMatch:
+            # should not happen, but fail silently just in case
+            pass
+    return None
+
+def get_changelink_html(obj, admin_site, show_name=False):
+    """ Return HTML link to change view for obj if registered in admin_site.
+
+    Optionally includes representation of object (%s) in link.
+    """
+    url = get_changelink_url(obj, admin_site)
+    if url is None:
+        return ''
+    if show_name:
+        text = ugettext(u'Edit %s') % force_unicode(obj)
+    else:
+        text = ugettext('Edit')
+    return (u' <a href="%(url)s" class="related-field-changelink changelink">'
+            '%(text)s</a>' % {'url': url, 'text': escape(text)})
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 0d1f2a9..8415566 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -4,6 +4,7 @@ Form Widget classes specific to the Django admin site.
 
 import copy
 from django import forms
+from django.contrib.admin.util import get_related_object, get_changelink_html
 from django.contrib.admin.templatetags.admin_static import static
 from django.core.urlresolvers import reverse
 from django.forms.widgets import RadioFieldRenderer
@@ -154,6 +155,10 @@ class ForeignKeyRawIdWidget(forms.TextInput):
             extra.append(u'<img src="%s" width="16" height="16" alt="%s" /></a>'
                             % (static('admin/img/selector-search.gif'), _('Lookup')))
         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
+
+        output.append(get_changelink_html(
+                get_related_object(self.rel, value),
+                self.admin_site, show_name=False))
         if value:
             output.append(self.label_for_value(value))
         return mark_safe(u''.join(output))
@@ -259,6 +264,9 @@ class RelatedFieldWidgetWrapper(forms.Widget):
                           % (related_url, name))
             output.append(u'<img src="%s" width="10" height="10" alt="%s"/></a>'
                           % (static('admin/img/icon_addlink.gif'), _('Add Another')))
+            output.append(get_changelink_html(
+                    get_related_object(self.rel, value),
+                    self.admin_site, show_name=True))
         return mark_safe(u''.join(output))
 
     def build_attrs(self, extra_attrs=None, **kwargs):
diff --git a/tests/regressiontests/admin_inlines/admin.py b/tests/regressiontests/admin_inlines/admin.py
index 4edd361..43d7232 100644
--- a/tests/regressiontests/admin_inlines/admin.py
+++ b/tests/regressiontests/admin_inlines/admin.py
@@ -109,6 +109,21 @@ class SottoCapoInline(admin.TabularInline):
     model = SottoCapo
 
 
+class IndividualInline(admin.StackedInline):
+    model = Individual
+    extra = 1
+
+
+class PhoneInline(admin.StackedInline):
+    model = Phone
+    extra = 1
+
+
+class EmailInline(admin.TabularInline):
+    model = Email
+    extra = 1
+
+
 site.register(TitleCollection, inlines=[TitleInline])
 # Test bug #12561 and #12778
 # only ModelAdmin media
@@ -124,3 +139,8 @@ site.register(Fashionista, inlines=[InlineWeakness])
 site.register(Holder4, Holder4Admin)
 site.register(Author, AuthorAdmin)
 site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline])
+
+site.register(Household, inlines=[IndividualInline, PhoneInline])
+site.register(Individual, inlines=[EmailInline])
+site.register(Email)
+# (Phone not registered)
diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
index 748280d..896b3d0 100644
--- a/tests/regressiontests/admin_inlines/models.py
+++ b/tests/regressiontests/admin_inlines/models.py
@@ -136,3 +136,23 @@ class Consigliere(models.Model):
 class SottoCapo(models.Model):
     name = models.CharField(max_length=100)
     capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+')
+
+
+# Test InlineAdminForm.admin_url:
+
+class Household(models.Model):
+    pass
+
+
+class Individual(models.Model):
+    household = models.ForeignKey(Household)
+
+
+class Phone(models.Model):
+    household = models.ForeignKey(Household)
+    number = models.CharField(max_length=64)
+
+
+class Email(models.Model):
+    individual = models.ForeignKey(Individual)
+    email = models.EmailField()
diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
index 955d620..6cf077b 100644
--- a/tests/regressiontests/admin_inlines/tests.py
+++ b/tests/regressiontests/admin_inlines/tests.py
@@ -1,12 +1,15 @@
+from django.core import urlresolvers
 from django.contrib.admin.helpers import InlineAdminForm
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 
 # local test models
+
 from models import (Holder, Inner, Holder2, Inner2, Holder3,
     Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child,
-    CapoFamiglia, Consigliere, SottoCapo)
-from admin import InnerInline
+    CapoFamiglia, Consigliere, SottoCapo, Household, Individual, Phone, Email)
+
+from admin import site, InnerInline
 
 
 class TestInline(TestCase):
@@ -196,3 +199,87 @@ class TestInlineAdminForm(TestCase):
         iaf = InlineAdminForm(None, None, {}, {}, joe)
         parent_ct = ContentType.objects.get_for_model(Parent)
         self.assertEqual(iaf.original.content_type, parent_ct)
+
+
+class TestAdminURL(TestCase):
+    urls = "regressiontests.admin_inlines.urls"
+    fixtures = ['admin-views-users.xml']
+    
+    def get_admin_url(self, obj_or_class, add=False):
+        params = [site.name, obj_or_class._meta.app_label,
+                obj_or_class._meta.module_name]
+        if add:
+            params.append("add")
+            args = ()
+        else:
+            params.append("change")
+            args = (obj_or_class.pk,)
+        return urlresolvers.reverse('%s:%s_%s_%s' % tuple(params), args=args)
+        
+    def setUp(self):
+        self.household = Household.objects.create()
+        self.individual = Individual.objects.create(household=self.household)
+        self.phone = Phone.objects.create(household=self.household,
+                                          number='1234567890')
+        self.email = Email.objects.create(individual=self.individual,
+                                          email='me@example.com')
+        
+        result = self.client.login(username='super', password='secret')
+        self.assertEqual(result, True)
+        
+    def tearDown(self):
+        self.client.logout()
+
+    def test_admin_url(self):
+        """
+        admin_url should be set for admin-registered inline models only.
+
+        Also check to ensure URLs look correct and only set on bound forms.
+        """
+
+        response = self.client.get(self.get_admin_url(self.household))
+        for inline_admin_fset in response.context[-1]['inline_admin_formsets']:
+            for inline_admin_form in inline_admin_fset:
+                if inline_admin_form.form._meta.model != Individual:
+                    self.assertFalse(
+                        getattr(inline_admin_form, 'admin_url', None),
+                        'admin_url set with unregistered model')
+                elif not inline_admin_form.original:
+                    self.assertFalse(
+                        getattr(inline_admin_form, 'admin_url', None),
+                        'admin_url set on unbound form!')
+                else:
+                    self.assertTrue(inline_admin_form.admin_url,
+                                    'admin_url not set')
+                    self.assertEqual(
+                        inline_admin_form.original, self.individual,
+                        'original is not expected object')
+                    self.assertEqual(
+                        inline_admin_form.admin_url,
+                        self.get_admin_url(inline_admin_form.original),
+                        'admin_url appears incorrect')
+
+    def test_link_rendering(self):
+        """ Confirm links are displayed where appropriate. """
+        LINK_CSS_CLASS = 'inline-changelink'
+        LINK_TEXT = 'edit separately'
+
+        # test StackedInline rendering
+        
+        response = self.client.get(self.get_admin_url(Household, 'add'))
+        self.assertNotContains(response, LINK_CSS_CLASS)
+        self.assertNotContains(response, LINK_TEXT)
+        
+        response = self.client.get(self.get_admin_url(self.household))
+        self.assertContains(response, LINK_CSS_CLASS)
+        self.assertContains(response, LINK_TEXT)
+
+        # test TabularInline rendering
+
+        response = self.client.get(self.get_admin_url(Individual, 'add'))
+        self.assertNotContains(response, LINK_CSS_CLASS)
+        self.assertNotContains(response, LINK_TEXT)
+        
+        response = self.client.get(self.get_admin_url(self.individual))
+        self.assertContains(response, LINK_CSS_CLASS)
+        self.assertContains(response, LINK_TEXT)    
diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py
index e4aae4f..3d47ab6 100644
--- a/tests/regressiontests/admin_views/admin.py
+++ b/tests/regressiontests/admin_views/admin.py
@@ -385,6 +385,14 @@ class PizzaAdmin(admin.ModelAdmin):
     readonly_fields = ('toppings',)
 
 
+class DonutOrderAdmin(admin.ModelAdmin):
+    raw_id_fields = ('donut',)
+
+
+class PizzaOrderAdmin(admin.ModelAdmin):
+    readonly_fields = ('pizza',)
+
+
 class WorkHourAdmin(admin.ModelAdmin):
     list_display = ('datum', 'employee')
     list_filter = ('employee',)
@@ -486,6 +494,7 @@ site.register(Post, PostAdmin)
 site.register(Gadget, GadgetAdmin)
 site.register(Villain)
 site.register(SuperVillain)
+site.register(Ambush)
 site.register(Plot)
 site.register(PlotDetails)
 site.register(CyclicOne)
@@ -512,7 +521,10 @@ site.register(Book, inlines=[ChapterInline])
 site.register(Promo)
 site.register(ChapterXtra1, ChapterXtra1Admin)
 site.register(Pizza, PizzaAdmin)
+site.register(PizzaOrder, PizzaOrderAdmin)
 site.register(Topping)
+site.register(Donut)
+site.register(DonutOrder, DonutOrderAdmin)
 site.register(Album, AlbumAdmin)
 site.register(Question)
 site.register(Answer)
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index bb8d026..f04dfea 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -433,6 +433,14 @@ class SuperSecretHideout(models.Model):
         return self.location
 
 
+class Ambush(models.Model):
+    hideout = models.ForeignKey(SecretHideout)
+    when = models.DateTimeField()
+
+    def __unicode__(self):
+        return u'Ambush %s at %s' % (self.hideout, self.when)
+
+
 class CyclicOne(models.Model):
     name = models.CharField(max_length=25)
     two = models.ForeignKey('CyclicTwo')
@@ -458,6 +466,30 @@ class Pizza(models.Model):
     toppings = models.ManyToManyField('Topping')
 
 
+class Donut(models.Model):
+    name = models.CharField(max_length=20)
+    toppings = models.ManyToManyField('Topping')
+
+    def __unicode__(self):
+        return self.name
+
+
+class DonutOrder(models.Model):
+    donut = models.ForeignKey(Donut)
+    quantity = models.IntegerField()
+
+    def __unicode__(self):
+        return '%s x %d' % (self.donut, self.quantity)
+
+
+class PizzaOrder(models.Model):
+    pizza = models.ForeignKey(Pizza)
+    quantity = models.IntegerField()
+
+    def __unicode__(self):
+        return '%s x %d' % (self.pizza, self.quantity)
+
+
 class Album(models.Model):
     owner = models.ForeignKey(User)
     title = models.CharField(max_length=30)
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index b6e7b9e..2761610 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -31,6 +31,7 @@ from django.utils.http import urlencode
 from django.utils import unittest
 
 # local test models
+
 from models import (Article, BarAccount, CustomArticle, EmptyModel,
     FooAccount, Gallery, ModelWithStringPrimaryKey,
     Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast,
@@ -38,7 +39,9 @@ from models import (Article, BarAccount, CustomArticle, EmptyModel,
     Category, Post, Plot, FunkyTag, Chapter, Book, Promo, WorkHour, Employee,
     Question, Answer, Inquisition, Actor, FoodDelivery,
     RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory,
-    ComplexSortedPerson, Parent, Child)
+    ComplexSortedPerson, Parent, Child, PlotDetails, Villain, SecretHideout,
+    Ambush, Pizza, PizzaOrder, Topping, Donut, DonutOrder)
+
 
 ERROR_MESSAGE = "Please enter the correct username and password \
 for a staff account. Note that both fields are case-sensitive."
@@ -3255,3 +3258,111 @@ class AdminCustomSaveRelatedTests(TestCase):
 
         self.assertEqual('Josh Stone', Parent.objects.latest('id').name)
         self.assertEqual([u'Catherine Stone', u'Paul Stone'], children_names)
+
+
+class RelatedLinksTest(TestCase):
+    urls = "regressiontests.admin_views.urls"
+    fixtures = ['admin-views-users.xml']
+    
+    def setUp(self):
+        self.client.login(username='super', password='secret')
+        self.RELATED_LINK_CSS_CLASS = 'related-field-changelink'
+        self.READONLY_LINK_CSS_CLASS = 'readonly-changelink'
+        self.black_knight = Villain.objects.create(name='Black Knight')
+        self.plot = Plot.objects.create(name='None shall <pass>',
+                                        team_leader=self.black_knight,
+                                        contact=self.black_knight)
+        self.plot_details = PlotDetails.objects.create(
+            plot=self.plot, details="I'll bite your legs off!")
+        self.hideout = SecretHideout.objects.create(villain=self.black_knight,
+                                                    location='forest')
+        self.ambush = Ambush.objects.create(hideout=self.hideout,
+                                            when=datetime.datetime.now())
+
+        self.pizza = Pizza.objects.create(name='Wafer-thin pizza')
+        self.topping = Topping.objects.create(name='Mint')
+        self.pizza.toppings.add(self.topping)
+        self.donut = Donut.objects.create(name='Wafer-thin donut')
+        self.donut.toppings.add(self.topping)
+        self.donut_order = DonutOrder.objects.create(donut=self.donut,
+                                                     quantity=1000000)
+        self.pizza_order = PizzaOrder.objects.create(pizza=self.pizza,
+                                                     quantity=50)
+        
+    def tearDown(self):
+        self.client.logout()
+
+    def test_foreignkey(self):
+        """ Confirm changelink appears for populated ForeignKey fields """
+        response = self.client.get('/test_admin/admin/admin_views/plot/add/')
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        response = self.client.get('/test_admin/admin/admin_views/plot/%d/' %
+                                   self.plot.pk)
+        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertContains(response, '>Edit %s</a>' % self.black_knight)
+
+    def test_onetoone(self):
+        """ Confirm changelink appears for populated OneToOne fields """
+        response = self.client.get(
+            '/test_admin/admin/admin_views/plotdetails/add/')
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/plotdetails/%d/' %
+            self.plot_details.pk)
+        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertContains(response, '>Edit %s</a>' % escape(self.plot))
+
+    def test_unregistered(self):
+        """ Confirm changelink does *not* appear for unregistered fields """
+        response = self.client.get(
+            '/test_admin/admin/admin_views/ambush/add/')
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/ambush/%d/' %
+            self.ambush.pk)
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        
+    def test_readonly(self):
+        """ Confirm changelink appears for populated readonly fields """
+        response = self.client.get(
+            '/test_admin/admin/admin_views/pizzaorder/add/')
+        self.assertNotContains(response, self.READONLY_LINK_CSS_CLASS)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/pizzaorder/%d/' %
+            self.pizza_order.pk)
+        self.assertContains(response, self.READONLY_LINK_CSS_CLASS)
+        self.assertContains(response, '>Edit</a>')
+
+    def test_manytomany(self):
+        """ Confirm changelinks do *not* appear for ManyToMany fields """
+        MULTIPLE_SELECT_STRING = '<select multiple="multiple"'
+        
+        response = self.client.get(
+            '/test_admin/admin/admin_views/pizza/add/')
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertNotContains(response, MULTIPLE_SELECT_STRING)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/pizza/%d/' % self.pizza.pk)
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertNotContains(response, MULTIPLE_SELECT_STRING)
+
+        # try non-readonly ManyToMany
+        response = self.client.get(
+            '/test_admin/admin/admin_views/donut/add/')
+        self.assertContains(response, MULTIPLE_SELECT_STRING)
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/donut/%d/' % self.donut.pk)
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertContains(response, MULTIPLE_SELECT_STRING)        
+
+    def test_raw_id(self):
+        """ Confirm links appears for populated raw_id_fields """
+        response = self.client.get(
+            '/test_admin/admin/admin_views/donutorder/add/')
+        self.assertNotContains(response, self.RELATED_LINK_CSS_CLASS)
+        response = self.client.get(
+            '/test_admin/admin/admin_views/donutorder/%d/' %
+            self.donut_order.pk)
+        self.assertContains(response, self.RELATED_LINK_CSS_CLASS)
+        self.assertContains(response, '>Edit</a>')
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
index a7bfe55..7d6702d 100644
--- a/tests/regressiontests/admin_widgets/tests.py
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -255,7 +255,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
         w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
         self.assertEqual(
             conditional_escape(w.render('test', band.pk, attrs={})),
-            '<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Linkin Park</strong>' % dict(admin_media_prefix(), bandpk=band.pk)
+            '<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <a href="/widget_admin/admin_widgets/band/%(bandpk)s/" class="related-field-changelink changelink">Edit</a>&nbsp;<strong>Linkin Park</strong>' % dict(admin_media_prefix(), bandpk=band.pk)
         )
 
     def test_relations_to_non_primary_key(self):
@@ -333,7 +333,7 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
 
         self.assertEqual(
             conditional_escape(w.render('test', [m1.pk])),
-            '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_media_prefix(), m1pk=m1.pk)
+            '<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a> <a href="/widget_admin/admin_widgets/member/%(m1pk)s/" class="related-field-changelink changelink">Edit</a>' % dict(admin_media_prefix(), m1pk=m1.pk)
         )
 
         self.assertEqual(w._has_changed(None, None), False)
