diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
a
|
b
|
|
353 | 353 | """Creates a manager that subclasses 'superclass' (which is a Manager) |
354 | 354 | and adds behavior for many-to-many related objects.""" |
355 | 355 | class ManyRelatedManager(superclass): |
356 | | def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, |
357 | | join_table=None, source_col_name=None, target_col_name=None): |
| 356 | def __init__(self, model=None, core_filters=None, instance=None, |
| 357 | symmetrical=None, join_table=None, source_col_name=None, |
| 358 | target_col_name=None, field_name=None): |
358 | 359 | super(ManyRelatedManager, self).__init__() |
359 | 360 | self.core_filters = core_filters |
360 | 361 | self.model = model |
… |
… |
|
365 | 366 | self.target_col_name = target_col_name |
366 | 367 | self.through = through |
367 | 368 | self._pk_val = self.instance._get_pk_val() |
| 369 | self.field_name = field_name |
368 | 370 | if self._pk_val is None: |
369 | 371 | raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) |
370 | 372 | |
… |
… |
|
434 | 436 | existing_ids = set([row[0] for row in cursor.fetchall()]) |
435 | 437 | |
436 | 438 | # Add the ones that aren't there already |
437 | | for obj_id in (new_ids - existing_ids): |
| 439 | new_ids = new_ids - existing_ids |
| 440 | for obj_id in new_ids: |
438 | 441 | cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ |
439 | 442 | (self.join_table, source_col_name, target_col_name), |
440 | 443 | [self._pk_val, obj_id]) |
| 444 | |
| 445 | added_objs = [obj for obj in objs if (isinstance(obj, self.model) and obj._get_pk_val() in new_ids) or obj in new_ids] |
| 446 | signals.m2m_add_items.send(sender=self.model, instance=self.instance, |
| 447 | field_name=self.field_name, objs=added_objs) |
| 448 | |
441 | 449 | transaction.commit_unless_managed() |
442 | 450 | |
443 | 451 | def _remove_items(self, source_col_name, target_col_name, *objs): |
… |
… |
|
460 | 468 | (self.join_table, source_col_name, |
461 | 469 | target_col_name, ",".join(['%s'] * len(old_ids))), |
462 | 470 | [self._pk_val] + list(old_ids)) |
| 471 | signals.m2m_remove_items.send(sender=self.model, instance=self.instance, |
| 472 | field_name=self.field_name, objs=objs) |
463 | 473 | transaction.commit_unless_managed() |
464 | 474 | |
465 | 475 | def _clear_items(self, source_col_name): |
466 | 476 | # source_col_name: the PK colname in join_table for the source object |
| 477 | signals.m2m_clear_items.send(sender=self.model, instance=self.instance, |
| 478 | field_name=self.field_name) |
467 | 479 | cursor = connection.cursor() |
468 | 480 | cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ |
469 | 481 | (self.join_table, source_col_name), |
… |
… |
|
500 | 512 | symmetrical=False, |
501 | 513 | join_table=qn(self.related.field.m2m_db_table()), |
502 | 514 | source_col_name=qn(self.related.field.m2m_reverse_name()), |
503 | | target_col_name=qn(self.related.field.m2m_column_name()) |
| 515 | target_col_name=qn(self.related.field.m2m_column_name()), |
| 516 | field_name=self.related.field.name |
504 | 517 | ) |
505 | 518 | |
506 | 519 | return manager |
… |
… |
|
545 | 558 | symmetrical=(self.field.rel.symmetrical and instance.__class__ == rel_model), |
546 | 559 | join_table=qn(self.field.m2m_db_table()), |
547 | 560 | source_col_name=qn(self.field.m2m_column_name()), |
548 | | target_col_name=qn(self.field.m2m_reverse_name()) |
| 561 | target_col_name=qn(self.field.m2m_reverse_name()), |
| 562 | field_name=self.field.name |
549 | 563 | ) |
550 | 564 | |
551 | 565 | return manager |
diff --git a/django/db/models/signals.py b/django/db/models/signals.py
a
|
b
|
|
12 | 12 | post_delete = Signal(providing_args=["instance"]) |
13 | 13 | |
14 | 14 | post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"]) |
| 15 | |
| 16 | m2m_add_items = Signal(providing_args=["instance", "field_name", "objs"]) |
| 17 | m2m_remove_items = Signal(providing_args=["instance", "field_name", "objs"]) |
| 18 | m2m_clear_items = Signal(providing_args=["instance", "field_name", "objs"]) |