Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Beefed up support for "lazy" related objects. Now, in addition to For…

…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...
commit df5fef33c965ba324563c4a8a1faec82ccfbe98c 1 parent 297a12c
@jacobian jacobian authored
View
74 django/db/models/fields/related.py
@@ -23,26 +23,64 @@
pending_lookups = {}
-def add_lookup(rel_cls, field):
- name = field.rel.to
- module = rel_cls.__module__
- key = (module, name)
- # Has the model already been loaded?
- # If so, resolve the string reference right away
- model = get_model(rel_cls._meta.app_label, field.rel.to, False)
+def add_lazy_relation(cls, field, relation):
+ """
+ Adds a lookup on ``cls`` when a related field is defined using a string,
+ i.e.::
+
+ class MyModel(Model):
+ 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:
field.rel.to = model
- field.do_related_class(model, rel_cls)
+ field.do_related_class(model, cls)
else:
- # Mark the related field for later lookup
- pending_lookups.setdefault(key, []).append((rel_cls, field))
-
+ key = (app_label, model_name)
+ value = (cls, field)
+ pending_lookups.setdefault(key, []).append(value)
+
def do_pending_lookups(sender):
- other_cls = sender
- key = (other_cls.__module__, other_cls.__name__)
- for rel_cls, field in pending_lookups.setdefault(key, []):
- field.rel.to = other_cls
- field.do_related_class(other_cls, rel_cls)
+ """
+ Handle any pending relations to the sending model. Sent from class_prepared.
+ """
+ key = (sender._meta.app_label, sender.__name__)
+ 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)
@@ -66,9 +104,7 @@ def contribute_to_class(self, cls, name):
sup.contribute_to_class(cls, name)
other = self.rel.to
if isinstance(other, basestring):
- if other == RECURSIVE_RELATIONSHIP_CONSTANT:
- self.rel.to = cls.__name__
- add_lookup(cls, self)
+ add_lazy_relation(cls, self, other)
else:
self.do_related_class(other, cls)
View
8 tests/modeltests/mutually_referential/models.py
@@ -1,18 +1,22 @@
"""
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 *
class Parent(Model):
name = CharField(max_length=100, core=True)
+
+ # Use a simple string for forward declarations.
bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
class Child(Model):
name = CharField(max_length=100)
- parent = ForeignKey(Parent)
+
+ # You can also explicitally specify the related app.
+ parent = ForeignKey("mutually_referential.Parent")
__test__ = {'API_TESTS':"""
# Create a Parent
Please sign in to comment.
Something went wrong with that request. Please try again.