diff --git a/AUTHORS b/AUTHORS
index be18d21..49a5b31 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -133,6 +133,7 @@ answer newbie questions, and generally made Django that much better:
     Afonso Fernández Nogueira <fonzzo.django@gmail.com>
     Matthew Flanagan <http://wadofstuff.blogspot.com>
     Eric Floehr <eric@intellovations.com>
+    Eric Florenzano <floguy@gmail.com>
     Vincent Foley <vfoleybourgon@yahoo.ca>
     Rudolph Froger <rfroger@estrate.nl>
     Jorge Gajon <gajon@gajon.org>
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 15bffce..5430dea 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -352,7 +352,7 @@ def many_to_many_sql_for_model(model, style):
     qn = connection.ops.quote_name
     inline_references = connection.features.inline_fk_references
     for f in opts.many_to_many:
-        if not isinstance(f.rel, generic.GenericRel):
+        if not isinstance(f.rel, generic.GenericRel) and f.creates_table:
             tablespace = f.db_tablespace or opts.db_tablespace
             if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys:
                 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True)
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index bc9faae..a84cf35 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -104,6 +104,8 @@ def get_validation_errors(outfile, app=None):
                         if r.get_accessor_name() == rel_query_name:
                             e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
 
+        seen_intermediary_signatures = []
+
         for i, f in enumerate(opts.many_to_many):
             # Check to see if the related m2m field will clash with any
             # existing fields, m2m fields, m2m related objects or related objects
@@ -113,6 +115,28 @@ def get_validation_errors(outfile, app=None):
                 # so skip the next section
                 if isinstance(f.rel.to, (str, unicode)):
                     continue
+            if hasattr(f.rel, 'through') and f.rel.through != None:
+                intermediary_model = None
+                for model in models.get_models():
+                    if model._meta.module_name == f.rel.through.lower():
+                        intermediary_model = model
+                if intermediary_model == None:
+                    e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not exist." % (f.name, f.rel.through))
+                else:
+                    signature = (f.rel.to, cls, intermediary_model)
+                    if signature in seen_intermediary_signatures:
+                        e.add(opts, "%s has two manually-defined m2m relationships through the same model (%s), which is not possible.  Please use a field on your intermediary model instead." % (cls._meta.object_name, intermediary_model._meta.object_name))
+                    else:
+                        seen_intermediary_signatures.append(signature)
+                    seen_related_fk, seen_this_fk = False, False
+                    for field in intermediary_model._meta.fields:
+                        if field.rel:
+                            if field.rel.to == f.rel.to:
+                                seen_related_fk = True
+                            elif field.rel.to == cls:
+                                seen_this_fk = True
+                    if not seen_related_fk or not seen_this_fk:
+                        e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
 
             rel_opts = f.rel.to._meta
             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 7d28ba1..9da7073 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -10,6 +10,7 @@ from django.core import validators
 from django import oldforms
 from django import newforms as forms
 from django.dispatch import dispatcher
+from new import instancemethod
 
 try:
     set
@@ -262,12 +263,13 @@ class ForeignRelatedObjectsDescriptor(object):
             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):
         def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
-                join_table=None, source_col_name=None, target_col_name=None):
+                join_table=None, source_col_name=None, target_col_name=None, 
+                through=None):
             super(ManyRelatedManager, self).__init__()
             self.core_filters = core_filters
             self.model = model
@@ -276,6 +278,7 @@ def create_many_related_manager(superclass):
             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)
@@ -283,22 +286,6 @@ def create_many_related_manager(superclass):
         def get_query_set(self):
             return superclass.get_query_set(self).filter(**(self.core_filters))
 
-        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
-
-        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
-
         def clear(self):
             self._clear_items(self.source_col_name)
 
@@ -375,6 +362,24 @@ def create_many_related_manager(superclass):
                 [self._pk_val])
             transaction.commit_unless_managed()
 
+    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
+
+    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 not through:
+        ManyRelatedManager.add = instancemethod(add, None, ManyRelatedManager)
+        ManyRelatedManager.remove = instancemethod(remove, None, ManyRelatedManager)
+
     return ManyRelatedManager
 
 class ManyRelatedObjectsDescriptor(object):
@@ -395,7 +400,7 @@ class ManyRelatedObjectsDescriptor(object):
         # 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(
@@ -405,7 +410,8 @@ class ManyRelatedObjectsDescriptor(object):
             symmetrical=False,
             join_table=qn(self.related.field.m2m_db_table()),
             source_col_name=qn(self.related.field.m2m_reverse_name()),
-            target_col_name=qn(self.related.field.m2m_column_name())
+            target_col_name=qn(self.related.field.m2m_column_name()),
+            through=self.related.field.rel.through
         )
 
         return manager
@@ -414,6 +420,10 @@ class ManyRelatedObjectsDescriptor(object):
         if instance is None:
             raise AttributeError, "Manager must be accessed via instance"
 
+        through = getattr(self.related.field.rel, 'through', None)
+        if through:
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through
+        
         manager = self.__get__(instance)
         manager.clear()
         manager.add(*value)
@@ -436,7 +446,7 @@ class ReverseManyRelatedObjectsDescriptor(object):
         # 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(
@@ -446,7 +456,8 @@ class ReverseManyRelatedObjectsDescriptor(object):
             symmetrical=(self.field.rel.symmetrical and instance.__class__ == rel_model),
             join_table=qn(self.field.m2m_db_table()),
             source_col_name=qn(self.field.m2m_column_name()),
