Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #10109 -- Removed the use of raw SQL in many-to-many fields by …

…introducing an autogenerated through model.

This is the first part of Alex Gaynor's GSoC project to add Multi-db support to Django.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11710 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 585b7acaa359fc1df07269c1a4b4756bdb6703f7 1 parent aba5389
Russell Keith-Magee authored November 03, 2009
5  django/contrib/admin/options.py
@@ -153,8 +153,9 @@ def formfield_for_manytomany(self, db_field, request=None, **kwargs):
153 153
         """
154 154
         Get a form Field for a ManyToManyField.
155 155
         """
156  
-        # If it uses an intermediary model, don't show field in admin.
157  
-        if db_field.rel.through is not None:
  156
+        # If it uses an intermediary model that isn't auto created, don't show
  157
+        # a field in admin.
  158
+        if not db_field.rel.through._meta.auto_created:
158 159
             return None
159 160
 
160 161
         if db_field.name in self.raw_id_fields:
2  django/contrib/contenttypes/generic.py
@@ -105,8 +105,6 @@ def __init__(self, to, **kwargs):
105 105
                             limit_choices_to=kwargs.pop('limit_choices_to', None),
106 106
                             symmetrical=kwargs.pop('symmetrical', True))
107 107
 
108  
-        # By its very nature, a GenericRelation doesn't create a table.
109  
-        self.creates_table = False
110 108
 
111 109
         # Override content-type/object-id field names on the related class
112 110
         self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
20  django/core/management/commands/syncdb.py
@@ -57,12 +57,15 @@ def handle_noargs(self, **options):
57 57
         # Create the tables for each model
58 58
         for app in models.get_apps():
59 59
             app_name = app.__name__.split('.')[-2]
60  
-            model_list = models.get_models(app)
  60
+            model_list = models.get_models(app, include_auto_created=True)
61 61
             for model in model_list:
62 62
                 # Create the model's database table, if it doesn't already exist.
63 63
                 if verbosity >= 2:
64 64
                     print "Processing %s.%s model" % (app_name, model._meta.object_name)
65  
-                if connection.introspection.table_name_converter(model._meta.db_table) in tables:
  65
+                opts = model._meta
  66
+                if (connection.introspection.table_name_converter(opts.db_table) in tables or
  67
+                    (opts.auto_created and
  68
+                    connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))):
66 69
                     continue
67 70
                 sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
68 71
                 seen_models.add(model)
@@ -78,19 +81,6 @@ def handle_noargs(self, **options):
78 81
                     cursor.execute(statement)
79 82
                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
80 83
 
81  
-        # Create the m2m tables. This must be done after all tables have been created
82  
-        # to ensure that all referred tables will exist.
83  
-        for app in models.get_apps():
84  
-            app_name = app.__name__.split('.')[-2]
85  
-            model_list = models.get_models(app)
86  
-            for model in model_list:
87  
-                if model in created_models:
88  
-                    sql = connection.creation.sql_for_many_to_many(model, self.style)
89  
-                    if sql:
90  
-                        if verbosity >= 2:
91  
-                            print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name)
92  
-                        for statement in sql:
93  
-                            cursor.execute(statement)
94 84
 
95 85
         transaction.commit_unless_managed()
96 86
 
15  django/core/management/sql.py
@@ -23,7 +23,7 @@ def sql_create(app, style):
23 23
     # We trim models from the current app so that the sqlreset command does not
24 24
     # generate invalid SQL (leaving models out of known_models is harmless, so
25 25
     # we can be conservative).
26  
-    app_models = models.get_models(app)
  26
+    app_models = models.get_models(app, include_auto_created=True)
27 27
     final_output = []
28 28
     tables = connection.introspection.table_names()
29 29
     known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models])
@@ -40,10 +40,6 @@ def sql_create(app, style):
40 40
         # Keep track of the fact that we've created the table for this model.
41 41
         known_models.add(model)
42 42
 
43  
-    # Create the many-to-many join tables.
44  
-    for model in app_models:
45  
-        final_output.extend(connection.creation.sql_for_many_to_many(model, style))
46  
-
47 43
     # Handle references to tables that are from other apps
48 44
     # but don't exist physically.
49 45
     not_installed_models = set(pending_references.keys())
@@ -82,7 +78,7 @@ def sql_delete(app, style):
82 78
     to_delete = set()
83 79
 
84 80
     references_to_delete = {}
85  
-    app_models = models.get_models(app)
  81
