Index: django/contrib/admin/widgets.py
===================================================================
--- django/contrib/admin/widgets.py	(revision 17048)
+++ django/contrib/admin/widgets.py	(working copy)
@@ -130,6 +130,13 @@
 
     def render(self, name, value, attrs=None):
         rel_to = self.rel.to
+        # custom fields may need the to_python
+        # conversion in order to facilitate 
+        # unicode conversion
+        try:
+            value = self.rel.to._meta.get_field(self.rel.field_name).to_python(value)
+        except AttributeError:
+            pass# 'ManyToManyRel' object has no attribute 'field_name'
         if attrs is None:
             attrs = {}
         extra = []
@@ -139,7 +146,7 @@
                                     (rel_to._meta.app_label,
                                     rel_to._meta.module_name),
                                     current_app=self.admin_site.name)
-
+        
             params = self.url_parameters()
             if params:
                 url = u'?' + u'&amp;'.join([u'%s=%s' % (k, v) for k, v in params.items()])
@@ -248,6 +255,13 @@
 
     def render(self, name, value, *args, **kwargs):
         rel_to = self.rel.to
+        # custom fields may need the to_python
+        # conversion in order to facilitate 
+        # unicode conversion
+        try:
+            value = self.rel.to._meta.get_field(self.rel.field_name).to_python(value)
+        except AttributeError:
+            pass# 'ManyToManyRel' object has no attribute 'field_name'
         info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
         self.widget.choices = self.choices
         output = [self.widget.render(name, value, *args, **kwargs)]
Index: django/forms/forms.py
===================================================================
--- django/forms/forms.py	(revision 17048)
+++ django/forms/forms.py	(working copy)
@@ -341,6 +341,14 @@
                     hidden_widget = field.hidden_widget()
                     initial_value = hidden_widget.value_from_datadict(
                         self.data, self.files, initial_prefixed_name)
+                # custom fields may need the to_python
+                # conversion in order to facilitate 
+                # unicode conversion
+                if isinstance(initial_value,list):
+                    # ManyToManyField uses a list
+                    initial_value = [field.to_python(v) for v in initial_value]
+                else:
+                    initial_value = field.to_python(initial_value)
                 if field.widget._has_changed(initial_value, data_value):
                     self._changed_data.append(name)
         return self._changed_data
Index: tests/regressiontests/admin_inlines/admin.py
===================================================================
--- tests/regressiontests/admin_inlines/admin.py	(revision 17048)
+++ tests/regressiontests/admin_inlines/admin.py	(working copy)
@@ -108,7 +108,29 @@
 class SottoCapoInline(admin.TabularInline):
     model = SottoCapo
 
+class FAdminInline(admin.TabularInline):
+    model = F
+    fk_name = 'fk1'
+    extra = 0
 
+class EAdmin(admin.ModelAdmin):
+    inlines = (FAdminInline,)
+    
+class GAdmin(admin.ModelAdmin):
+    raw_id_fields = ("relation",)
+    
+class HAdmin(admin.ModelAdmin):
+    raw_id_fields = ("relation",)
+
+admin.site.register(A)
+admin.site.register(B)
+admin.site.register(C)
+admin.site.register(D)
+admin.site.register(E,EAdmin)
+admin.site.register(G,GAdmin)
+admin.site.register(H,HAdmin)
+
+
 site.register(TitleCollection, inlines=[TitleInline])
 # Test bug #12561 and #12778
 # only ModelAdmin media
