state.py 25 KB

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