diff -r eff0b46ff6fa django/contrib/admin/options.py
--- a/django/contrib/admin/options.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/contrib/admin/options.py	Fri Oct 30 15:36:20 2009 +0800
@@ -153,8 +153,9 @@
         """
         Get a form Field for a ManyToManyField.
         """
-        # If it uses an intermediary model, don't show field in admin.
-        if db_field.rel.through is not None:
+        # If it uses an intermediary model that isn't auto created, don't show
+        # a field in admin.
+        if not db_field.rel.through._meta.auto_created:
             return None
 
         if db_field.name in self.raw_id_fields:
diff -r eff0b46ff6fa django/contrib/contenttypes/generic.py
--- a/django/contrib/contenttypes/generic.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/contrib/contenttypes/generic.py	Fri Oct 30 15:36:20 2009 +0800
@@ -105,8 +105,6 @@
                             limit_choices_to=kwargs.pop('limit_choices_to', None),
                             symmetrical=kwargs.pop('symmetrical', True))
 
-        # By its very nature, a GenericRelation doesn't create a table.
-        self.creates_table = False
 
         # Override content-type/object-id field names on the related class
         self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
diff -r eff0b46ff6fa django/core/management/commands/syncdb.py
--- a/django/core/management/commands/syncdb.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/core/management/commands/syncdb.py	Fri Oct 30 15:36:20 2009 +0800
@@ -57,12 +57,15 @@
         # Create the tables for each model
         for app in models.get_apps():
             app_name = app.__name__.split('.')[-2]
-            model_list = models.get_models(app)
+            model_list = models.get_models(app, include_auto_created=True)
             for model in model_list:
                 # Create the model's database table, if it doesn't already exist.
                 if verbosity >= 2:
                     print "Processing %s.%s model" % (app_name, model._meta.object_name)
