south.orm: 212 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/south/orm.py

Stats: 0 executed, 200 missed, 12 excluded, 188 ignored

  1. """
  2. South's fake ORM; lets you not have to write SQL inside migrations.
  3. Roughly emulates the real Django ORM, to a point.
  4. """
  5. import inspect
  6. from django.db import models
  7. from django.db.models.loading import cache
  8. from django.core.exceptions import ImproperlyConfigured
  9. from south.db import db
  10. from south.utils import ask_for_it_by_name, datetime_utils
  11. from south.hacks import hacks
  12. from south.exceptions import UnfreezeMeLater, ORMBaseNotIncluded, ImpossibleORMUnfreeze
  13. class ModelsLocals(object):
  14. """
  15. Custom dictionary-like class to be locals();
  16. falls back to lowercase search for items that don't exist
  17. (because we store model names as lowercase).
  18. """
  19. def __init__(self, data):
  20. self.data = data
  21. def __getitem__(self, key):
  22. try:
  23. return self.data[key]
  24. except KeyError:
  25. return self.data[key.lower()]
  26. # Stores already-created ORMs.
  27. _orm_cache = {}
  28. def FakeORM(*args):
  29. """
  30. Creates a Fake Django ORM.
  31. This is actually a memoised constructor; the real class is _FakeORM.
  32. """
  33. if not args in _orm_cache:
  34. _orm_cache[args] = _FakeORM(*args)
  35. return _orm_cache[args]
  36. class LazyFakeORM(object):
  37. """
  38. In addition to memoising the ORM call, this function lazily generates them
  39. for a Migration class. Assign the result of this to (for example)
  40. .orm, and as soon as .orm is accessed the ORM will be created.
  41. """
  42. def __init__(self, *args):
  43. self._args = args
  44. self.orm = None
  45. def __get__(self, obj, type=None):
  46. if not self.orm:
  47. self.orm = FakeORM(*self._args)
  48. return self.orm
  49. class _FakeORM(object):
  50. """
  51. Simulates the Django ORM at some point in time,
  52. using a frozen definition on the Migration class.
  53. """
  54. def __init__(self, cls, app):
  55. self.default_app = app
  56. self.cls = cls
  57. # Try loading the models off the migration class; default to no models.
  58. self.models = {}
  59. try:
  60. self.models_source = cls.models
  61. except AttributeError:
  62. return
  63. # Start a 'new' AppCache
  64. hacks.clear_app_cache()
  65. # Now, make each model's data into a FakeModel
  66. # We first make entries for each model that are just its name
  67. # This allows us to have circular model dependency loops
  68. model_names = []
  69. for name, data in self.models_source.items():
  70. # Make sure there's some kind of Meta
  71. if "Meta" not in data:
  72. data['Meta'] = {}
  73. try:
  74. app_label, model_name = name.split(".", 1)
  75. except ValueError:
  76. app_label = self.default_app
  77. model_name = name
  78. # If there's an object_name in the Meta, use it and remove it
  79. if "object_name" in data['Meta']:
  80. model_name = data['Meta']['object_name']
  81. del data['Meta']['object_name']
  82. name = "%s.%s" % (app_label, model_name)
  83. self.models[name.lower()] = name
  84. model_names.append((name.lower(), app_label, model_name, data))
  85. # Loop until model_names is entry, or hasn't shrunk in size since
  86. # last iteration.
  87. # The make_model method can ask to postpone a model; it's then pushed
  88. # to the back of the queue. Because this is currently only used for
  89. # inheritance, it should thus theoretically always decrease by one.
  90. last_size = None
  91. while model_names:
  92. # First, make sure we've shrunk.
  93. if len(model_names) == last_size:
  94. raise ImpossibleORMUnfreeze()
  95. last_size = len(model_names)
  96. # Make one run through
  97. postponed_model_names = []
  98. for name, app_label, model_name, data in model_names:
  99. try:
  100. self.models[name] = self.make_model(app_label, model_name, data)
  101. except UnfreezeMeLater:
  102. postponed_model_names.append((name, app_label, model_name, data))
  103. # Reset
  104. model_names = postponed_model_names
  105. # And perform the second run to iron out any circular/backwards depends.
  106. self.retry_failed_fields()
  107. # Force evaluation of relations on the models now
  108. for model in self.models.values():
  109. model._meta.get_all_field_names()
  110. # Reset AppCache
  111. hacks.unclear_app_cache()
  112. def __iter__(self):
  113. return iter(self.models.values())
  114. def __getattr__(self, key):
  115. fullname = (self.default_app+"."+key).lower()
  116. try:
  117. return self.models[fullname]
  118. except KeyError:
  119. raise AttributeError("The model '%s' from the app '%s' is not available in this migration. (Did you use orm.ModelName, not orm['app.ModelName']?)" % (key, self.default_app))
  120. def __getitem__(self, key):
  121. # Detect if they asked for a field on a model or not.
  122. if ":" in key:
  123. key, fname = key.split(":")
  124. else:
  125. fname = None
  126. # Now, try getting the model
  127. key = key.lower()
  128. try:
  129. model = self.models[key]
  130. except KeyError:
  131. try:
  132. app, model = key.split(".", 1)
  133. except ValueError:
  134. raise KeyError("The model '%s' is not in appname.modelname format." % key)
  135. else:
  136. raise KeyError("The model '%s' from the app '%s' is not available in this migration." % (model, app))
  137. # If they asked for a field, get it.
  138. if fname:
  139. return model._meta.get_field_by_name(fname)[0]
  140. else:
  141. return model
  142. def eval_in_context(self, code, app, extra_imports={}):
  143. "Evaluates the given code in the context of the migration file."
  144. # Drag in the migration module's locals (hopefully including models.py)
  145. fake_locals = dict(inspect.getmodule(self.cls).__dict__)
  146. # Remove all models from that (i.e. from modern models.py), to stop pollution
  147. for key, value in fake_locals.items():
  148. if isinstance(value, type) and issubclass(value, models.Model) and hasattr(value, "_meta"):
  149. del fake_locals[key]
  150. # We add our models into the locals for the eval
  151. fake_locals.update(dict([
  152. (name.split(".")[-1], model)
  153. for name, model in self.models.items()
  154. ]))
  155. # Make sure the ones for this app override.
  156. fake_locals.update(dict([
  157. (name.split(".")[-1], model)
  158. for name, model in self.models.items()
  159. if name.split(".")[0] == app
  160. ]))
  161. # Ourselves as orm, to allow non-fail cross-app referencing
  162. fake_locals['orm'] = self
  163. # And a fake _ function
  164. fake_locals['_'] = lambda x: x
  165. # Datetime; there should be no datetime direct accesses
  166. fake_locals['datetime'] = datetime_utils
  167. # Now, go through the requested imports and import them.
  168. for name, value in extra_imports.items():
  169. # First, try getting it out of locals.
  170. parts = value.split(".")
  171. try:
  172. obj = fake_locals[parts[0]]
  173. for part in parts[1:]:
  174. obj = getattr(obj, part)
  175. except (KeyError, AttributeError):
  176. pass
  177. else:
  178. fake_locals[name] = obj
  179. continue
  180. # OK, try to import it directly
  181. try:
  182. fake_locals[name] = ask_for_it_by_name(value)
  183. except ImportError:
  184. if name == "SouthFieldClass":
  185. raise ValueError("Cannot import the required field '%s'" % value)
  186. else:
  187. print "WARNING: Cannot import '%s'" % value
  188. # Use ModelsLocals to make lookups work right for CapitalisedModels
  189. fake_locals = ModelsLocals(fake_locals)
  190. return eval(code, globals(), fake_locals)
  191. def make_meta(self, app, model, data, stub=False):
  192. "Makes a Meta class out of a dict of eval-able arguments."
  193. results = {'app_label': app}
  194. for key, code in data.items():
  195. # Some things we never want to use.
  196. if key in ["_bases", "_ormbases"]:
  197. continue
  198. # Some things we don't want with stubs.
  199. if stub and key in ["order_with_respect_to"]:
  200. continue
  201. # OK, add it.
  202. try:
  203. results[key] = self.eval_in_context(code, app)
  204. except (NameError, AttributeError), e:
  205. raise ValueError("Cannot successfully create meta field '%s' for model '%s.%s': %s." % (
  206. key, app, model, e
  207. ))
  208. return type("Meta", tuple(), results)
  209. def make_model(self, app, name, data):
  210. "Makes a Model class out of the given app name, model name and pickled data."
  211. # Extract any bases out of Meta
  212. if "_ormbases" in data['Meta']:
  213. # Make sure everything we depend on is done already; otherwise, wait.
  214. for key in data['Meta']['_ormbases']:
  215. key = key.lower()
  216. if key not in self.models:
  217. raise ORMBaseNotIncluded("Cannot find ORM base %s" % key)
  218. elif isinstance(self.models[key], basestring):
  219. # Then the other model hasn't been unfrozen yet.
  220. # We postpone ourselves; the situation will eventually resolve.
  221. raise UnfreezeMeLater()
  222. bases = [self.models[key.lower()] for key in data['Meta']['_ormbases']]
  223. # Perhaps the old style?
  224. elif "_bases" in data['Meta']:
  225. bases = map(ask_for_it_by_name, data['Meta']['_bases'])
  226. # Ah, bog standard, then.
  227. else:
  228. bases = [models.Model]
  229. # Turn the Meta dict into a basic class
  230. meta = self.make_meta(app, name, data['Meta'], data.get("_stub", False))
  231. failed_fields = {}
  232. fields = {}
  233. stub = False
  234. # Now, make some fields!
  235. for fname, params in data.items():
  236. # If it's the stub marker, ignore it.
  237. if fname == "_stub":
  238. stub = bool(params)
  239. continue
  240. elif fname == "Meta":
  241. continue
  242. elif not params:
  243. raise ValueError("Field '%s' on model '%s.%s' has no definition." % (fname, app, name))
  244. elif isinstance(params, (str, unicode)):
  245. # It's a premade definition string! Let's hope it works...
  246. code = params
  247. extra_imports = {}
  248. else:
  249. # If there's only one parameter (backwards compat), make it 3.
  250. if len(params) == 1:
  251. params = (params[0], [], {})
  252. # There should be 3 parameters. Code is a tuple of (code, what-to-import)
  253. if len(params) == 3:
  254. code = "SouthFieldClass(%s)" % ", ".join(
  255. params[1] +
  256. ["%s=%s" % (n, v) for n, v in params[2].items()]
  257. )
  258. extra_imports = {"SouthFieldClass": params[0]}
  259. else:
  260. raise ValueError("Field '%s' on model '%s.%s' has a weird definition length (should be 1 or 3 items)." % (fname, app, name))
  261. try:
  262. # Execute it in a probably-correct context.
  263. field = self.eval_in_context(code, app, extra_imports)
  264. except (NameError, AttributeError, AssertionError, KeyError):
  265. # It might rely on other models being around. Add it to the
  266. # model for the second pass.
  267. failed_fields[fname] = (code, extra_imports)
  268. else:
  269. fields[fname] = field
  270. # Find the app in the Django core, and get its module
  271. more_kwds = {}
  272. try:
  273. app_module = models.get_app(app)
  274. more_kwds['__module__'] = app_module.__name__
  275. except ImproperlyConfigured:
  276. # The app this belonged to has vanished, but thankfully we can still
  277. # make a mock model, so ignore the error.
  278. more_kwds['__module__'] = '_south_mock'
  279. more_kwds['Meta'] = meta
  280. # Make our model
  281. fields.update(more_kwds)
  282. model = type(
  283. str(name),
  284. tuple(bases),
  285. fields,
  286. )
  287. # If this is a stub model, change Objects to a whiny class
  288. if stub:
  289. model.objects = WhinyManager()
  290. # Also, make sure they can't instantiate it
  291. model.__init__ = whiny_method
  292. else:
  293. model.objects = NoDryRunManager(model.objects)
  294. if failed_fields:
  295. model._failed_fields = failed_fields
  296. return model
  297. def retry_failed_fields(self):
  298. "Tries to re-evaluate the _failed_fields for each model."
  299. for modelkey, model in self.models.items():
  300. app, modelname = modelkey.split(".", 1)
  301. if hasattr(model, "_failed_fields"):
  302. for fname, (code, extra_imports) in model._failed_fields.items():
  303. try:
  304. field = self.eval_in_context(code, app, extra_imports)
  305. except (NameError, AttributeError, AssertionError, KeyError), e:
  306. # It's failed again. Complain.
  307. raise ValueError("Cannot successfully create field '%s' for model '%s': %s." % (
  308. fname, modelname, e
  309. ))
  310. else:
  311. # Startup that field.
  312. model.add_to_class(fname, field)
  313. class WhinyManager(object):
  314. "A fake manager that whines whenever you try to touch it. For stub models."
  315. def __getattr__(self, key):
  316. raise AttributeError("You cannot use items from a stub model.")
  317. class NoDryRunManager(object):
  318. """
  319. A manager that always proxies through to the real manager,
  320. unless a dry run is in progress.
  321. """
  322. def __init__(self, real):
  323. self.real = real
  324. def __getattr__(self, name):
  325. if db.dry_run:
  326. raise AttributeError("You are in a dry run, and cannot access the ORM.\nWrap ORM sections in 'if not db.dry_run:', or if the whole migration is only a data migration, set no_dry_run = True on the Migration class.")
  327. return getattr(self.real, name)
  328. def whiny_method(*a, **kw):
  329. raise ValueError("You cannot instantiate a stub model.")