+    app_models = models.get_models(app, include_auto_created=True)
86 82
     for model in app_models:
87 83
         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
88 84
             # The table exists, so it needs to be dropped
@@ -97,13 +93,6 @@ def sql_delete(app, style):
97 93
         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
98 94
             output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
99 95
 
100  
-    # Output DROP TABLE statements for many-to-many tables.
101  
-    for model in app_models:
102  
-        opts = model._meta
103  
-        for f in opts.local_many_to_many:
104  
-            if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names:
105  
-                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
106  
-
107 96
     # Close database connection explicitly, in case this output is being piped
108 97
     # directly into a database client, to avoid locking issues.
109 98
     if cursor:
151  django/core/management/validation.py
@@ -79,27 +79,28 @@ def get_validation_errors(outfile, app=None):
79 79
                 rel_opts = f.rel.to._meta
80 80
                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
81 81
                 rel_query_name = f.related_query_name()
82  
-                for r in rel_opts.fields:
83  
-                    if r.name == rel_name:
84  
-                        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))
85  
-                    if r.name == rel_query_name:
86  
-                        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))
87  
-                for r in rel_opts.local_many_to_many:
88  
-                    if r.name == rel_name:
89  
-                        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))
90  
-                    if r.name == rel_query_name:
91  
-                        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))
92  
-                for r in rel_opts.get_all_related_many_to_many_objects():
93  
-                    if r.get_accessor_name() == rel_name:
94  
-                        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))
95  
-                    if r.get_accessor_name() == rel_query_name:
96  
-                        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))
97  
-                for r in rel_opts.get_all_related_objects():
98  
-                    if r.field is not f:
  82
+                if not f.rel.is_hidden():
  83
+                    for r in rel_opts.fields:
  84
+                        if r.name == rel_name:
  85
+                            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))
  86
+                        if r.name == rel_query_name:
  87
+                            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))
  88
+                    for r in rel_opts.local_many_to_many:
  89
+                        if r.name == rel_name:
  90
+                            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))
  91
+                        if r.name == rel_query_name:
  92
+                            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))
  93
+                    for r in rel_opts.get_all_related_many_to_many_objects():
99 94
                         if r.get_accessor_name() == rel_name:
100  
-                            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))
  95
+                            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))
101 96
                         if r.get_accessor_name() == rel_query_name:
102  
-                            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))
  97
+                            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))
  98
+                    for r in rel_opts.get_all_related_objects():
  99
+                        if r.field is not f:
  100
+                            if r.get_accessor_name() == rel_name:
  101
+                                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))
  102
+                            if r.get_accessor_name() == rel_query_name:
  103
+                                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))
103 104
 
104 105
         seen_intermediary_signatures = []
105 106
         for i, f in enumerate(opts.local_many_to_many):
@@ -117,48 +118,80 @@ def get_validation_errors(outfile, app=None):
117 118
             if f.unique:
118 119
                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
119 120
 
120  
-            if getattr(f.rel, 'through', None) is not None:
121  
-                if hasattr(f.rel, 'through_model'):
122  
-                    from_model, to_model = cls, f.rel.to
123  
-                    if from_model == to_model and f.rel.symmetrical:
124  
-                        e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
125  
-                    seen_from, seen_to, seen_self = False, False, 0
126  
-                    for inter_field in f.rel.through_model._meta.fields:
127  
-                        rel_to = getattr(inter_field.rel, 'to', None)
128  
-                        if from_model == to_model: # relation to self
129  
-                            if rel_to == from_model:
130  
-                                seen_self += 1
131  
-                            if seen_self > 2:
132  
-                                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))
133  
-                        else:
134  
-                            if rel_to == from_model:
135  
-                                if seen_from:
136  
-                                    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))
137  
-                                else:
138  
-                                    seen_from = True
139  
-                            elif rel_to == to_model:
140  
-                                if seen_to:
141  
-                                    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))
142  
-                                else:
143  
-                                    seen_to = True
144  
-                    if f.rel.through_model not in models.get_models():
145  
-                        e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
146  
-                    signature = (f.rel.to, cls, f.rel.through_model)
147  
-                    if signature in seen_intermediary_signatures:
148  
-                        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))
  121
+            if f.rel.through is not None and not isinstance(f.rel.through, basestring):
  122
+                from_model, to_model = cls, f.rel.to
  123
+                if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
  124
+                    e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
  125
+                seen_from, seen_to, seen_self = False, False, 0
  126
