Kaydet (Commit) 585b7aca authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

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

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
üst aba53893
...@@ -153,8 +153,9 @@ class BaseModelAdmin(object): ...@@ -153,8 +153,9 @@ class BaseModelAdmin(object):
""" """
Get a form Field for a ManyToManyField. Get a form Field for a ManyToManyField.
""" """
# If it uses an intermediary model, don't show field in admin. # If it uses an intermediary model that isn't auto created, don't show
if db_field.rel.through is not None: # a field in admin.
if not db_field.rel.through._meta.auto_created:
return None return None
if db_field.name in self.raw_id_fields: if db_field.name in self.raw_id_fields:
......
...@@ -105,8 +105,6 @@ class GenericRelation(RelatedField, Field): ...@@ -105,8 +105,6 @@ class GenericRelation(RelatedField, Field):
limit_choices_to=kwargs.pop('limit_choices_to', None), limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', True)) 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 # Override content-type/object-id field names on the related class
self.object_id_field_name = kwargs.pop("object_id_field", "object_id") self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
......
...@@ -57,12 +57,15 @@ class Command(NoArgsCommand): ...@@ -57,12 +57,15 @@ class Command(NoArgsCommand):
# Create the tables for each model # Create the tables for each model
for app in models.get_apps(): for app in models.get_apps():
app_name = app.__name__.split('.')[-2] 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: for model in model_list:
# Create the model's database table, if it doesn't already exist. # Create the model's database table, if it doesn't already exist.
if verbosity >= 2: if verbosity >= 2:
print "Processing %s.%s model" % (app_name, model._meta.object_name) 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 continue
sql, references = connection.creation.sql_create_model(model, self.style, seen_models) sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
seen_models.add(model) seen_models.add(model)
...@@ -78,19 +81,6 @@ class Command(NoArgsCommand): ...@@ -78,19 +81,6 @@ class Command(NoArgsCommand):
cursor.execute(statement) cursor.execute(statement)
tables.append(connection.introspection.table_name_converter(model._meta.db_table)) 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() transaction.commit_unless_managed()
......
...@@ -23,7 +23,7 @@ def sql_create(app, style): ...@@ -23,7 +23,7 @@ def sql_create(app, style):
# We trim models from the current app so that the sqlreset command does not # 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 # generate invalid SQL (leaving models out of known_models is harmless, so
# we can be conservative). # we can be conservative).
app_models = models.get_models(app) app_models = models.get_models(app, include_auto_created=True)
final_output = [] final_output = []
tables = connection.introspection.table_names() tables = connection.introspection.table_names()
known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models]) 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,10 +40,6 @@ def sql_create(app, style):
# Keep track of the fact that we've created the table for this model. # Keep track of the fact that we've created the table for this model.
known_models.add(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 # Handle references to tables that are from other apps
# but don't exist physically. # but don't exist physically.
not_installed_models = set(pending_references.keys()) not_installed_models = set(pending_references.keys())
...@@ -82,7 +78,7 @@ def sql_delete(app, style): ...@@ -82,7 +78,7 @@ def sql_delete(app, style):
to_delete = set() to_delete = set()
references_to_delete = {} references_to_delete = {}
app_models = models.get_models(app) app_models = models.get_models(app, include_auto_created=True)
for model in app_models: for model in app_models:
if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names: if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
# The table exists, so it needs to be dropped # The table exists, so it needs to be dropped
...@@ -97,13 +93,6 @@ def sql_delete(app, style): ...@@ -97,13 +93,6 @@ def sql_delete(app, style):
if connection.introspection.table_name_converter(model._meta.db_table) in table_names: 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.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 # Close database connection explicitly, in case this output is being piped
# directly into a database client, to avoid locking issues. # directly into a database client, to avoid locking issues.
if cursor: if cursor:
......
...@@ -56,7 +56,7 @@ class Serializer(base.Serializer): ...@@ -56,7 +56,7 @@ class Serializer(base.Serializer):
self._current[field.name] = smart_unicode(related, strings_only=True) self._current[field.name] = smart_unicode(related, strings_only=True)
def handle_m2m_field(self, obj, field): 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) self._current[field.name] = [smart_unicode(related._get_pk_val(), strings_only=True)
for related in getattr(obj, field.name).iterator()] for related in getattr(obj, field.name).iterator()]
......
...@@ -98,7 +98,7 @@ class Serializer(base.Serializer): ...@@ -98,7 +98,7 @@ class Serializer(base.Serializer):
serialized as references to the object's PK (i.e. the related *data* serialized as references to the object's PK (i.e. the related *data*
is not dumped, just the relation). is not dumped, just the relation).
""" """
if field.creates_table: if field.rel.through._meta.auto_created:
self._start_relational_field(field) self._start_relational_field(field)
for relobj in getattr(obj, field.name).iterator(): for relobj in getattr(obj, field.name).iterator():
self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())}) self.xml.addQuickElement("object", attrs={"pk" : smart_unicode(relobj._get_pk_val())})
...@@ -233,4 +233,3 @@ def getInnerText(node): ...@@ -233,4 +233,3 @@ def getInnerText(node):
else: else:
pass pass
return u"".join(inner_text) return u"".join(inner_text)
...@@ -434,7 +434,7 @@ class Model(object): ...@@ -434,7 +434,7 @@ class Model(object):
else: else:
meta = cls._meta meta = cls._meta
if origin: if origin and not meta.auto_created:
signals.pre_save.send(sender=origin, instance=self, raw=raw) signals.pre_save.send(sender=origin, instance=self, raw=raw)
# If we are in a raw save, save the object exactly as presented. # If we are in a raw save, save the object exactly as presented.
...@@ -507,7 +507,7 @@ class Model(object): ...@@ -507,7 +507,7 @@ class Model(object):
setattr(self, meta.pk.attname, result) setattr(self, meta.pk.attname, result)
transaction.commit_unless_managed() transaction.commit_unless_managed()
if origin: if origin and not meta.auto_created:
signals.post_save.send(sender=origin, instance=self, signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw) created=(not record_exists), raw=raw)
...@@ -544,7 +544,12 @@ class Model(object): ...@@ -544,7 +544,12 @@ class Model(object):
rel_descriptor = cls.__dict__[rel_opts_name] rel_descriptor = cls.__dict__[rel_opts_name]
break break
else: 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() delete_qs = rel_descriptor.delete_manager(self).all()
for sub_obj in delete_qs: for sub_obj in delete_qs:
sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null) sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
......
...@@ -131,19 +131,25 @@ class AppCache(object): ...@@ -131,19 +131,25 @@ class AppCache(object):
self._populate() self._populate()
return self.app_errors 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. Given a module containing models, returns a list of the models.
Otherwise returns a list of all installed 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() self._populate()
if app_mod: 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: else:
model_list = [] model_list = []
for app_entry in self.app_models.itervalues(): for app_entry in self.app_models.itervalues():
model_list.extend(app_entry.values()) 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): def get_model(self, app_label, model_name, seed_cache=True):
""" """
......
...@@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]| ...@@ -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', DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by', 'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace', 'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract', 'managed', 'proxy') 'abstract', 'managed', 'proxy', 'auto_created')
class Options(object): class Options(object):
def __init__(self, meta, app_label=None): def __init__(self, meta, app_label=None):
...@@ -47,6 +47,7 @@ class Options(object): ...@@ -47,6 +47,7 @@ class Options(object):
self.proxy_for_model = None self.proxy_for_model = None
self.parents = SortedDict() self.parents = SortedDict()
self.duplicate_targets = {} self.duplicate_targets = {}
self.auto_created = False
# To handle various inheritance situations, we need to track where # To handle various inheritance situations, we need to track where
# managers came from (concrete or abstract base classes). # managers came from (concrete or abstract base classes).
...@@ -487,4 +488,3 @@ class Options(object): ...@@ -487,4 +488,3 @@ class Options(object):
Returns the index of the primary key field in the self.fields list. Returns the index of the primary key field in the self.fields list.
""" """
return self.fields.index(self.pk) return self.fields.index(self.pk)
...@@ -1028,7 +1028,8 @@ def delete_objects(seen_objs): ...@@ -1028,7 +1028,8 @@ def delete_objects(seen_objs):
# Pre-notify all instances to be deleted. # Pre-notify all instances to be deleted.
for pk_val, instance in items: 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] pk_list = [pk for pk,instance in items]
del_query = sql.DeleteQuery(cls, connection) del_query = sql.DeleteQuery(cls, connection)
...@@ -1062,7 +1063,8 @@ def delete_objects(seen_objs): ...@@ -1062,7 +1063,8 @@ def delete_objects(seen_objs):
if field.rel and field.null and field.rel.to in seen_objs: if field.rel and field.null and field.rel.to in seen_objs:
setattr(instance, field.attname, None) 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) setattr(instance, cls._meta.pk.attname, None)
if forced_managed: if forced_managed:
......
...@@ -25,6 +25,9 @@ their deprecation, as per the :ref:`Django deprecation policy ...@@ -25,6 +25,9 @@ their deprecation, as per the :ref:`Django deprecation policy
* ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection`` * ``SMTPConnection``. The 1.2 release deprecated the ``SMTPConnection``
class in favor of a generic E-mail backend API. class in favor of a generic E-mail backend API.
* 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 * 2.0
* ``django.views.defaults.shortcut()``. This function has been moved * ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the to ``django.contrib.contenttypes.views.shortcut()`` as part of the
......
...@@ -182,6 +182,7 @@ class UniqueM2M(models.Model): ...@@ -182,6 +182,7 @@ class UniqueM2M(models.Model):
""" Model to test for unique ManyToManyFields, which are invalid. """ """ Model to test for unique ManyToManyFields, which are invalid. """
unique_people = models.ManyToManyField( Person, unique=True ) unique_people = models.ManyToManyField( Person, unique=True )
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 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 "decimal_places" attribute.
invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
......
...@@ -133,7 +133,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add' ...@@ -133,7 +133,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add'
>>> rock.members.create(name='Anne') >>> rock.members.create(name='Anne')
Traceback (most recent call last): 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. # Remove has similar complications, and is not provided either.
>>> rock.members.remove(jim) >>> rock.members.remove(jim)
...@@ -160,7 +160,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove' ...@@ -160,7 +160,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
>>> rock.members = backup >>> rock.members = backup
Traceback (most recent call last): 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. # Let's re-save those instances that we've cleared.
>>> m1.save() >>> m1.save()
...@@ -184,7 +184,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add' ...@@ -184,7 +184,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'add'
>>> bob.group_set.create(name='Funk') >>> bob.group_set.create(name='Funk')
Traceback (most recent call last): 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. # Remove has similar complications, and is not provided either.
>>> jim.group_set.remove(rock) >>> jim.group_set.remove(rock)
...@@ -209,7 +209,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove' ...@@ -209,7 +209,7 @@ AttributeError: 'ManyRelatedManager' object has no attribute 'remove'
>>> jim.group_set = backup >>> jim.group_set = backup
Traceback (most recent call last): 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. # Let's re-save those instances that we've cleared.
>>> m1.save() >>> m1.save()
...@@ -334,4 +334,4 @@ AttributeError: Cannot set values on a ManyToManyField which specifies an interm ...@@ -334,4 +334,4 @@ AttributeError: Cannot set values on a ManyToManyField which specifies an interm
# QuerySet's distinct() method can correct this problem. # QuerySet's distinct() method can correct this problem.
>>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct() >>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct()
[<Person: Jane>, <Person: Jim>] [<Person: Jane>, <Person: Jim>]
"""} """}
\ No newline at end of file
...@@ -84,22 +84,22 @@ __test__ = {'API_TESTS':""" ...@@ -84,22 +84,22 @@ __test__ = {'API_TESTS':"""
>>> bob.group_set = [] >>> bob.group_set = []
Traceback (most recent call last): 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 = [] >>> roll.members = []
Traceback (most recent call last): 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') >>> rock.members.create(name='Anne')
Traceback (most recent call last): 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') >>> bob.group_set.create(name='Funk')
Traceback (most recent call last): 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 # Now test that the intermediate with a relationship outside
# the current app (i.e., UserMembership) workds # the current app (i.e., UserMembership) workds
......
...@@ -110,6 +110,36 @@ class DerivedM(BaseM): ...@@ -110,6 +110,36 @@ class DerivedM(BaseM):
return "PK = %d, base_name = %s, derived_name = %s" \ return "PK = %d, base_name = %s, derived_name = %s" \
% (self.customPK, self.base_name, self.derived_name) % (self.customPK, self.base_name, self.derived_name)
# Check that abstract classes don't get m2m tables autocreated.
class Person(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)
def __unicode__(self):
return self.name
class AbstractEvent(models.Model):
name = models.CharField(max_length=100)
attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
class Meta:
abstract = True
ordering = ('name',)
def __unicode__(self):
return self.name
class BirthdayParty(AbstractEvent):
pass
class BachelorParty(AbstractEvent):
pass
class MessyBachelorParty(BachelorParty):
pass
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
# Regression for #7350, #7202 # Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an # Check that when you create a Parent object with a specific reference to an
...@@ -318,5 +348,41 @@ True ...@@ -318,5 +348,41 @@ True
>>> ParkingLot3._meta.get_ancestor_link(Place).name # the child->parent link >>> ParkingLot3._meta.get_ancestor_link(Place).name # the child->parent link
"parent" "parent"
# Check that many-to-many relations defined on an abstract base class
# are correctly inherited (and created) on the child class.
>>> p1 = Person.objects.create(name='Alice')
>>> p2 = Person.objects.create(name='Bob')
>>> p3 = Person.objects.create(name='Carol')
>>> p4 = Person.objects.create(name='Dave')
>>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice')
>>> birthday.attendees = [p1, p3]
>>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob')
>>> bachelor.attendees = [p2, p4]
>>> print p1.birthdayparty_set.all()
[<BirthdayParty: Birthday party for Alice>]
>>> print p1.bachelorparty_set.all()
[]
>>> print p2.bachelorparty_set.all()
[<BachelorParty: Bachelor party for Bob>]
# Check that a subclass of a subclass of an abstract model
# doesn't get it's own accessor.
>>> p2.messybachelorparty_set.all()
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'messybachelorparty_set'
# ... but it does inherit the m2m from it's parent
>>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave')
>>> messy.attendees = [p4]
>>> p4.bachelorparty_set.all()
[<BachelorParty: Bachelor party for Bob>, <BachelorParty: Bachelor party for Dave>]
"""} """}
...@@ -105,6 +105,9 @@ class Anchor(models.Model): ...@@ -105,6 +105,9 @@ class Anchor(models.Model):
data = models.CharField(max_length=30) data = models.CharField(max_length=30)
class Meta:
ordering = ('id',)
class UniqueAnchor(models.Model): class UniqueAnchor(models.Model):
"""This is a model that can be used as """This is a model that can be used as
something for other models to point at""" something for other models to point at"""
...@@ -135,7 +138,7 @@ class FKDataToO2O(models.Model): ...@@ -135,7 +138,7 @@ class FKDataToO2O(models.Model):
class M2MIntermediateData(models.Model): class M2MIntermediateData(models.Model):
data = models.ManyToManyField(Anchor, null=True, through='Intermediate') data = models.ManyToManyField(Anchor, null=True, through='Intermediate')
class Intermediate(models.Model): class Intermediate(models.Model):
left = models.ForeignKey(M2MIntermediateData) left = models.ForeignKey(M2MIntermediateData)
right = models.ForeignKey(Anchor) right = models.ForeignKey(Anchor)
...@@ -242,7 +245,7 @@ class AbstractBaseModel(models.Model): ...@@ -242,7 +245,7 @@ class AbstractBaseModel(models.Model):
class InheritAbstractModel(AbstractBaseModel): class InheritAbstractModel(AbstractBaseModel):
child_data = models.IntegerField() child_data = models.IntegerField()
class BaseModel(models.Model): class BaseModel(models.Model):
parent_data = models.IntegerField() parent_data = models.IntegerField()
...@@ -252,4 +255,3 @@ class InheritBaseModel(BaseModel): ...@@ -252,4 +255,3 @@ class InheritBaseModel(BaseModel):
class ExplicitInheritBaseModel(BaseModel): class ExplicitInheritBaseModel(BaseModel):
parent = models.OneToOneField(BaseModel) parent = models.OneToOneField(BaseModel)
child_data = models.IntegerField() child_data = models.IntegerField()
\ No newline at end of file
"""
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
"""}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment