Ticket #13552: 13552-take-1.diff

File 13552-take-1.diff, 14.0 KB (added by Andrew Godwin, 14 years ago)

Patch that adds "using" keyword for *_save, *_delete, m2m_changed

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    a b  
    456456            meta = cls._meta
    457457
    458458        if origin and not meta.auto_created:
    459             signals.pre_save.send(sender=origin, instance=self, raw=raw)
     459            signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
    460460
    461461        # If we are in a raw save, save the object exactly as presented.
    462462        # That means that we don't try to be smart about saving attributes
     
    540540        # Signal that the save is complete
    541541        if origin and not meta.auto_created:
    542542            signals.post_save.send(sender=origin, instance=self,
    543                 created=(not record_exists), raw=raw)
     543                created=(not record_exists), raw=raw, using=using)
    544544
    545545    save_base.alters_data = True
    546546
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    566566                    # duplicate data row for symmetrical reverse entries.
    567567                    signals.m2m_changed.send(sender=rel.through, action='pre_add',
    568568                        instance=self.instance, reverse=self.reverse,
    569                         model=self.model, pk_set=new_ids)
     569                        model=self.model, pk_set=new_ids, using=db)
    570570                # Add the ones that aren't there already
    571571                for obj_id in new_ids:
    572572                    self.through._default_manager.using(db).create(**{
     
    578578                    # duplicate data row for symmetrical reverse entries.
    579579                    signals.m2m_changed.send(sender=rel.through, action='post_add',
    580580                        instance=self.instance, reverse=self.reverse,
    581                         model=self.model, pk_set=new_ids)
     581                        model=self.model, pk_set=new_ids, using=db)
    582582
    583583        def _remove_items(self, source_field_name, target_field_name, *objs):
    584584            # source_col_name: the PK colname in join_table for the source object
     
    594594                        old_ids.add(obj.pk)
    595595                    else:
    596596                        old_ids.add(obj)
     597                # Work out what DB we're operating on
     598                db = router.db_for_write(self.through.__class__, instance=self.instance)
     599                # Send a signal to the other end if need be.
    597600                if self.reverse or source_field_name == self.source_field_name:
    598601                    # Don't send the signal when we are deleting the
    599602                    # duplicate data row for symmetrical reverse entries.
    600603                    signals.m2m_changed.send(sender=rel.through, action="pre_remove",
    601604                        instance=self.instance, reverse=self.reverse,
    602                         model=self.model, pk_set=old_ids)
     605                        model=self.model, pk_set=old_ids, using=db)
    603606                # Remove the specified objects from the join table
    604                 db = router.db_for_write(self.through.__class__, instance=self.instance)
    605607                self.through._default_manager.using(db).filter(**{
    606608                    source_field_name: self._pk_val,
    607609                    '%s__in' % target_field_name: old_ids
     
    611613                    # duplicate data row for symmetrical reverse entries.
    612614                    signals.m2m_changed.send(sender=rel.through, action="post_remove",
    613615                        instance=self.instance, reverse=self.reverse,
    614                         model=self.model, pk_set=old_ids)
     616                        model=self.model, pk_set=old_ids, using=db)
    615617
    616618        def _clear_items(self, source_field_name):
     619            db = router.db_for_write(self.through.__class__, instance=self.instance)
    617620            # source_col_name: the PK colname in join_table for the source object
    618621            if self.reverse or source_field_name == self.source_field_name:
    619622                # Don't send the signal when we are clearing the
    620623                # duplicate data rows for symmetrical reverse entries.
    621624                signals.m2m_changed.send(sender=rel.through, action="pre_clear",
    622625                    instance=self.instance, reverse=self.reverse,
    623                     model=self.model, pk_set=None)
    624             db = router.db_for_write(self.through.__class__, instance=self.instance)
     626                    model=self.model, pk_set=None, using=db)
    625627            self.through._default_manager.using(db).filter(**{
    626628                source_field_name: self._pk_val
    627629            }).delete()
     
    630632                # duplicate data rows for symmetrical reverse entries.
    631633                signals.m2m_changed.send(sender=rel.through, action="post_clear",
    632634                    instance=self.instance, reverse=self.reverse,
    633                     model=self.model, pk_set=None)
     635                    model=self.model, pk_set=None, using=db)
    634636
    635637    return ManyRelatedManager
    636638
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    a b  
    13111311            # Pre-notify all instances to be deleted.
    13121312            for pk_val, instance in items:
    13131313                if not cls._meta.auto_created:
    1314                     signals.pre_delete.send(sender=cls, instance=instance)
     1314                    signals.pre_delete.send(sender=cls, instance=instance, \
     1315                        using=using)
    13151316
    13161317            pk_list = [pk for pk,instance in items]
    13171318
     
    13431344                        setattr(instance, field.attname, None)
    13441345
    13451346                if not cls._meta.auto_created:
    1346                     signals.post_delete.send(sender=cls, instance=instance)
     1347                    signals.post_delete.send(sender=cls, instance=instance, using=using)
    13471348                setattr(instance, cls._meta.pk.attname, None)
    13481349
    13491350        if forced_managed:
  • django/db/models/signals.py

    diff --git a/django/db/models/signals.py b/django/db/models/signals.py
    a b  
    55pre_init = Signal(providing_args=["instance", "args", "kwargs"])
    66post_init = Signal(providing_args=["instance"])
    77
    8 pre_save = Signal(providing_args=["instance", "raw"])
    9 post_save = Signal(providing_args=["instance", "raw", "created"])
     8pre_save = Signal(providing_args=["instance", "raw", "using"])
     9post_save = Signal(providing_args=["instance", "raw", "created", "using"])
    1010
    11 pre_delete = Signal(providing_args=["instance"])
    12 post_delete = Signal(providing_args=["instance"])
     11pre_delete = Signal(providing_args=["instance", "using"])
     12post_delete = Signal(providing_args=["instance", "using"])
    1313
    1414post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
    1515
    16 m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set"])
     16m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"])
  • docs/ref/signals.txt

    diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt
    a b  
    113113    ``instance``
    114114        The actual instance being saved.
    115115
     116    ``using``
     117        The database alias being used.
     118
    116119post_save
    117120---------
    118121
     
    133136    ``created``
    134137        A boolean; ``True`` if a new record was created.
    135138
     139    ``using``
     140        The database alias being used.
     141
    136142pre_delete
    137143----------
    138144
     
    150156    ``instance``
    151157        The actual instance being deleted.
    152158
     159    ``using``
     160        The database alias being used.
     161
    153162post_delete
    154163-----------
    155164
     
    170179        Note that the object will no longer be in the database, so be very
    171180        careful what you do with this instance.
    172181
     182    ``using``
     183        The database alias being used.
     184
    173185m2m_changed
    174186-----------
    175187
     
    228240
    229241        For the ``"clear"`` action, this is ``None``.
    230242
     243    ``using``
     244        The database alias being used.
     245
    231246For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled
    232247like this:
    233248
     
    266281                    ``Pizza``)
    267282
    268283    ``pk_set``      ``[t.id]`` (since only ``Topping t`` was added to the relation)
     284       
     285        ``using``       ``"default"`` (since the default router sends writes here)
    269286    ==============  ============================================================
    270287
    271288And if we would then do something like this:
     
    293310
    294311    ``pk_set``      ``[p.id]`` (since only ``Pizza p`` was removed from the
    295312                    relation)
     313       
     314        ``using``       ``"default"`` (since the default router sends writes here)
    296315    ==============  ============================================================
    297316
    298317class_prepared
  • tests/modeltests/signals/models.py

    diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py
    a b  
    1111    def __unicode__(self):
    1212        return u"%s %s" % (self.first_name, self.last_name)
    1313
     14class Project(models.Model):
     15    people = models.ManyToManyField(Person)
     16
     17
    1418def pre_save_test(signal, sender, instance, **kwargs):
    1519    print 'pre_save signal,', instance
    1620    if kwargs.get('raw'):
  • tests/modeltests/signals/tests.py

    diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
    a b  
    11from django.db.models import signals
     2from django.db import DEFAULT_DB_ALIAS, router
    23from django.test import TestCase
    3 from modeltests.signals.models import Person
     4from modeltests.signals.models import Person, Project
    45
    5 class MyReceiver(object):
     6class DisconnectReceiver(object):
     7    """
     8    Used to test the disconnection of signals.
     9    """
    610    def __init__(self, param):
    711        self.param = param
    812        self._run = False
     
    1014    def __call__(self, signal, sender, **kwargs):
    1115        self._run = True
    1216        signal.disconnect(receiver=self, sender=sender)
     17 
     18class DatabaseReceiver(object):
     19    """
     20    Used in the tests for the database argument in signals (#13552)
     21    """
     22    def __init__(self):
     23        self._database = None
     24
     25    def __call__(self, signal, sender, **kwargs):
     26        self._database = kwargs['using']
     27   
     28class TestRouter(object):
     29    """
     30    A router that sends all writes to the other database.
     31    """
     32    def db_for_write(self, model, **hints):
     33        return "other"
    1334
    1435class SignalTests(TestCase):
     36   
     37    multi_db = True
     38   
     39    def _write_to_other(self):
     40        "Sends all writes to 'other'."
     41        self.old_routers = router.routers
     42        router.routers = [TestRouter()]
     43
     44    def _write_to_default(self):
     45        "Sends all writes to the default DB"
     46        router.routers = self.old_routers
     47   
    1548    def test_disconnect_in_dispatch(self):
    1649        """
    1750        Test that signals that disconnect when being called don't mess future
    1851        dispatching.
    1952        """
    20         a, b = MyReceiver(1), MyReceiver(2)
     53        a, b = DisconnectReceiver(1), DisconnectReceiver(2)
    2154        signals.post_save.connect(sender=Person, receiver=a)
    2255        signals.post_save.connect(sender=Person, receiver=b)
    2356        p = Person.objects.create(first_name='John', last_name='Smith')
     
    2558        self.failUnless(a._run)
    2659        self.failUnless(b._run)
    2760        self.assertEqual(signals.post_save.receivers, [])
    28        
     61   
     62    def test_database_arg_save_and_delete(self):
     63        """
     64        Tests that the pre/post_save signal contains the correct database.
     65        (#13552)
     66        """
     67        # Make some signal receivers
     68        pre_save_receiver = DatabaseReceiver()
     69        post_save_receiver = DatabaseReceiver()
     70        pre_delete_receiver = DatabaseReceiver()
     71        post_delete_receiver = DatabaseReceiver()
     72        # Make model and connect receivers
     73        signals.pre_save.connect(sender=Person, receiver=pre_save_receiver)
     74        signals.post_save.connect(sender=Person, receiver=post_save_receiver)
     75        signals.pre_delete.connect(sender=Person, receiver=pre_delete_receiver)
     76        signals.post_delete.connect(sender=Person, receiver=post_delete_receiver)
     77        p = Person.objects.create(first_name='Darth', last_name='Vader')
     78        # Save and test receivers got calls
     79        p.save()
     80        self.assertEqual(pre_save_receiver._database, DEFAULT_DB_ALIAS)
     81        self.assertEqual(post_save_receiver._database, DEFAULT_DB_ALIAS)
     82        # Delete, and test
     83        p.delete()
     84        self.assertEqual(pre_delete_receiver._database, DEFAULT_DB_ALIAS)
     85        self.assertEqual(post_delete_receiver._database, DEFAULT_DB_ALIAS)
     86        # Save again to a different database
     87        p.save(using="other")
     88        self.assertEqual(pre_save_receiver._database, "other")
     89        self.assertEqual(post_save_receiver._database, "other")
     90        # Delete, and test
     91        p.delete(using="other")
     92        self.assertEqual(pre_delete_receiver._database, "other")
     93        self.assertEqual(post_delete_receiver._database, "other")
     94   
     95    def test_database_arg_m2m(self):
     96        """
     97        Test that the m2m_changed signal has a correct database arg (#13552)
     98        """
     99        # Make a receiver
     100        receiver = DatabaseReceiver()
     101        # Connect it, and make the models
     102        signals.m2m_changed.connect(receiver=receiver)
     103        pe = Person.objects.create(first_name='Jane', last_name='Smith')
     104        pr = Project.objects.create()
     105        # Test addition
     106        pr.people.add(pe)
     107        self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
     108        self._write_to_other()
     109        pr.people.add(pe)
     110        self._write_to_default()
     111        self.assertEqual(receiver._database, "other")
     112        # Test removal
     113        pr.people.remove(pe)
     114        self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
     115        self._write_to_other()
     116        pr.people.remove(pe)
     117        self._write_to_default()
     118        self.assertEqual(receiver._database, "other")
     119        # Test clearing
     120        pr.people.clear()
     121        self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
     122        self._write_to_other()
     123        pr.people.clear()
     124        self._write_to_default()
     125        self.assertEqual(receiver._database, "other")
     126 No newline at end of file
Back to Top