app/django/db/models/fields/related.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- a/app/django/db/models/fields/related.py	Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/db/models/fields/related.py	Tue Oct 14 16:00:59 2008 +0000
@@ -1,30 +1,25 @@
 from django.db import connection, transaction
 from django.db.models import signals, get_model
-from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class, FieldDoesNotExist
+from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
 from django.db.models.related import RelatedObject
+from django.db.models.query import QuerySet
 from django.db.models.query_utils import QueryWrapper
-from django.utils.text import capfirst
+from django.utils.encoding import smart_unicode
 from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
 from django.utils.functional import curry
-from django.utils.encoding import smart_unicode
-from django.core import validators
-from django import oldforms
-from django import newforms as forms
-from django.dispatch import dispatcher
+from django.core import exceptions
+from django import forms
 
 try:
     set
 except NameError:
     from sets import Set as set   # Python 2.3 fallback
 
-# Values for Relation.edit_inline.
-TABULAR, STACKED = 1, 2
-
 RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
 
 pending_lookups = {}
 
-def add_lazy_relation(cls, field, relation):
+def add_lazy_relation(cls, field, relation, operation):
     """
     Adds a lookup on ``cls`` when a related field is defined using a string,
     i.e.::
@@ -46,6 +41,8 @@
     If the other model hasn't yet been loaded -- almost a given if you're using
     lazy relationships -- then the relation won't be set up until the
     class_prepared signal fires at the end of model initialization.
+
+    operation is the work that must be performed once the relation can be resolved.
     """
     # Check for recursive relations
     if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
@@ -67,31 +64,21 @@
     # is prepared.
     model = get_model(app_label, model_name, False)
     if model:
-        field.rel.to = model
-        field.do_related_class(model, cls)
+        operation(field, model, cls)
     else:
         key = (app_label, model_name)
-        value = (cls, field)
+        value = (cls, field, operation)
         pending_lookups.setdefault(key, []).append(value)
 
-def do_pending_lookups(sender):
+def do_pending_lookups(sender, **kwargs):
     """
     Handle any pending relations to the sending model. Sent from class_prepared.
     """
     key = (sender._meta.app_label, sender.__name__)
-    for cls, field in pending_lookups.pop(key, []):
-        field.rel.to = sender
-        field.do_related_class(sender, cls)
-
-dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
+    for cls, field, operation in pending_lookups.pop(key, []):
+        operation(field, sender, cls)
 
-def manipulator_valid_rel_key(f, self, field_data, all_data):
-    "Validates that the value is a valid foreign key"
-    klass = f.rel.to
-    try:
-        klass._default_manager.get(**{f.rel.field_name: field_data})
-    except klass.DoesNotExist:
-        raise validators.ValidationError, _("Please enter a valid %s.") % f.verbose_name
+signals.class_prepared.connect(do_pending_lookups)
 
 #HACK
 class RelatedField(object):
@@ -103,23 +90,30 @@
 
         if hasattr(sup, 'contribute_to_class'):
             sup.contribute_to_class(cls, name)
-        other = self.rel.to
-        if isinstance(other, basestring):
-            add_lazy_relation(cls, self, other)
-        else:
-            self.do_related_class(other, cls)
+
         if not cls._meta.abstract and self.rel.related_name:
             self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
 
+        other = self.rel.to
+        if isinstance(other, basestring):
+            def resolve_related_class(field, model, cls):
+                field.rel.to = model
+                field.do_related_class(model, cls)
+            add_lazy_relation(cls, self, other, resolve_related_class)
+        else:
+            self.do_related_class(other, cls)
+
     def set_attributes_from_rel(self):
         self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
-        self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name
+        if self.verbose_name is None:
+            self.verbose_name = self.rel.to._meta.verbose_name
         self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
 
     def do_related_class(self, other, cls):
         self.set_attributes_from_rel()
         related = RelatedObject(other, cls, self)
-        self.contribute_to_related_class(other, related)
+        if not cls._meta.abstract:
+            self.contribute_to_related_class(other, related)
 
     def get_db_prep_lookup(self, lookup_type, value):
         # If we are doing a lookup on a Related Field, we must be
