Ticket #5390: t5390-r12120.1.diff
| File t5390-r12120.1.diff, 13.1 kB (added by russellm, 2 months ago) |
|---|
-
a/django/db/models/fields/related.py
old new 513 513 source_field_name: self._pk_val, 514 514 '%s__in' % target_field_name: new_ids, 515 515 }) 516 vals = set(vals) 517 516 new_ids = new_ids - set(vals) 518 517 # Add the ones that aren't there already 519 for obj_id in (new_ids - vals):518 for obj_id in new_ids: 520 519 self.through._default_manager.using(self.instance._state.db).create(**{ 521 520 '%s_id' % source_field_name: self._pk_val, 522 521 '%s_id' % target_field_name: obj_id, 523 522 }) 523 signals.m2m_changed.send(sender=rel.through, action='add', 524 instance=self.instance, reverse=(rel.to != self.model), 525 model=self.model, pk_set=new_ids) 524 526 525 527 def _remove_items(self, source_field_name, target_field_name, *objs): 526 528 # source_col_name: the PK colname in join_table for the source object … … 541 543 source_field_name: self._pk_val, 542 544 '%s__in' % target_field_name: old_ids 543 545 }).delete() 546 signals.m2m_changed.send(sender=rel.through, action="remove", 547 instance=self.instance, reverse=(rel.to != self.model), 548 model=self.model, pk_set=old_ids) 544 549 545 550 def _clear_items(self, source_field_name): 546 551 # source_col_name: the PK colname in join_table for the source object 552 signals.m2m_changed.send(sender=rel.through, action="clear", 553 instance=self.instance, reverse=(rel.to != self.model), 554 model=self.model, pk_set=None) 547 555 self.through._default_manager.using(self.instance._state.db).filter(**{ 548 556 source_field_name: self._pk_val 549 557 }).delete() … … 593 601 manager.clear() 594 602 manager.add(*value) 595 603 604 596 605 class ReverseManyRelatedObjectsDescriptor(object): 597 606 # This class provides the functionality that makes the related-object 598 607 # managers available as attributes on a model class, for fields that have -
a/django/db/models/signals.py
old new 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_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set"]) -
a/docs/ref/signals.txt
old new 170 170 Note that the object will no longer be in the database, so be very 171 171 careful what you do with this instance. 172 172 173 m2m_changed 174 ----------- 175 176 .. data:: django.db.models.signals.m2m_changed 177 :module: 178 179 Sent when a :class:`ManyToManyField` is changed on a model instance. 180 Strictly speaking, this is not a model signal since it is sent by the 181 :class:`ManyToManyField`, but since it complements the 182 :data:`pre_save`/:data:`post_save` and :data:`pre_delete`/:data:`post_delete` 183 when it comes to tracking changes to models, it is included here. 184 185 Arguments sent with this signal: 186 187 ``sender`` 188 The intermediate model class describing the :class:`ManyToManyField`. 189 This class is automatically created when a many-to-many field is 190 defined; it you can access it using the ``through`` attribute on the 191 many-to-many field. 192 193 ``instance`` 194 The instance whose many-to-many relation is updated. This can be an 195 instance of the ``sender``, or of the class the :class:`ManyToManyField` 196 is related to. 197 198 ``action`` 199 A string indicating the type of update that is done on the relation. 200 This can be one of the following: 201 202 ``"add"`` 203 Sent *after* one or more objects are added to the relation 204 ``"remove"`` 205 Sent *after* one or more objects are removed from the relation 206 ``"clear"`` 207 Sent *before* the relation is cleared 208 209 ``reverse`` 210 Indicates which side of the relation is updated (i.e., if it is the 211 forward or reverse relation that is being modified). 212 213 ``model`` 214 The class of the objects that are added to, removed from or cleared 215 from the relation. 216 217 ``pk_set`` 218 With the ``"add"`` and ``"remove"`` action, this is a list of 219 primary key values that have been added to or removed from the relation. 220 221 For the ``"clear"`` action, this is ``None``. 222 223 For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled 224 like this: 225 226 .. code-block:: python 227 228 class Topping(models.Model): 229 # ... 230 231 class Pizza(models.Model): 232 # ... 233 toppings = models.ManyToManyField(Topping) 234 235 If we would do something like this: 236 237 .. code-block:: python 238 239 >>> p = Pizza.object.create(...) 240 >>> t = Topping.objects.create(...) 241 >>> p.toppings.add(t) 242 243 the arguments sent to a :data:`m2m_changed` handler would be: 244 245 ============== ============================================================ 246 Argument Value 247 ============== ============================================================ 248 ``sender`` ``Pizza.toppings.through`` (the intermediate m2m class) 249 250 ``instance`` ``p`` (the ``Pizza`` instance being modified) 251 252 ``action`` ``"add"`` 253 254 ``reverse`` ``False`` (``Pizza`` contains the :class:`ManyToManyField`, 255 so this call modifies the forward relation) 256 257 ``model`` ``Topping`` (the class of the objects added to the 258 ``Pizza``) 259 260 ``pk_set`` ``[t.id]`` (since only ``Topping t`` was added to the relation) 261 ============== ============================================================ 262 263 And if we would then do something like this: 264 265 .. code-block:: python 266 267 >>> t.pizza_set.remove(p) 268 269 the arguments sent to a :data:`m2m_changed` handler would be: 270 271 ============== ============================================================ 272 Argument Value 273 ============== ============================================================ 274 ``sender`` ``Pizza.toppings.through`` (the intermediate m2m class) 275 276 ``instance`` ``t`` (the ``Topping`` instance being modified) 277 278 ``action`` ``"remove"`` 279 280 ``reverse`` ``True`` (``Pizza`` contains the :class:`ManyToManyField`, 281 so this call modifies the reverse relation) 282 283 ``model`` ``Pizza`` (the class of the objects removed from the 284 ``Topping``) 285 286 ``pk_set`` ``[p.id]`` (since only ``Pizza p`` was removed from the 287 relation) 288 ============== ============================================================ 289 173 290 class_prepared 174 291 -------------- 175 292 -
a/docs/topics/signals.txt
old new 29 29 Sent before or after a model's :meth:`~django.db.models.Model.delete` 30 30 method is called. 31 31 32 * :data:`django.db.models.signals.m2m_changed` 33 34 Sent when a :class:`ManyToManyField` on a model is changed. 32 35 33 36 * :data:`django.core.signals.request_started` & 34 37 :data:`django.core.signals.request_finished` -
/dev/null
old new 1 -
/dev/null
old new 1 """ 2 Testing signals emitted on changing m2m relations. 3 """ 4 5 from django.db import models 6 7 class Part(models.Model): 8 name = models.CharField(max_length=20) 9 10 def __unicode__(self): 11 return self.name 12 13 class Car(models.Model): 14 name = models.CharField(max_length=20) 15 default_parts = models.ManyToManyField(Part) 16 optional_parts = models.ManyToManyField(Part, related_name='cars_optional') 17 18 def __unicode__(self): 19 return self.name 20 21 class SportsCar(Car): 22 price = models.IntegerField(max_length=20) 23 24 def m2m_changed_test(signal, sender, **kwargs): 25 print 'm2m_changed signal' 26 print 'instance:', kwargs['instance'] 27 print 'action:', kwargs['action'] 28 print 'reverse:', kwargs['reverse'] 29 print 'model:', kwargs['model'] 30 if kwargs['pk_set']: 31 print 'objects:',kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) 32 33 34 __test__ = {'API_TESTS':""" 35 # Install a listener on one of the two m2m relations. 36 >>> models.signals.m2m_changed.connect(m2m_changed_test, Car.optional_parts.through) 37 38 # Test the add, remove and clear methods on both sides of the 39 # many-to-many relation 40 41 >>> c1 = Car.objects.create(name='VW') 42 >>> c2 = Car.objects.create(name='BMW') 43 >>> c3 = Car.objects.create(name='Toyota') 44 >>> p1 = Part.objects.create(name='Wheelset') 45 >>> p2 = Part.objects.create(name='Doors') 46 >>> p3 = Part.objects.create(name='Engine') 47 >>> p4 = Part.objects.create(name='Airbag') 48 >>> p5 = Part.objects.create(name='Sunroof') 49 50 # adding some default parts to our car - no signal listener installed 51 >>> c1.default_parts.add(p4, p5) 52 53 # Now install a listener 54 >>> models.signals.m2m_changed.connect(m2m_changed_test, Car.default_parts.through) 55 56 >>> c1.default_parts.add(p1, p2, p3) 57 m2m_changed signal 58 instance: VW 59 action: add 60 reverse: False 61 model: <class 'modeltests.m2m_signals.models.Part'> 62 objects: [<Part: Wheelset>, <Part: Doors>, <Part: Engine>] 63 64 # give the BMW and Toyata some doors as well 65 >>> p2.car_set.add(c2, c3) 66 m2m_changed signal 67 instance: Doors 68 action: add 69 reverse: True 70 model: <class 'modeltests.m2m_signals.models.Car'> 71 objects: [<Car: BMW>, <Car: Toyota>] 72 73 # remove the engine from the VW and the airbag (which is not set but is returned) 74 >>> c1.default_parts.remove(p3, p4) 75 m2m_changed signal 76 instance: VW 77 action: remove 78 reverse: False 79 model: <class 'modeltests.m2m_signals.models.Part'> 80 objects: [<Part: Engine>, <Part: Airbag>] 81 82 # give the VW some optional parts (second relation to same model) 83 >>> c1.optional_parts.add(p4,p5) 84 m2m_changed signal 85 instance: VW 86 action: add 87 reverse: False 88 model: <class 'modeltests.m2m_signals.models.Part'> 89 objects: [<Part: Airbag>, <Part: Sunroof>] 90 91 # add airbag to all the cars (even though the VW already has one) 92 >>> p4.cars_optional.add(c1, c2, c3) 93 m2m_changed signal 94 instance: Airbag 95 action: add 96 reverse: True 97 model: <class 'modeltests.m2m_signals.models.Car'> 98 objects: [<Car: BMW>, <Car: Toyota>] 99 100 # remove airbag from the VW (reverse relation with custom related_name) 101 >>> p4.cars_optional.remove(c1) 102 m2m_changed signal 103 instance: Airbag 104 action: remove 105 reverse: True 106 model: <class 'modeltests.m2m_signals.models.Car'> 107 objects: [<Car: VW>] 108 109 # clear all parts of the VW 110 >>> c1.default_parts.clear() 111 m2m_changed signal 112 instance: VW 113 action: clear 114 reverse: False 115 model: <class 'modeltests.m2m_signals.models.Part'> 116 117 # take all the doors off of cars 118 >>> p2.car_set.clear() 119 m2m_changed signal 120 instance: Doors 121 action: clear 122 reverse: True 123 model: <class 'modeltests.m2m_signals.models.Car'> 124 125 # take all the airbags off of cars (clear reverse relation with custom related_name) 126 >>> p4.cars_optional.clear() 127 m2m_changed signal 128 instance: Airbag 129 action: clear 130 reverse: True 131 model: <class 'modeltests.m2m_signals.models.Car'> 132 133 # alternative ways of setting relation: 134 135 >>> c1.default_parts.create(name='Windows') 136 m2m_changed signal 137 instance: VW 138 action: add 139 reverse: False 140 model: <class 'modeltests.m2m_signals.models.Part'> 141 objects: [<Part: Windows>] 142 <Part: Windows> 143 144 # direct assignment clears the set first, then adds 145 >>> c1.default_parts = [p1,p2,p3] 146 m2m_changed signal 147 instance: VW 148 action: clear 149 reverse: False 150 model: <class 'modeltests.m2m_signals.models.Part'> 151 m2m_changed signal 152 instance: VW 153 action: add 154 reverse: False 155 model: <class 'modeltests.m2m_signals.models.Part'> 156 objects: [<Part: Wheelset>, <Part: Doors>, <Part: Engine>] 157 158 # Check that signals still work when model inheritance is involved 159 >>> c4 = SportsCar.objects.create(name='Bugatti', price='1000000') 160 >>> c4.default_parts = [p2] 161 m2m_changed signal 162 instance: Bugatti 163 action: clear 164 reverse: False 165 model: <class 'modeltests.m2m_signals.models.Part'> 166 m2m_changed signal 167 instance: Bugatti 168 action: add 169 reverse: False 170 model: <class 'modeltests.m2m_signals.models.Part'> 171 objects: [<Part: Doors>] 172 173 >>> p3.car_set.add(c4) 174 m2m_changed signal 175 instance: Engine 176 action: add 177 reverse: True 178 model: <class 'modeltests.m2m_signals.models.Car'> 179 objects: [<Car: Bugatti>] 180 181 >>> models.signals.m2m_changed.disconnect(m2m_changed_test) 182 183 """}