+                for inter_field in f.rel.through._meta.fields:
  127
+                    rel_to = getattr(inter_field.rel, 'to', None)
  128
+                    if from_model == to_model: # relation to self
  129
+                        if rel_to == from_model:
  130
+                            seen_self += 1
  131
+                        if seen_self > 2:
  132
+                            e.add(opts, "Intermediary model %s has more than "
  133
+                                "two foreign keys to %s, which is ambiguous "
  134
+                                "and is not permitted." % (
  135
+                                    f.rel.through._meta.object_name,
  136
+                                    from_model._meta.object_name
  137
+                                )
  138
+                            )
149 139
                     else:
150  
-                        seen_intermediary_signatures.append(signature)
151  
-                    seen_related_fk, seen_this_fk = False, False
152  
-                    for field in f.rel.through_model._meta.fields:
153  
-                        if field.rel:
154  
-                            if not seen_related_fk and field.rel.to == f.rel.to:
155  
-                                seen_related_fk = True
156  
-                            elif field.rel.to == cls:
157  
-                                seen_this_fk = True
158  
-                    if not seen_related_fk or not seen_this_fk:
159  
-                        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))
  140
+                        if rel_to == from_model:
  141
+                            if seen_from:
  142
+                                e.add(opts, "Intermediary model %s has more "
  143
+                                    "than one foreign key to %s, which is "
  144
+                                    "ambiguous and is not permitted." % (
  145
+                                        f.rel.through._meta.object_name,
  146
+                                         from_model._meta.object_name
  147
+                                     )
  148
+                                 )
  149
+                            else:
  150
+                                seen_from = True
  151
+                        elif rel_to == to_model:
  152
+                            if seen_to:
  153
+                                e.add(opts, "Intermediary model %s has more "
  154
+                                    "than one foreign key to %s, which is "
  155
+                                    "ambiguous and is not permitted." % (
  156
+                                        f.rel.through._meta.object_name,
  157
+                                        rel_to._meta.object_name
  158
+                                    )
  159
+                                )
  160
+                            else:
  161
+                                seen_to = True
  162
+                if f.rel.through not in models.get_models(include_auto_created=True):
  163
+                    e.add(opts, "'%s' specifies an m2m relation through model "
  164
+                        "%s, which has not been installed." % (f.name, f.rel.through)
  165
+                    )
  166
+                signature = (f.rel.to, cls, f.rel.through)
  167
+                if signature in seen_intermediary_signatures:
  168
+                    e.add(opts, "The model %s has two manually-defined m2m "
  169
+                        "relations through the model %s, which is not "
  170
+                        "permitted. Please consider using an extra field on "
  171
+                        "your intermediary model instead." % (
  172
+                            cls._meta.object_name,
  173
+                            f.rel.through._meta.object_name
  174
+                        )
  175
+                    )
160 176
                 else:
161  
-                    e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
  177
+                    seen_intermediary_signatures.append(signature)
  178
+                seen_related_fk, seen_this_fk = False, False
  179
+                for field in f.rel.through._meta.fields:
  180
+                    if field.rel:
  181
+                        if not seen_related_fk and field.rel.to == f.rel.to:
  182
+                            seen_related_fk = True
  183
+                        elif field.rel.to == cls:
  184
+                            seen_this_fk = True
  185
+                if not seen_related_fk or not seen_this_fk:
  186
+                    e.add(opts, "'%s' has a manually-defined m2m relation "
  187
+                        "through model %s, which does not have foreign keys "
  188
+                        "to %s and %s" % (f.name, f.rel.through._meta.object_name,
  189
+                            f.rel.to._meta.object_name, cls._meta.object_name)
  190
+                    )
  191
+            elif isinstance(f.rel.through, basestring):
  192
+                e.add(opts, "'%s' specifies an m2m relation through model %s, "
  193
+                    "which has not been installed" % (f.name, f.rel.through)
  194
+                )
162 195
 
163 196
             rel_opts = f.rel.to._meta
164 197
             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
2  django/core/serializers/python.py
@@ -56,7 +56,7 @@ def handle_fk_field(self, obj, field):
56 56
         self._current[field.name] = smart_unicode(related, strings_only=True)
57 57
 
58 58
     def handle_m2m_field(self, obj, field):
59  
-        if field.creates_table:
  59
+        if field.rel.through._meta.auto_created:
60 60
             self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
61 61
                                for related in getattr(obj, field.name).iterator()]
62 62
 