@@ -131,20 +125,31 @@
             # that object. In certain conditions (especially one-to-one relations),
             # the primary key may itself be an object - so we need to keep drilling
             # down until we hit a value that can be used for a comparison.
-            v = value
+            v, field = value, None
             try:
                 while True:
-                    v = getattr(v, v._meta.pk.name)
+                    v, field = getattr(v, v._meta.pk.name), v._meta.pk
             except AttributeError:
                 pass
+            if field:
+                if lookup_type in ('range', 'in'):
+                    v = [v]
+                v = field.get_db_prep_lookup(lookup_type, v)
+                if isinstance(v, list):
+                    v = v[0]
             return v
 
         if hasattr(value, 'as_sql'):
             sql, params = value.as_sql()
             return QueryWrapper(('(%s)' % sql), params)
-        if lookup_type == 'exact':
+
+        # FIXME: lt and gt are explicitally allowed to make
+        # get_(next/prev)_by_date work; other lookups are not allowed since that
+        # gets messy pretty quick. This is a good candidate for some refactoring
+        # in the future.
+        if lookup_type in ['exact', 'gt', 'lt']:
             return [pk_trace(value)]
-        if lookup_type == 'in':
+        if lookup_type in ('range', 'in'):
             return [pk_trace(v) for v in value]
         elif lookup_type == 'isnull':
             return []
@@ -182,14 +187,29 @@
     def __set__(self, instance, value):
         if instance is None:
             raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
+
+        # The similarity of the code below to the code in
+        # ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
+        # of small differences that would make a common base class convoluted.
+
+        # If null=True, we can assign null here, but otherwise the value needs
+        # to be an instance of the related class.
+        if value is None and self.related.field.null == False:
+            raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
+                                (instance._meta.object_name, self.related.get_accessor_name()))
+        elif value is not None and not isinstance(value, self.related.model):
+            raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
+                                (value, instance._meta.object_name,
+                                 self.related.get_accessor_name(), self.related.opts.object_name))
+
         # Set the value of the related field
         setattr(value, self.related.field.rel.get_related_field().attname, instance)
 
-        # Clear the cache, if it exists
-        try:
-            delattr(value, self.related.field.get_cache_name())
-        except AttributeError:
-            pass
+        # Since we already know what the related object is, seed the related
+        # object caches now, too. This avoids another db hit if you get the
+        # object you just set.
+        setattr(instance, self.cache_name, value)
+        setattr(value, self.related.field.get_cache_name(), instance)
 
 class ReverseSingleRelatedObjectDescriptor(object):
     # This class provides the functionality that makes the related-object
@@ -218,13 +238,31 @@
                 params = {'%s__pk' % self.field.rel.field_name: val}
             else:
                 params = {'%s__exact' % self.field.rel.field_name: val}
-            rel_obj = self.field.rel.to._default_manager.get(**params)
+
+            # If the related manager indicates that it should be used for
+            # related fields, respect that.
+            rel_mgr = self.field.rel.to._default_manager
+            if getattr(rel_mgr, 'use_for_related_fields', False):
+                rel_obj = rel_mgr.get(**params)
+            else:
+                rel_obj = QuerySet(self.field.rel.to).get(**params)
             setattr(instance, cache_name, rel_obj)
             return rel_obj
 
     def __set__(self, instance, value):
         if instance is None:
             raise AttributeError, "%s must be accessed via instance" % self._field.name
+
+        # If null=True, we can assign null here, but otherwise the value needs
+        # to be an instance of the related class.
+        if value is None and self.field.null == False:
+            raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
+                                (instance._meta.object_name, self.field.name))
+        elif value is not None and not isinstance(value, self.field.rel.to):
+            raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
+                                (value, instance._meta.object_name,
+                                 self.field.name, self.field.rel.to._meta.object_name))
+
         # Set the value of the related field
         try:
             val = getattr(value, self.field.rel.get_related_field().attname)
@@ -232,11 +270,10 @@
             val = None
         setattr(instance, self.field.attname, val)
 