-                if connection.introspection.table_name_converter(model._meta.db_table) in tables:
+                opts = model._meta
+                if (connection.introspection.table_name_converter(opts.db_table) in tables or
+                    (opts.auto_created and
+                    connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
                     continue
                 sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
                 seen_models.add(model)
@@ -78,19 +81,6 @@
                     cursor.execute(statement)
                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
 
-        # Create the m2m tables. This must be done after all tables have been created
-        # to ensure that all referred tables will exist.
-        for app in models.get_apps():
-            app_name = app.__name__.split('.')[-2]
-            model_list = models.get_models(app)
-            for model in model_list:
-                if model in created_models:
-                    sql = connection.creation.sql_for_many_to_many(model, self.style)
-                    if sql:
-                        if verbosity >= 2:
-                            print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
-                        for statement in sql:
-                            cursor.execute(statement)
 
         transaction.commit_unless_managed()
 
diff -r eff0b46ff6fa django/core/management/sql.py
--- a/django/core/management/sql.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/core/management/sql.py	Fri Oct 30 15:36:20 2009 +0800
@@ -23,7 +23,7 @@
     # We trim models from the current app so that the sqlreset command does not
     # generate invalid SQL (leaving models out of known_models is harmless, so
     # we can be conservative).
-    app_models = models.get_models(app)
+    app_models = models.get_models(app, include_auto_created=True)
     final_output = []
     tables = connection.introspection.table_names()
     known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
@@ -40,10 +40,6 @@
         # Keep track of the fact that we've created the table for this model.
         known_models.add(model)
 
-    # Create the many-to-many join tables.
-    for model in app_models:
-        final_output.extend(connection.creation.sql_for_many_to_many(model, style))
-
     # Handle references to tables that are from other apps
     # but don't exist physically.
     not_installed_models = set(pending_references.keys())
@@ -82,7 +78,7 @@
     to_delete = set()
 
     references_to_delete = {}
-    app_models = models.get_models(app)
+    app_models = models.get_models(app, include_auto_created=True)
     for model in app_models:
         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
             # The table exists, so it needs to be dropped
@@ -97,13 +93,6 @@
         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
             output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
 
-    # Output DROP TABLE statements for many-to-many tables.
-    for model in app_models:
-        opts = model._meta
-        for f in opts.local_many_to_many:
-            if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
-                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
-
     # Close database connection explicitly, in case this output is being piped
     # directly into a database client, to avoid locking issues.
     if cursor:
diff -r eff0b46ff6fa django/core/management/validation.py
--- a/django/core/management/validation.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/core/management/validation.py	Fri Oct 30 15:36:20 2009 +0800
@@ -79,27 +79,28 @@
                 rel_opts = f.rel.to._meta
                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
                 rel_query_name = f.related_query_name()
-                for r in rel_opts.fields:
-                    if r.name == rel_name:
-                        e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
-                    if r.name == rel_query_name:
-                        e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
-                for r in rel_opts.local_many_to_many:
-                    if r.name == rel_name:
-                        e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
-                    if r.name == rel_query_name:
-                        e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
-                for r in rel_opts.get_all_related_many_to_many_objects():
-                    if r.get_accessor_name() == rel_name:
-                        e.add(opts, "Accessor for field '%s' clashes with related m2m 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))
-                    if r.get_accessor_name() == rel_query_name:
-                        e.add(opts, "Reverse query name for field '%s' clashes with related m2m 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))
-                for r in rel_opts.get_all_related_objects():
-                    if r.field is not f:
+                if not f.rel.is_hidden():
+                    for r in rel_opts.fields:
+                        if r.name == rel_name:
+                            e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                        if r.name == rel_query_name:
+                            e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                    for r in rel_opts.local_many_to_many:
+                        if r.name == rel_name:
+                            e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                        if r.name == rel_query_name:
+                            e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
+                    for r in rel_opts.get_all_related_many_to_many_objects():
                         if r.get_accessor_name() == rel_name:
-                            e.add(opts, "Accessor 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))
+                            e.add(opts, "Accessor for field '%s' clashes with related m2m 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))
                         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))
+                            e.add(opts, "Reverse query name for field '%s' clashes with related m2m 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))
+                    for r in rel_opts.get_all_related_objects():
+                        if r.field is not f:
+                            if r.get_accessor_name() == rel_name:
+                                e.add(opts, "Accessor 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))
+                            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):
@@ -117,48 +118,80 @@
             if f.unique:
                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
 
-            if getattr(f.rel, 'through', None) is not None:
-                if hasattr(f.rel, 'through_model'):
-                    from_model, to_model = cls, f.rel.to
-                    if from_model == to_model and f.rel.symmetrical:
-                        e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
-                    seen_from, seen_to, seen_self = False, False, 0
-                    for inter_field in f.rel.through_model._meta.fields:
-                        rel_to = getattr(inter_field.rel, 'to', None)
-                        if from_model == to_model: # relation to self
-                            if rel_to == from_model:
-                                seen_self += 1
-                            if seen_self > 2:
-                                e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
-                        else:
-                            if rel_to == from_model:
-                                if seen_from:
-                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
-                                else:
-                                    seen_from = True
-                            elif rel_to == to_model:
-                                if seen_to:
-                                    e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
-                                else:
-                                    seen_to = True
-                    if f.rel.through_model not in models.get_models():
-                        e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
-                    signature = (f.rel.to, cls, f.rel.through_model)
-                    if signature in seen_intermediary_signatures:
-                        e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
+            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
+                from_model, to_model = cls, f.rel.to
+                if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
+                    e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
+                seen_from, seen_to, seen_self = False, False, 0
+                for inter_field in f.rel.through._meta.fields:
+                    rel_to = getattr(inter_field.rel, 'to', None)
+                    if from_model == to_model: # relation to self
+                        if rel_to == from_model:
+                            seen_self += 1
+                        if seen_self > 2:
+                            e.add(opts, "Intermediary model %s has more than "
+                                "two foreign keys to %s, which is ambiguous "
+                                "and is not permitted." % (
+                                    f.rel.through._meta.object_name,
+                                    from_model._meta.object_name
+                                )
+                            )
                     else:
-                        seen_intermediary_signatures.append(signature)
-                    seen_related_fk, seen_this_fk = False, False
-                    for field in f.rel.through_model._meta.fields:
-                        if field.rel:
-                            if not seen_related_fk and 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 relation through 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))
+                        if rel_to == from_model:
+                            if seen_from:
+                                e.add(opts, "Intermediary model %s has more "
+                                    "than one foreign key to %s, which is "
+                                    "ambiguous and is not permitted." % (
+                                        f.rel.through._meta.object_name,
+                                         from_model._meta.object_name
+                                     )
+                                 )
+                            else:
+                                seen_from = True
+                        elif rel_to == to_model:
+                            if seen_to:
+                                e.add(opts, "Intermediary model %s has more "
+                                    "than one foreign key to %s, which is "
+                                    "ambiguous and is not permitted." % (
+                                        f.rel.through._meta.object_name,
+                                        rel_to._meta.object_name
+                                    )
+                                )
+                            else:
+                                seen_to = True
+                if f.rel.through not in models.get_models(include_auto_created=True):
+                    e.add(opts, "'%s' specifies an m2m relation through model "
+                        "%s, which has not been installed." % (f.name, f.rel.through)
+                    )
+                signature = (f.rel.to, cls, f.rel.through)
+                if signature in seen_intermediary_signatures:
+                    e.add(opts, "The model %s has two manually-defined m2m "
+                        "relations through the model %s, which is not "
+                        "permitted. Please consider using an extra field on "
+                        "your intermediary model instead." % (
+                            cls._meta.object_name,
+                            f.rel.through._meta.object_name
+                        )
+                    )
                 else:
-                    e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
+                    seen_intermediary_signatures.append(signature)
+                seen_related_fk, seen_this_fk = False, False
+                for field in f.rel.through._meta.fields:
+                    if field.rel:
+                        if not seen_related_fk and 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 relation "
+                        "through model %s, which does not have foreign keys "
+                        "to %s and %s" % (f.name, f.rel.through._meta.object_name,
+                            f.rel.to._meta.object_name, cls._meta.object_name)
+                    )
+            elif isinstance(f.rel.through, basestring):
+                e.add(opts, "'%s' specifies an m2m relation through model %s, "
+                    "which has not been installed" % (f.name, f.rel.through)
+                )
 
             rel_opts = f.rel.to._meta
             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
diff -r eff0b46ff6fa django/core/serializers/python.py
--- a/django/core/serializers/python.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/core/serializers/python.py	Fri Oct 30 15:36:20 2009 +0800
@@ -56,7 +56,7 @@
         self._current[field.name] = smart_unicode(related, strings_only=True)
 
     def handle_m2m_field(self, obj, field):
-        if field.creates_table:
+        if field.rel.through._meta.auto_created:
             self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
                                for related in getattr(obj, field.name).iterator()]
 
diff -r eff0b46ff6fa django/core/serializers/xml_serializer.py
--- a/django/core/serializers/xml_serializer.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/core/serializers/xml_serializer.py	Fri Oct 30 15:36:20 2009 +0800
@@ -98,7 +98,7 @@
         serialized as references to the object's PK (i.e. the related *data*
         is not dumped, just the relation).
         """
-        if field.creates_table:
+        if field.rel.through._meta.auto_created:
             self._start_relational_field(field)
             for relobj in getattr(obj, field.name).iterator():
                 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
@@ -233,4 +233,3 @@
         else:
            pass
     return u"".join(inner_text)
-
diff -r eff0b46ff6fa django/db/models/base.py
--- a/django/db/models/base.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/db/models/base.py	Fri Oct 30 15:36:20 2009 +0800
@@ -429,7 +429,7 @@
         else:
             meta = cls._meta
 
-        if origin:
+        if origin and not meta.auto_created:
             signals.pre_save.send(sender=origin, instance=self, raw=raw)
 
         # If we are in a raw save, save the object exactly as presented.
@@ -502,7 +502,7 @@
                     setattr(self, meta.pk.attname, result)
             transaction.commit_unless_managed()
 
-        if origin:
+        if origin and not meta.auto_created:
             signals.post_save.send(sender=origin, instance=self,
                 created=(not record_exists), raw=raw)
 
@@ -539,7 +539,12 @@
                         rel_descriptor = cls.__dict__[rel_opts_name]
                         break
                 else:
-                    raise AssertionError("Should never get here.")
+                    # in the case of a hidden fkey just skip it, it'll get
+                    # processed as an m2m
+                    if not related.field.rel.is_hidden():
+                        raise AssertionError("Should never get here.")
+                    else:
+                        continue
                 delete_qs = rel_descriptor.delete_manager(self).all()
                 for sub_obj in delete_qs:
                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
diff -r eff0b46ff6fa django/db/models/fields/related.py
--- a/django/db/models/fields/related.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/db/models/fields/related.py	Fri Oct 30 15:36:20 2009 +0800
@@ -58,6 +58,10 @@
             # If we can't split, assume a model in current app
             app_label = cls._meta.app_label
             model_name = relation
+        except AttributeError:
+            # If it doesn't have a split it's actually a model class
+            app_label = relation._meta.app_label
+            model_name = relation._meta.object_name
 
     # Try to look up the related model, and if it's already loaded resolve the
     # string right away. If get_model returns None, it means that the related
@@ -96,7 +100,7 @@
             self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
 
         other = self.rel.to
-        if isinstance(other, basestring):
+        if isinstance(other, basestring) or other._meta.pk is None:
             def resolve_related_class(field, model, cls):
                 field.rel.to = model
                 field.do_related_class(model, cls)
@@ -401,22 +405,22 @@
 
         return manager
 
-def create_many_related_manager(superclass, through=False):
+def create_many_related_manager(superclass, rel=False):
     """Creates a manager that subclasses 'superclass' (which is a Manager)
     and adds behavior for many-to-many related objects."""
+    through = rel.through
     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_field_name=None, target_field_name=None):
             super(ManyRelatedManager, self).__init__()
             self.core_filters = core_filters
             self.model = model
             self.symmetrical = symmetrical
             self.instance = instance
-            self.join_table = join_table
-            self.source_col_name = source_col_name
-            self.target_col_name = target_col_name
+            self.source_field_name = source_field_name
+            self.target_field_name = target_field_name
             self.through = through
-            self._pk_val = self.instance._get_pk_val()
+            self._pk_val = self.instance.pk
             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." % instance.__class__.__name__)
 
@@ -425,36 +429,37 @@
 
         # If the ManyToMany relation has an intermediary model,
         # the add and remove methods do not exist.
-        if through is None:
+        if rel.through._meta.auto_created:
             def add(self, *objs):
-                self._add_items(self.source_col_name, self.target_col_name, *objs)
+                self._add_items(self.source_field_name, self.target_field_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)
+                    self._add_items(self.target_field_name, self.source_field_name, *objs)
             add.alters_data = True
 
             def remove(self, *objs):
-                self._remove_items(self.source_col_name, self.target_col_name, *objs)
+                self._remove_items(self.source_field_name, self.target_field_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)
+                    self._remove_items(self.target_field_name, self.source_field_name, *objs)
             remove.alters_data = True
 
         def clear(self):
-            self._clear_items(self.source_col_name)
+            self._clear_items(self.source_field_name)
 
             # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table
             if self.symmetrical:
-                self._clear_items(self.target_col_name)
+                self._clear_items(self.target_field_name)
         clear.alters_data = True
 
         def create(self, **kwargs):
             # 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
+            if not rel.through._meta.auto_created:
+                opts = through._meta
+                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
             new_obj = super(ManyRelatedManager, self).create(**kwargs)
             self.add(new_obj)
             return new_obj
@@ -470,41 +475,38 @@
             return obj, created
         get_or_create.alters_data = True
 
-        def _add_items(self, source_col_name, target_col_name, *objs):
+        def _add_items(self, source_field_name, target_field_name, *objs):
             # join_table: name of the m2m link table
-            # source_col_name: the PK colname in join_table for the source object
-            # target_col_name: the PK colname in join_table for the target object
+            # source_field_name: the PK fieldname in join_table for the source object
+            # target_col_name: the PK fielname in join_table for the target object
             # *objs - objects to add. Either object instances, or primary keys of object instances.
 
             # If there aren't any objects, there is nothing to do.
+            from django.db.models import Model
             if objs:
-                from django.db.models.base import Model
-                # Check that all the objects are of the right type
                 new_ids = set()
                 for obj in objs:
                     if isinstance(obj, self.model):
-                        new_ids.add(obj._get_pk_val())
+                        new_ids.add(obj.pk)
                     elif isinstance(obj, Model):
                         raise TypeError, "'%s' instance expected" % self.model._meta.object_name
                     else:
                         new_ids.add(obj)
-                # Add the newly created or already existing objects to the join table.
-                # First find out which items are already added, to avoid adding them twice
-                cursor = connection.cursor()
-                cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \
-                    (target_col_name, self.join_table, source_col_name,
-                    target_col_name, ",".join(['%s'] * len(new_ids))),
-                    [self._pk_val] + list(new_ids))
-                existing_ids = set([row[0] for row in cursor.fetchall()])
+                vals = self.through._default_manager.values_list(target_field_name, flat=True)
+                vals = vals.filter(**{
+                    source_field_name: self._pk_val,
+                    '%s__in' % target_field_name: new_ids,
+                })
+                vals = set(vals)
 
                 # Add the ones that aren't there already
-                for obj_id in (new_ids - existing_ids):
-                    cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
-                        (self.join_table, source_col_name, target_col_name),
-                        [self._pk_val, obj_id])
-                transaction.commit_unless_managed()
+                for obj_id in (new_ids - vals):
+                    self.through._default_manager.create(**{
+                        '%s_id' % source_field_name: self._pk_val,
+                        '%s_id' % target_field_name: obj_id,
+                    })
 
-        def _remove_items(self, source_col_name, target_col_name, *objs):
+        def _remove_items(self, source_field_name, target_field_name, *objs):
             # source_col_name: the PK colname in join_table for the source object
             # target_col_name: the PK colname in join_table for the target object
             # *objs - objects to remove
@@ -515,24 +517,20 @@
                 old_ids = set()
                 for obj in objs:
                     if isinstance(obj, self.model):
-                        old_ids.add(obj._get_pk_val())
+                        old_ids.add(obj.pk)
                     else:
                         old_ids.add(obj)
                 # Remove the specified objects from the join table
-                cursor = connection.cursor()
-                cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
-                    (self.join_table, source_col_name,
-                    target_col_name, ",".join(['%s'] * len(old_ids))),
-                    [self._pk_val] + list(old_ids))
-                transaction.commit_unless_managed()
+                self.through._default_manager.filter(**{
+                    source_field_name: self._pk_val,
+                    '%s__in' % target_field_name: old_ids
+                }).delete()
 
-        def _clear_items(self, source_col_name):
+        def _clear_items(self, source_field_name):
             # source_col_name: the PK colname in join_table for the source object
-            cursor = connection.cursor()
-            cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
-                (self.join_table, source_col_name),
-                [self._pk_val])
-            transaction.commit_unless_managed()
+            self.through._default_manager.filter(**{
+                source_field_name: self._pk_val
+            }).delete()
 
     return ManyRelatedManager
 
@@ -554,17 +552,15 @@
         # model's default manager.
         rel_model = self.related.model
         superclass = rel_model._default_manager.__class__
-        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through)
+        RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
 
-        qn = connection.ops.quote_name
         manager = RelatedManager(
             model=rel_model,
             core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
             instance=instance,
             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())
+            source_field_name=self.related.field.m2m_reverse_field_name(),
+            target_field_name=self.related.field.m2m_field_name()
         )
 
         return manager
@@ -573,9 +569,9 @@
         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
+        if not self.related.field.rel.through._meta.auto_created:
+            opts = self.related.field.rel.through._meta
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
 
         manager = self.__get__(instance)
         manager.clear()
@@ -599,17 +595,15 @@
         # model's default manager.
         rel_model=self.field.rel.to
         superclass = rel_model._default_manager.__class__
-        RelatedManager = create_many_related_manager(superclass, self.field.rel.through)
+        RelatedManager = create_many_related_manager(superclass, self.field.rel)
 
-        qn = connection.ops.quote_name
         manager = RelatedManager(
             model=rel_model,
             core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
             instance=instance,
             symmetrical=(self.field.rel.symmetrical and isinstance(instance, 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())
+            source_field_name=self.field.m2m_field_name(),
+            target_field_name=self.field.m2m_reverse_field_name()
         )
 
         return manager
@@ -618,9 +612,9 @@
         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
+        if not self.field.rel.through._meta.auto_created:
+            opts = self.field.rel.through._meta
+            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
 
         manager = self.__get__(instance)
         manager.clear()
@@ -642,6 +636,10 @@
         self.multiple = True
         self.parent_link = parent_link
 
+    def is_hidden(self):
+        "Should the related object be hidden?"
+        return self.related_name and self.related_name[-1] == '+'
+
     def get_related_field(self):
         """
         Returns the Field in the 'to' object to which this relationship is
