You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

611 lines
25 KiB

  1. from __future__ import unicode_literals
  2. import copy
  3. from collections import OrderedDict
  4. from contextlib import contextmanager
  5. from django.apps import AppConfig
  6. from django.apps.registry import Apps, apps as global_apps
  7. from django.conf import settings
  8. from django.db import models
  9. from django.db.models.fields.proxy import OrderWrt
  10. from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
  11. from django.db.models.options import DEFAULT_NAMES, normalize_together
  12. from django.db.models.utils import make_model_tuple
  13. from django.utils import six
  14. from django.utils.encoding import force_text, smart_text
  15. from django.utils.functional import cached_property
  16. from django.utils.module_loading import import_string
  17. from django.utils.version import get_docs_version
  18. from .exceptions import InvalidBasesError
  19. def _get_app_label_and_model_name(model, app_label=''):
  20. if isinstance(model, six.string_types):
  21. split = model.split('.', 1)
  22. return (tuple(split) if len(split) == 2 else (app_label, split[0]))
  23. else:
  24. return model._meta.app_label, model._meta.model_name
  25. def get_related_models_recursive(model):
  26. """
  27. Returns all models that have a direct or indirect relationship
  28. to the given model.
  29. Relationships are either defined by explicit relational fields, like
  30. ForeignKey, ManyToManyField or OneToOneField, or by inheriting from another
  31. model (a superclass is related to its subclasses, but not vice versa). Note,
  32. however, that a model inheriting from a concrete model is also related to
  33. its superclass through the implicit *_ptr OneToOneField on the subclass.
  34. """
  35. def _related_models(m):
  36. return [
  37. f.related_model for f in m._meta.get_fields(include_parents=True, include_hidden=True)
  38. if f.is_relation and f.related_model is not None and not isinstance(f.related_model, six.string_types)
  39. ] + [
  40. subclass for subclass in m.__subclasses__()
  41. if issubclass(subclass, models.Model)
  42. ]
  43. seen = set()
  44. queue = _related_models(model)
  45. for rel_mod in queue:
  46. rel_app_label, rel_model_name = rel_mod._meta.app_label, rel_mod._meta.model_name
  47. if (rel_app_label, rel_model_name) in seen:
  48. continue
  49. seen.add((rel_app_label, rel_model_name))
  50. queue.extend(_related_models(rel_mod))
  51. return seen - {(model._meta.app_label, model._meta.model_name)}
  52. class ProjectState(object):
  53. """
  54. Represents the entire project's overall state.
  55. This is the item that is passed around - we do it here rather than at the
  56. app level so that cross-app FKs/etc. resolve properly.
  57. """
  58. def __init__(self, models=None, real_apps=None):
  59. self.models = models or {}
  60. # Apps to include from main registry, usually unmigrated ones
  61. self.real_apps = real_apps or []
  62. def add_model(self, model_state):
  63. app_label, model_name = model_state.app_label, model_state.name_lower
  64. self.models[(app_label, model_name)] = model_state
  65. if 'apps' in self.__dict__: # hasattr would cache the property
  66. self.reload_model(app_label, model_name)
  67. def remove_model(self, app_label, model_name):
  68. del self.models[app_label, model_name]
  69. if 'apps' in self.__dict__: # hasattr would cache the property
  70. self.apps.unregister_model(app_label, model_name)
  71. # Need to do this explicitly since unregister_model() doesn't clear
  72. # the cache automatically (#24513)
  73. self.apps.clear_cache()
  74. def reload_model(self, app_label, model_name):
  75. if 'apps' in self.__dict__: # hasattr would cache the property
  76. try:
  77. old_model = self.apps.get_model(app_label, model_name)
  78. except LookupError:
  79. related_models = set()
  80. else:
  81. # Get all relations to and from the old model before reloading,
  82. # as _meta.apps may change
  83. related_models = get_related_models_recursive(old_model)
  84. # Get all outgoing references from the model to be rendered
  85. model_state = self.models[(app_label, model_name)]
  86. # Directly related models are the models pointed to by ForeignKeys,
  87. # OneToOneFields, and ManyToManyFields.
  88. direct_related_models = set()
  89. for name, field in model_state.fields:
  90. if field.is_relation:
  91. if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT:
  92. continue
  93. rel_app_label, rel_model_name = _get_app_label_and_model_name(field.related_model, app_label)
  94. direct_related_models.add((rel_app_label, rel_model_name.lower()))
  95. # For all direct related models recursively get all related models.
  96. related_models.update(direct_related_models)
  97. for rel_app_label, rel_model_name in direct_related_models:
  98. try:
  99. rel_model = self.apps.get_model(rel_app_label, rel_model_name)
  100. except LookupError:
  101. pass
  102. else:
  103. related_models.update(get_related_models_recursive(rel_model))
  104. # Include the model itself
  105. related_models.add((app_label, model_name))
  106. # Unregister all related models
  107. with self.apps.bulk_update():
  108. for rel_app_label, rel_model_name in related_models:
  109. self.apps.unregister_model(rel_app_label, rel_model_name)
  110. states_to_be_rendered = []
  111. # Gather all models states of those models that will be rerendered.
  112. # This includes:
  113. # 1. All related models of unmigrated apps
  114. for model_state in self.apps.real_models:
  115. if (model_state.app_label, model_state.name_lower) in related_models:
  116. states_to_be_rendered.append(model_state)
  117. # 2. All related models of migrated apps
  118. for rel_app_label, rel_model_name in related_models:
  119. try:
  120. model_state = self.models[rel_app_label, rel_model_name]
  121. except KeyError:
  122. pass
  123. else:
  124. states_to_be_rendered.append(model_state)
  125. # Render all models
  126. self.apps.render_multiple(states_to_be_rendered)
  127. def clone(self):
  128. "Returns an exact copy of this ProjectState"
  129. new_state = ProjectState(
  130. models={k: v.clone() for k, v in self.models.items()},
  131. real_apps=self.real_apps,
  132. )
  133. if 'apps' in self.__dict__:
  134. new_state.apps = self.apps.clone()
  135. return new_state
  136. @cached_property
  137. def apps(self):
  138. return StateApps(self.real_apps, self.models)
  139. @property
  140. def concrete_apps(self):
  141. self.apps = StateApps(self.real_apps, self.models, ignore_swappable=True)
  142. return self.apps
  143. @classmethod
  144. def from_apps(cls, apps):
  145. "Takes in an Apps and returns a ProjectState matching it"
  146. app_models = {}
  147. for model in apps.get_models(include_swapped=True):
  148. model_state = ModelState.from_model(model)
  149. app_models[(model_state.app_label, model_state.name_lower)] = model_state
  150. return cls(app_models)
  151. def __eq__(self, other):
  152. if set(self.models.keys()) != set(other.models.keys()):
  153. return False
  154. if set(self.real_apps) != set(other.real_apps):
  155. return False
  156. return all(model == other.models[key] for key, model in self.models.items())
  157. def __ne__(self, other):
  158. return not (self == other)
  159. class AppConfigStub(AppConfig):
  160. """
  161. Stubs a Django AppConfig. Only provides a label, and a dict of models.
  162. """
  163. # Not used, but required by AppConfig.__init__
  164. path = ''
  165. def __init__(self, label):
  166. self.label = label
  167. # App-label and app-name are not the same thing, so technically passing
  168. # in the label here is wrong. In practice, migrations don't care about
  169. # the app name, but we need something unique, and the label works fine.
  170. super(AppConfigStub, self).__init__(label, None)
  171. def import_models(self, all_models):
  172. self.models = all_models
  173. class StateApps(Apps):
  174. """
  175. Subclass of the global Apps registry class to better handle dynamic model
  176. additions and removals.
  177. """
  178. def __init__(self, real_apps, models, ignore_swappable=False):
  179. # Any apps in self.real_apps should have all their models included
  180. # in the render. We don't use the original model instances as there
  181. # are some variables that refer to the Apps object.
  182. # FKs/M2Ms from real apps are also not included as they just
  183. # mess things up with partial states (due to lack of dependencies)
  184. self.real_models = []
  185. for app_label in real_apps:
  186. app = global_apps.get_app_config(app_label)
  187. for model in app.get_models():
  188. self.real_models.append(ModelState.from_model(model, exclude_rels=True))
  189. # Populate the app registry with a stub for each application.
  190. app_labels = {model_state.app_label for model_state in models.values()}
  191. app_configs = [AppConfigStub(label) for label in sorted(real_apps + list(app_labels))]
  192. super(StateApps, self).__init__(app_configs)
  193. self.render_multiple(list(models.values()) + self.real_models)
  194. # There shouldn't be any operations pending at this point.
  195. pending_models = set(self._pending_operations)
  196. if ignore_swappable:
  197. pending_models -= {make_model_tuple(settings.AUTH_USER_MODEL)}
  198. if pending_models:
  199. raise ValueError(self._pending_models_error(pending_models))
  200. def _pending_models_error(self, pending_models):
  201. """
  202. Almost all internal uses of lazy operations are to resolve string model
  203. references in related fields. We can extract the fields from those
  204. operations and use them to provide a nicer error message.
  205. This will work for any function passed to lazy_related_operation() that
  206. has a keyword argument called 'field'.
  207. """
  208. def extract_field(operation):
  209. # operation is annotated with the field in
  210. # apps.registry.Apps.lazy_model_operation().
  211. return getattr(operation, 'field', None)
  212. def extract_field_names(operations):
  213. return (str(field) for field in map(extract_field, operations) if field)
  214. get_ops = self._pending_operations.__getitem__
  215. # Ordered list of pairs of the form
  216. # ((app_label, model_name), [field_name_1, field_name_2, ...])
  217. models_fields = sorted(
  218. (model_key, sorted(extract_field_names(get_ops(model_key))))
  219. for model_key in pending_models
  220. )
  221. def model_text(model_key, fields):
  222. field_list = ", ".join(fields)
  223. field_text = " (referred to by fields: %s)" % field_list if fields else ""
  224. return ("%s.%s" % model_key) + field_text
  225. msg = "Unhandled pending operations for models:"
  226. return "\n ".join([msg] + [model_text(*i) for i in models_fields])
  227. @contextmanager
  228. def bulk_update(self):
  229. # Avoid clearing each model's cache for each change. Instead, clear
  230. # all caches when we're finished updating the model instances.
  231. ready = self.ready
  232. self.ready = False
  233. try:
  234. yield
  235. finally:
  236. self.ready = ready
  237. self.clear_cache()
  238. def render_multiple(self, model_states):
  239. # We keep trying to render the models in a loop, ignoring invalid
  240. # base errors, until the size of the unrendered models doesn't
  241. # decrease by at least one, meaning there's a base dependency loop/
  242. # missing base.
  243. if not model_states:
  244. return
  245. # Prevent that all model caches are expired for each render.
  246. with self.bulk_update():
  247. unrendered_models = model_states
  248. while unrendered_models:
  249. new_unrendered_models = []
  250. for model in unrendered_models:
  251. try:
  252. model.render(self)
  253. except InvalidBasesError:
  254. new_unrendered_models.append(model)
  255. if len(new_unrendered_models) == len(unrendered_models):
  256. raise InvalidBasesError(
  257. "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an "
  258. "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see "
  259. "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
  260. "for more" % (new_unrendered_models, get_docs_version())
  261. )
  262. unrendered_models = new_unrendered_models
  263. def clone(self):
  264. """
  265. Return a clone of this registry, mainly used by the migration framework.
  266. """
  267. clone = StateApps([], {})
  268. clone.all_models = copy.deepcopy(self.all_models)
  269. clone.app_configs = copy.deepcopy(self.app_configs)
  270. # No need to actually clone them, they'll never change
  271. clone.real_models = self.real_models
  272. return clone
  273. def register_model(self, app_label, model):
  274. self.all_models[app_label][model._meta.model_name] = model
  275. if app_label not in self.app_configs:
  276. self.app_configs[app_label] = AppConfigStub(app_label)
  277. self.app_configs[app_label].models = OrderedDict()
  278. self.app_configs[app_label].models[model._meta.model_name] = model
  279. self.do_pending_operations(model)
  280. self.clear_cache()
  281. def unregister_model(self, app_label, model_name):
  282. try:
  283. del self.all_models[app_label][model_name]
  284. del self.app_configs[app_label].models[model_name]
  285. except KeyError:
  286. pass
  287. class ModelState(object):
  288. """
  289. Represents a Django Model. We don't use the actual Model class
  290. as it's not designed to have its options changed - instead, we
  291. mutate this one and then render it into a Model as required.
  292. Note that while you are allowed to mutate .fields, you are not allowed
  293. to mutate the Field instances inside there themselves - you must instead
  294. assign new ones, as these are not detached during a clone.
  295. """
  296. def __init__(self, app_label, name, fields, options=None, bases=None, managers=None):
  297. self.app_label = app_label
  298. self.name = force_text(name)
  299. self.fields = fields
  300. self.options = options or {}
  301. self.bases = bases or (models.Model, )
  302. self.managers = managers or []
  303. # Sanity-check that fields is NOT a dict. It must be ordered.
  304. if isinstance(self.fields, dict):
  305. raise ValueError("ModelState.fields cannot be a dict - it must be a list of 2-tuples.")
  306. for name, field in fields:
  307. # Sanity-check that fields are NOT already bound to a model.
  308. if hasattr(field, 'model'):
  309. raise ValueError(
  310. 'ModelState.fields cannot be bound to a model - "%s" is.' % name
  311. )
  312. # Sanity-check that relation fields are NOT referring to a model class.
  313. if field.is_relation and hasattr(field.related_model, '_meta'):
  314. raise ValueError(
  315. 'ModelState.fields cannot refer to a model class - "%s.to" does. '
  316. 'Use a string reference instead.' % name
  317. )
  318. if field.many_to_many and hasattr(field.remote_field.through, '_meta'):
  319. raise ValueError(
  320. 'ModelState.fields cannot refer to a model class - "%s.through" does. '
  321. 'Use a string reference instead.' % name
  322. )
  323. @cached_property
  324. def name_lower(self):
  325. return self.name.lower()
  326. @classmethod
  327. def from_model(cls, model, exclude_rels=False):
  328. """
  329. Feed me a model, get a ModelState representing it out.
  330. """
  331. # Deconstruct the fields
  332. fields = []
  333. for field in model._meta.local_fields:
  334. if getattr(field, "remote_field", None) and exclude_rels:
  335. continue
  336. if isinstance(field, OrderWrt):
  337. continue
  338. name = force_text(field.name, strings_only=True)
  339. try:
  340. fields.append((name, field.clone()))
  341. except TypeError as e:
  342. raise TypeError("Couldn't reconstruct field %s on %s: %s" % (
  343. name,
  344. model._meta.label,
  345. e,
  346. ))
  347. if not exclude_rels:
  348. for field in model._meta.local_many_to_many:
  349. name = force_text(field.name, strings_only=True)
  350. try:
  351. fields.append((name, field.clone()))
  352. except TypeError as e:
  353. raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
  354. name,
  355. model._meta.object_name,
  356. e,
  357. ))
  358. # Extract the options
  359. options = {}
  360. for name in DEFAULT_NAMES:
  361. # Ignore some special options
  362. if name in ["apps", "app_label"]:
  363. continue
  364. elif name in model._meta.original_attrs:
  365. if name == "unique_together":
  366. ut = model._meta.original_attrs["unique_together"]
  367. options[name] = set(normalize_together(ut))
  368. elif name == "index_together":
  369. it = model._meta.original_attrs["index_together"]
  370. options[name] = set(normalize_together(it))
  371. else:
  372. options[name] = model._meta.original_attrs[name]
  373. # Force-convert all options to text_type (#23226)
  374. options = cls.force_text_recursive(options)
  375. # If we're ignoring relationships, remove all field-listing model
  376. # options (that option basically just means "make a stub model")
  377. if exclude_rels:
  378. for key in ["unique_together", "index_together", "order_with_respect_to"]:
  379. if key in options:
  380. del options[key]
  381. def flatten_bases(model):
  382. bases = []
  383. for base in model.__bases__:
  384. if hasattr(base, "_meta") and base._meta.abstract:
  385. bases.extend(flatten_bases(base))
  386. else:
  387. bases.append(base)
  388. return bases
  389. # We can't rely on __mro__ directly because we only want to flatten
  390. # abstract models and not the whole tree. However by recursing on
  391. # __bases__ we may end up with duplicates and ordering issues, we
  392. # therefore discard any duplicates and reorder the bases according
  393. # to their index in the MRO.
  394. flattened_bases = sorted(set(flatten_bases(model)), key=lambda x: model.__mro__.index(x))
  395. # Make our record
  396. bases = tuple(
  397. (
  398. base._meta.label_lower
  399. if hasattr(base, "_meta") else
  400. base
  401. )
  402. for base in flattened_bases
  403. )
  404. # Ensure at least one base inherits from models.Model
  405. if not any((isinstance(base, six.string_types) or issubclass(base, models.Model)) for base in bases):
  406. bases = (models.Model,)
  407. # Constructs all managers on the model
  408. managers_mapping = {}
  409. def reconstruct_manager(mgr):
  410. as_manager, manager_path, qs_path, args, kwargs = mgr.deconstruct()
  411. if as_manager:
  412. qs_class = import_string(qs_path)
  413. instance = qs_class.as_manager()
  414. else:
  415. manager_class = import_string(manager_path)
  416. instance = manager_class(*args, **kwargs)
  417. # We rely on the ordering of the creation_counter of the original
  418. # instance
  419. name = force_text(mgr.name)
  420. managers_mapping[name] = (mgr.creation_counter, instance)
  421. if hasattr(model, "_default_manager"):
  422. default_manager_name = force_text(model._default_manager.name)
  423. # Make sure the default manager is always the first
  424. if model._default_manager.use_in_migrations:
  425. reconstruct_manager(model._default_manager)
  426. else:
  427. # Force this manager to be the first and thus default
  428. managers_mapping[default_manager_name] = (0, models.Manager())
  429. # Sort all managers by their creation counter
  430. for _, manager, _ in sorted(model._meta.managers):
  431. if manager.name == "_base_manager" or not manager.use_in_migrations:
  432. continue
  433. reconstruct_manager(manager)
  434. # Sort all managers by their creation counter but take only name and
  435. # instance for further processing
  436. managers = [
  437. (name, instance) for name, (cc, instance) in
  438. sorted(managers_mapping.items(), key=lambda v: v[1])
  439. ]
  440. # If the only manager on the model is the default manager defined
  441. # by Django (`objects = models.Manager()`), this manager will not
  442. # be added to the model state.
  443. if managers == [('objects', models.Manager())]:
  444. managers = []
  445. else:
  446. managers = []
  447. # Construct the new ModelState
  448. return cls(
  449. model._meta.app_label,
  450. model._meta.object_name,
  451. fields,
  452. options,
  453. bases,
  454. managers,
  455. )
  456. @classmethod
  457. def force_text_recursive(cls, value):
  458. if isinstance(value, six.string_types):
  459. return smart_text(value)
  460. elif isinstance(value, list):
  461. return [cls.force_text_recursive(x) for x in value]
  462. elif isinstance(value, tuple):
  463. return tuple(cls.force_text_recursive(x) for x in value)
  464. elif isinstance(value, set):
  465. return set(cls.force_text_recursive(x) for x in value)
  466. elif isinstance(value, dict):
  467. return {
  468. cls.force_text_recursive(k): cls.force_text_recursive(v)
  469. for k, v in value.items()
  470. }
  471. return value
  472. def construct_managers(self):
  473. "Deep-clone the managers using deconstruction"
  474. # Sort all managers by their creation counter
  475. sorted_managers = sorted(self.managers, key=lambda v: v[1].creation_counter)
  476. for mgr_name, manager in sorted_managers:
  477. mgr_name = force_text(mgr_name)
  478. as_manager, manager_path, qs_path, args, kwargs = manager.deconstruct()
  479. if as_manager:
  480. qs_class = import_string(qs_path)
  481. yield mgr_name, qs_class.as_manager()
  482. else:
  483. manager_class = import_string(manager_path)
  484. yield mgr_name, manager_class(*args, **kwargs)
  485. def clone(self):
  486. "Returns an exact copy of this ModelState"
  487. return self.__class__(
  488. app_label=self.app_label,
  489. name=self.name,
  490. fields=list(self.fields),
  491. options=dict(self.options),
  492. bases=self.bases,
  493. managers=list(self.managers),
  494. )
  495. def render(self, apps):
  496. "Creates a Model object from our current state into the given apps"
  497. # First, make a Meta object
  498. meta_contents = {'app_label': self.app_label, "apps": apps}
  499. meta_contents.update(self.options)
  500. meta = type(str("Meta"), tuple(), meta_contents)
  501. # Then, work out our bases
  502. try:
  503. bases = tuple(
  504. (apps.get_model(base) if isinstance(base, six.string_types) else base)
  505. for base in self.bases
  506. )
  507. except LookupError:
  508. raise InvalidBasesError("Cannot resolve one or more bases from %r" % (self.bases,))
  509. # Turn fields into a dict for the body, add other bits
  510. body = {name: field.clone() for name, field in self.fields}
  511. body['Meta'] = meta
  512. body['__module__'] = "__fake__"
  513. # Restore managers
  514. body.update(self.construct_managers())
  515. # Then, make a Model object (apps.register_model is called in __new__)
  516. return type(
  517. str(self.name),
  518. bases,
  519. body,
  520. )
  521. def get_field_by_name(self, name):
  522. for fname, field in self.fields:
  523. if fname == name:
  524. return field
  525. raise ValueError("No field called %s on model %s" % (name, self.name))
  526. def __repr__(self):
  527. return "<ModelState: '%s.%s'>" % (self.app_label, self.name)
  528. def __eq__(self, other):
  529. return (
  530. (self.app_label == other.app_label) and
  531. (self.name == other.name) and
  532. (len(self.fields) == len(other.fields)) and
  533. all((k1 == k2 and (f1.deconstruct()[1:] == f2.deconstruct()[1:]))
  534. for (k1, f1), (k2, f2) in zip(self.fields, other.fields)) and
  535. (self.options == other.options) and
  536. (self.bases == other.bases) and
  537. (self.managers == other.managers)
  538. )
  539. def __ne__(self, other):
  540. return not (self == other)