Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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.