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_widgets/fields.py
===================================================================
--- tests/regressiontests/admin_widgets/fields.py	(revision 0)
+++ tests/regressiontests/admin_widgets/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_widgets/models.py
===================================================================
--- tests/regressiontests/admin_widgets/models.py	(revision 17048)
+++ tests/regressiontests/admin_widgets/models.py	(working copy)
@@ -1,5 +1,7 @@
 from django.db import models
 from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse 
+from fields import UUIDField
 
 
 class MyFileField(models.FileField):
@@ -99,3 +101,36 @@
     """
     name = models.CharField(max_length=20)
     companies = models.ManyToManyField(Company)
+
+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):
+        return "/widget_admin/admin_widgets/%s/%s/" % (
+            self.__class__.__name__.lower(),
+            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 G(Base):
+    relation = models.ForeignKey(A)
+    
+class H(Base):
+    relation = models.ManyToManyField(A)
\ No newline at end of file
Index: tests/regressiontests/admin_widgets/tests.py
===================================================================
--- tests/regressiontests/admin_widgets/tests.py	(revision 17048)
+++ tests/regressiontests/admin_widgets/tests.py	(working copy)
@@ -7,18 +7,25 @@
 from django.conf import settings
 from django.contrib import admin
 from django.contrib.admin import widgets
+from django.contrib.auth.models import User
 from django.core.files.storage import default_storage
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.db.models import DateField
+from django.http import HttpResponse
 from django.test import TestCase as DjangoTestCase
 from django.utils import translation
+from django.utils.encoding import force_unicode
 from django.utils.html import conditional_escape
 from django.utils.unittest import TestCase
 
 from . import models
 from .widgetadmin import site as widget_admin_site
 
+from re import search,DOTALL
 
+from .models import A,B,C,D,G,H
+
+
 admin_media_prefix = lambda: {
     'ADMIN_MEDIA_PREFIX': "%sadmin/" % settings.STATIC_URL,
 }
@@ -372,3 +379,157 @@
         # Used to fail with a name error.
         w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
         self.assertFalse(w.can_add_related)
+
+class TestsFor17122(DjangoTestCase):
+    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 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_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)
Index: tests/regressiontests/admin_widgets/widgetadmin.py
===================================================================
--- tests/regressiontests/admin_widgets/widgetadmin.py	(revision 17048)
+++ tests/regressiontests/admin_widgets/widgetadmin.py	(working copy)
@@ -25,6 +25,13 @@
 class EventAdmin(admin.ModelAdmin):
     raw_id_fields = ['band']
 
+class GAdmin(admin.ModelAdmin):
+    raw_id_fields = ("relation",)
+    
+class HAdmin(admin.ModelAdmin):
+    raw_id_fields = ("relation",)
+
+
 site = WidgetAdmin(name='widget-admin')
 
 site.register(models.User)
@@ -41,3 +48,11 @@
 site.register(models.Bee)
 
 site.register(models.Advisor)
+
+
+site.register(models.A)
+site.register(models.B)
+site.register(models.C)
+site.register(models.D)
+site.register(models.G,GAdmin)
+site.register(models.H,HAdmin)
\ No newline at end of file
