Skip to content

Commit

Permalink
Beefed up support for "lazy" related objects. Now, in addition to For…
Browse files Browse the repository at this point in the history
…eignKey("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
  • Loading branch information
jacobian committed Feb 26, 2008
1 parent 297a12c commit df5fef3
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 21 deletions.
74 changes: 55 additions & 19 deletions django/db/models/fields/related.py
Expand Up @@ -23,26 +23,64 @@


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)


Expand All @@ -66,9 +104,7 @@ def contribute_to_class(self, cls, name):
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)


Expand Down
8 changes: 6 additions & 2 deletions tests/modeltests/mutually_referential/models.py
@@ -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
Expand Down

0 comments on commit df5fef3

Please sign in to comment.