state.py 25 KB

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