3  django/core/serializers/xml_serializer.py
@@ -98,7 +98,7 @@ def handle_m2m_field(self, obj, field):
98 98
         serialized as references to the object's PK (i.e. the related *data*
99 99
         is not dumped, just the relation).
100 100
         """
101  
-        if field.creates_table:
  101
+        if field.rel.through._meta.auto_created:
102 102
             self._start_relational_field(field)
103 103
             for relobj in getattr(obj, field.name).iterator():
104 104
                 self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
@@ -233,4 +233,3 @@ def getInnerText(node):
233 233
         else:
234 234
            pass
235 235
     return u"".join(inner_text)
236  
-
11  django/db/models/base.py
@@ -434,7 +434,7 @@ def save_base(self, raw=False, cls=None, origin=None,
434 434
         else:
435 435
             meta = cls._meta
436 436
 
437  
-        if origin:
  437
+        if origin and not meta.auto_created:
438 438
             signals.pre_save.send(sender=origin, instance=self, raw=raw)
439 439
 
440 440
         # If we are in a raw save, save the object exactly as presented.
@@ -507,7 +507,7 @@ def save_base(self, raw=False, cls=None, origin=None,
507 507
                     setattr(self, meta.pk.attname, result)
508 508
             transaction.commit_unless_managed()
509 509
 
510  
-        if origin:
  510
+        if origin and not meta.auto_created:
511 511
             signals.post_save.send(sender=origin, instance=self,
512 512
                 created=(not record_exists), raw=raw)
513 513
 
@@ -544,7 +544,12 @@ def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
544 544
                         rel_descriptor = cls.__dict__[rel_opts_name]
545 545
                         break
546 546
                 else:
547  
-                    raise AssertionError("Should never get here.")
  547
+                    # in the case of a hidden fkey just skip it, it'll get
  548
+                    # processed as an m2m
  549
+                    if not related.field.rel.is_hidden():
  550
+                        raise AssertionError("Should never get here.")
  551
+                    else:
  552
+                        continue
548 553
                 delete_qs = rel_descriptor.delete_manager(self).all()
549 554
                 for sub_obj in delete_qs:
550 555
                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
289  django/db/models/fields/related.py
@@ -58,6 +58,10 @@ class MyModel(Model):
@@ -96,7 +100,7 @@ def contribute_to_class(self, cls, name):
@@ -401,22 +405,22 @@ def clear(self):
@@ -425,36 +429,37 @@ def get_query_set(self):
@@ -470,41 +475,38 @@ def get_or_create(self, **kwargs):
@@ -515,24 +517,20 @@ def _remove_items(self, source_col_name, target_col_name, *objs):
@@ -554,17 +552,15 @@ def __get__(self, instance, instance_type=None):
@@ -573,9 +569,9 @@ def __set__(self, instance, value):
@@ -599,17 +595,15 @@ def __get__(self, instance, instance_type=None):
@@ -618,9 +612,9 @@ def __set__(self, instance, value):
@@ -642,6 +636,10 @@ def __init__(self, to, field_name, related_name=None,
@@ -673,6 +671,10 @@ def __init__(self, to, related_name=None, limit_choices_to=None,
@@ -690,7 +692,6 @@ def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
@@ -743,7 +744,12 @@ def contribute_to_class(self, cls, name):
@@ -790,6 +796,43 @@ def formfield(self, **kwargs):
@@ -806,10 +849,7 @@ def __init__(self, to, **kwargs):
@@ -822,62 +862,45 @@ def get_choices_default(self):
@@ -919,10 +942,17 @@ def contribute_to_class(self, cls, name):
@@ -933,11 +963,8 @@ def contribute_to_class(self, cls, name):
@@ -946,15 +973,17 @@ def resolve_through_model(field, model, cls):
12  django/db/models/loading.py
@@ -131,19 +131,25 @@ def get_app_errors(self):
131 131
         self._populate()
132 132
         return self.app_errors
133 133
 
134  
-    def get_models(self, app_mod=None):
  134
+    def get_models(self, app_mod=None, include_auto_created=False):
135 135
         """
136 136
         Given a module containing models, returns a list of the models.
137 137
         Otherwise returns a list of all installed models.
  138
+
  139
+        By default, auto-created models (i.e., m2m models without an
  140
+        explicit intermediate table) are not included. However, if you
  141
