summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2009-11-03 14:02:49 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2009-11-03 14:02:49 +0000
commit585b7acaa359fc1df07269c1a4b4756bdb6703f7 (patch)
tree8fd3d96de629257ee7bb51e757e20b74df6b3f22 /django/db
parentaba5389326372be43b2a3bdcda16646fd197e807 (diff)
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
Diffstat (limited to 'django/db')
-rw-r--r--django/db/models/base.py11
-rw-r--r--django/db/models/fields/related.py285
-rw-r--r--django/db/models/loading.py12
-rw-r--r--django/db/models/options.py4
-rw-r--r--django/db/models/query.py6
5 files changed, 180 insertions, 138 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index ce8dda204a..c7f6ba2f7c 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -434,7 +434,7 @@ class Model(object):
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.
@@ -507,7 +507,7 @@ class Model(object):
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)
@@ -544,7 +544,12 @@ class Model(object):
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 --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 529898ea27..d4e418ea89 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -58,6 +58,10 @@ def add_lazy_relation(cls, field, relation, operation):
# 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 @@ class RelatedField(object):
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 @@ class ForeignRelatedObjectsDescriptor(object):
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 @@ def create_many_related_manager(superclass, through=False):
# 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 @@ def create_many_related_manager(superclass, through=False):
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 fieldname 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 @@ def create_many_related_manager(superclass, through=False):
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 @@ class ManyRelatedObjectsDescriptor(object):
# 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 @@ class ManyRelatedObjectsDescriptor(object):
if instance is None:
raise AttributeError, "Manager must be accessed via instance"
- through = getattr(self.related.field.rel, 'through', None)
- if through 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 @@ class ReverseManyRelatedObjectsDescriptor(object):
# model's default manager.
rel_model=self.field.rel.to
superclass = rel_model._default_manager.__class__
- RelatedManager = create_many_related_manager(superclass, 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 @@ class ReverseManyRelatedObjectsDescriptor(object):
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 @@ class ManyToOneRel(object):
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 @@ class ManyToManyRel(object):
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 @@ class ForeignKey(RelatedField, Field):
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 @@ class ForeignKey(RelatedField, Field):
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,43 @@ class OneToOneField(ForeignKey):
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
+ 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()
+ meta = type('Meta', (object,), {
+ 'db_table': field._get_m2m_db_table(klass._meta),
+ 'managed': managed,
+ 'auto_created': klass,
+ 'unique_together': (from_, to)
+ })
+ 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 +849,7 @@ class ManyToManyField(RelatedField, Field):
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 +862,45 @@ class ManyToManyField(RelatedField, Field):
def _get_m2m_db_table(self, opts):
"Function that can be curried to provide the m2m table name for this relation"
if self.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
- 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
+ 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_name(self, related):
+ def _get_m2m_reverse_attr(self, related, attr):
"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
+ 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
+ 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 +942,17 @@ class ManyToManyField(RelatedField, Field):
# 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)
+
+ # The intermediate m2m model is not auto created if:
+ # 1) There is a manually specified intermediate, or
+ # 2) The class owning the m2m field is abstract.
+ if not self.rel.through and not cls._meta.abstract:
+ 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 +963,8 @@ class ManyToManyField(RelatedField, Field):
# 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 +973,17 @@ class ManyToManyField(RelatedField, Field):
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 --git a/django/db/models/loading.py b/django/db/models/loading.py
index e07aab4efe..4ab1d5005a 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -131,19 +131,25 @@ class AppCache(object):
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 --git a/django/db/models/options.py b/django/db/models/options.py
index 34dd2aac34..05ff54a333 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|
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 @@ class Options(object):
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 @@ class Options(object):
Returns the index of the primary key field in the self.fields list.
"""
return self.fields.index(self.pk)
-
diff --git a/django/db/models/query.py b/django/db/models/query.py
index d6d290584e..36949ac390 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1028,7 +1028,8 @@ def delete_objects(seen_objs):
# 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 @@ def delete_objects(seen_objs):
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: