-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed #21391 -- Allow model signals to lazily reference their senders.
- Loading branch information
Showing
7 changed files
with
197 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,70 @@ | ||
from collections import defaultdict | ||
|
||
from django.db.models.loading import get_model | ||
from django.dispatch import Signal | ||
from django.utils import six | ||
|
||
|
||
class_prepared = Signal(providing_args=["class"]) | ||
|
||
pre_init = Signal(providing_args=["instance", "args", "kwargs"], use_caching=True) | ||
post_init = Signal(providing_args=["instance"], use_caching=True) | ||
|
||
pre_save = Signal(providing_args=["instance", "raw", "using", "update_fields"], | ||
use_caching=True) | ||
post_save = Signal(providing_args=["instance", "raw", "created", "using", "update_fields"], use_caching=True) | ||
class ModelSignal(Signal): | ||
""" | ||
Signal subclass that allows the sender to be lazily specified as a string | ||
of the `app_label.ModelName` form. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super(ModelSignal, self).__init__(*args, **kwargs) | ||
self.unresolved_references = defaultdict(list) | ||
class_prepared.connect(self._resolve_references) | ||
|
||
def _resolve_references(self, sender, **kwargs): | ||
opts = sender._meta | ||
reference = (opts.app_label, opts.object_name) | ||
try: | ||
receivers = self.unresolved_references.pop(reference) | ||
except KeyError: | ||
pass | ||
else: | ||
for receiver, weak, dispatch_uid in receivers: | ||
super(ModelSignal, self).connect( | ||
receiver, sender=sender, weak=weak, dispatch_uid=dispatch_uid | ||
) | ||
|
||
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): | ||
if isinstance(sender, six.string_types): | ||
try: | ||
app_label, object_name = sender.split('.') | ||
except ValueError: | ||
raise ValueError( | ||
"Specified sender must either be a model or a " | ||
"model name of the 'app_label.ModelName' form." | ||
) | ||
sender = get_model(app_label, object_name, only_installed=False) | ||
if sender is None: | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
aaugustin
Member
|
||
reference = (app_label, object_name) | ||
self.unresolved_references[reference].append( | ||
(receiver, weak, dispatch_uid) | ||
) | ||
return | ||
super(ModelSignal, self).connect( | ||
receiver, sender=sender, weak=weak, dispatch_uid=dispatch_uid | ||
) | ||
|
||
pre_delete = Signal(providing_args=["instance", "using"], use_caching=True) | ||
post_delete = Signal(providing_args=["instance", "using"], use_caching=True) | ||
pre_init = ModelSignal(providing_args=["instance", "args", "kwargs"], use_caching=True) | ||
post_init = ModelSignal(providing_args=["instance"], use_caching=True) | ||
|
||
pre_save = ModelSignal(providing_args=["instance", "raw", "using", "update_fields"], | ||
use_caching=True) | ||
post_save = ModelSignal(providing_args=["instance", "raw", "created", "using", "update_fields"], use_caching=True) | ||
|
||
pre_delete = ModelSignal(providing_args=["instance", "using"], use_caching=True) | ||
post_delete = ModelSignal(providing_args=["instance", "using"], use_caching=True) | ||
|
||
m2m_changed = ModelSignal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True) | ||
|
||
pre_migrate = Signal(providing_args=["app", "create_models", "verbosity", "interactive", "db"]) | ||
pre_syncdb = pre_migrate | ||
post_migrate = Signal(providing_args=["class", "app", "created_models", "verbosity", "interactive", "db"]) | ||
post_syncdb = post_migrate | ||
|
||
m2m_changed = Signal(providing_args=["action", "instance", "reverse", "model", "pk_set", "using"], use_caching=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@charettes Is there a reason for checking if the model exists right now, rather than always adding it to unresolved_references?
Switching randomly between two code paths depending on the import sequence is fragile. I'm trying to eliminate such behavior in the app-loading refactor.