diff --git a/AUTHORS b/AUTHORS
index fe4755b..6622871 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -144,6 +144,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/contrib/admin/options.py b/django/contrib/admin/options.py
index 29ce10a..671278c 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -174,7 +174,10 @@ class BaseModelAdmin(object):
                 kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
             else:
                 if isinstance(db_field, models.ManyToManyField):
-                    if db_field.name in self.raw_id_fields:
+                    # If it uses an intermediary model, don't show field in admin.
+                    if db_field.rel.through != None:
+                        return None
+                    elif db_field.name in self.raw_id_fields:
                         kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
                         kwargs['help_text'] = ''
                     elif db_field.name in (self.filter_vertical + self.filter_horizontal):
diff --git a/django/core/management/sql.py b/django/core/management/sql.py
index 1ccb100..c3279c1 100644
--- a/django/core/management/sql.py
+++ b/django/core/management/sql.py
@@ -354,7 +354,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.local_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 45dc899..693bf65 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -102,6 +102,7 @@ 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.local_many_to_many):
             # Check to see if the related m2m field will clash with any
             # existing fields, m2m fields, m2m related objects or related
@@ -112,6 +113,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 0278081..ec2ca03 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -310,7 +310,7 @@ 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):
@@ -324,6 +324,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)
@@ -333,15 +334,13 @@ def create_many_related_manager(superclass):
 
         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)
@@ -356,6 +355,8 @@ def create_many_related_manager(superclass):
         clear.alters_data = True
 
         def create(self, **kwargs):
+            if through:
+                raise AttributeError, "'ManyRelatedManager' object has no attribute 'create'"
             new_obj = self.model(**kwargs)
             new_obj.save()
             self.add(new_obj)
@@ -423,6 +424,12 @@ def create_many_related_manager(superclass):
                 [self._pk_val])
             transaction.commit_unless_managed()
 
+    # If it's an intermediary model, detach the add, remove, and create methods
+    # from this ManyRelatedManager.
+    if through:
+        del ManyRelatedManager.add
+        del ManyRelatedManager.remove
+
     return ManyRelatedManager
 
 class ManyRelatedObjectsDescriptor(object):
@@ -443,7 +450,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(
@@ -462,6 +469,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)
@@ -484,7 +495,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(
@@ -503,6 +514,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)
@@ -554,7 +569,7 @@ class OneToOneRel(ManyToOneRel):
 
 class ManyToManyRel(object):
     def __init__(self, to, num_in_admin=0, related_name=None,
-        limit_choices_to=None, symmetrical=True):
+        limit_choices_to=None, symmetrical=True, through=None):
         self.to = to
         self.num_in_admin = num_in_admin
         self.related_name = related_name
@@ -564,6 +579,7 @@ class ManyToManyRel(object):
         self.edit_inline = False
         self.symmetrical = symmetrical
         self.multiple = True
+        self.through = through
 
 class ForeignKey(RelatedField, Field):
     empty_strings_allowed = False
@@ -695,8 +711,14 @@ class ManyToManyField(RelatedField, Field):
             num_in_admin=kwargs.pop('num_in_admin', 0),
             related_name=kwargs.pop('related_name', None),
             limit_choices_to=kwargs.pop('limit_choices_to', None),
-            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 
         Field.__init__(self, **kwargs)
 
         msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
@@ -711,7 +733,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)
@@ -719,7 +743,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'
@@ -727,7 +755,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'
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 3948a5f..02c75b1 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -10,7 +10,7 @@ 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.fields.proxy import OrderWrt
-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.utils.translation import activate, deactivate_all, get_language, string_concat
 from django.utils.encoding import force_unicode, smart_str
 from django.utils.datastructures import SortedDict