@@ -673,6 +671,10 @@
         self.multiple = True
         self.through = through
 
+    def is_hidden(self):
+        "Should the related object be hidden?"
+        return self.related_name and self.related_name[-1] == '+'
+
     def get_related_field(self):
         """
         Returns the field in the to' object to which this relationship is tied
@@ -690,7 +692,6 @@
             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', None)
 
         kwargs['rel'] = rel_class(to, to_field,
@@ -743,7 +744,12 @@
         cls._meta.duplicate_targets[self.column] = (target, "o2m")
 
     def contribute_to_related_class(self, cls, related):
-        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
+        # Internal FK's - i.e., those with a related name ending with '+' -
+        # don't get a related descriptor.
+        if not self.rel.is_hidden():
+            setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
+        if self.rel.field_name is None:
+            self.rel.field_name = cls._meta.pk.name
 
     def formfield(self, **kwargs):
         defaults = {
@@ -790,6 +796,42 @@
             return None
         return super(OneToOneField, self).formfield(**kwargs)
 
+def create_many_to_many_intermediary_model(field, klass):
+    from django.db import models
+    managed = True
+    if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
+        to = field.rel.to
+        to_model = field.rel.to
+        def set_managed(field, model, cls):
+            field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
+        add_lazy_relation(klass, field, to_model, set_managed)
+    elif isinstance(field.rel.to, basestring):
+        to = klass._meta.object_name
+        to_model = klass
+        managed = klass._meta.managed
+    else:
+        to = field.rel.to._meta.object_name
+        to_model = field.rel.to
+        managed = klass._meta.managed or to_model._meta.managed
+    meta = type('Meta', (object,), {
+        'db_table': field._get_m2m_db_table(klass._meta),
+        'managed': managed,
+        'auto_created': klass,
+    })
+    name = '%s_%s' % (klass._meta.object_name, field.name)
+    if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name:
+        from_ = 'from_%s' % to.lower()
+        to = to.lower()
+    else:
+        from_ = klass._meta.object_name.lower()
+        to = to.lower()
+    return type(name, (models.Model,), {
+        'Meta': meta,
+        '__module__': klass.__module__,
+        from_: models.ForeignKey(klass, related_name='%s+' % name),
+        to: models.ForeignKey(to_model, related_name='%s+' % name)
+    })
+
 class ManyToManyField(RelatedField, Field):
     def __init__(self, to, **kwargs):
         try:
@@ -806,10 +848,7 @@
 
         self.db_table = kwargs.pop('db_table', None)
         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)
 
@@ -822,62 +861,45 @@
     def _get_m2m_db_table(self, opts):
         "Function that can be curried to provide the m2m table name for this relation"
         if self.rel.through is not None:
-            return self.rel.through_model._meta.db_table
+            return self.rel.through._meta.db_table
         elif self.db_table:
             return self.db_table
         else:
             return util.truncate_name('%s_%s' % (opts.db_table, self.name),
                                       connection.ops.max_name_length())
 
-    def _get_m2m_column_name(self, related):
+    def _get_m2m_attr(self, related, attr):
         "Function that can be curried to provide the source column name for the m2m table"
-        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
+        cache_attr = '_m2m_%s_cache' % attr
+        if hasattr(self, cache_attr):
+            return getattr(self, cache_attr)
+        for f in self.rel.through._meta.fields:
+            if hasattr(f,'rel') and f.rel and f.rel.to == related.model:
+                setattr(self, cache_attr, getattr(f, attr))
+                return getattr(self, cache_attr)
+
+    def _get_m2m_reverse_attr(self, related, attr):
+        "Function that can be curried to provide the related column name for the m2m table"
+        cache_attr = '_m2m_reverse_%s_cache' % attr
+        if hasattr(self, cache_attr):
+            return getattr(self, cache_attr)
+        found = False
+        for f in self.rel.through._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:
+                        setattr(self, cache_attr, getattr(f, attr))
                         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"
-        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
+                    else:
+                        found = True
+                else:
+                    setattr(self, cache_attr, getattr(f, attr))
+                    break
+        return getattr(self, cache_attr)
 
     def isValidIDList(self, field_data, all_data):
         "Validates that the value is a valid list of foreign keys"
@@ -919,10 +941,14 @@
         # 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:
+        if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
             self.rel.related_name = "%s_rel_+" % name
 
         super(ManyToManyField, self).contribute_to_class(cls, name)
+
+        if not self.rel.through:
+            self.rel.through = create_many_to_many_intermediary_model(self, cls)
+
         # Add the descriptor for the m2m relation
         setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self))
 
@@ -933,11 +959,8 @@
         # work correctly.
         if isinstance(self.rel.through, basestring):
             def resolve_through_model(field, model, cls):
-                field.rel.through_model = model
+                field.rel.through = 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
@@ -946,15 +969,17 @@
         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.
-        if related.model != related.parent_model or not self.rel.symmetrical:
-            # Add the descriptor for the m2m relation
+        # Internal M2Ms (i.e., those with a related name ending with '+')
+        # don't get a related descriptor.
+        if not self.rel.is_hidden():
             setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related))
 
         # Set up the accessors for the column names on the m2m table
-        self.m2m_column_name = curry(self._get_m2m_column_name, related)
-        self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related)
+        self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')
+        self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
+
+        self.m2m_field_name = curry(self._get_m2m_attr, related, 'name')
+        self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name')
 
     def set_attributes_from_rel(self):
         pass
diff -r eff0b46ff6fa django/db/models/loading.py
--- a/django/db/models/loading.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/db/models/loading.py	Fri Oct 30 15:36:20 2009 +0800
@@ -131,19 +131,25 @@
         self._populate()
         return self.app_errors
 
-    def get_models(self, app_mod=None):
+    def get_models(self, app_mod=None, include_auto_created=False):
         """
         Given a module containing models, returns a list of the models.
         Otherwise returns a list of all installed models.