-        # Clear the cache, if it exists
-        try:
-            delattr(instance, self.field.get_cache_name())
-        except AttributeError:
-            pass
+        # Since we already know what the related object is, seed the related
+        # object cache now, too. This avoids another db hit if you get the
+        # object you just set.
+        setattr(instance, self.field.get_cache_name(), value)
 
 class ForeignRelatedObjectsDescriptor(object):
     # This class provides the functionality that makes the related-object
@@ -269,11 +306,17 @@
             add.alters_data = True
 
             def create(self, **kwargs):
-                new_obj = self.model(**kwargs)
-                self.add(new_obj)
-                return new_obj
+                kwargs.update({rel_field.name: instance})
+                return super(RelatedManager, self).create(**kwargs)
             create.alters_data = True
 
+            def get_or_create(self, **kwargs):
+                # Update kwargs with the related object that this
+                # ForeignRelatedObjectsDescriptor knows about.
+                kwargs.update({rel_field.name: instance})
+                return super(RelatedManager, self).get_or_create(**kwargs)
+            get_or_create.alters_data = True
+
             # remove() and clear() are only provided if the ForeignKey can have a value of null.
             if rel_field.null:
                 def remove(self, *objs):
@@ -294,7 +337,9 @@
                 clear.alters_data = True
 
         manager = RelatedManager()
-        manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)}
+        attname = rel_field.rel.get_related_field().name
+        manager.core_filters = {'%s__%s' % (rel_field.name, attname):
+                getattr(instance, attname)}
         manager.model = self.related.model
 
         return manager
@@ -310,7 +355,7 @@
             manager.clear()
         manager.add(*value)
 
-def create_many_related_manager(superclass):
+def create_many_related_manager(superclass, through=False):
     """Creates a manager that subclasses 'superclass' (which is a Manager)
     and adds behavior for many-to-many related objects."""
     class ManyRelatedManager(superclass):
@@ -324,28 +369,32 @@
             self.join_table = join_table
             self.source_col_name = source_col_name
             self.target_col_name = target_col_name
+            self.through = through
             self._pk_val = self.instance._get_pk_val()
             if self._pk_val is None:
-                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model)
+                raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
 
         def get_query_set(self):
-            return superclass.get_query_set(self).filter(**(self.core_filters))
+            return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters))
 
-        def add(self, *objs):
-            self._add_items(self.source_col_name, self.target_col_name, *objs)
+        # If the ManyToMany relation has an intermediary model,
+        # the add and remove methods do not exist.
+        if through is None:
+            def add(self, *objs):
+                self._add_items(self.source_col_name, self.target_col_name, *objs)
 
-            # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
-            if self.symmetrical:
-                self._add_items(self.target_col_name, self.source_col_name, *objs)
-        add.alters_data = True
+                # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
+                if self.symmetrical:
+                    self._add_items(self.target_col_name, self.source_col_name, *objs)
+            add.alters_data = True
 
-        def remove(self, *objs):
-            self._remove_items(self.source_col_name, self.target_col_name, *objs)
+            def remove(self, *objs):
+                self._remove_items(self.source_col_name, self.target_col_name, *objs)
 
-            # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
-            if self.symmetrical:
-                self._remove_items(self.target_col_name, self.source_col_name, *objs)
-        remove.alters_data = True
+                # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table
+                if self.symmetrical:
+                    self._remove_items(self.target_col_name, self.source_col_name, *objs)
+            remove.alters_data = True
 
         def clear(self):
             self._clear_items(self.source_col_name)
@@ -356,12 +405,25 @@
         clear.alters_data = True
 
         def create(self, **kwargs):
-            new_obj = self.model(**kwargs)
-            new_obj.save()
+            # This check needs to be done here, since we can't later remove this
+            # from the method lookup table, as we do with add and remove.
+            if through is not None:
+                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+            new_obj = super(ManyRelatedManager, self).create(**kwargs)
             self.add(new_obj)
             return new_obj
         create.alters_data = True
 
