Andrew Godwin avatar Andrew Godwin committed e1ac69a

Fix bug with new ORMness and new long-style field references.

Comments (0)

Files changed (3)

south/modelsinspector.py

 """
 
 import modelsparser
+from south.utils import get_attribute
 
 from django.db import models
 from django.db.models.base import ModelBase
     """
 
 
-def get_attribute(item, attribute):
-    """
-    Like getattr, but recursive (i.e. you can ask for 'foo.bar.yay'.)
-    """
-    value = item
-    for part in attribute.split("."):
-        value = getattr(value, part)
-    return value
-
-
 def get_value(field, descriptor):
     """
     Gets an attribute value from a Field instance and formats it.
 from django.db.models.loading import cache
 
 from south.db import db
+from south.utils import ask_for_it_by_name
 
 
 class ModelsLocals(object):
             return model
     
     
-    def eval_in_context(self, code, app):
+    def eval_in_context(self, code, app, extra_imports={}):
         "Evaluates the given code in the context of the migration file."
         
         # Drag in the migration module's locals (hopefully including models.py)
         # Datetime; there should be no datetime direct accesses
         fake_locals['datetime'] = datetime
         
+        # Now, go through the requested imports and import them.
+        for name, value in extra_imports.items():
+            # First, try getting it out of locals.
+            parts = value.split(".")
+            try:
+                obj = fake_locals[parts[0]]
+                for part in parts[1:]:
+                    obj = getattr(obj, part)
+            except (KeyError, AttributeError):
+                pass
+            else:
+                fake_locals[name] = obj
+                continue
+            # OK, try to import it directly
+            try:
+                fake_locals[name] = ask_for_it_by_name(value)
+            except ImportError:
+                print "WARNING: Cannot import '%s'" % value
+        
         # Use ModelsLocals to make lookups work right for CapitalisedModels
         fake_locals = ModelsLocals(fake_locals)
         
         
         # Now, make some fields!
         for fname, params in data.items():
+            # If it's the stub marker, ignore it.
             if fname == "_stub":
                 stub = bool(params)
                 continue
             elif isinstance(params, (str, unicode)):
                 # It's a premade definition string! Let's hope it works...
                 code = params
-            elif len(params) == 1:
-                code = "%s()" % params[0]
-            elif len(params) == 3:
-                code = "%s(%s)" % (
-                    params[0],
-                    ", ".join(
+                extra_imports = {}
+            else:
+                # If there's only one parameter (backwards compat), make it 3.
+                if len(params) == 1:
+                    params = (params[0], [], {})
+                # There should be 3 parameters. Code is a tuple of (code, what-to-import)
+                if len(params) == 3:
+                    code = "SouthFieldClass(%s)" % ", ".join(
                         params[1] +
                         ["%s=%s" % (n, v) for n, v in params[2].items()]
-                    ),
-                )
-            else:
-                raise ValueError("Field '%s' on model '%s.%s' has a weird definition length (should be 1 or 3 items)." % (fname, app, name))
+                    )
+                    extra_imports = {"SouthFieldClass": params[0]}
+                else:
+                    raise ValueError("Field '%s' on model '%s.%s' has a weird definition length (should be 1 or 3 items)." % (fname, app, name))
             
             try:
-                field = self.eval_in_context(code, app)
+                # Execute it in a probably-correct context.
+                field = self.eval_in_context(code, app, extra_imports)
             except (NameError, AttributeError, AssertionError, KeyError):
                 # It might rely on other models being around. Add it to the
                 # model for the second pass.
-                failed_fields[fname] = code
+                failed_fields[fname] = (code, extra_imports)
             else:
                 fields[fname] = field
         
         for modelkey, model in self.models.items():
             app, modelname = modelkey.split(".", 1)
             if hasattr(model, "_failed_fields"):
-                for fname, code in model._failed_fields.items():
+                for fname, (code, extra_imports) in model._failed_fields.items():
                     try:
-                        field = self.eval_in_context(code, app)
+                        field = self.eval_in_context(code, app, extra_imports)
                     except (NameError, AttributeError, AssertionError, KeyError), e:
                         # It's failed again. Complain.
                         raise ValueError("Cannot successfully create field '%s' for model '%s': %s." % (
         return getattr(self.real, name)
 
 
-def ask_for_it_by_name(name):
-    "Returns an object referenced by absolute path."
-    bits = name.split(".")
-    modulename = ".".join(bits[:-1])
-    module = __import__(modulename, {}, {}, bits[-1])
-    return getattr(module, bits[-1])
-
-
 def whiny_method(*a, **kw):
     raise ValueError("You cannot instantiate a stub model.")
+"""
+Generally helpful utility functions.
+"""
+
+
+def ask_for_it_by_name(name):
+    "Returns an object referenced by absolute path."
+    bits = name.split(".")
+    modulename = ".".join(bits[:-1])
+    module = __import__(modulename, {}, {}, bits[-1])
+    return getattr(module, bits[-1])
+
+
+def get_attribute(item, attribute):
+    """
+    Like getattr, but recursive (i.e. you can ask for 'foo.bar.yay'.)
+    """
+    value = item
+    for part in attribute.split("."):
+        value = getattr(value, part)
+    return value
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.