Index: tests/regressiontests/admin_inlines/fields.py
===================================================================
--- tests/regressiontests/admin_inlines/fields.py	(revision 0)
+++ tests/regressiontests/admin_inlines/fields.py	(working copy)
@@ -0,0 +1,139 @@
+from django.db import models
+import uuid
+from sqlite3 import Binary
+
+class UUIDVersionError(Exception):
+    pass
+
+class UUIDField(models.Field):
+    
+    __metaclass__ = models.SubfieldBase
+    empty_strings_allowed = False
+
+    def __init__(self, primary_key=False, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
+        if primary_key and not auto:
+            auto = True
+        if auto:
+            kwargs['blank'] = True
+            kwargs.setdefault('editable', False)
+        self.auto = auto
+        self.version = version
+        if version == 1:
+            self.node, self.clock_seq = node, clock_seq
+        elif version == 3 or version == 5:
+            self.namespace, self.name = namespace, name
+        kwargs['max_length'] = 36
+        super(UUIDField,self).__init__(verbose_name, name, primary_key, **kwargs)
+
+    def contribute_to_class(self, cls, name):
+        if self.primary_key:
+            assert not cls._meta.has_auto_field, \
+              "A model can't have more than one AutoField: %s %s %s; have %s" % \
+               (self, cls, name, cls._meta.auto_field)
+            super(UUIDField, self).contribute_to_class(cls, name)
+            cls._meta.has_auto_field = True
+            cls._meta.auto_field = self
+        else:
+            super(UUIDField, self).contribute_to_class(cls, name)
+
+    def create_uuid(self):
+        if not self.version or self.version == 4:
+            return uuid.uuid4()
+        elif self.version == 1:
+            return uuid.uuid1(self.node, self.clock_seq)
+        elif self.version == 2:
+            raise UUIDVersionError("UUID version 2 is not supported.")
+        elif self.version == 3:
+            return uuid.uuid3(self.namespace, self.name)
+        elif self.version == 5:
+            return uuid.uuid5(self.namespace, self.name)
+        else:
+            raise UUIDVersionError("UUID version %s is not valid." % self.version)
+    
+    def db_type(self, connection):
+        """
+            Returns the database column data type for the Field, 
+            taking into account the connection object, and the 
+            settings associated with it.
+        """
+        return 'Binary(16)'
+    
+    def to_python(self, value):
+        """
+            Converts a value as returned by your database 
+            (or a serializer) to a Python object.
+        """
+        if isinstance(value,models.Model):
+            # This happens with related fields 
+            # when the relation uses a UUIDField
+            # as the primary key.
+            value = value.pk
+        if isinstance(value,buffer):
+            value = "%s" % value
+        if value is None:
+            value = ''
+        if isinstance(value,basestring) and not value:
+            pass
+        elif not isinstance(value,uuid.UUID):
+            try:
+                value = uuid.UUID(value)
+            except ValueError:
+                value = uuid.UUID(bytes=value)
+        return unicode(value)
+    
+    def get_prep_value(self, value):
+        """
+            New in Django 1.2
+            This is the reverse of to_python() when working with 
+            the database backends (as opposed to serialization).
+        """
+        if isinstance(value,buffer):
+            value = "%s" % value
+        if value is None:
+            value = ''
+        if isinstance(value,basestring) and not value:
+            pass
+        elif not isinstance(value,uuid.UUID):
+            try:
+                value = uuid.UUID(value).bytes
+            except ValueError:
+                value = uuid.UUID(bytes=value).bytes
+        return value
+
+    def get_db_prep_value(self, value, connection, prepared=False):
+        value = super(UUIDField,self).get_db_prep_value(value, connection=connection, prepared=prepared)
+        if connection.settings_dict['ENGINE'] == 'django.db.backends.sqlite3':
+            value = Binary(value)
+        return value
+
+    def pre_save(self, model_instance, add):
+        """
+            This method is called just prior to get_db_prep_save() 
+            and should return the value of the appropriate attribute 
+            from model_instance for this field. If the model is 
+            being saved to the database for the first time, the add 
+            parameter will be True, otherwise it will be False.
+        """
+        if self.auto and add:
+            value = unicode(self.create_uuid())
+            setattr(model_instance, self.attname, value)
+        else:
+            value = super(UUIDField, self).pre_save(model_instance, add)
+            if self.auto and not value:
+                value = unicode(self.create_uuid())
+                setattr(model_instance, self.attname, value)
+        return value
+    
+    def formfield(self, **kwargs):
+        if self.primary_key:
+            return None
+        else:
+            return super(UUIDField,self).formfield(**kwargs)
+    
+    def value_to_string(self, obj):
+        """
+            This method is used by the serializers to convert the 
+            field into a string for output.
+        """
+        value = self._get_val_from_obj(obj)
+        return self.to_python(value)
\ No newline at end of file
Index: tests/regressiontests/admin_inlines/models.py
===================================================================
--- tests/regressiontests/admin_inlines/models.py	(revision 17048)
+++ tests/regressiontests/admin_inlines/models.py	(working copy)
@@ -5,6 +5,8 @@
 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
+from django.core.urlresolvers import reverse 
+from .fields import UUIDField
 
 
 class Parent(models.Model):
@@ -136,3 +138,44 @@
 class SottoCapo(models.Model):
     name = models.CharField(max_length=100)
     capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+')
+
+class Base(models.Model):
+    class Meta():
+        abstract = True
+    
+    binary_id = UUIDField(primary_key=True)
+    
+    def __unicode__(self):
+        return u"%s.__unicode__() resulting pk: %s" % (self.__class__.__name__, self.pk)
+    
+    def get_change_url(self):
+        # {{ app_label }}_{{ model_name }}_change     object_id
+        viewname = "admin:%s_%s_change" % (self._meta.app_label,self.__class__.__name__)
+        return reverse(viewname.lower(), args=(self.pk,))
+    
+class A(Base):
+    pass
+
+class B(Base):
+    relation = models.ForeignKey(A)
+
+class C(Base):
+    relation = models.OneToOneField(A)
+    
+class D(Base):
+    relation = models.ManyToManyField(A)
+
+class E(Base):
+    relation = models.ManyToManyField(A, through='F')
+
+class F(Base):
+    fk1 = models.ForeignKey(E)
+    fk2 = models.ForeignKey(A,related_name="fa_fk2")
+    m2m1 = models.ManyToManyField(A,related_name="fa_m2m1")
+    one1 = models.OneToOneField(B)
+
+class G(Base):
+    relation = models.ForeignKey(A)
+    
+class H(Base):
+    relation = models.ManyToManyField(A)
\ No newline at end of file
Index: tests/regressiontests/admin_inlines/tests.py
===================================================================
--- tests/regressiontests/admin_inlines/tests.py	(revision 17048)
+++ tests/regressiontests/admin_inlines/tests.py	(working copy)
@@ -1,16 +1,22 @@
 from __future__ import absolute_import
+from re import search,DOTALL
 
 from django.contrib.admin.helpers import InlineAdminForm
 from django.contrib.auth.models import User, Permission
 from django.contrib.contenttypes.models import ContentType
+from django.http import HttpResponse
 from django.test import TestCase
+from django.utils.encoding import force_unicode
 
+
 # local test models
 from .admin import InnerInline
 from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
     OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book)
 