+        def get_or_create(self, **kwargs):
+            obj, created = \
+                    super(ManyRelatedManager, self).get_or_create(**kwargs)
+            # We only need to add() if created because if we got an object back
+            # from get() then the relationship already exists.
+            if created:
+                self.add(obj)
+            return obj, created
+        get_or_create.alters_data = True
+
         def _add_items(self, source_col_name, target_col_name, *objs):
             # join_table: name of the m2m link table
             # source_col_name: the PK colname in join_table for the source object
@@ -443,7 +505,7 @@
         # model's default manager.
         rel_model = self.related.model
         superclass = rel_model._default_manager.__class__
-        RelatedManager = create_many_related_manager(superclass)
+        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
 
         qn = connection.ops.quote_name
         manager = RelatedManager(
@@ -462,6 +524,10 @@
         if instance is None:
             raise AttributeError, "Manager must be accessed via instance"
 
+        through = getattr(self.related.field.rel, 'through', None)
+        if through is not None:
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through
+
         manager = self.__get__(instance)
         manager.clear()
         manager.add(*value)
@@ -484,7 +550,7 @@
         # model's default manager.
         rel_model=self.field.rel.to
         superclass = rel_model._default_manager.__class__
-        RelatedManager = create_many_related_manager(superclass)
+        RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
 
         qn = connection.ops.quote_name
         manager = RelatedManager(
@@ -503,28 +569,27 @@
         if instance is None:
             raise AttributeError, "Manager must be accessed via instance"
 
+        through = getattr(self.field.rel, 'through', None)
+        if through is not None:
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s's Manager instead." % through
+
         manager = self.__get__(instance)
         manager.clear()
         manager.add(*value)
 
 class ManyToOneRel(object):
-    def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
-            max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
-            related_name=None, limit_choices_to=None, lookup_overrides=None,
-            raw_id_admin=False, parent_link=False):
+    def __init__(self, to, field_name, related_name=None,
+            limit_choices_to=None, lookup_overrides=None, parent_link=False):
         try:
             to._meta
         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
             assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
         self.to, self.field_name = to, field_name
-        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
-        self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
-        self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
+        self.related_name = related_name
         if limit_choices_to is None:
             limit_choices_to = {}
         self.limit_choices_to = limit_choices_to
         self.lookup_overrides = lookup_overrides or {}
-        self.raw_id_admin = raw_id_admin
         self.multiple = True
         self.parent_link = parent_link
 
@@ -540,36 +605,24 @@
         return data[0]
 
 class OneToOneRel(ManyToOneRel):
-    def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
-            max_num_in_admin=None, num_extra_on_change=None, edit_inline=False,
-            related_name=None, limit_choices_to=None, lookup_overrides=None,
-            raw_id_admin=False, parent_link=False):
-        # NOTE: *_num_in_admin and num_extra_on_change are intentionally
-        # ignored here. We accept them as parameters only to match the calling
-        # signature of ManyToOneRel.__init__().
-        super(OneToOneRel, self).__init__(to, field_name, num_in_admin,
-                edit_inline=edit_inline, related_name=related_name,
-                limit_choices_to=limit_choices_to,
-                lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin,
-                parent_link=parent_link)
+    def __init__(self, to, field_name, related_name=None,
+            limit_choices_to=None, lookup_overrides=None, parent_link=False):
+        super(OneToOneRel, self).__init__(to, field_name,
+                related_name=related_name, limit_choices_to=limit_choices_to,
+                lookup_overrides=lookup_overrides, parent_link=parent_link)
         self.multiple = False
 
 class ManyToManyRel(object):
