diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index cae33cc..99df955 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -222,7 +222,7 @@ def items_for_result(cl, result):
             url = cl.url_for_result(result)
             # Convert the pk to something that can be used in Javascript.
             # Problem cases are long ints (23L) and non-ASCII strings.
-            result_id = repr(force_unicode(getattr(result, pk)))[1:]
+            result_id = repr(force_unicode(getattr(result, cl.to_field)))[1:]
             yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
                 (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
         else:
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 0a5ab37..a6a206d 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -24,6 +24,7 @@ ORDER_VAR = 'o'
 ORDER_TYPE_VAR = 'ot'
 PAGE_VAR = 'p'
 SEARCH_VAR = 'q'
+TO_FIELD_VAR = 't'
 IS_POPUP_VAR = 'pop'
 ERROR_FLAG = 'e'
 
@@ -52,9 +53,12 @@ class ChangeList(object):
             self.page_num = 0
         self.show_all = ALL_VAR in request.GET
         self.is_popup = IS_POPUP_VAR in request.GET
+        self.to_field = request.GET.get(TO_FIELD_VAR)
         self.params = dict(request.GET.items())
         if PAGE_VAR in self.params:
             del self.params[PAGE_VAR]
+        if TO_FIELD_VAR in self.params:
+            del self.params[TO_FIELD_VAR]
         if ERROR_FLAG in self.params:
             del self.params[ERROR_FLAG]
 
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 15720a7..50e55dc 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -41,20 +41,20 @@ class FilteredSelectMultiple(forms.SelectMultiple):
 
 class AdminDateWidget(forms.TextInput):
     class Media:
-        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
+        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
               settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
-        
+
     def __init__(self, attrs={}):
         super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
 
 class AdminTimeWidget(forms.TextInput):
     class Media:
-        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
+        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
               settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
 
     def __init__(self, attrs={}):
         super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
-    
+
 class AdminSplitDateTime(forms.SplitDateTimeWidget):
     """
     A SplitDateTime Widget that has some admin-specific styling.
@@ -86,7 +86,7 @@ class AdminFileWidget(forms.FileInput):
     """
     def __init__(self, attrs={}):
         super(AdminFileWidget, self).__init__(attrs)
-        
+
     def render(self, name, value, attrs=None):
         output = []
         if value and hasattr(value, "url"):
@@ -105,11 +105,13 @@ class ForeignKeyRawIdWidget(forms.TextInput):
         super(ForeignKeyRawIdWidget, self).__init__(attrs)
 
     def render(self, name, value, attrs=None):
+        from django.contrib.admin.views.main import TO_FIELD_VAR
         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
+        params = {}
         if self.rel.limit_choices_to:
-            url = '?' + '&amp;'.join(['%s=%s' % (k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()])
-        else:
-            url = ''
+            params.update(dict([(k, ','.join(v)) for k, v in self.rel.limit_choices_to.items()]))
+        params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
+        url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
         if not attrs.has_key('class'):
           attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
@@ -121,11 +123,12 @@ class ForeignKeyRawIdWidget(forms.TextInput):
         if value:
             output.append(self.label_for_value(value))
         return mark_safe(u''.join(output))
-    
+
     def label_for_value(self, value):
-        return '&nbsp;<strong>%s</strong>' % \
-            truncate_words(self.rel.to.objects.get(pk=value), 14)
-            
+        key = self.rel.get_related_field().name
+        obj = self.rel.to.objects.get(**{key: value})
+        return '&nbsp;<strong>%s</strong>' % truncate_words(obj, 14)
+
 class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
     """
     A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
@@ -133,7 +136,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
     """
     def __init__(self, rel, attrs=None):
         super(ManyToManyRawIdWidget, self).__init__(rel, attrs)
-    
+
     def render(self, name, value, attrs=None):
         attrs['class'] = 'vManyToManyRawIdAdminField'
         if value:
@@ -141,7 +144,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
         else:
             value = ''
         return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
-    
+
     def label_for_value(self, value):
         return ''
 
@@ -152,7 +155,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
         if value:
             return [value]
         return None
-    
+
     def _has_changed(self, initial, data):
         if initial is None:
             initial = []
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index ef19477..679f63c 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -691,7 +691,12 @@ class ForeignKey(RelatedField, Field):
         setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
 
     def formfield(self, **kwargs):
-        defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
+        defaults = {
+            'form_class': forms.ModelChoiceField,
+            'queryset': self.rel.to._default_manager.complex_filter(
+                                                    self.rel.limit_choices_to),
+            'to_field_name': self.rel.field_name,
+        }
         defaults.update(kwargs)
         return super(ForeignKey, self).formfield(**defaults)
 
diff --git a/django/forms/models.py b/django/forms/models.py
index c153512..3e5d9f8 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -548,14 +548,21 @@ class ModelChoiceIterator(object):
         if self.field.cache_choices:
             if self.field.choice_cache is None:
                 self.field.choice_cache = [
-                    (obj.pk, self.field.label_from_instance(obj))
-                    for obj in self.queryset.all()
+                    self.choice(obj) for obj in self.queryset.all()
                 ]
             for choice in self.field.choice_cache:
                 yield choice
         else:
             for obj in self.queryset.all():
-                yield (obj.pk, self.field.label_from_instance(obj))
+                yield self.choice(obj)
+
+    def choice(self, obj):
+        if self.field.to_field_name:
+            key = getattr(obj, self.field.to_field_name)
+        else:
+            key = obj.pk
+        return (key, self.field.label_from_instance(obj))
+
 
 class ModelChoiceField(ChoiceField):
     """A ChoiceField whose choices are a model QuerySet."""
@@ -568,7 +575,7 @@ class ModelChoiceField(ChoiceField):
 
     def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
                  required=True, widget=None, label=None, initial=None,
-                 help_text=None, *args, **kwargs):
+                 help_text=None, to_field_name=None, *args, **kwargs):
         self.empty_label = empty_label
         self.cache_choices = cache_choices
 