+        specify include_auto_created=True, they will be.
138 142
         """
139 143
         self._populate()
140 144
         if app_mod:
141  
-            return self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
  145
+            model_list = self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict()).values()
142 146
         else:
143 147
             model_list = []
144 148
             for app_entry in self.app_models.itervalues():
145 149
                 model_list.extend(app_entry.values())
146  
-            return model_list
  150
+        if not include_auto_created:
  151
+            return filter(lambda o: not o._meta.auto_created, model_list)
  152
+        return model_list
147 153
 
148 154
     def get_model(self, app_label, model_name, seed_cache=True):
149 155
         """
4  django/db/models/options.py
@@ -21,7 +21,7 @@
21 21
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
22 22
                  'unique_together', 'permissions', 'get_latest_by',
23 23
                  'order_with_respect_to', 'app_label', 'db_tablespace',
24  
-                 'abstract', 'managed', 'proxy')
  24
+                 'abstract', 'managed', 'proxy', 'auto_created')
25 25
 
26 26
 class Options(object):
27 27
     def __init__(self, meta, app_label=None):
@@ -47,6 +47,7 @@ def __init__(self, meta, app_label=None):
47 47
         self.proxy_for_model = None
48 48
         self.parents = SortedDict()
49 49
         self.duplicate_targets = {}
  50
+        self.auto_created = False
50 51
 
51 52
         # To handle various inheritance situations, we need to track where
52 53
         # managers came from (concrete or abstract base classes).
@@ -487,4 +488,3 @@ def pk_index(self):
487 488
         Returns the index of the primary key field in the self.fields list.
488 489
         """
489 490
         return self.fields.index(self.pk)
490  
-
6  django/db/models/query.py
@@ -1028,7 +1028,8 @@ def delete_objects(seen_objs):
1028 1028
 
1029 1029
             # Pre-notify all instances to be deleted.
1030 1030
             for pk_val, instance in items:
1031  
-                signals.pre_delete.send(sender=cls, instance=instance)
  1031
+                if not cls._meta.auto_created:
  1032
+                    signals.pre_delete.send(sender=cls, instance=instance)
1032 1033
 
1033 1034
             pk_list = [pk for pk,instance in items]
1034 1035
             del_query = sql.DeleteQuery(cls, connection)
@@ -1062,7 +1063,8 @@ def delete_objects(seen_objs):
1062 1063
                     if field.rel and field.null and field.rel.to in seen_objs:
1063 1064
                         setattr(instance, field.attname, None)
1064 1065
 
1065  
-                signals.post_delete.send(sender=cls, instance=instance)
  1066
+                if not cls._meta.auto_created:
  1067
+                    signals.post_delete.send(sender=cls, instance=instance)
1066 1068
                 setattr(instance, cls._meta.pk.attname, None)
1067 1069
 
1068 1070
         if forced_managed:
3  docs/internals/deprecation.txt
@@ -25,6 +25,9 @@ their deprecation, as per the :ref:`Django deprecation policy
25 25
         * ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection``
26 26
           class in favor of a generic E-mail backend API.
27 27
 
  28
+        * The many to many SQL generation functions on the database backends
  29
+          will be removed.  These have been deprecated since the 1.2 release.
  30
+
28 31
     * 2.0
29 32
         * ``django.views.defaults.shortcut()``. This function has been moved
30 33
           to ``django.contrib.contenttypes.views.shortcut()`` as part of the
1  tests/modeltests/invalid_models/models.py
@@ -182,6 +182,7 @@ class UniqueM2M(models.Model):
182 182
     """ Model to test for unique ManyToManyFields, which are invalid. """
183 183
     unique_people = models.ManyToManyField( Person, unique=True )
184 184
 
  185
+
185 186
 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
186 187
 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
187 188
 invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
10  tests/modeltests/m2m_through/models.py
@@ -133,7 +133,7 @@ class Friendship(models.Model):
133 133
 >>> rock.members.create(name='Anne')
134 134
 Traceback (most recent call last):
135 135
 ...
136  
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
  136
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
137 137
 
138 138
 # Remove has similar complications, and is not provided either.
139 139
 >>> rock.members.remove(jim)
@@ -160,7 +160,7 @@ class Friendship(models.Model):
160 160
 >>> rock.members = backup
161 161
 Traceback (most recent call last):
162 162
 ...
163  
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  163
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
164 164
 
165 165
 # Let's re-save those instances that we've cleared.
166 166
 >>> m1.save()
@@ -184,7 +184,7 @@ class Friendship(models.Model):
184 184
 >>> bob.group_set.create(name='Funk')
185 185
 Traceback (most recent call last):
186 186
 ...
187  
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use Membership's Manager instead.
  187
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead.
188 188
 
189 189
 # Remove has similar complications, and is not provided either.
190 190
 >>> jim.group_set.remove(rock)
@@ -209,7 +209,7 @@ class Friendship(models.Model):
209 209
 >>> jim.group_set = backup
210 210
 Traceback (most recent call last):
211 211
 ...
212  
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  212
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through.Membership's Manager instead.
213 213
 
214 214
 # Let's re-save those instances that we've cleared.
215 215
 >>> m1.save()
@@ -334,4 +334,4 @@ class Friendship(models.Model):
334 334
 # QuerySet's distinct() method can correct this problem.
335 335
 >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct()
336 336
 [<Person: Jane>, <Person: Jim>]
337  
-"""}
  337