-    def __init__(self, to, num_in_admin=0, related_name=None,
-        filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
+    def __init__(self, to, related_name=None, limit_choices_to=None,
+            symmetrical=True, through=None):
         self.to = to
-        self.num_in_admin = num_in_admin
         self.related_name = related_name
-        self.filter_interface = filter_interface
         if limit_choices_to is None:
             limit_choices_to = {}
         self.limit_choices_to = limit_choices_to
-        self.edit_inline = False
-        self.raw_id_admin = raw_id_admin
         self.symmetrical = symmetrical
         self.multiple = True
-
-        assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
+        self.through = through
 
 class ForeignKey(RelatedField, Field):
     empty_strings_allowed = False
@@ -579,24 +632,14 @@
         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
             assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
         else:
+            assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
             to_field = to_field or to._meta.pk.name
-        kwargs['verbose_name'] = kwargs.get('verbose_name', '')
-
-        if 'edit_inline_type' in kwargs:
-            import warnings
-            warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.", DeprecationWarning)
-            kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
+        kwargs['verbose_name'] = kwargs.get('verbose_name', None)
 
         kwargs['rel'] = rel_class(to, to_field,
-            num_in_admin=kwargs.pop('num_in_admin', 3),
-            min_num_in_admin=kwargs.pop('min_num_in_admin', None),
-            max_num_in_admin=kwargs.pop('max_num_in_admin', None),
-            num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
-            edit_inline=kwargs.pop('edit_inline', False),
             related_name=kwargs.pop('related_name', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
             lookup_overrides=kwargs.pop('lookup_overrides', None),
-            raw_id_admin=kwargs.pop('raw_id_admin', False),
             parent_link=kwargs.pop('parent_link', False))
         Field.__init__(self, **kwargs)
 
@@ -608,23 +651,6 @@
     def get_validator_unique_lookup_type(self):
         return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
 
-    def prepare_field_objs_and_params(self, manipulator, name_prefix):
-        params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
-        if self.rel.raw_id_admin:
-            field_objs = self.get_manipulator_field_objs()
-            params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
-        else:
-            if self.radio_admin:
-                field_objs = [oldforms.RadioSelectField]
-                params['ul_class'] = get_ul_class(self.radio_admin)
-            else:
-                if self.null:
-                    field_objs = [oldforms.NullSelectField]
-                else:
-                    field_objs = [oldforms.SelectField]
-            params['choices'] = self.get_choices_default()
-        return field_objs, params
-
     def get_default(self):
         "Here we check if the default value is an object and return the to_field if so."
         field_default = super(ForeignKey, self).get_default()
@@ -632,44 +658,43 @@
             return getattr(field_default, self.rel.get_related_field().attname)
         return field_default
 
-    def get_manipulator_field_objs(self):
-        rel_field = self.rel.get_related_field()
-        if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
-            return rel_field.get_manipulator_field_objs()
-        else:
-            return [oldforms.IntegerField]
-
     def get_db_prep_save(self, value):
         if value == '' or value == None:
             return None
         else:
             return self.rel.get_related_field().get_db_prep_save(value)
 
-    def flatten_data(self, follow, obj=None):
+    def value_to_string(self, obj):
         if not obj:
             # In required many-to-one fields with only one available choice,
             # select that one available choice. Note: For SelectFields
-            # (radio_admin=False), we have to check that the length of choices
-            # is *2*, not 1, because SelectFields always have an initial
-            # "blank" value. Otherwise (radio_admin=True), we check that the
-            # length is 1.
-            if not self.blank and (not self.rel.raw_id_admin or self.choices):
+            # we have to check that the length of choices is *2*, not 1,
+            # because SelectFields always have an initial "blank" value.
+            if not self.blank and self.choices:
                 choice_list = self.get_choices_default()
-                if self.radio_admin and len(choice_list) == 1:
-                    return {self.attname: choice_list[0][0]}
-                if not self.radio_admin and len(choice_list) == 2:
-                    return {self.attname: choice_list[1][0]}
-        return Field.flatten_data(self, follow, obj)
+                if len(choice_list) == 2:
+                    return smart_unicode(choice_list[1][0])
+        return Field.value_to_string(self, obj)
 
     def contribute_to_class(self, cls, name):
         super(ForeignKey, self).contribute_to_class(cls, name)
         setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
+        if isinstance(self.rel.to, basestring):
+            target = self.rel.to
+        else:
+            target = self.rel.to._meta.db_table
+        cls._meta.duplicate_targets[self.column] = (target, "o2m")
 
     def contribute_to_related_class(self, cls, related):
         setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
 
     def formfield(self, **kwargs):
-        defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()}
+        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)
 
@@ -678,8 +703,13 @@
         # of the field to which it points. An exception is if the ForeignKey
         # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
         # in which case the column type is simply that of an IntegerField.