-            target_col_name=qn(self.field.m2m_reverse_name())
+            target_col_name=qn(self.field.m2m_reverse_name()),
+            through=self.field.rel.through
         )
 
         return manager
@@ -455,6 +466,10 @@ class ReverseManyRelatedObjectsDescriptor(object):
         if instance is None:
             raise AttributeError, "Manager must be accessed via instance"
 
+        through = getattr(self.field.rel, 'through', None)
+        if through:
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model.  Use %s's Manager instead." % through
+
         manager = self.__get__(instance)
         manager.clear()
         manager.add(*value)
@@ -648,8 +663,14 @@ class ManyToManyField(RelatedField, Field):
             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'].through:
+            self.creates_table = False
+            assert not self.db_table, "Cannot specify a db_table if an intermediary model is used." 
+        else:
+            self.creates_table = True
         if kwargs["rel"].raw_id_admin:
             kwargs.setdefault("validator_list", []).append(self.isValidIDList)
         Field.__init__(self, **kwargs)
@@ -672,7 +693,9 @@ class ManyToManyField(RelatedField, Field):
 
     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 != None: 
+            return get_model(opts.app_label, self.rel.through)._meta.db_table
+        elif self.db_table:
             return self.db_table
         else:
             return '%s_%s' % (opts.db_table, self.name)
@@ -680,7 +703,11 @@ class ManyToManyField(RelatedField, Field):
     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:
+        if self.rel.through != None:
+            field = related.model._meta.get_related_object(self.rel.through).field
+            attname, column = field.get_attname_column() 
+            return column 
+        elif related.model == related.parent_model:
             return 'from_' + related.model._meta.object_name.lower() + '_id'
         else:
             return related.model._meta.object_name.lower() + '_id'
@@ -688,7 +715,11 @@ class ManyToManyField(RelatedField, Field):
     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:
+        if self.rel.through != None:
+            field = related.parent_model._meta.get_related_object(self.rel.through).field
+            attname, column = field.get_attname_column() 
+            return column
+        elif 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'
@@ -809,7 +840,8 @@ class OneToOneRel(ManyToOneRel):
 
 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):
+        filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True,
+        through = None):
         self.to = to
         self.num_in_admin = num_in_admin
         self.related_name = related_name
@@ -821,5 +853,6 @@ class ManyToManyRel(object):
         self.raw_id_admin = raw_id_admin
         self.symmetrical = symmetrical
         self.multiple = True
+        self.through = through
 
         assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 37ace0a..f0af416 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -2,7 +2,7 @@ from django.conf import settings
 from django.db.models.related import RelatedObject
 from django.db.models.fields.related import ManyToManyRel
 from django.db.models.fields import AutoField, FieldDoesNotExist
-from django.db.models.loading import get_models, app_cache_ready
+from django.db.models.loading import get_models, get_model, app_cache_ready
 from django.db.models.query import orderlist2sql
 from django.db.models import Manager
 from django.utils.translation import activate, deactivate_all, get_language, string_concat
@@ -162,6 +162,15 @@ class Options(object):
             follow = self.get_follow()
         return [f for f in self.get_all_related_objects() if follow.get(f.name, None)]
 
+    def get_related_object(self, from_model):
+        "Gets the RelatedObject which links from from_model to this model."
+        if isinstance(from_model, str):
+            from_model = get_model(self.app_label, from_model)
+        for related_object in self.get_all_related_objects():
+            if related_object.model == from_model:
+                return related_object
+        return None
+
     def get_data_holders(self, follow=None):
         if follow == None:
             follow = self.get_follow()
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index 8a480a2..191950d 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -111,6 +111,31 @@ class Car(models.Model):
 class MissingRelations(models.Model):
     rel1 = models.ForeignKey("Rel1")
     rel2 = models.ManyToManyField("Rel2")
+    
+class MissingManualM2MModel(models.Model):
+    name = models.CharField(max_length=5)
+    missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel")
+    
+class Person(models.Model):
+    name = models.CharField(max_length=5)
+
+class Group(models.Model):
+    name = models.CharField(max_length=5)
+    primary = models.ManyToManyField(Person, through="Membership", related_name="primary")
+    secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary")
+
+class GroupTwo(models.Model):
+    name = models.CharField(max_length=5)
+    primary = models.ManyToManyField(Person, through="Membership")
+    secondary = models.ManyToManyField(Group, through="MembershipMissingFK")
+
+class Membership(models.Model):
+    person = models.ForeignKey(Person)
+    group = models.ForeignKey(Group)
+    not_default_or_null = models.CharField(max_length=5)
+
+class MembershipMissingFK(models.Model):
+    person = models.ForeignKey(Person)
 
 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
@@ -197,4 +222,8 @@ invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi
 invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'.
 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed
 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed
+invalid_models.group: Group has two manually-defined m2m relationships through the same model (Membership), which is not possible.  Please use a field on your intermediary model instead.
+invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relationship through a model (MissingM2MModel) which does not exist.
+invalid_models.grouptwo: primary has a manually-defined m2m relationship through a model (Membership) which does not have foreign keys to Person and GroupTwo
+invalid_models.grouptwo: secondary has a manually-defined m2m relationship through a model (MembershipMissingFK) which does not have foreign keys to Group and GroupTwo
 """
