Beefed up support for "lazy" related objects. Now, in addition to ForeignKey("Model") you can also say ForeignKey("app.Model"). This means that cross-app recursive relations now work.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7158 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
297a12c2d1
commit
df5fef33c9
|
@ -23,26 +23,64 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
|
||||||
|
|
||||||
pending_lookups = {}
|
pending_lookups = {}
|
||||||
|
|
||||||
def add_lookup(rel_cls, field):
|
def add_lazy_relation(cls, field, relation):
|
||||||
name = field.rel.to
|
"""
|
||||||
module = rel_cls.__module__
|
Adds a lookup on ``cls`` when a related field is defined using a string,
|
||||||
key = (module, name)
|
i.e.::
|
||||||
# Has the model already been loaded?
|
|
||||||
# If so, resolve the string reference right away
|
class MyModel(Model):
|
||||||
model = get_model(rel_cls._meta.app_label, field.rel.to, False)
|
fk = ForeignKey("AnotherModel")
|
||||||
|
|
||||||
|
This string can be:
|
||||||
|
|
||||||
|
* RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive
|
||||||
|
relation.
|
||||||
|
|
||||||
|
* The name of a model (i.e "AnotherModel") to indicate another model in
|
||||||
|
the same app.
|
||||||
|
|
||||||
|
* An app-label and model name (i.e. "someapp.AnotherModel") to indicate
|
||||||
|
another model in a different app.
|
||||||
|
|
||||||
|
If the other model hasn't yet been loaded -- almost a given if you're using
|
||||||
|
lazy relationships -- then the relation won't be set up until the
|
||||||
|
class_prepared signal fires at the end of model initialization.
|
||||||
|
"""
|
||||||
|
# Check for recursive relations
|
||||||
|
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||||
|
app_label = cls._meta.app_label
|
||||||
|
model_name = cls.__name__
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Look for an "app.Model" relation
|
||||||
|
try:
|
||||||
|
app_label, model_name = relation.split(".")
|
||||||
|
except ValueError:
|
||||||
|
# If we can't split, assume a model in current app
|
||||||
|
app_label = cls._meta.app_label
|
||||||
|
model_name = relation
|
||||||
|
|
||||||
|
# Try to look up the related model, and if it's already loaded resolve the
|
||||||
|
# string right away. If get_model returns None, it means that the related
|
||||||
|
# model isn't loaded yet, so we need to pend the relation until the class
|
||||||
|
# is prepared.
|
||||||
|
model = get_model(app_label, model_name, False)
|
||||||
if model:
|
if model:
|
||||||
field.rel.to = model
|
field.rel.to = model
|
||||||
field.do_related_class(model, rel_cls)
|
field.do_related_class(model, cls)
|
||||||
else:
|
else:
|
||||||
# Mark the related field for later lookup
|
key = (app_label, model_name)
|
||||||
pending_lookups.setdefault(key, []).append((rel_cls, field))
|
value = (cls, field)
|
||||||
|
pending_lookups.setdefault(key, []).append(value)
|
||||||
|
|
||||||
def do_pending_lookups(sender):
|
def do_pending_lookups(sender):
|
||||||
other_cls = sender
|
"""
|
||||||
key = (other_cls.__module__, other_cls.__name__)
|
Handle any pending relations to the sending model. Sent from class_prepared.
|
||||||
for rel_cls, field in pending_lookups.setdefault(key, []):
|
"""
|
||||||
field.rel.to = other_cls
|
key = (sender._meta.app_label, sender.__name__)
|
||||||
field.do_related_class(other_cls, rel_cls)
|
for cls, field in pending_lookups.pop(key, []):
|
||||||
|
field.rel.to = sender
|
||||||
|
field.do_related_class(sender, cls)
|
||||||
|
|
||||||
dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
|
dispatcher.connect(do_pending_lookups, signal=signals.class_prepared)
|
||||||
|
|
||||||
|
@ -66,9 +104,7 @@ class RelatedField(object):
|
||||||
sup.contribute_to_class(cls, name)
|
sup.contribute_to_class(cls, name)
|
||||||
other = self.rel.to
|
other = self.rel.to
|
||||||
if isinstance(other, basestring):
|
if isinstance(other, basestring):
|
||||||
if other == RECURSIVE_RELATIONSHIP_CONSTANT:
|
add_lazy_relation(cls, self, other)
|
||||||
self.rel.to = cls.__name__
|
|
||||||
add_lookup(cls, self)
|
|
||||||
else:
|
else:
|
||||||
self.do_related_class(other, cls)
|
self.do_related_class(other, cls)
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
"""
|
"""
|
||||||
24. Mutually referential many-to-one relationships
|
24. Mutually referential many-to-one relationships
|
||||||
|
|
||||||
To define a many-to-one relationship, use ``ForeignKey()`` .
|
Strings can be used instead of model literals to set up "lazy" relations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db.models import *
|
from django.db.models import *
|
||||||
|
|
||||||
class Parent(Model):
|
class Parent(Model):
|
||||||
name = CharField(max_length=100, core=True)
|
name = CharField(max_length=100, core=True)
|
||||||
|
|
||||||
|
# Use a simple string for forward declarations.
|
||||||
bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
|
bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
|
||||||
|
|
||||||
class Child(Model):
|
class Child(Model):
|
||||||
name = CharField(max_length=100)
|
name = CharField(max_length=100)
|
||||||
parent = ForeignKey(Parent)
|
|
||||||
|
# You can also explicitally specify the related app.
|
||||||
|
parent = ForeignKey("mutually_referential.Parent")
|
||||||
|
|
||||||
__test__ = {'API_TESTS':"""
|
__test__ = {'API_TESTS':"""
|
||||||
# Create a Parent
|
# Create a Parent
|
||||||
|
|
Loading…
Reference in New Issue