1
0
mirror of https://github.com/django/django.git synced 2025-10-24 22:26:08 +00:00

Fixed #21391 -- Allow model signals to lazily reference their senders.

This commit is contained in:
Simon Charette
2013-11-04 23:11:51 -05:00
parent 03bc0a8ac5
commit eb38257e51
7 changed files with 197 additions and 15 deletions

View File

@@ -1,5 +1,6 @@
import collections
import sys
import types
from django.conf import settings
from django.core.management.color import color_style
@@ -25,7 +26,7 @@ def get_validation_errors(outfile, app=None):
validates all models of all installed apps. Writes errors, if any, to outfile.
Returns number of errors.
"""
from django.db import models, connection
from django.db import connection, models
from django.db.models.loading import get_app_errors
from django.db.models.deletion import SET_NULL, SET_DEFAULT
@@ -363,6 +364,8 @@ def get_validation_errors(outfile, app=None):
for it in opts.index_together:
validate_local_fields(e, opts, "index_together", it)
validate_model_signals(e)
return len(e.errors)
@@ -382,3 +385,28 @@ def validate_local_fields(e, opts, field_name, fields):
e.add(opts, '"%s" refers to %s. ManyToManyFields are not supported in %s.' % (field_name, f.name, field_name))
if f not in opts.local_fields:
e.add(opts, '"%s" refers to %s. This is not in the same model as the %s statement.' % (field_name, f.name, field_name))
def validate_model_signals(e):
"""Ensure lazily referenced model signals senders are installed."""
from django.db import models
for name in dir(models.signals):
obj = getattr(models.signals, name)
if isinstance(obj, models.signals.ModelSignal):
for reference, receivers in obj.unresolved_references.items():
for receiver, _, _ in receivers:
# The receiver is either a function or an instance of class
# defining a `__call__` method.
if isinstance(receiver, types.FunctionType):
description = "The `%s` function" % receiver.__name__
else:
description = "An instance of the `%s` class" % receiver.__class__.__name__
e.add(
receiver.__module__,
"%s was connected to the `%s` signal "
"with a lazy reference to the '%s' sender, "
"which has not been installed." % (
description, name, '.'.join(reference)
)
)

View File

@@ -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.
"""
pre_delete = Signal(providing_args=["instance", "using"], use_caching=True)
post_delete = Signal(providing_args=["instance", "using"], use_caching=True)
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:
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_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)