Index: django/django/db/models/fields/related.py
===================================================================
--- django/django/db/models/fields/related.py	(revision 9097)
+++ django/django/db/models/fields/related.py	(working copy)
@@ -424,6 +424,33 @@
             return obj, created
         get_or_create.alters_data = True
 
+        def _add_items_signal(func):
+          def _add_items(self, source_col_name, target_col_name, *objs):
+            func(self, source_col_name, target_col_name, *objs)
+            if objs:
+              self._object_added_signal(objs)
+          return _add_items
+          
+        def _remove_items_signal(func):
+            def _remove_items(self, source_col_name, target_col_name, *objs):
+              func(self, source_col_name, target_col_name, *objs)
+              if objs:
+                self._object_removed_signal(objs)
+            return _remove_items
+ 
+        def _clear_items_signal(func):
+          def _clear_items(self, source_col_name):
+            # check if there are any registered receivers
+            # if none, don't waste time performing query
+            will_signal = self._test_signal_has_receivers(signals.m2m_related_data_removed)
+            if will_signal:
+              removed = list(self.all())
+            func(self,source_col_name)
+            # check again, why fire off signal if no one is listening?
+            if will_signal:
+              self._object_removed_signal(removed)
+          return _clear_items
+
         def _add_items(self, source_col_name, target_col_name, *objs):
             # join_table: name of the m2m link table
             # source_col_name: the PK colname in join_table for the source object
@@ -455,6 +482,9 @@
                         [self._pk_val, obj_id])
                 transaction.commit_unless_managed()
 
+        # add fire off signal when items are added
+        _add_items = _add_items_signal(_add_items)
+
         def _remove_items(self, source_col_name, target_col_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
@@ -477,6 +507,8 @@
                     [self._pk_val] + list(old_ids))
                 transaction.commit_unless_managed()
 
+        _remove_items = _remove_items_signal(_remove_items)
+
         def _clear_items(self, source_col_name):
             # source_col_name: the PK colname in join_table for the source object
             cursor = connection.cursor()
@@ -484,7 +516,18 @@
                 (self.join_table, source_col_name),
                 [self._pk_val])
             transaction.commit_unless_managed()
+            
+        _clear_items = _clear_items_signal(_clear_items)
 
+        def _object_removed_signal(self, objs):
+            signals.m2m_related_data_removed.send(sender=self.instance, objs_removed=objs, join_table=self.join_table )
+        
+        def _object_added_signal(self,objs):
+            signals.m2m_related_data_added.send(sender=self.instance, objs_added=objs , join_table = self.join_table)   
+
+        def _test_signal_has_receivers(self, signal):
+          return len(signal.receivers) > 0
+          
     return ManyRelatedManager
 
 class ManyRelatedObjectsDescriptor(object):
Index: django/django/db/models/signals.py
===================================================================
--- django/django/db/models/signals.py	(revision 9097)
+++ django/django/db/models/signals.py	(working copy)
@@ -12,3 +12,6 @@
 post_delete = Signal(providing_args=["instance"])
 
 post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
+
+m2m_related_data_added = Signal(providing_args=['objs_added','join_table'])
+m2m_related_data_removed = Signal(providing_args=['objs_removed','join_table'])
\ No newline at end of file
Index: django/tests/modeltests/m2m_signals/__init__.py
===================================================================
Index: django/tests/modeltests/m2m_signals/models.py
===================================================================
--- django/tests/modeltests/m2m_signals/models.py	(revision 0)
+++ django/tests/modeltests/m2m_signals/models.py	(revision 0)
@@ -0,0 +1,55 @@
+"""
+Testing signals fired when item(s) are added to a many-to-many relation, 
+when items are removed and when the relation is cleared
+"""
+print('testing m2m signals')
+
+from django.db import models
+from django.db.models import signals
+
+class Pizza(models.Model):
+    name = models.CharField(max_length=60)
+
+class Topping(models.Model):
+    name = models.CharField(max_length=50)
+    pizzas = models.ManyToManyField(Pizza)  
+
+def relation_made(signal, sender, **named):
+    names = str([str(x.name) for x in named['objs_added']])
+    print '%s just associated with %s' % (sender.name, names)
+
+def relation_unmade(signal, sender, **named):
+    names = str([str(x.name) for x in named['objs_removed']])
+    print '%s just disassociated with %s' % (sender.name, names)
+    
+signals.m2m_related_data_added.connect(relation_made)
+signals.m2m_related_data_removed.connect(relation_unmade)
+
+__test__ = {'API_TESTS':"""
+>>> p1, p2, p3 = Pizza(name='veggie'), Pizza(name='cheese'), Pizza(name='pepperoni')
+>>> p1.save()
+>>> p2.save()
+>>> p3.save()
+>>> t1, t2, t3, t4= Topping(name='cheese'), Topping(name='red pepper'), \
+                    Topping(name='mushrooms'), Topping(name='pepperoni')
+>>> t1.save()
+>>> t2.save()
+>>> t3.save()
+>>> t4.save()
+
+# associate some data
+>>> p1.topping_set.add(t1,t2,t3)
+veggie just associated with ['cheese', 'red pepper', 'mushrooms']
+>>> p2.topping_set.add(t1)
+cheese just associated with ['cheese']
+>>> p3.topping_set.add(t1,t3,t4)
+pepperoni just associated with ['cheese', 'mushrooms', 'pepperoni']
+>>> p3.topping_set.remove(t3)
+pepperoni just disassociated with ['mushrooms']
+>>> p3.topping_set.clear()
+pepperoni just disassociated with ['cheese', 'pepperoni']
+>>> t1.pizzas.add(p3)
+cheese just associated with ['pepperoni']
+>>> t1.pizzas.clear()
+cheese just disassociated with ['veggie', 'cheese', 'pepperoni']
+"""}
Index: django/docs/ref/signals.txt
===================================================================
--- django/docs/ref/signals.txt	(revision 9097)
+++ django/docs/ref/signals.txt	(working copy)
@@ -162,7 +162,45 @@
 
         Note that the object will no longer be in the database, so be very
         careful what you do with this instance.
+        
+m2m_related_data_added
+-----------
 
+.. data:: django.db.models.signals.m2m_related_data_added
+   :module: 
+   
+Like :data:`m2m_related_data_removed`, but sent when data is added.
+
+Arguments sent with this signal:
+
+    ``sender``
+        The instance which just had data asscoiated with it.
+
+    ``objs_added``
+        The object(s) which were just associated with the sender.
+        
+    ``join_table``  
+		The join table between the relevant many-to-many relationship.
+		
+m2m_related_data_removed
+-----------
+
+.. data:: django.db.models.signals.m2m_related_data_added
+   :module: 
+   
+Like :data:`m2m_related_data_added`, but sent when data is removed.
+
+Arguments sent with this signal:
+
+    ``sender``
+        The instance which just had data asscoiated with it.
+
+    ``objs_removed``
+        The object(s) which were just disassociated with the sender.
+        
+    ``join_table``  
+		The join table between the relevant many-to-many relationship.
+
 class_prepared
 --------------
 
