Ticket #10109: m2m-refactor.r11683.diff
File m2m-refactor.r11683.diff, 58.8 KB (added by , 15 years ago) |
---|
-
django/contrib/admin/options.py
diff -r eff0b46ff6fa django/contrib/admin/options.py
a b 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: -
django/contrib/contenttypes/generic.py
diff -r eff0b46ff6fa django/contrib/contenttypes/generic.py
a b 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 = False110 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") -
django/core/management/commands/syncdb.py
diff -r eff0b46ff6fa django/core/management/commands/syncdb.py
a b 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 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 created82 # 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 -
django/core/management/sql.py
diff -r eff0b46ff6fa django/core/management/sql.py
a b 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 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 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 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._meta103 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: -
django/core/management/validation.py
diff -r eff0b46ff6fa django/core/management/validation.py
a b 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 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() -
django/core/serializers/python.py
diff -r eff0b46ff6fa django/core/serializers/python.py
a b 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 -
django/core/serializers/xml_serializer.py
diff -r eff0b46ff6fa django/core/serializers/xml_serializer.py
a b 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 233 else: 234 234 pass 235 235 return u"".join(inner_text) 236 -
django/db/models/base.py
diff -r eff0b46ff6fa django/db/models/base.py
a b 429 429 else: 430 430 meta = cls._meta 431 431 432 if origin :432 if origin and not meta.auto_created: 433 433 signals.pre_save.send(sender=origin, instance=self, raw=raw) 434 434 435 435 # If we are in a raw save, save the object exactly as presented. … … 502 502 setattr(self, meta.pk.attname, result) 503 503 transaction.commit_unless_managed() 504 504 505 if origin :505 if origin and not meta.auto_created: 506 506 signals.post_save.send(sender=origin, instance=self, 507 507 created=(not record_exists), raw=raw) 508 508 … … 539 539 rel_descriptor = cls.__dict__[rel_opts_name] 540 540 break 541 541 else: 542 raise AssertionError("Should never get here.") 542 # in the case of a hidden fkey just skip it, it'll get 543 # processed as an m2m 544 if not related.field.rel.is_hidden(): 545 raise AssertionError("Should never get here.") 546 else: 547 continue 543 548 delete_qs = rel_descriptor.delete_manager(self).all() 544 549 for sub_obj in delete_qs: 545 550 sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null) -
django/db/models/fields/related.py
diff -r eff0b46ff6fa django/db/models/fields/related.py
a b 58 58 # If we can't split, assume a model in current app 59 59 app_label = cls._meta.app_label 60 60 model_name = relation 61 except AttributeError: 62 # If it doesn't have a split it's actually a model class 63 app_label = relation._meta.app_label 64 model_name = relation._meta.object_name 61 65 62 66 # Try to look up the related model, and if it's already loaded resolve the 63 67 # string right away. If get_model returns None, it means that the related … … 96 100 self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()} 97 101 98 102 other = self.rel.to 99 if isinstance(other, basestring) :103 if isinstance(other, basestring) or other._meta.pk is None: 100 104 def resolve_related_class(field, model, cls): 101 105 field.rel.to = model 102 106 field.do_related_class(model, cls) … … 401 405 402 406 return manager 403 407 404 def create_many_related_manager(superclass, through=False):408 def create_many_related_manager(superclass, rel=False): 405 409 """Creates a manager that subclasses 'superclass' (which is a Manager) 406 410 and adds behavior for many-to-many related objects.""" 411 through = rel.through 407 412 class ManyRelatedManager(superclass): 408 413 def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, 409 join_table=None, source_ col_name=None, target_col_name=None):414 join_table=None, source_field_name=None, target_field_name=None): 410 415 super(ManyRelatedManager, self).__init__() 411 416 self.core_filters = core_filters 412 417 self.model = model 413 418 self.symmetrical = symmetrical 414 419 self.instance = instance 415 self.join_table = join_table 416 self.source_col_name = source_col_name 417 self.target_col_name = target_col_name 420 self.source_field_name = source_field_name 421 self.target_field_name = target_field_name 418 422 self.through = through 419 self._pk_val = self.instance. _get_pk_val()423 self._pk_val = self.instance.pk 420 424 if self._pk_val is None: 421 425 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) 422 426 … … 425 429 426 430 # If the ManyToMany relation has an intermediary model, 427 431 # the add and remove methods do not exist. 428 if through is None:432 if rel.through._meta.auto_created: 429 433 def add(self, *objs): 430 self._add_items(self.source_ col_name, self.target_col_name, *objs)434 self._add_items(self.source_field_name, self.target_field_name, *objs) 431 435 432 436 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 433 437 if self.symmetrical: 434 self._add_items(self.target_ col_name, self.source_col_name, *objs)438 self._add_items(self.target_field_name, self.source_field_name, *objs) 435 439 add.alters_data = True 436 440 437 441 def remove(self, *objs): 438 self._remove_items(self.source_ col_name, self.target_col_name, *objs)442 self._remove_items(self.source_field_name, self.target_field_name, *objs) 439 443 440 444 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 441 445 if self.symmetrical: 442 self._remove_items(self.target_ col_name, self.source_col_name, *objs)446 self._remove_items(self.target_field_name, self.source_field_name, *objs) 443 447 remove.alters_data = True 444 448 445 449 def clear(self): 446 self._clear_items(self.source_ col_name)450 self._clear_items(self.source_field_name) 447 451 448 452 # If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table 449 453 if self.symmetrical: 450 self._clear_items(self.target_ col_name)454 self._clear_items(self.target_field_name) 451 455 clear.alters_data = True 452 456 453 457 def create(self, **kwargs): 454 458 # This check needs to be done here, since we can't later remove this 455 459 # from the method lookup table, as we do with add and remove. 456 if through is not None: 457 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 460 if not rel.through._meta.auto_created: 461 opts = through._meta 462 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) 458 463 new_obj = super(ManyRelatedManager, self).create(**kwargs) 459 464 self.add(new_obj) 460 465 return new_obj … … 470 475 return obj, created 471 476 get_or_create.alters_data = True 472 477 473 def _add_items(self, source_ col_name, target_col_name, *objs):478 def _add_items(self, source_field_name, target_field_name, *objs): 474 479 # join_table: name of the m2m link table 475 # source_ col_name: the PK colname in join_table for the source object476 # target_col_name: the PK colname in join_table for the target object480 # source_field_name: the PK fieldname in join_table for the source object 481 # target_col_name: the PK fielname in join_table for the target object 477 482 # *objs - objects to add. Either object instances, or primary keys of object instances. 478 483 479 484 # If there aren't any objects, there is nothing to do. 485 from django.db.models import Model 480 486 if objs: 481 from django.db.models.base import Model482 # Check that all the objects are of the right type483 487 new_ids = set() 484 488 for obj in objs: 485 489 if isinstance(obj, self.model): 486 new_ids.add(obj. _get_pk_val())490 new_ids.add(obj.pk) 487 491 elif isinstance(obj, Model): 488 492 raise TypeError, "'%s' instance expected" % self.model._meta.object_name 489 493 else: 490 494 new_ids.add(obj) 491 # Add the newly created or already existing objects to the join table. 492 # First find out which items are already added, to avoid adding them twice 493 cursor = connection.cursor() 494 cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 495 (target_col_name, self.join_table, source_col_name, 496 target_col_name, ",".join(['%s'] * len(new_ids))), 497 [self._pk_val] + list(new_ids)) 498 existing_ids = set([row[0] for row in cursor.fetchall()]) 495 vals = self.through._default_manager.values_list(target_field_name, flat=True) 496 vals = vals.filter(**{ 497 source_field_name: self._pk_val, 498 '%s__in' % target_field_name: new_ids, 499 }) 500 vals = set(vals) 499 501 500 502 # Add the ones that aren't there already 501 for obj_id in (new_ids - existing_ids):502 cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \503 (self.join_table, source_col_name, target_col_name),504 [self._pk_val, obj_id])505 transaction.commit_unless_managed()503 for obj_id in (new_ids - vals): 504 self.through._default_manager.create(**{ 505 '%s_id' % source_field_name: self._pk_val, 506 '%s_id' % target_field_name: obj_id, 507 }) 506 508 507 def _remove_items(self, source_ col_name, target_col_name, *objs):509 def _remove_items(self, source_field_name, target_field_name, *objs): 508 510 # source_col_name: the PK colname in join_table for the source object 509 511 # target_col_name: the PK colname in join_table for the target object 510 512 # *objs - objects to remove … … 515 517 old_ids = set() 516 518 for obj in objs: 517 519 if isinstance(obj, self.model): 518 old_ids.add(obj. _get_pk_val())520 old_ids.add(obj.pk) 519 521 else: 520 522 old_ids.add(obj) 521 523 # Remove the specified objects from the join table 522 cursor = connection.cursor() 523 cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 524 (self.join_table, source_col_name, 525 target_col_name, ",".join(['%s'] * len(old_ids))), 526 [self._pk_val] + list(old_ids)) 527 transaction.commit_unless_managed() 524 self.through._default_manager.filter(**{ 525 source_field_name: self._pk_val, 526 '%s__in' % target_field_name: old_ids 527 }).delete() 528 528 529 def _clear_items(self, source_ col_name):529 def _clear_items(self, source_field_name): 530 530 # source_col_name: the PK colname in join_table for the source object 531 cursor = connection.cursor() 532 cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ 533 (self.join_table, source_col_name), 534 [self._pk_val]) 535 transaction.commit_unless_managed() 531 self.through._default_manager.filter(**{ 532 source_field_name: self._pk_val 533 }).delete() 536 534 537 535 return ManyRelatedManager 538 536 … … 554 552 # model's default manager. 555 553 rel_model = self.related.model 556 554 superclass = rel_model._default_manager.__class__ 557 RelatedManager = create_many_related_manager(superclass, self.related.field.rel .through)555 RelatedManager = create_many_related_manager(superclass, self.related.field.rel) 558 556 559 qn = connection.ops.quote_name560 557 manager = RelatedManager( 561 558 model=rel_model, 562 559 core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, 563 560 instance=instance, 564 561 symmetrical=False, 565 join_table=qn(self.related.field.m2m_db_table()), 566 source_col_name=qn(self.related.field.m2m_reverse_name()), 567 target_col_name=qn(self.related.field.m2m_column_name()) 562 source_field_name=self.related.field.m2m_reverse_field_name(), 563 target_field_name=self.related.field.m2m_field_name() 568 564 ) 569 565 570 566 return manager … … 573 569 if instance is None: 574 570 raise AttributeError, "Manager must be accessed via instance" 575 571 576 through = getattr(self.related.field.rel, 'through', None)577 if through is not None:578 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s 's Manager instead." % through572 if not self.related.field.rel.through._meta.auto_created: 573 opts = self.related.field.rel.through._meta 574 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) 579 575 580 576 manager = self.__get__(instance) 581 577 manager.clear() … … 599 595 # model's default manager. 600 596 rel_model=self.field.rel.to 601 597 superclass = rel_model._default_manager.__class__ 602 RelatedManager = create_many_related_manager(superclass, self.field.rel .through)598 RelatedManager = create_many_related_manager(superclass, self.field.rel) 603 599 604 qn = connection.ops.quote_name605 600 manager = RelatedManager( 606 601 model=rel_model, 607 602 core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, 608 603 instance=instance, 609 604 symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)), 610 join_table=qn(self.field.m2m_db_table()), 611 source_col_name=qn(self.field.m2m_column_name()), 612 target_col_name=qn(self.field.m2m_reverse_name()) 605 source_field_name=self.field.m2m_field_name(), 606 target_field_name=self.field.m2m_reverse_field_name() 613 607 ) 614 608 615 609 return manager … … 618 612 if instance is None: 619 613 raise AttributeError, "Manager must be accessed via instance" 620 614 621 through = getattr(self.field.rel, 'through', None)622 if through is not None:623 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s 's Manager instead." % through615 if not self.field.rel.through._meta.auto_created: 616 opts = self.field.rel.through._meta 617 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) 624 618 625 619 manager = self.__get__(instance) 626 620 manager.clear() … … 642 636 self.multiple = True 643 637 self.parent_link = parent_link 644 638 639 def is_hidden(self): 640 "Should the related object be hidden?" 641 return self.related_name and self.related_name[-1] == '+' 642 645 643 def get_related_field(self): 646 644 """ 647 645 Returns the Field in the 'to' object to which this relationship is … … 673 671 self.multiple = True 674 672 self.through = through 675 673 674 def is_hidden(self): 675 "Should the related object be hidden?" 676 return self.related_name and self.related_name[-1] == '+' 677 676 678 def get_related_field(self): 677 679 """ 678 680 Returns the field in the to' object to which this relationship is tied … … 690 692 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) 691 693 else: 692 694 assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) 693 to_field = to_field or to._meta.pk.name694 695 kwargs['verbose_name'] = kwargs.get('verbose_name', None) 695 696 696 697 kwargs['rel'] = rel_class(to, to_field, … … 743 744 cls._meta.duplicate_targets[self.column] = (target, "o2m") 744 745 745 746 def contribute_to_related_class(self, cls, related): 746 setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 747 # Internal FK's - i.e., those with a related name ending with '+' - 748 # don't get a related descriptor. 749 if not self.rel.is_hidden(): 750 setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related)) 751 if self.rel.field_name is None: 752 self.rel.field_name = cls._meta.pk.name 747 753 748 754 def formfield(self, **kwargs): 749 755 defaults = { … … 790 796 return None 791 797 return super(OneToOneField, self).formfield(**kwargs) 792 798 799 def create_many_to_many_intermediary_model(field, klass): 800 from django.db import models 801 managed = True 802 if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: 803 to = field.rel.to 804 to_model = field.rel.to 805 def set_managed(field, model, cls): 806 field.rel.through._meta.managed = model._meta.managed or cls._meta.managed 807 add_lazy_relation(klass, field, to_model, set_managed) 808 elif isinstance(field.rel.to, basestring): 809 to = klass._meta.object_name 810 to_model = klass 811 managed = klass._meta.managed 812 else: 813 to = field.rel.to._meta.object_name 814 to_model = field.rel.to 815 managed = klass._meta.managed or to_model._meta.managed 816 meta = type('Meta', (object,), { 817 'db_table': field._get_m2m_db_table(klass._meta), 818 'managed': managed, 819 'auto_created': klass, 820 }) 821 name = '%s_%s' % (klass._meta.object_name, field.name) 822 if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or field.rel.to == klass._meta.object_name: 823 from_ = 'from_%s' % to.lower() 824 to = to.lower() 825 else: 826 from_ = klass._meta.object_name.lower() 827 to = to.lower() 828 return type(name, (models.Model,), { 829 'Meta': meta, 830 '__module__': klass.__module__, 831 from_: models.ForeignKey(klass, related_name='%s+' % name), 832 to: models.ForeignKey(to_model, related_name='%s+' % name) 833 }) 834 793 835 class ManyToManyField(RelatedField, Field): 794 836 def __init__(self, to, **kwargs): 795 837 try: … … 806 848 807 849 self.db_table = kwargs.pop('db_table', None) 808 850 if kwargs['rel'].through is not None: 809 self.creates_table = False810 851 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 811 else:812 self.creates_table = True813 852 814 853 Field.__init__(self, **kwargs) 815 854 … … 822 861 def _get_m2m_db_table(self, opts): 823 862 "Function that can be curried to provide the m2m table name for this relation" 824 863 if self.rel.through is not None: 825 return self.rel.through _model._meta.db_table864 return self.rel.through._meta.db_table 826 865 elif self.db_table: 827 866 return self.db_table 828 867 else: 829 868 return util.truncate_name('%s_%s' % (opts.db_table, self.name), 830 869 connection.ops.max_name_length()) 831 870 832 def _get_m2m_ column_name(self, related):871 def _get_m2m_attr(self, related, attr): 833 872 "Function that can be curried to provide the source column name for the m2m table" 834 try: 835 return self._m2m_column_name_cache 836 except: 837 if self.rel.through is not None: 838 for f in self.rel.through_model._meta.fields: 839 if hasattr(f,'rel') and f.rel and f.rel.to == related.model: 840 self._m2m_column_name_cache = f.column 873 cache_attr = '_m2m_%s_cache' % attr 874 if hasattr(self, cache_attr): 875 return getattr(self, cache_attr) 876 for f in self.rel.through._meta.fields: 877 if hasattr(f,'rel') and f.rel and f.rel.to == related.model: 878 setattr(self, cache_attr, getattr(f, attr)) 879 return getattr(self, cache_attr) 880 881 def _get_m2m_reverse_attr(self, related, attr): 882 "Function that can be curried to provide the related column name for the m2m table" 883 cache_attr = '_m2m_reverse_%s_cache' % attr 884 if hasattr(self, cache_attr): 885 return getattr(self, cache_attr) 886 found = False 887 for f in self.rel.through._meta.fields: 888 if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: 889 if related.model == related.parent_model: 890 # If this is an m2m-intermediate to self, 891 # the first foreign key you find will be 892 # the source column. Keep searching for 893 # the second foreign key. 894 if found: 895 setattr(self, cache_attr, getattr(f, attr)) 841 896 break 842 # If this is an m2m relation to self, avoid the inevitable name clash 843 elif related.model == related.parent_model: 844 self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' 845 else: 846 self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' 847 848 # Return the newly cached value 849 return self._m2m_column_name_cache 850 851 def _get_m2m_reverse_name(self, related): 852 "Function that can be curried to provide the related column name for the m2m table" 853 try: 854 return self._m2m_reverse_name_cache 855 except: 856 if self.rel.through is not None: 857 found = False 858 for f in self.rel.through_model._meta.fields: 859 if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: 860 if related.model == related.parent_model: 861 # If this is an m2m-intermediate to self, 862 # the first foreign key you find will be 863 # the source column. Keep searching for 864 # the second foreign key. 865 if found: 866 self._m2m_reverse_name_cache = f.column 867 break 868 else: 869 found = True 870 else: 871 self._m2m_reverse_name_cache = f.column 872 break 873 # If this is an m2m relation to self, avoid the inevitable name clash 874 elif related.model == related.parent_model: 875 self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' 876 else: 877 self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' 878 879 # Return the newly cached value 880 return self._m2m_reverse_name_cache 897 else: 898 found = True 899 else: 900 setattr(self, cache_attr, getattr(f, attr)) 901 break 902 return getattr(self, cache_attr) 881 903 882 904 def isValidIDList(self, field_data, all_data): 883 905 "Validates that the value is a valid list of foreign keys" … … 919 941 # specify *what* on my non-reversible relation?!"), so we set it up 920 942 # automatically. The funky name reduces the chance of an accidental 921 943 # clash. 922 if self.rel.symmetrical and self.rel.to == "self" and self.rel.related_name is None:944 if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): 923 945 self.rel.related_name = "%s_rel_+" % name 924 946 925 947 super(ManyToManyField, self).contribute_to_class(cls, name) 948 949 if not self.rel.through: 950 self.rel.through = create_many_to_many_intermediary_model(self, cls) 951 926 952 # Add the descriptor for the m2m relation 927 953 setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 928 954 … … 933 959 # work correctly. 934 960 if isinstance(self.rel.through, basestring): 935 961 def resolve_through_model(field, model, cls): 936 field.rel.through _model= model962 field.rel.through = model 937 963 add_lazy_relation(cls, self, self.rel.through, resolve_through_model) 938 elif self.rel.through:939 self.rel.through_model = self.rel.through940 self.rel.through = self.rel.through._meta.object_name941 964 942 965 if isinstance(self.rel.to, basestring): 943 966 target = self.rel.to … … 946 969 cls._meta.duplicate_targets[self.column] = (target, "m2m") 947 970 948 971 def contribute_to_related_class(self, cls, related): 949 # m2m relations to self do not have a ManyRelatedObjectsDescriptor, 950 # as it would be redundant - unless the field is non-symmetrical. 951 if related.model != related.parent_model or not self.rel.symmetrical: 952 # Add the descriptor for the m2m relation 972 # Internal M2Ms (i.e., those with a related name ending with '+') 973 # don't get a related descriptor. 974 if not self.rel.is_hidden(): 953 975 setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(related)) 954 976 955 977 # Set up the accessors for the column names on the m2m table 956 self.m2m_column_name = curry(self._get_m2m_column_name, related) 957 self.m2m_reverse_name = curry(self._get_m2m_reverse_name, related) 978 self.m2m_column_name = curry(self._get_m2m_attr, related, 'column') 979 self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column') 980 981 self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') 982 self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name') 958 983 959 984 def set_attributes_from_rel(self): 960 985 pass -
django/db/models/loading.py
diff -r eff0b46ff6fa django/db/models/loading.py
a b 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 returnself.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 """ -
django/db/models/options.py
diff -r eff0b46ff6fa django/db/models/options.py
a b 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 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 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 -
django/db/models/query.py
diff -r eff0b46ff6fa django/db/models/query.py
a b 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 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: -
docs/internals/deprecation.txt
diff -r eff0b46ff6fa docs/internals/deprecation.txt
a b 22 22 * The old imports for CSRF functionality (``django.contrib.csrf.*``), 23 23 which moved to core in 1.2, will be removed. 24 24 25 * 1.4 26 * The many to many SQL generation functions on the database backends 27 will be removed. These have been deprecated since the 1.2 release. 28 25 29 * 2.0 26 30 * ``django.views.defaults.shortcut()``. This function has been moved 27 31 to ``django.contrib.contenttypes.views.shortcut()`` as part of the -
tests/modeltests/invalid_models/models.py
diff -r eff0b46ff6fa tests/modeltests/invalid_models/models.py
a b 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. -
tests/modeltests/m2m_through/models.py
diff -r eff0b46ff6fa tests/modeltests/m2m_through/models.py
a b 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 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 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 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 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 """} 338 No newline at end of file 337 """} -
tests/regressiontests/m2m_through_regress/models.py
diff -r eff0b46ff6fa tests/regressiontests/m2m_through_regress/models.py
a b 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 -
tests/regressiontests/serializers_regress/models.py
diff -r eff0b46ff6fa tests/regressiontests/serializers_regress/models.py
a b 105 105 106 106 data = models.CharField(max_length=30) 107 107 108 class Meta: 109 ordering = ('id',) 110 108 111 class UniqueAnchor(models.Model): 109 112 """This is a model that can be used as 110 113 something for other models to point at""" … … 135 138 136 139 class M2MIntermediateData(models.Model): 137 140 data = models.ManyToManyField(Anchor, null=True, through='Intermediate') 138 141 139 142 class Intermediate(models.Model): 140 143 left = models.ForeignKey(M2MIntermediateData) 141 144 right = models.ForeignKey(Anchor) … … 242 245 243 246 class InheritAbstractModel(AbstractBaseModel): 244 247 child_data = models.IntegerField() 245 248 246 249 class BaseModel(models.Model): 247 250 parent_data = models.IntegerField() 248 251 … … 252 255 class ExplicitInheritBaseModel(BaseModel): 253 256 parent = models.OneToOneField(BaseModel) 254 257 child_data = models.IntegerField() 255 256 No newline at end of file -
new file tests/regressiontests/signals_regress/models.py
diff -r eff0b46ff6fa tests/regressiontests/signals_regress/models.py
- + 1 """ 2 Testing signals before/after saving and deleting. 3 """ 4 5 from django.db import models 6 7 class Author(models.Model): 8 name = models.CharField(max_length=20) 9 10 def __unicode__(self): 11 return self.name 12 13 class Book(models.Model): 14 name = models.CharField(max_length=20) 15 authors = models.ManyToManyField(Author) 16 17 def __unicode__(self): 18 return self.name 19 20 def pre_save_test(signal, sender, instance, **kwargs): 21 print 'pre_save signal,', instance 22 if kwargs.get('raw'): 23 print 'Is raw' 24 25 def post_save_test(signal, sender, instance, **kwargs): 26 print 'post_save signal,', instance 27 if 'created' in kwargs: 28 if kwargs['created']: 29 print 'Is created' 30 else: 31 print 'Is updated' 32 if kwargs.get('raw'): 33 print 'Is raw' 34 35 def pre_delete_test(signal, sender, instance, **kwargs): 36 print 'pre_delete signal,', instance 37 print 'instance.id is not None: %s' % (instance.id != None) 38 39 def post_delete_test(signal, sender, instance, **kwargs): 40 print 'post_delete signal,', instance 41 print 'instance.id is not None: %s' % (instance.id != None) 42 43 __test__ = {'API_TESTS':""" 44 45 # Save up the number of connected signals so that we can check at the end 46 # that all the signals we register get properly unregistered (#9989) 47 >>> pre_signals = (len(models.signals.pre_save.receivers), 48 ... len(models.signals.post_save.receivers), 49 ... len(models.signals.pre_delete.receivers), 50 ... len(models.signals.post_delete.receivers)) 51 52 >>> models.signals.pre_save.connect(pre_save_test) 53 >>> models.signals.post_save.connect(post_save_test) 54 >>> models.signals.pre_delete.connect(pre_delete_test) 55 >>> models.signals.post_delete.connect(post_delete_test) 56 57 >>> a1 = Author(name='Neal Stephenson') 58 >>> a1.save() 59 pre_save signal, Neal Stephenson 60 post_save signal, Neal Stephenson 61 Is created 62 63 >>> b1 = Book(name='Snow Crash') 64 >>> b1.save() 65 pre_save signal, Snow Crash 66 post_save signal, Snow Crash 67 Is created 68 69 # Assigning to m2m shouldn't generate an m2m signal 70 >>> b1.authors = [a1] 71 72 # Removing an author from an m2m shouldn't generate an m2m signal 73 >>> b1.authors = [] 74 75 >>> models.signals.post_delete.disconnect(post_delete_test) 76 >>> models.signals.pre_delete.disconnect(pre_delete_test) 77 >>> models.signals.post_save.disconnect(post_save_test) 78 >>> models.signals.pre_save.disconnect(pre_save_test) 79 80 # Check that all our signals got disconnected properly. 81 >>> post_signals = (len(models.signals.pre_save.receivers), 82 ... len(models.signals.post_save.receivers), 83 ... len(models.signals.pre_delete.receivers), 84 ... len(models.signals.post_delete.receivers)) 85 86 >>> pre_signals == post_signals 87 True 88 89 """}