Commit df5fef33 authored by Jacob Kaplan-Moss's avatar Jacob Kaplan-Moss
Browse files

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

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
parent 297a12c2
Loading
Loading
Loading
Loading
+55 −19
Original line number Diff line number Diff line
@@ -23,26 +23,64 @@ RECURSIVE_RELATIONSHIP_CONSTANT = 'self'

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 @@ class RelatedField(object):
            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)

+6 −2
Original line number Diff line number Diff line
"""
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