+
+        By default, auto-created models (i.e., m2m models without an
+        explicit intermediate table) are not included. However, if you
+        specify include_auto_created=True, they will be.
         """
         self._populate()
         if app_mod:
-            return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
+            model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
         else:
             model_list = []
             for app_entry in self.app_models.itervalues():
                 model_list.extend(app_entry.values())
-            return model_list
+        if not include_auto_created:
+            return filter(lambda o: not o._meta.auto_created, model_list)
+        return model_list
 
     def get_model(self, app_label, model_name, seed_cache=True):
         """
diff -r eff0b46ff6fa django/db/models/options.py
--- a/django/db/models/options.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/db/models/options.py	Fri Oct 30 15:36:20 2009 +0800
@@ -21,7 +21,7 @@
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
                  'unique_together', 'permissions', 'get_latest_by',
                  'order_with_respect_to', 'app_label', 'db_tablespace',
-                 'abstract', 'managed', 'proxy')
+                 'abstract', 'managed', 'proxy', 'auto_created')
 
 class Options(object):
     def __init__(self, meta, app_label=None):
@@ -47,6 +47,7 @@
         self.proxy_for_model = None
         self.parents = SortedDict()
         self.duplicate_targets = {}
+        self.auto_created = False
 
         # To handle various inheritance situations, we need to track where
         # managers came from (concrete or abstract base classes).