@@ -578,6 +585,7 @@ class ModelChoiceField(ChoiceField):
                        *args, **kwargs)
         self.queryset = queryset
         self.choice_cache = None
+        self.to_field_name = to_field_name
 
     def _get_queryset(self):
         return self._queryset
@@ -620,7 +628,8 @@ class ModelChoiceField(ChoiceField):
         if value in EMPTY_VALUES:
             return None
         try:
-            value = self.queryset.get(pk=value)
+            key = self.to_field_name or 'pk'
+            value = self.queryset.get(**{key: value})
         except self.queryset.model.DoesNotExist:
             raise ValidationError(self.error_messages['invalid_choice'])
         return value
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 252d46d..9c9ac82 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -78,7 +78,7 @@ class BetterWriter(Writer):
 class WriterProfile(models.Model):
     writer = models.OneToOneField(Writer, primary_key=True)
     age = models.PositiveIntegerField()
-    
+
     def __unicode__(self):
         return "%s is %s" % (self.writer, self.age)
 
@@ -136,7 +136,14 @@ class Price(models.Model):
 class ArticleStatus(models.Model):
     status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
 
+class Inventory(models.Model):
+   barcode = models.PositiveIntegerField(unique=True)
+   parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
+   name = models.CharField(blank=False, max_length=20)
 
+   def __unicode__(self):
+      return self.name
+      
 __test__ = {'API_TESTS': """
 >>> from django import forms
 >>> from django.forms.models import ModelForm, model_to_dict
@@ -1134,7 +1141,7 @@ u'1,2,3'
 Traceback (most recent call last):
 ...
 ValidationError: [u'Enter only digits separated by commas.']
->>> f.clean(',,,,') 
+>>> f.clean(',,,,')
 u',,,,'
 >>> f.clean('1.2')
 Traceback (most recent call last):
@@ -1203,4 +1210,36 @@ Traceback (most recent call last):
 ...
 ValidationError: [u'Select a valid choice. z is not one of the available choices.']
 
+# Foreign keys which use to_field #############################################
+
+>>> apple = Inventory.objects.create(barcode=86, name='Apple')
+>>> pear = Inventory.objects.create(barcode=22, name='Pear')
+>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
+
+>>> field = ModelChoiceField(Inventory.objects.all(), to_field_name='barcode')
+>>> for choice in field.choices:
+...     print choice
+(u'', u'---------')
+(86, u'Apple')
+(22, u'Pear')
+(87, u'Core')
+
+>>> class InventoryForm(ModelForm):
+...     class Meta:
+...         model = Inventory
+>>> form = InventoryForm(instance=core)
+>>> print form['parent']
+<select name="parent" id="id_parent">
+<option value="">---------</option>
+<option value="86" selected="selected">Apple</option>
+<option value="22">Pear</option>
+<option value="87">Core</option>
+</select>
+
+>>> data = model_to_dict(core)
+>>> data['parent'] = '22'
+>>> form = InventoryForm(data=data, instance=core)
+>>> core = form.save()
+>>> core.parent
+<Inventory: Pear>
 """}
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
index e178a75..2bc9075 100644
--- a/tests/regressiontests/admin_widgets/models.py
+++ b/tests/regressiontests/admin_widgets/models.py
@@ -5,14 +5,14 @@ from django.core.files.storage import default_storage
 
 class Member(models.Model):
     name = models.CharField(max_length=100)
-    
+
     def __unicode__(self):
         return self.name
 
 class Band(models.Model):
     name = models.CharField(max_length=100)
     members = models.ManyToManyField(Member)
-    
+
     def __unicode__(self):
         return self.name
 
@@ -20,10 +20,18 @@ class Album(models.Model):
     band = models.ForeignKey(Band)
     name = models.CharField(max_length=100)
     cover_art = models.FileField(upload_to='albums')
-    
+
     def __unicode__(self):
         return self.name
 
+class Inventory(models.Model):
+   barcode = models.PositiveIntegerField(unique=True)
+   parent = models.ForeignKey('self', to_field='barcode', blank=True, null=True)
+   name = models.CharField(blank=False, max_length=20)
+
+   def __unicode__(self):
+      return self.name
+
 __test__ = {'WIDGETS_TESTS': """
 >>> from datetime import datetime
 >>> from django.utils.html import escape, conditional_escape
@@ -84,6 +92,15 @@ True
 >>> w._has_changed([1, 2], [u'1', u'3'])
 True
 
+# Check that ForeignKeyRawIdWidget works with fields which aren't related to
+# the model's primary key.
+>>> apple = Inventory.objects.create(barcode=86, name='Apple')
+>>> pear = Inventory.objects.create(barcode=22, name='Pear')
+>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
+>>> rel = Inventory._meta.get_field('parent').rel
+>>> w = ForeignKeyRawIdWidget(rel)
+>>> print w.render('test', core.parent_id, attrs={})
+<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="/admin_media/img/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Apple</strong>
 """ % {
     'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
     'STORAGE_URL': default_storage.url(''),
