Fixed #13552 -- Added a 'using' parameter to database signals. Thanks to gmandx for the suggestion, and Andrew Godwin for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13538 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-08-07 06:27:52 +00:00
parent 458e3a3ff9
commit 7e06065d8b
7 changed files with 157 additions and 22 deletions

View File

@ -190,6 +190,7 @@ answer newbie questions, and generally made Django that much better:
martin.glueck@gmail.com
Artyom Gnilov <boobsd@gmail.com>
Ben Godfrey <http://aftnn.org>
Andrew Godwin <andrew@aeracode.org>
GomoX <gomo@datafull.com>
Guilherme Mesquita Gondim <semente@taurinus.org>
Mario Gonzalez <gonzalemario@gmail.com>

View File

@ -456,7 +456,7 @@ class Model(object):
meta = cls._meta
if origin and not meta.auto_created:
signals.pre_save.send(sender=origin, instance=self, raw=raw)
signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
# If we are in a raw save, save the object exactly as presented.
# That means that we don't try to be smart about saving attributes
@ -540,7 +540,7 @@ class Model(object):
# Signal that the save is complete
if origin and not meta.auto_created:
signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw)
created=(not record_exists), raw=raw, using=using)
save_base.alters_data = True

View File

@ -566,7 +566,7 @@ def create_many_related_manager(superclass, rel=False):
# duplicate data row for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action='pre_add',
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=new_ids)
model=self.model, pk_set=new_ids, using=db)
# Add the ones that aren't there already
for obj_id in new_ids:
self.through._default_manager.using(db).create(**{
@ -578,7 +578,7 @@ def create_many_related_manager(superclass, rel=False):
# duplicate data row for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action='post_add',
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=new_ids)
model=self.model, pk_set=new_ids, using=db)
def _remove_items(self, source_field_name, target_field_name, *objs):
# source_col_name: the PK colname in join_table for the source object
@ -594,14 +594,16 @@ def create_many_related_manager(superclass, rel=False):
old_ids.add(obj.pk)
else:
old_ids.add(obj)
# Work out what DB we're operating on
db = router.db_for_write(self.through.__class__, instance=self.instance)
# Send a signal to the other end if need be.
if self.reverse or source_field_name == self.source_field_name:
# Don't send the signal when we are deleting the
# duplicate data row for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action="pre_remove",
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=old_ids)
model=self.model, pk_set=old_ids, using=db)
# Remove the specified objects from the join table
db = router.db_for_write(self.through.__class__, instance=self.instance)
self.through._default_manager.using(db).filter(**{
source_field_name: self._pk_val,
'%s__in' % target_field_name: old_ids
@ -611,17 +613,17 @@ def create_many_related_manager(superclass, rel=False):
# duplicate data row for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action="post_remove",
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=old_ids)
model=self.model, pk_set=old_ids, using=db)
def _clear_items(self, source_field_name):
db = router.db_for_write(self.through.__class__, instance=self.instance)
# source_col_name: the PK colname in join_table for the source object
if self.reverse or source_field_name == self.source_field_name:
# Don't send the signal when we are clearing the
# duplicate data rows for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action="pre_clear",
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=None)
db = router.db_for_write(self.through.__class__, instance=self.instance)
model=self.model, pk_set=None, using=db)
self.through._default_manager.using(db).filter(**{
source_field_name: self._pk_val
}).delete()
@ -630,7 +632,7 @@ def create_many_related_manager(superclass, rel=False):
# duplicate data rows for symmetrical reverse entries.
signals.m2m_changed.send(sender=rel.through, action="post_clear",
instance=self.instance, reverse=self.reverse,
model=self.model, pk_set=None)
model=self.model, pk_set=None, using=db)
return ManyRelatedManager

View File

@ -1311,7 +1311,8 @@ def delete_objects(seen_objs, using):
# Pre-notify all instances to be deleted.
for pk_val, instance in items:
if not cls._meta.auto_created:
signals.pre_delete.send(sender=cls, instance=instance)
signals.pre_delete.send(sender=cls, instance=instance,
using=using)
pk_list = [pk for pk,instance in items]
@ -1343,7 +1344,7 @@ def delete_objects(seen_objs, using):
setattr(instance, field.attname, None)
if not cls._meta.auto_created:
signals.post_delete.send(sender=cls, instance=instance)
signals.post_delete.send(sender=cls, instance=instance, using=using)
setattr(instance, cls._meta.pk.attname, None)
if forced_managed:

View File

@ -5,12 +5,12 @@ class_prepared = Signal(providing_args=["class"])
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
post_init = Signal(providing_args=["instance"])
pre_save = Signal(providing_args=["instance", "raw"])
post_save = Signal(providing_args=["instance", "raw", "created"])
pre_save = Signal(providing_args=["instance", "raw", "using"])
post_save = Signal(providing_args=["instance", "raw", "created", "using"])
pre_delete = Signal(providing_args=["instance"])
post_delete = Signal(providing_args=["instance"])
pre_delete = Signal(providing_args=["instance", "using"])
post_delete = Signal(providing_args=["instance", "using"])
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set"])
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"])

View File