@@ -487,4 +488,3 @@
         Returns the index of the primary key field in the self.fields list.
         """
         return self.fields.index(self.pk)
-
diff -r eff0b46ff6fa django/db/models/query.py
--- a/django/db/models/query.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/django/db/models/query.py	Fri Oct 30 15:36:20 2009 +0800
@@ -1028,7 +1028,8 @@
 
             # Pre-notify all instances to be deleted.
             for pk_val, instance in items:
-                signals.pre_delete.send(sender=cls, instance=instance)
+                if not cls._meta.auto_created:
+                    signals.pre_delete.send(sender=cls, instance=instance)
 
             pk_list = [pk for pk,instance in items]
             del_query = sql.DeleteQuery(cls, connection)
@@ -1062,7 +1063,8 @@
                     if field.rel and field.null and field.rel.to in seen_objs:
                         setattr(instance, field.attname, None)
 
-                signals.post_delete.send(sender=cls, instance=instance)
+                if not cls._meta.auto_created:
+                    signals.post_delete.send(sender=cls, instance=instance)
                 setattr(instance, cls._meta.pk.attname, None)
 
         if forced_managed:
diff -r eff0b46ff6fa docs/internals/deprecation.txt
--- a/docs/internals/deprecation.txt	Fri Oct 30 00:17:29 2009 +0000
+++ b/docs/internals/deprecation.txt	Fri Oct 30 15:36:20 2009 +0800
@@ -22,6 +22,10 @@
         * The old imports for CSRF functionality (``django.contrib.csrf.*``),
           which moved to core in 1.2, will be removed.
 
+    * 1.4
+        * The many to many SQL generation functions on the database backends
+          will be removed.  These have been deprecated since the 1.2 release.
+
     * 2.0
         * ``django.views.defaults.shortcut()``. This function has been moved
           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
diff -r eff0b46ff6fa tests/modeltests/invalid_models/models.py
--- a/tests/modeltests/invalid_models/models.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/tests/modeltests/invalid_models/models.py	Fri Oct 30 15:36:20 2009 +0800
@@ -182,6 +182,7 @@
     """ Model to test for unique ManyToManyFields, which are invalid. """
     unique_people = models.ManyToManyField( Person, unique=True )
 
+
 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
 invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
diff -r eff0b46ff6fa tests/modeltests/m2m_through/models.py
--- a/tests/modeltests/m2m_through/models.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/tests/modeltests/m2m_through/models.py	Fri Oct 30 15:36:20 2009 +0800
@@ -133,7 +133,7 @@
 >>> rock.members.create(name='Anne')
 Traceback (most recent call last):
 ...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
 
 # Remove has similar complications, and is not provided either.
 >>> rock.members.remove(jim)
@@ -160,7 +160,7 @@
 >>> rock.members = backup
 Traceback (most recent call last):
 ...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
 
 # Let's re-save those instances that we've cleared.
 >>> m1.save()
@@ -184,7 +184,7 @@
 >>> bob.group_set.create(name='Funk')
 Traceback (most recent call last):
 ...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
 
 # Remove has similar complications, and is not provided either.
 >>> jim.group_set.remove(rock)
@@ -209,7 +209,7 @@
 >>> jim.group_set = backup
 Traceback (most recent call last):
 ...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
 
 # Let's re-save those instances that we've cleared.
 >>> m1.save()
@@ -334,4 +334,4 @@
 # QuerySet's distinct() method can correct this problem.
 >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct()
 [<Person: Jane>, <Person: Jim>]
-"""}
\ No newline at end of file
+"""}
diff -r eff0b46ff6fa tests/regressiontests/m2m_through_regress/models.py
--- a/tests/regressiontests/m2m_through_regress/models.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/tests/regressiontests/m2m_through_regress/models.py	Fri Oct 30 15:36:20 2009 +0800
@@ -84,22 +84,22 @@
 >>> bob.group_set = []
 Traceback (most recent call last):
 ...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
 
 >>> roll.members = []
 Traceback (most recent call last):
 ...
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
 
 >>> rock.members.create(name='Anne')
 Traceback (most recent call last):
 ...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
 
 >>> bob.group_set.create(name='Funk')
 Traceback (most recent call last):
 ...
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
 
 # Now test that the intermediate with a relationship outside
 # the current app (i.e., UserMembership) workds
diff -r eff0b46ff6fa tests/regressiontests/serializers_regress/models.py
--- a/tests/regressiontests/serializers_regress/models.py	Fri Oct 30 00:17:29 2009 +0000
+++ b/tests/regressiontests/serializers_regress/models.py	Fri Oct 30 15:36:20 2009 +0800
@@ -105,6 +105,9 @@
 
     data = models.CharField(max_length=30)
 
+    class Meta:
+        ordering = ('id',)
+
 class UniqueAnchor(models.Model):
     """This is a model that can be used as
     something for other models to point at"""
@@ -135,7 +138,7 @@
 
 class M2MIntermediateData(models.Model):
     data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
-    
+
 class Intermediate(models.Model):
     left = models.ForeignKey(M2MIntermediateData)
     right = models.ForeignKey(Anchor)
@@ -242,7 +245,7 @@
 
 class InheritAbstractModel(AbstractBaseModel):
     child_data = models.IntegerField()
-    
+
 class BaseModel(models.Model):
     parent_data = models.IntegerField()
 
@@ -252,4 +255,3 @@
 class ExplicitInheritBaseModel(BaseModel):
     parent = models.OneToOneField(BaseModel)
     child_data = models.IntegerField()
-    
\ No newline at end of file
diff -r eff0b46ff6fa tests/regressiontests/signals_regress/models.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/signals_regress/models.py	Fri Oct 30 15:36:20 2009 +0800
@@ -0,0 +1,89 @@
+"""
+Testing signals before/after saving and deleting.
+"""
+
+from django.db import models
+
+class Author(models.Model):
+    name = models.CharField(max_length=20)
+
+    def __unicode__(self):
+        return self.name
+
+class Book(models.Model):
+    name = models.CharField(max_length=20)
+    authors = models.ManyToManyField(Author)
+
+    def __unicode__(self):
+        return self.name
+
+def pre_save_test(signal, sender, instance, **kwargs):
+    print 'pre_save signal,', instance
+    if kwargs.get('raw'):
+        print 'Is raw'
+
+def post_save_test(signal, sender, instance, **kwargs):
+    print 'post_save signal,', instance
+    if 'created' in kwargs:
+        if kwargs['created']:
+            print 'Is created'
+        else:
+            print 'Is updated'
+    if kwargs.get('raw'):
+        print 'Is raw'
+
+def pre_delete_test(signal, sender, instance, **kwargs):
+    print 'pre_delete signal,', instance
+    print 'instance.id is not None: %s' % (instance.id != None)
+
+def post_delete_test(signal, sender, instance, **kwargs):
+    print 'post_delete signal,', instance
+    print 'instance.id is not None: %s' % (instance.id != None)
+
+__test__ = {'API_TESTS':"""
+
+# Save up the number of connected signals so that we can check at the end
+# that all the signals we register get properly unregistered (#9989)
+>>> pre_signals = (len(models.signals.pre_save.receivers),
+...                len(models.signals.post_save.receivers),
+...                len(models.signals.pre_delete.receivers),
+...                len(models.signals.post_delete.receivers))
+
+>>> models.signals.pre_save.connect(pre_save_test)
+>>> models.signals.post_save.connect(post_save_test)
+>>> models.signals.pre_delete.connect(pre_delete_test)
+>>> models.signals.post_delete.connect(post_delete_test)
+
+>>> a1 = Author(name='Neal Stephenson')
+>>> a1.save()
+pre_save signal, Neal Stephenson
+post_save signal, Neal Stephenson
+Is created
+
+>>> b1 = Book(name='Snow Crash')
+>>> b1.save()
+pre_save signal, Snow Crash
+post_save signal, Snow Crash
+Is created
+
+# Assigning to m2m shouldn't generate an m2m signal
+>>> b1.authors = [a1]
+
+# Removing an author from an m2m shouldn't generate an m2m signal
+>>> b1.authors = []
+
+>>> models.signals.post_delete.disconnect(post_delete_test)
+>>> models.signals.pre_delete.disconnect(pre_delete_test)
+>>> models.signals.post_save.disconnect(post_save_test)
+>>> models.signals.pre_save.disconnect(pre_save_test)
+
+# Check that all our signals got disconnected properly.
+>>> post_signals = (len(models.signals.pre_save.receivers),
+...                 len(models.signals.post_save.receivers),
+...                 len(models.signals.pre_delete.receivers),
+...                 len(models.signals.post_delete.receivers))
+
+>>> pre_signals == post_signals
+True
+
+"""}