+        # If the database needs similar types for key fields however, the only
+        # thing we can do is making AutoField an IntegerField.
         rel_field = self.rel.get_related_field()
-        if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)):
+        if (isinstance(rel_field, AutoField) or
+                (not connection.features.related_fields_match_type and
+                isinstance(rel_field, (PositiveIntegerField,
+                                       PositiveSmallIntegerField)))):
             return IntegerField().db_type()
         return rel_field.db_type()
 
@@ -692,8 +722,6 @@
     """
     def __init__(self, to, to_field=None, **kwargs):
         kwargs['unique'] = True
-        if 'num_in_admin' not in kwargs:
-            kwargs['num_in_admin'] = 0
         super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
 
     def contribute_to_related_class(self, cls, related):
@@ -702,59 +730,98 @@
         if not cls._meta.one_to_one_field:
             cls._meta.one_to_one_field = self
 
+    def formfield(self, **kwargs):
+        if self.rel.parent_link:
+            return None
+        return super(OneToOneField, self).formfield(**kwargs)
+
 class ManyToManyField(RelatedField, Field):
     def __init__(self, to, **kwargs):
+        try:
+            assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
+        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
+            assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
+
         kwargs['verbose_name'] = kwargs.get('verbose_name', None)
         kwargs['rel'] = ManyToManyRel(to,
-            num_in_admin=kwargs.pop('num_in_admin', 0),
             related_name=kwargs.pop('related_name', None),
-            filter_interface=kwargs.pop('filter_interface', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
-            raw_id_admin=kwargs.pop('raw_id_admin', False),
-            symmetrical=kwargs.pop('symmetrical', True))
+            symmetrical=kwargs.pop('symmetrical', True),
+            through=kwargs.pop('through', None))
+
         self.db_table = kwargs.pop('db_table', None)
-        if kwargs["rel"].raw_id_admin:
-            kwargs.setdefault("validator_list", []).append(self.isValidIDList)
+        if kwargs['rel'].through is not None:
+            self.creates_table = False
+            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
+        else:
+            self.creates_table = True
+
         Field.__init__(self, **kwargs)
 
-        if self.rel.raw_id_admin:
-            msg = ugettext_lazy('Separate multiple IDs with commas.')
-        else:
-            msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
+        msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
         self.help_text = string_concat(self.help_text, ' ', msg)
 
-    def get_manipulator_field_objs(self):
-        if self.rel.raw_id_admin:
-            return [oldforms.RawIdAdminField]
-        else:
-            choices = self.get_choices_default()
-            return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
-
     def get_choices_default(self):
         return Field.get_choices(self, include_blank=False)
 
     def _get_m2m_db_table(self, opts):
         "Function that can be curried to provide the m2m table name for this relation"
-        if self.db_table:
+        if self.rel.through is not None:
+            return self.rel.through_model._meta.db_table
+        elif self.db_table:
             return self.db_table
         else:
             return '%s_%s' % (opts.db_table, self.name)
 
     def _get_m2m_column_name(self, related):
         "Function that can be curried to provide the source column name for the m2m table"
-        # If this is an m2m relation to self, avoid the inevitable name clash
-        if related.model == related.parent_model:
-            return 'from_' + related.model._meta.object_name.lower() + '_id'
-        else:
-            return related.model._meta.object_name.lower() + '_id'
+        try:
+            return self._m2m_column_name_cache
+        except:
+            if self.rel.through is not None:
+                for f in self.rel.through_model._meta.fields:
+                    if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
+                        self._m2m_column_name_cache = f.column
+                        break
+            # If this is an m2m relation to self, avoid the inevitable name clash
+            elif related.model == related.parent_model:
+                self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id'
+            else:
+                self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id'
+
+            # Return the newly cached value
+            return self._m2m_column_name_cache
 
     def _get_m2m_reverse_name(self, related):
         "Function that can be curried to provide the related column name for the m2m table"
-        # If this is an m2m relation to self, avoid the inevitable name clash
-        if related.model == related.parent_model:
-            return 'to_' + related.parent_model._meta.object_name.lower() + '_id'
-        else:
-            return related.parent_model._meta.object_name.lower() + '_id'
+        try:
+            return self._m2m_reverse_name_cache
+        except:
+            if self.rel.through is not None:
+                found = False
+                for f in self.rel.through_model._meta.fields:
+                    if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model:
+                        if related.model == related.parent_model:
+                            # If this is an m2m-intermediate to self,
+                            # the first foreign key you find will be
+                            # the source column. Keep searching for
+                            # the second foreign key.
+                            if found:
+                                self._m2m_reverse_name_cache = f.column
+                                break
+                            else:
+                                found = True
+                        else:
+                            self._m2m_reverse_name_cache = f.column
+                            break
+            # If this is an m2m relation to self, avoid the inevitable name clash
+            elif related.model == related.parent_model:
+                self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id'
+            else:
+                self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id'
+
+            # Return the newly cached value
+            return self._m2m_reverse_name_cache
 
     def isValidIDList(self, field_data, all_data):
         "Validates that the value is a valid list of foreign keys"
@@ -767,30 +834,38 @@
         objects = mod._default_manager.in_bulk(pks)
         if len(objects) != len(pks):
             badkeys = [k for k in pks if k not in objects]
-            raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
-                    "Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
+            raise exceptions.ValidationError(
+                ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
+                          "Please enter valid %(self)s IDs. The values %(value)r are invalid.",
+                          len(badkeys)) % {
                 'self': self.verbose_name,
                 'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
-            }
+            })
 
-    def flatten_data(self, follow, obj = None):
-        new_data = {}
+    def value_to_string(self, obj):
+        data = ''
         if obj:
-            instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()]
-            if self.rel.raw_id_admin:
-                new_data[self.name] = u",".join([smart_unicode(id) for id in instance_ids])
-            else:
-                new_data[self.name] = instance_ids
+            qs = getattr(obj, self.name).all()
+            data = [instance._get_pk_val() for instance in qs]
         else:
             # In required many-to-many fields with only one available choice,
             # select that one available choice.
-            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin:
+            if not self.blank:
                 choices_list = self.get_choices_default()
                 if len(choices_list) == 1:
-                    new_data[self.name] = [choices_list[0][0]]
-        return new_data
+                    data = [choices_list[0][0]]
+        return smart_unicode(data)
 
     def contribute_to_class(self, cls, name):
+        # To support multiple relations to self, it's useful to have a non-None
+        # related name on symmetrical relations for internal reasons. The
+        # concept doesn't make a lot of sense externally ("you want me to
+        # specify *what* on my non-reversible relation?!"), so we set it up
+        # automatically. The funky name reduces the chance of an accidental
+        # clash.
+        if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None:
+            self.rel.related_name = "%s_rel_+" % name
+
         super(ManyToManyField, self).contribute_to_class(cls, name)
         # Add the descriptor for the m2m relation
         setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
@@ -798,6 +873,22 @@
         # Set up the accessor for the m2m table name for the relation
         self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
 
+        # Populate some necessary rel arguments so that cross-app relations
+        # work correctly.
+        if isinstance(self.rel.through, basestring):
+            def resolve_through_model(field, model, cls):
+                field.rel.through_model = model
+            add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
+        elif self.rel.through:
+            self.rel.through_model = self.rel.through
+            self.rel.through = self.rel.through._meta.object_name
+
+        if isinstance(self.rel.to, basestring):
+            target = self.rel.to
+        else:
+            target = self.rel.to._meta.db_table
+        cls._meta.duplicate_targets[self.column] = (target, "m2m")
+
     def contribute_to_related_class(self, cls, related):
         # m2m relations to self do not have a ManyRelatedObjectsDescriptor,
         # as it would be redundant - unless the field is non-symmetrical.
@@ -820,7 +911,7 @@
         setattr(instance, self.attname, data)
 
     def formfield(self, **kwargs):
-        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
+        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)}
         defaults.update(kwargs)
         # If initial is passed in, it's a list of related objects, but the
         # MultipleChoiceField takes a list of IDs.