+"""}
8  tests/regressiontests/m2m_through_regress/models.py
@@ -84,22 +84,22 @@ class B(models.Model):
84 84
 >>> bob.group_set = []
85 85
 Traceback (most recent call last):
86 86
 ...
87  
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  87
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
88 88
 
89 89
 >>> roll.members = []
90 90
 Traceback (most recent call last):
91 91
 ...
92  
-AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  92
+AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
93 93
 
94 94
 >>> rock.members.create(name='Anne')
95 95
 Traceback (most recent call last):
96 96
 ...
97  
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  97
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
98 98
 
99 99
 >>> bob.group_set.create(name='Funk')
100 100
 Traceback (most recent call last):
101 101
 ...
102  
-AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use Membership's Manager instead.
  102
+AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model.  Use m2m_through_regress.Membership's Manager instead.
103 103
 
104 104
 # Now test that the intermediate with a relationship outside
105 105
 # the current app (i.e., UserMembership) workds
66  tests/regressiontests/model_inheritance_regress/models.py
@@ -110,6 +110,36 @@ def __unicode__(self):
110 110
         return "PK = %d, base_name = %s, derived_name = %s" \
111 111
                 % (self.customPK, self.base_name, self.derived_name)
112 112
 
  113
+# Check that abstract classes don't get m2m tables autocreated.
  114
+class Person(models.Model):
  115
+    name = models.CharField(max_length=100)
  116
+
  117
+    class Meta:
  118
+        ordering = ('name',)
  119
+
  120
+    def __unicode__(self):
  121
+        return self.name
  122
+
  123
+class AbstractEvent(models.Model):
  124
+    name = models.CharField(max_length=100)
  125
+    attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
  126
+
  127
+    class Meta:
  128
+        abstract = True
  129
+        ordering = ('name',)
  130
+
  131
+    def __unicode__(self):
  132
+        return self.name
  133
+
  134
+class BirthdayParty(AbstractEvent):
  135
+    pass
  136
+
  137
+class BachelorParty(AbstractEvent):
  138
+    pass
  139
+
  140
+class MessyBachelorParty(BachelorParty):
  141
+    pass
  142
+
113 143
 __test__ = {'API_TESTS':"""
114 144
 # Regression for #7350, #7202
115 145
 # Check that when you create a Parent object with a specific reference to an
@@ -318,5 +348,41 @@ def __unicode__(self):
318 348
 >>> ParkingLot3._meta.get_ancestor_link(Place).name  # the child->parent link
319 349
 "parent"
320 350
 
  351
+# Check that many-to-many relations defined on an abstract base class
  352
+# are correctly inherited (and created) on the child class.
  353
+>>> p1 = Person.objects.create(name='Alice')
  354
+>>> p2 = Person.objects.create(name='Bob')
  355
+>>> p3 = Person.objects.create(name='Carol')
  356
+>>> p4 = Person.objects.create(name='Dave')
  357
+
  358
+>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice')
  359
+>>> birthday.attendees = [p1, p3]
  360
+
  361
+>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
  362
+>>> bachelor.attendees = [p2, p4]
  363
+
  364
+>>> print p1.birthdayparty_set.all()
  365
+[<BirthdayParty: Birthday party for Alice>]
  366
+
  367
+>>> print p1.bachelorparty_set.all()
  368
+[]
  369
+
  370
+>>> print p2.bachelorparty_set.all()
  371
+[<BachelorParty: Bachelor party for Bob>]
  372
+
  373
+# Check that a subclass of a subclass of an abstract model
  374
+# doesn't get it's own accessor.