@@ -373,6 +373,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 48e574a..ba74c4a 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -110,6 +110,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.
@@ -195,4 +220,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
 """
diff --git a/tests/modeltests/m2m_manual/__init__.py b/tests/modeltests/m2m_manual/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/modeltests/m2m_manual/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/modeltests/m2m_manual/models.py b/tests/modeltests/m2m_manual/models.py
new file mode 100644
index 0000000..01764e3
--- /dev/null
+++ b/tests/modeltests/m2m_manual/models.py
@@ -0,0 +1,278 @@
+from django.db import models
+from datetime import datetime
+
+# M2M described on one of the models
+class Person(models.Model):
+    name = models.CharField(max_length=128)
+
+    def __unicode__(self):
+        return self.name
+
+class Group(models.Model):
+    name = models.CharField(max_length=128)
+    members = models.ManyToManyField(Person, through='Membership')
+    custom_members = models.ManyToManyField(Person, through='CustomMembership', related_name="custom")
+    nodefaultsnonulls = models.ManyToManyField(Person, through='TestNoDefaultsOrNulls', related_name="testnodefaultsnonulls")
+    
+    def __unicode__(self):
+        return self.name
+
+class Membership(models.Model):
+    person = models.ForeignKey(Person)
+    group = models.ForeignKey(Group)
+    date_joined = models.DateTimeField(default=datetime.now)
+    invite_reason = models.CharField(max_length=64, null=True)
+    
+    def __unicode__(self):
+        return "%s is a member of %s" % (self.person.name, self.group.name)
+
+class CustomMembership(models.Model):
+    person = models.ForeignKey(Person, db_column="custom_person_column", related_name="custom_person_related_name")
+    group = models.ForeignKey(Group)
+    weird_fk = models.ForeignKey(Membership, null=True)
+    date_joined = models.DateTimeField(default=datetime.now)
+    
+    def __unicode__(self):
+        return "%s is a member of %s" % (self.person.name, self.group.name)
+    
+    class Meta:
+        db_table = "test_table"
+
+class TestNoDefaultsOrNulls(models.Model):
+    person = models.ForeignKey(Person)
+    group = models.ForeignKey(Group)
+    nodefaultnonull = models.CharField(max_length=5)
+
+__test__ = {'API_TESTS':"""
+>>> from datetime import datetime
+
+### Creation and Saving Tests ###
+
+>>> bob = Person.objects.create(name = 'Bob')
+>>> jim = Person.objects.create(name = 'Jim')
+>>> jane = Person.objects.create(name = 'Jane')
+>>> rock = Group.objects.create(name = 'Rock')
+>>> roll = Group.objects.create(name = 'Roll')
+
+# We start out by making sure that the Group 'rock' has no members.
+>>> rock.members.all()
+[]
+
+# To make Jim a member of Group Rock, simply create a Membership object.
+>>> m1 = Membership.objects.create(person = jim, group = rock)
+
+# We can do the same for Jane and Rock.
+>>> m2 = Membership.objects.create(person = jane, group = rock)
+
+# Let's check to make sure that it worked.  Jane and Jim should be members of Rock.
+>>> rock.members.all()
+[<Person: Jim>, <Person: Jane>]
+
+# Now we can add a bunch more Membership objects to test with.
+>>> m3 = Membership.objects.create(person = bob, group = roll)
+>>> m4 = Membership.objects.create(person = jim, group = roll)
+>>> m5 = Membership.objects.create(person = jane, group = roll)
+
+# We can get Jim's Group membership as with any ForeignKey.
+>>> jim.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+# Querying the intermediary model works like normal.  
+# In this case we get Jane's membership to Rock.
+>>> m = Membership.objects.get(person = jane, group = rock)
+>>> m
+<Membership: Jane is a member of Rock>
+
+# Now we set some date_joined dates for further testing.
+>>> m2.invite_reason = "She was just awesome."
+>>> m2.date_joined = datetime(2006, 1, 1)
+>>> m2.save()
+
+>>> m5.date_joined = datetime(2004, 1, 1)
+>>> m5.save()
+
+>>> m3.date_joined = datetime(2004, 1, 1)
+>>> m3.save()
+
+# It's not only get that works.  Filter works like normal as well.
+>>> Membership.objects.filter(person = jim)
+[<Membership: Jim is a member of Rock>, <Membership: Jim is a member of Roll>]
+
+
+### Forward Descriptors Tests ###
+
+# Due to complications with adding via an intermediary model, the add method is
+# not provided.
+>>> rock.members.add(bob)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'add'
+
+>>> rock.members.create(name = 'Anne')
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'create'
+
+# Remove has similar complications, and is not provided either.
+>>> rock.members.remove(jim)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
+
+# Here we back up the list of all members of Rock.
+>>> backup = list(rock.members.all())
+
+# ...and we verify that it has worked.
+>>> backup
+[<Person: Jim>, <Person: Jane>]
+
+# The clear function should still work.
+>>> rock.members.clear()
+
+# Now there will be no members of Rock.
+>>> rock.members.all()
+[]
+
+# Assignment should not work with models specifying a through model for many of
+# the same reasons as adding.
+>>> rock.members = backup
+Traceback (most recent call last):
+...
+AttributeError: Cannot set values on a ManyToManyField which specifies a through model.  Use Membership's Manager instead.
+
+# Let's re-save those instances that we've cleared.
+>>> m1.save()
+>>> m2.save()
+
+# Verifying that those instances were re-saved successfully.
+>>> rock.members.all()
+[<Person: Jim>, <Person: Jane>]
+
+
+### Reverse Descriptors Tests ###
+
+# Due to complications with adding via an intermediary model, the add method is
+# not provided.
+>>> bob.group_set.add(rock)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'add'
+
+# Create is another method that should not work correctly, as it suffers from
+# the same problems as add.
+>>> bob.group_set.create(name = 'Funk')
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'create'
+
+# Remove has similar complications, and is not provided either.
+>>> jim.group_set.remove(rock)
+Traceback (most recent call last):
+...
+AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
+
+# Here we back up the list of all of Jim's groups.
+>>> backup = list(jim.group_set.all())
+>>> backup
+[<Group: Rock>, <Group: Roll>]
+
+# The clear function should still work.
+>>> jim.group_set.clear()
+
+# Now Jim will be in no groups.
+>>> jim.group_set.all()
+[]
+
+# Assignment should not work with models specifying a through model for many of
+# the same reasons as adding.
+>>> jim.group_set = backup
+Traceback (most recent call last):
+...
+AttributeError: Cannot set values on a ManyToManyField which specifies a through model.  Use Membership's Manager instead.
+
+# Let's re-save those instances that we've cleared.
+>>> m1.save()
+>>> m4.save()
+
+# Verifying that those instances were re-saved successfully.
+>>> jim.group_set.all()
+[<Group: Rock>, <Group: Roll>]
+
+### Custom Tests ###
+
+# Let's see if we can query through our second relationship.
+>>> rock.custom_members.all()
+[]
+
+# We can query in the opposite direction as well.
+>>> bob.custom.all()
+[]
+
+# Let's create some membership objects in this custom relationship.
+>>> cm1 = CustomMembership.objects.create(person = bob, group = rock)
+>>> cm2 = CustomMembership.objects.create(person = jim, group = rock)
+
+# If we get the number of people in Rock, it should be both Bob and Jim.
+>>> rock.custom_members.all()
+[<Person: Bob>, <Person: Jim>]
+
+# Bob should only be in one custom group.
+>>> bob.custom.all()
+[<Group: Rock>]
+
+# Let's make sure our new descriptors don't conflict with the FK related_name.
+>>> bob.custom_person_related_name.all()
+[<CustomMembership: Bob is a member of Rock>]
+
+### QUERY TESTS ###
+
+# We can query for the related model by using its attribute name (members, in 
+# this case).
+>>> Group.objects.filter(members__name='Bob')
+[<Group: Roll>]
+
+# To query through the intermediary model, we specify its model name.
+# In this case, membership.
+>>> Group.objects.filter(membership__invite_reason = "She was just awesome.")
+[<Group: Rock>]
+
+# If we want to query in the reverse direction by the related model, use its
+# model name (group, in this case).
+>>> Person.objects.filter(group__name="Rock")
+[<Person: Jim>, <Person: Jane>]
+
+# If the m2m field has specified a related_name, using that will work.
+>>> Person.objects.filter(custom__name="Rock")
+[<Person: Bob>, <Person: Jim>]
+
+# To query through the intermediary model in the reverse direction, we again
+# specify its model name (membership, in this case).
+>>> Person.objects.filter(membership__invite_reason = "She was just awesome.")
+[<Person: Jane>]
+
+# Let's see all of the groups that Jane joined after 1 Jan 2005:
+>>> Group.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 
+... membership__person = jane)
+[<Group: Rock>]
+
+# Queries also work in the reverse direction: Now let's see all of the people 
+# that have joined Rock since 1 Jan 2005:
+>>> Person.objects.filter(membership__date_joined__gt = datetime(2005, 1, 1), 
+... membership__group = rock)
+[<Person: Jim>, <Person: Jane>]
+
+# Conceivably, queries through membership could return correct, but non-unique
+# querysets.  To demonstrate this, we query for all people who have joined a 
+# group after 2004:
+>>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1))
+[<Person: Jim>, <Person: Jim>, <Person: Jane>]
+
+# Jim showed up twice, because he joined two groups ('Rock', and 'Roll'):
+>>> [(m.person.name, m.group.name) for m in 
+... Membership.objects.filter(date_joined__gt = datetime(2004, 1, 1))]
+[(u'Jim', u'Rock'), (u'Jane', u'Rock'), (u'Jim', u'Roll')]
+
+# QuerySet's distinct() method can correct this problem.
+>>> Person.objects.filter(membership__date_joined__gt = datetime(2004, 1, 1)).distinct()
+[<Person: Jim>, <Person: Jane>]
+"""}
\ No newline at end of file
