diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py index 9ad3a8a321..16cadc7a2f 100644 --- a/django/db/migrations/operations/models.py +++ b/django/db/migrations/operations/models.py @@ -306,6 +306,7 @@ class RenameModel(ModelOperation): # Repoint all fields pointing to the old model to the new one. old_model_tuple = app_label, self.old_name_lower new_remote_model = '%s.%s' % (app_label, self.new_name) + to_reload = [] for (model_app_label, model_name), model_state in state.models.items(): model_changed = False for index, (name, field) in enumerate(model_state.fields): @@ -331,7 +332,9 @@ class RenameModel(ModelOperation): model_state.fields[index] = name, changed_field model_changed = True if model_changed: - state.reload_model(model_app_label, model_name, delay=True) + to_reload.append((model_app_label, model_name)) + # Reload models related to old model before removing the old model. + state.reload_models(to_reload, delay=True) # Remove the old model. state.remove_model(app_label, self.old_name_lower) state.reload_model(app_label, self.new_name_lower, delay=True) diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index f659153a15..e44f737106 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -112,75 +112,91 @@ class ProjectState(object): # the cache automatically (#24513) self.apps.clear_cache() - def reload_model(self, app_label, model_name, delay=False): + def _find_reload_model(self, app_label, model_name, delay=False): if delay: self.is_delayed = True - if 'apps' in self.__dict__: # hasattr would cache the property - try: - old_model = self.apps.get_model(app_label, model_name) - except LookupError: - related_models = set() + related_models = set() + + try: + old_model = self.apps.get_model(app_label, model_name) + except LookupError: + pass + else: + # Get all relations to and from the old model before reloading, + # as _meta.apps may change + if delay: + related_models = get_related_models_tuples(old_model) + else: + related_models = get_related_models_recursive(old_model) + + # Get all outgoing references from the model to be rendered + model_state = self.models[(app_label, model_name)] + # Directly related models are the models pointed to by ForeignKeys, + # OneToOneFields, and ManyToManyFields. + direct_related_models = set() + for name, field in model_state.fields: + if field.is_relation: + if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT: + continue + rel_app_label, rel_model_name = _get_app_label_and_model_name(field.related_model, app_label) + direct_related_models.add((rel_app_label, rel_model_name.lower())) + + # For all direct related models recursively get all related models. + related_models.update(direct_related_models) + for rel_app_label, rel_model_name in direct_related_models: + try: + rel_model = self.apps.get_model(rel_app_label, rel_model_name) + except LookupError: + pass else: - # Get all relations to and from the old model before reloading, - # as _meta.apps may change if delay: - related_models = get_related_models_tuples(old_model) + related_models.update(get_related_models_tuples(rel_model)) else: - related_models = get_related_models_recursive(old_model) + related_models.update(get_related_models_recursive(rel_model)) - # Get all outgoing references from the model to be rendered - model_state = self.models[(app_label, model_name)] - # Directly related models are the models pointed to by ForeignKeys, - # OneToOneFields, and ManyToManyFields. - direct_related_models = set() - for name, field in model_state.fields: - if field.is_relation: - if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT: - continue - rel_app_label, rel_model_name = _get_app_label_and_model_name(field.related_model, app_label) - direct_related_models.add((rel_app_label, rel_model_name.lower())) + # Include the model itself + related_models.add((app_label, model_name)) - # For all direct related models recursively get all related models. - related_models.update(direct_related_models) - for rel_app_label, rel_model_name in direct_related_models: - try: - rel_model = self.apps.get_model(rel_app_label, rel_model_name) - except LookupError: - pass - else: - if delay: - related_models.update(get_related_models_tuples(rel_model)) - else: - related_models.update(get_related_models_recursive(rel_model)) + return related_models - # Include the model itself - related_models.add((app_label, model_name)) + def reload_model(self, app_label, model_name, delay=False): + if 'apps' in self.__dict__: # hasattr would cache the property + related_models = self._find_reload_model(app_label, model_name, delay) + self._reload(related_models) - # Unregister all related models - with self.apps.bulk_update(): - for rel_app_label, rel_model_name in related_models: - self.apps.unregister_model(rel_app_label, rel_model_name) + def reload_models(self, models, delay=True): + if 'apps' in self.__dict__: # hasattr would cache the property + related_models = set() + for app_label, model_name in models: + related_models.update(self._find_reload_model(app_label, model_name, delay)) + self._reload(related_models) - states_to_be_rendered = [] - # Gather all models states of those models that will be rerendered. - # This includes: - # 1. All related models of unmigrated apps - for model_state in self.apps.real_models: - if (model_state.app_label, model_state.name_lower) in related_models: - states_to_be_rendered.append(model_state) - - # 2. All related models of migrated apps + def _reload(self, related_models): + # Unregister all related models + with self.apps.bulk_update(): for rel_app_label, rel_model_name in related_models: - try: - model_state = self.models[rel_app_label, rel_model_name] - except KeyError: - pass - else: - states_to_be_rendered.append(model_state) + self.apps.unregister_model(rel_app_label, rel_model_name) - # Render all models - self.apps.render_multiple(states_to_be_rendered) + states_to_be_rendered = [] + # Gather all models states of those models that will be rerendered. + # This includes: + # 1. All related models of unmigrated apps + for model_state in self.apps.real_models: + if (model_state.app_label, model_state.name_lower) in related_models: + states_to_be_rendered.append(model_state) + + # 2. All related models of migrated apps + for rel_app_label, rel_model_name in related_models: + try: + model_state = self.models[rel_app_label, rel_model_name] + except KeyError: + pass + else: + states_to_be_rendered.append(model_state) + + # Render all models + self.apps.render_multiple(states_to_be_rendered) def clone(self): "Returns an exact copy of this ProjectState"