@ -113,6 +113,9 @@ Arguments sent with this signal:
``instance``
The actual instance being saved.
``using``
The database alias being used.
post_save
---------
@ -133,6 +136,9 @@ Arguments sent with this signal:
``created``
A boolean; ``True`` if a new record was created.
``using``
The database alias being used.
pre_delete
----------
@ -150,6 +156,9 @@ Arguments sent with this signal:
``instance``
The actual instance being deleted.
``using``
The database alias being used.
post_delete
-----------
@ -170,6 +179,9 @@ Arguments sent with this signal:
Note that the object will no longer be in the database, so be very
careful what you do with this instance.
``using``
The database alias being used.
m2m_changed
-----------
@ -228,6 +240,9 @@ Arguments sent with this signal:
For the ``"clear"`` action, this is ``None``.
``using``
The database alias being used.
For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled
like this:
@ -266,6 +281,8 @@ the arguments sent to a :data:`m2m_changed` handler would be:
``Pizza``)
``pk_set`` ``[t.id]`` (since only ``Topping t`` was added to the relation)
``using`` ``"default"`` (since the default router sends writes here)
============== ============================================================
And if we would then do something like this:
@ -293,6 +310,8 @@ the arguments sent to a :data:`m2m_changed` handler would be:
``pk_set`` ``[p.id]`` (since only ``Pizza p`` was removed from the
relation)
``using`` ``"default"`` (since the default router sends writes here)
============== ============================================================
class_prepared

View File

@ -7,6 +7,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.core import management
from django.db import connections, router, DEFAULT_DB_ALIAS
from django.db.models import signals
from django.db.utils import ConnectionRouter
from django.test import TestCase
@ -1619,3 +1620,114 @@ class PickleQuerySetTestCase(TestCase):
Book.objects.using(db).create(title='Dive into Python', published=datetime.date(2009, 5, 4))
qs = Book.objects.all()
self.assertEqual(qs.db, pickle.loads(pickle.dumps(qs)).db)
class DatabaseReceiver(object):
"""
Used in the tests for the database argument in signals (#13552)
"""
def __call__(self, signal, sender, **kwargs):
self._database = kwargs['using']
class WriteToOtherRouter(object):
"""
A router that sends all writes to the other database.
"""
def db_for_write(self, model, **hints):
return "other"
class SignalTests(TestCase):
multi_db = True
def setUp(self):
self.old_routers = router.routers
def tearDown(self):
router.routser = self.old_routers
def _write_to_other(self):
"Sends all writes to 'other'."
router.routers = [WriteToOtherRouter()]
def _write_to_default(self):
"Sends all writes to the default DB"
router.routers = self.old_routers
def test_database_arg_save_and_delete(self):
"""
Tests that the pre/post_save signal contains the correct database.
(#13552)
"""
# Make some signal receivers
pre_save_receiver = DatabaseReceiver()
post_save_receiver = DatabaseReceiver()
pre_delete_receiver = DatabaseReceiver()
post_delete_receiver = DatabaseReceiver()
# Make model and connect receivers
signals.pre_save.connect(sender=Person, receiver=pre_save_receiver)
signals.post_save.connect(sender=Person, receiver=post_save_receiver)
signals.pre_delete.connect(sender=Person, receiver=pre_delete_receiver)
signals.post_delete.connect(sender=Person, receiver=post_delete_receiver)
p = Person.objects.create(name='Darth Vader')
# Save and test receivers got calls
p.save()
self.assertEqual(pre_save_receiver._database, DEFAULT_DB_ALIAS)
self.assertEqual(post_save_receiver._database, DEFAULT_DB_ALIAS)
# Delete, and test
p.delete()
self.assertEqual(pre_delete_receiver._database, DEFAULT_DB_ALIAS)
self.assertEqual(post_delete_receiver._database, DEFAULT_DB_ALIAS)
# Save again to a different database
p.save(using="other")
self.assertEqual(pre_save_receiver._database, "other")
self.assertEqual(post_save_receiver._database, "other")
# Delete, and test
p.delete(using="other")
self.assertEqual(pre_delete_receiver._database, "other")
self.assertEqual(post_delete_receiver._database, "other")
def test_database_arg_m2m(self):
"""
Test that the m2m_changed signal has a correct database arg (#13552)
"""
# Make a receiver
receiver = DatabaseReceiver()
# Connect it, and make the models
signals.m2m_changed.connect(receiver=receiver)
b = Book.objects.create(title="Pro Django",
published=datetime.date(2008, 12, 16))
p = Person.objects.create(name="Marty Alchin")
# Test addition
b.authors.add(p)
self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
self._write_to_other()
b.authors.add(p)
self._write_to_default()
self.assertEqual(receiver._database, "other")
# Test removal
b.authors.remove(p)
self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
self._write_to_other()
b.authors.remove(p)
self._write_to_default()
self.assertEqual(receiver._database, "other")
# Test addition in reverse
p.book_set.add(b)
self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
self._write_to_other()
p.book_set.add(b)
self._write_to_default()
self.assertEqual(receiver._database, "other")
# Test clearing
b.authors.clear()
self.assertEqual(receiver._database, DEFAULT_DB_ALIAS)
self._write_to_other()
b.authors.clear()
self._write_to_default()
self.assertEqual(receiver._database, "other")