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:
parent
458e3a3ff9
commit
7e06065d8b
1
AUTHORS
1
AUTHORS
|
@ -190,6 +190,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
martin.glueck@gmail.com
|
martin.glueck@gmail.com
|
||||||
Artyom Gnilov <boobsd@gmail.com>
|
Artyom Gnilov <boobsd@gmail.com>
|
||||||
Ben Godfrey <http://aftnn.org>
|
Ben Godfrey <http://aftnn.org>
|
||||||
|
Andrew Godwin <andrew@aeracode.org>
|
||||||
GomoX <gomo@datafull.com>
|
GomoX <gomo@datafull.com>
|
||||||
Guilherme Mesquita Gondim <semente@taurinus.org>
|
Guilherme Mesquita Gondim <semente@taurinus.org>
|
||||||
Mario Gonzalez <gonzalemario@gmail.com>
|
Mario Gonzalez <gonzalemario@gmail.com>
|
||||||
|
|
|
@ -456,7 +456,7 @@ class Model(object):
|
||||||
meta = cls._meta
|
meta = cls._meta
|
||||||
|
|
||||||
if origin and not meta.auto_created:
|
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.
|
# 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
|
# 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
|
# Signal that the save is complete
|
||||||
if origin and not meta.auto_created:
|
if origin and not meta.auto_created:
|
||||||
signals.post_save.send(sender=origin, instance=self,
|
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
|
save_base.alters_data = True
|
||||||
|
|
||||||
|
|
|
@ -566,7 +566,7 @@ def create_many_related_manager(superclass, rel=False):
|
||||||
# duplicate data row for symmetrical reverse entries.
|
# duplicate data row for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action='pre_add',
|
signals.m2m_changed.send(sender=rel.through, action='pre_add',
|
||||||
instance=self.instance, reverse=self.reverse,
|
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
|
# Add the ones that aren't there already
|
||||||
for obj_id in new_ids:
|
for obj_id in new_ids:
|
||||||
self.through._default_manager.using(db).create(**{
|
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.
|
# duplicate data row for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action='post_add',
|
signals.m2m_changed.send(sender=rel.through, action='post_add',
|
||||||
instance=self.instance, reverse=self.reverse,
|
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):
|
def _remove_items(self, source_field_name, target_field_name, *objs):
|
||||||
# source_col_name: the PK colname in join_table for the source object
|
# 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)
|
old_ids.add(obj.pk)
|
||||||
else:
|
else:
|
||||||
old_ids.add(obj)
|
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:
|
if self.reverse or source_field_name == self.source_field_name:
|
||||||
# Don't send the signal when we are deleting the
|
# Don't send the signal when we are deleting the
|
||||||
# duplicate data row for symmetrical reverse entries.
|
# duplicate data row for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action="pre_remove",
|
signals.m2m_changed.send(sender=rel.through, action="pre_remove",
|
||||||
instance=self.instance, reverse=self.reverse,
|
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
|
# 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(**{
|
self.through._default_manager.using(db).filter(**{
|
||||||
source_field_name: self._pk_val,
|
source_field_name: self._pk_val,
|
||||||
'%s__in' % target_field_name: old_ids
|
'%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.
|
# duplicate data row for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action="post_remove",
|
signals.m2m_changed.send(sender=rel.through, action="post_remove",
|
||||||
instance=self.instance, reverse=self.reverse,
|
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):
|
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
|
# source_col_name: the PK colname in join_table for the source object
|
||||||
if self.reverse or source_field_name == self.source_field_name:
|
if self.reverse or source_field_name == self.source_field_name:
|
||||||
# Don't send the signal when we are clearing the
|
# Don't send the signal when we are clearing the
|
||||||
# duplicate data rows for symmetrical reverse entries.
|
# duplicate data rows for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action="pre_clear",
|
signals.m2m_changed.send(sender=rel.through, action="pre_clear",
|
||||||
instance=self.instance, reverse=self.reverse,
|
instance=self.instance, reverse=self.reverse,
|
||||||
model=self.model, pk_set=None)
|
model=self.model, pk_set=None, using=db)
|
||||||
db = router.db_for_write(self.through.__class__, instance=self.instance)
|
|
||||||
self.through._default_manager.using(db).filter(**{
|
self.through._default_manager.using(db).filter(**{
|
||||||
source_field_name: self._pk_val
|
source_field_name: self._pk_val
|
||||||
}).delete()
|
}).delete()
|
||||||
|
@ -630,7 +632,7 @@ def create_many_related_manager(superclass, rel=False):
|
||||||
# duplicate data rows for symmetrical reverse entries.
|
# duplicate data rows for symmetrical reverse entries.
|
||||||
signals.m2m_changed.send(sender=rel.through, action="post_clear",
|
signals.m2m_changed.send(sender=rel.through, action="post_clear",
|
||||||
instance=self.instance, reverse=self.reverse,
|
instance=self.instance, reverse=self.reverse,
|
||||||
model=self.model, pk_set=None)
|
model=self.model, pk_set=None, using=db)
|
||||||
|
|
||||||
return ManyRelatedManager
|
return ManyRelatedManager
|
||||||
|
|
||||||
|
|
|
@ -1311,7 +1311,8 @@ def delete_objects(seen_objs, using):
|
||||||
# Pre-notify all instances to be deleted.
|
# Pre-notify all instances to be deleted.
|
||||||
for pk_val, instance in items:
|
for pk_val, instance in items:
|
||||||
if not cls._meta.auto_created:
|
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]
|
pk_list = [pk for pk,instance in items]
|
||||||
|
|
||||||
|
@ -1343,7 +1344,7 @@ def delete_objects(seen_objs, using):
|
||||||
setattr(instance, field.attname, None)
|
setattr(instance, field.attname, None)
|
||||||
|
|
||||||
if not cls._meta.auto_created:
|
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)
|
setattr(instance, cls._meta.pk.attname, None)
|
||||||
|
|
||||||
if forced_managed:
|
if forced_managed:
|
||||||
|
|
|
@ -5,12 +5,12 @@ class_prepared = Signal(providing_args=["class"])
|
||||||
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
|
pre_init = Signal(providing_args=["instance", "args", "kwargs"])
|
||||||
post_init = Signal(providing_args=["instance"])
|
post_init = Signal(providing_args=["instance"])
|
||||||
|
|
||||||
pre_save = Signal(providing_args=["instance", "raw"])
|
pre_save = Signal(providing_args=["instance", "raw", "using"])
|
||||||
post_save = Signal(providing_args=["instance", "raw", "created"])
|
post_save = Signal(providing_args=["instance", "raw", "created", "using"])
|
||||||
|
|
||||||
pre_delete = Signal(providing_args=["instance"])
|
pre_delete = Signal(providing_args=["instance", "using"])
|
||||||
post_delete = Signal(providing_args=["instance"])
|
post_delete = Signal(providing_args=["instance", "using"])
|
||||||
|
|
||||||
post_syncdb = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive"])
|
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"])
|
||||||
|
|
|
@ -33,9 +33,9 @@ module system.
|
||||||
If you override these methods on your model, you must call the parent class'
|
If you override these methods on your model, you must call the parent class'
|
||||||
methods for this signals to be sent.
|
methods for this signals to be sent.
|
||||||
|
|
||||||
Note also that Django stores signal handlers as weak references by default,
|
Note also that Django stores signal handlers as weak references by default,
|
||||||
so if your handler is a local function, it may be garbage collected. To
|
so if your handler is a local function, it may be garbage collected. To
|
||||||
prevent this, pass ``weak=False`` when you call the signal's :meth:`~django.dispatch.Signal.connect`.
|
prevent this, pass ``weak=False`` when you call the signal's :meth:`~django.dispatch.Signal.connect`.
|
||||||
|
|
||||||
pre_init
|
pre_init
|
||||||
--------
|
--------
|
||||||
|
@ -113,6 +113,9 @@ Arguments sent with this signal:
|
||||||
``instance``
|
``instance``
|
||||||
The actual instance being saved.
|
The actual instance being saved.
|
||||||
|
|
||||||
|
``using``
|
||||||
|
The database alias being used.
|
||||||
|
|
||||||
post_save
|
post_save
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
@ -133,6 +136,9 @@ Arguments sent with this signal:
|
||||||
``created``
|
``created``
|
||||||
A boolean; ``True`` if a new record was created.
|
A boolean; ``True`` if a new record was created.
|
||||||
|
|
||||||
|
``using``
|
||||||
|
The database alias being used.
|
||||||
|
|
||||||
pre_delete
|
pre_delete
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -150,6 +156,9 @@ Arguments sent with this signal:
|
||||||
``instance``
|
``instance``
|
||||||
The actual instance being deleted.
|
The actual instance being deleted.
|
||||||
|
|
||||||
|
``using``
|
||||||
|
The database alias being used.
|
||||||
|
|
||||||
post_delete
|
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
|
Note that the object will no longer be in the database, so be very
|
||||||
careful what you do with this instance.
|
careful what you do with this instance.
|
||||||
|
|
||||||
|
``using``
|
||||||
|
The database alias being used.
|
||||||
|
|
||||||
m2m_changed
|
m2m_changed
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -215,8 +227,8 @@ Arguments sent with this signal:
|
||||||
Sent *after* the relation is cleared
|
Sent *after* the relation is cleared
|
||||||
|
|
||||||
``reverse``
|
``reverse``
|
||||||
Indicates which side of the relation is updated (i.e., if it is the
|
Indicates which side of the relation is updated (i.e., if it is the
|
||||||
forward or reverse relation that is being modified).
|
forward or reverse relation that is being modified).
|
||||||
|
|
||||||
``model``
|
``model``
|
||||||
The class of the objects that are added to, removed from or cleared
|
The class of the objects that are added to, removed from or cleared
|
||||||
|
@ -228,6 +240,9 @@ Arguments sent with this signal:
|
||||||
|
|
||||||
For the ``"clear"`` action, this is ``None``.
|
For the ``"clear"`` action, this is ``None``.
|
||||||
|
|
||||||
|
``using``
|
||||||
|
The database alias being used.
|
||||||
|
|
||||||
For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled
|
For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
|
@ -266,6 +281,8 @@ the arguments sent to a :data:`m2m_changed` handler would be:
|
||||||
``Pizza``)
|
``Pizza``)
|
||||||
|
|
||||||
``pk_set`` ``[t.id]`` (since only ``Topping t`` was added to the relation)
|
``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:
|
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
|
``pk_set`` ``[p.id]`` (since only ``Pizza p`` was removed from the
|
||||||
relation)
|
relation)
|
||||||
|
|
||||||
|
``using`` ``"default"`` (since the default router sends writes here)
|
||||||
============== ============================================================
|
============== ============================================================
|
||||||
|
|
||||||
class_prepared
|
class_prepared
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.db import connections, router, DEFAULT_DB_ALIAS
|
from django.db import connections, router, DEFAULT_DB_ALIAS
|
||||||
|
from django.db.models import signals
|
||||||
from django.db.utils import ConnectionRouter
|
from django.db.utils import ConnectionRouter
|
||||||
from django.test import TestCase
|
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))
|
Book.objects.using(db).create(title='Dive into Python', published=datetime.date(2009, 5, 4))
|
||||||
qs = Book.objects.all()
|
qs = Book.objects.all()
|
||||||
self.assertEqual(qs.db, pickle.loads(pickle.dumps(qs)).db)
|
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")
|
||||||
|
|
Loading…
Reference in New Issue