+from .models import A,B,C,D,E,F,G,H
 
+
 class TestInline(TestCase):
     urls = "regressiontests.admin_inlines.urls"
     fixtures = ['admin-views-users.xml']
@@ -380,3 +386,261 @@
         self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
         self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id)
         self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
+
+class TestsFor17122(TestCase):
+    def setUp(self):
+        # create the admin user
+        user = User()
+        user.username = "admin"
+        user.set_password("admin")
+        user.is_staff = True
+        user.is_superuser = True
+        user.save()
+        
+        # log in
+        result = self.client.login(username="admin", password="admin")
+        self.assertEqual(result, True)
+        
+        # create instances to work with
+        a1 = A()
+        a2 = A()
+        a3 = A()
+        a1.save()
+        a2.save()
+        a3.save()
+        self.a1 = a1
+        self.a2 = a2
+        self.a3 = a3
+        
+        # create foreign keys to test
+        b1 = B(relation=a1)
+        b2 = B(relation=a2)
+        b3 = B(relation=a3)
+        b1.save()
+        b2.save()
+        b3.save()
+        self.b1 = b1
+        self.b2 = b2
+        self.b3 = b3
+        
+        # create one to ones for testing
+        c1 = C(relation=a1)
+        c1.save()
+        self.c1 = c1
+
+        # create many to manys for testing
+        d1 = D()
+        d1.save()
+        d1.relation.add(a1)
+        d1.relation.add(a2)
+        d1.save()
+        self.d1 = d1
+        
+        # create relation for inline
+        e1 = E()
+        e1.save()
+        self.e1 = e1
+        
+        # create inline
+        f1 = F()
+        f1.fk1 = self.e1
+        f1.fk2 = self.a1
+        f1.one1 = self.b1
+        f1.save()
+        f1.m2m1.add(self.a1)
+        f1.m2m1.add(self.a2)
+        f1.save()
+        self.f1 = f1
+        
+        # query the admin
+        response = self.client.get(self.e1.get_change_url())
+        
+        # If the response supports deferred rendering and hasn't been rendered
+        # yet, then ensure that it does get rendered before proceeding further.
+        if (hasattr(response, 'render') and callable(response.render)
+            and not response.is_rendered):
+            response.render()
+        content = response.content
+        
+        # create a dict for the inline parse
+        results = {}
+        
+        # parse inline response content
+        for field_name in ['fk2','one1','m2m1']:
+            result = search(
+                    r'<select.*?name="f_set-0-%s".*?>(?P<%s>.*?)</select>'%(
+                            field_name,
+                            field_name
+                    ),
+                    content,
+                    DOTALL
+            )
+            if result is not None and len(result.groups(field_name)) == 1:
+                results[field_name] = result.groups(field_name)[0]
+            else:
+                results[field_name] = ""
+            
+        # store for tests
+        self.inline_results = results
+        self.inline_content = content
+        self.inline_response = response
+        
+        # create foreign keys to test raw id widget
+        g1 = G(relation=a1)
+        g1.save()
+        self.g1 = g1
+        
+        # create m2m to test raw id widget
+        h1 = H()
+        h1.save()
+        h1.relation.add(a1)
+        h1.relation.add(a2)
+        h1.save()
+        self.h1 = h1
+        
+    def tearDown(self):
+        self.client.logout()
+        
+    def test_ForeignKey_render(self):
+        response = self.client.get(self.b1.get_change_url())
+        self.assertContains(response,
+            '<option value="%s" selected="selected">%s</option>' % (
+                    self.b1.relation.pk,
+                    self.b1.relation
+            )
+        )
+        for a in A.objects.all().exclude(pk=self.b1.relation.pk):
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+    
+    def test_OneToOneField_render(self):
+        response = self.client.get(self.c1.get_change_url())
+        self.assertContains(response,
+            '<option value="%s" selected="selected">%s</option>' % (
+                    self.c1.relation.pk,
+                    self.c1.relation
+            )
+        )
+        for a in A.objects.all().exclude(pk=self.c1.relation.pk):
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+    
+    def test_ManyToManyField_render(self):
+        response = self.client.get(self.d1.get_change_url())
+        others = A.objects.all()
+        for a in self.d1.relation.all():
+            self.assertContains(response,
+                '<option value="%s" selected="selected">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+            others = others.exclude(pk=a.pk)
+        for a in others:
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+    
+    def test_inline_hidden_input(self):
+        text = '<input type="hidden" name="f_set-__prefix__-fk1" value="%s" id="id_f_set-__prefix__-fk1" />' % self.f1.fk1.pk
+        self.assertContains(self.inline_response, text)
+        
+    def test_inline_ForeignKey_render(self):
+        response = HttpResponse(self.inline_results["fk2"])
+        self.assertContains(response,
+            '<option value="%s" selected="selected">%s</option>' % (
+                    self.f1.fk2.pk,
+                    self.f1.fk2
+            )
+        )
+        for a in A.objects.all().exclude(pk=self.f1.fk2.pk):
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+            
+    def test_inline_OneToOneField_render(self):
+        response = HttpResponse(self.inline_results["one1"])
+        self.assertContains(response,
+            '<option value="%s" selected="selected">%s</option>' % (
+                    self.f1.one1.pk,
+                    self.f1.one1
+            )
+        )
+        for b in B.objects.all().exclude(pk=self.f1.one1.pk):
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        b.pk,
+                        b
+                )
+            )
+    
+    def test_inline_ManyToManyField_render(self):
+        response = HttpResponse(self.inline_results["m2m1"])
+        others = A.objects.all()
+        for a in self.f1.m2m1.all():
+            self.assertContains(response,
+                '<option value="%s" selected="selected">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+            others = others.exclude(pk=a.pk)
+        for a in others:
+            self.assertContains(response,
+                '<option value="%s">%s</option>' % (
+                        a.pk,
+                        a
+                )
+            )
+    
+    def test_ForeignKeyRawIdWidget_render(self):
+        response = self.client.get(self.g1.get_change_url())
+        result = search(
+                r'<input.*?(name="relation".*?value="%s"|value="%s".*?name="relation").*?>'%(
+                        self.g1.relation.pk,
+                        self.g1.relation.pk,
+                ),
+                str(response),
+                DOTALL
+        )
+        self.assertTrue(result,"ForeignKeyRawIdWidget failed with non-unicode pk.")
+        
+    def test_ManyToManyRawIdWidget_render(self):
+        response = self.client.get(self.h1.get_change_url())
+        result = search(
+                r'<input.*?(?:name="relation".*?value="(?P<value1>[a-zA-Z0-9,-]*?)"|value="(?P<value2>[a-zA-Z0-9,-]*?)".*?name="relation").*?>',
+                str(response),
+                DOTALL
+        )
+        if result.group("value1"):
+            value = result.group("value1")
+        elif result.group("value2"):
+            value = result.group("value2")
+        else:
+            value = ""
+        observed_pks = set([force_unicode(pk) for pk in value.split(",")])
+        relation_pks = set([force_unicode(h.pk) for h in self.h1.relation.all()])
+        msg = "ManyToManyRawIdWidget did not render properly."
+        if hasattr(self,"longMessage") and not self.longMessage:
+            msg = "%s Enable longMessage to see the difference between rendered pks and stored pks." % msg
+        if hasattr(self,"assertSetEqual"):
+            self.assertSetEqual(observed_pks, relation_pks, msg)
+        else:
+            diff1 = observed_pks.difference(relation_pks)
+            diff2 = relation_pks.difference(observed_pks)
+            if diff1 or diff2:
+                self.fail(msg)
\ No newline at end of file
