def render(self, include_real=None, ignore_swappable=False, skip_cache=False): "Turns the project state into actual models in a new Apps" if self.apps is None or skip_cache: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. # FKs/M2Ms from real apps are also not included as they just # mess things up with partial states (due to lack of dependencies) real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model, exclude_rels=True)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError( "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an " "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see " "https://docs.djangoproject.com/en/1.7/topics/migrations/#dependencies " "for more" % new_unrendered_models ) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items(): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: app_label = "%s.%s" % (lookup_model[0], lookup_model[1]) if app_label == settings.AUTH_USER_MODEL and ignore_swappable: continue # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" msg = "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}" raise ValueError(msg.format(field=operations[0][1], model=lookup_model)) else: do_pending_lookups(model) try: return self.apps finally: if skip_cache: self.apps = None
def render(self): "Turns the project state into actual models in a new Apps" if self.apps is None: self.apps = Apps() # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) app_configs = [AppConfigStub(label) for label in sorted(app_labels)] self.apps.populate_apps(app_configs) self.apps.populate_models() # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models return self.apps
def get_models(): result = [] apps = Apps(settings.INSTALLED_APPS) for app_config in apps.get_app_configs(): try: import_module('%s.comments' % app_config.module.__name__) except ImportError: pass except Exception as e: raise e for attr_name in dir(app_config.models_module): attr = getattr(app_config.models_module, attr_name) if isinstance(attr, ModelBase) and attr.__module__ == '%s.models' % app_config.module.__name__: result.append(attr) return result
def render(self, include_real=None): "Turns the project state into actual models in a new Apps" if self.apps is None: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items(): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: # If the lookup failed to something that looks like AUTH_USER_MODEL, # give a better error message about how you can't change it (#22563) extra_message = "" if "%s.%s" % (lookup_model[0], lookup_model[1]) == settings.AUTH_USER_MODEL: extra_message = ( "\nThe missing model matches AUTH_USER_MODEL; if you've changed the value of this" + "\nsetting after making a migration, be aware that this is not supported. If you" + "\nchange AUTH_USER_MODEL you must delete and recreate migrations for its app." ) # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" raise ValueError("Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}{extra_message}".format( field = operations[0][1], model = lookup_model, extra_message = extra_message, )) else: do_pending_lookups(model) return self.apps
def test_dynamic_load(self): """ Makes a new model at runtime and ensures it goes into the right place. """ old_models = apps.get_models(apps.get_app_config("apps").models_module) # Construct a new model in a new app registry body = {} new_apps = Apps() meta_contents = {"app_label": "apps", "apps": new_apps} meta = type(str("Meta"), tuple(), meta_contents) body["Meta"] = meta body["__module__"] = TotallyNormal.__module__ temp_model = type(str("SouthPonies"), (models.Model,), body) # Make sure it appeared in the right place! self.assertEqual(old_models, apps.get_models(apps.get_app_config("apps").models_module)) with self.assertRaises(LookupError): apps.get_model("apps", "SouthPonies") self.assertEqual(new_apps.get_model("apps", "SouthPonies"), temp_model)
def test_dynamic_load(self): """ Makes a new model at runtime and ensures it goes into the right place. """ old_models = list(apps.get_app_config("apps").get_models()) # Construct a new model in a new app registry body = {} new_apps = Apps(["apps"]) meta_contents = { 'app_label': "apps", 'apps': new_apps, } meta = type("Meta", tuple(), meta_contents) body['Meta'] = meta body['__module__'] = TotallyNormal.__module__ temp_model = type("SouthPonies", (models.Model,), body) # Make sure it appeared in the right place! self.assertEqual(list(apps.get_app_config("apps").get_models()), old_models) with self.assertRaises(LookupError): apps.get_model("apps", "SouthPonies") self.assertEqual(new_apps.get_model("apps", "SouthPonies"), temp_model)
def test_create(self): """ Tests making a ProjectState from an Apps """ new_apps = Apps(["migrations"]) class Author(models.Model): name = models.CharField(max_length=255) bio = models.TextField() age = models.IntegerField(blank=True, null=True) class Meta: app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] index_together = ["bio", "age"] class AuthorProxy(Author): class Meta: app_label = "migrations" apps = new_apps proxy = True ordering = ["name"] class SubAuthor(Author): width = models.FloatField(null=True) class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author) contributors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps verbose_name = "tome" db_table = "test_tome" class Food(models.Model): food_mgr = FoodManager('a', 'b') food_qs = FoodQuerySet.as_manager() food_no_mgr = NoMigrationFoodManager('x', 'y') class Meta: app_label = "migrations" apps = new_apps class FoodNoManagers(models.Model): class Meta: app_label = "migrations" apps = new_apps class FoodNoDefaultManager(models.Model): food_no_mgr = NoMigrationFoodManager('x', 'y') food_mgr = FoodManager('a', 'b') food_qs = FoodQuerySet.as_manager() class Meta: app_label = "migrations" apps = new_apps mgr1 = FoodManager('a', 'b') mgr2 = FoodManager('x', 'y', c=3, d=4) class FoodOrderedManagers(models.Model): # The managers on this model should be ordered by their creation # counter and not by the order in model body food_no_mgr = NoMigrationFoodManager('x', 'y') food_mgr2 = mgr2 food_mgr1 = mgr1 class Meta: app_label = "migrations" apps = new_apps project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] author_proxy_state = project_state.models['migrations', 'authorproxy'] sub_author_state = project_state.models['migrations', 'subauthor'] book_state = project_state.models['migrations', 'book'] food_state = project_state.models['migrations', 'food'] food_no_managers_state = project_state.models['migrations', 'foodnomanagers'] food_no_default_manager_state = project_state.models[ 'migrations', 'foodnodefaultmanager'] food_order_manager_state = project_state.models['migrations', 'foodorderedmanagers'] self.assertEqual(author_state.app_label, "migrations") self.assertEqual(author_state.name, "Author") self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"]) self.assertEqual(author_state.fields[1][1].max_length, 255) self.assertEqual(author_state.fields[2][1].null, False) self.assertEqual(author_state.fields[3][1].null, True) self.assertEqual( author_state.options, { "unique_together": {("name", "bio")}, "index_together": {("bio", "age")} }) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(book_state.app_label, "migrations") self.assertEqual(book_state.name, "Book") self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertEqual(book_state.fields[2][1].null, False) self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField") self.assertEqual(book_state.options, { "verbose_name": "tome", "db_table": "test_tome" }) self.assertEqual(book_state.bases, (models.Model, )) self.assertEqual(author_proxy_state.app_label, "migrations") self.assertEqual(author_proxy_state.name, "AuthorProxy") self.assertEqual(author_proxy_state.fields, []) self.assertEqual(author_proxy_state.options, { "proxy": True, "ordering": ["name"] }) self.assertEqual(author_proxy_state.bases, ("migrations.author", )) self.assertEqual(sub_author_state.app_label, "migrations") self.assertEqual(sub_author_state.name, "SubAuthor") self.assertEqual(len(sub_author_state.fields), 2) self.assertEqual(sub_author_state.bases, ("migrations.author", )) # The default manager is used in migrations self.assertEqual([name for name, mgr in food_state.managers], ['food_mgr']) self.assertTrue( all( isinstance(name, six.text_type) for name, mgr in food_state.managers)) self.assertEqual(food_state.managers[0][1].args, ('a', 'b', 1, 2)) # No explicit managers defined. Migrations will fall back to the default self.assertEqual(food_no_managers_state.managers, []) # food_mgr is used in migration but isn't the default mgr, hence add the # default self.assertEqual( [name for name, mgr in food_no_default_manager_state.managers], ['food_no_mgr', 'food_mgr']) self.assertTrue( all( isinstance(name, six.text_type) for name, mgr in food_no_default_manager_state.managers)) self.assertEqual( food_no_default_manager_state.managers[0][1].__class__, models.Manager) self.assertIsInstance(food_no_default_manager_state.managers[1][1], FoodManager) self.assertEqual( [name for name, mgr in food_order_manager_state.managers], ['food_mgr1', 'food_mgr2']) self.assertTrue( all( isinstance(name, six.text_type) for name, mgr in food_order_manager_state.managers)) self.assertEqual( [mgr.args for name, mgr in food_order_manager_state.managers], [('a', 'b', 1, 2), ('x', 'y', 3, 4)])
def setUp(self): self.apps = Apps(['migrations.related_models_app'])
class Meta: abstract = True apps = Apps()
def test_wait_for_apps_ready_checks_for_exception(self): app_reg = Apps() app_reg.ready_event.set() # thread.is_alive() is False if it's not started. dead_thread = threading.Thread() self.assertFalse(self.reloader.wait_for_apps_ready(app_reg, dead_thread))
class Meta: apps = Apps() app_label = "channels" db_table = "django_channels"
class Meta: verbose_name = 'a model created on the fly' app_label = 'my_great_app' apps = Apps()
def handle_django_settings(filename): '''Attempts to load a Django project and get package dependencies from settings. Tested using Django 1.4 and 1.8. Not sure if some nuances are missed in the other versions. ''' old_sys_path = sys.path[:] dirpath = os.path.dirname(filename) project = os.path.basename(dirpath) cwd = os.getcwd() project_path = os.path.normpath(os.path.join(dirpath, '..')) if project_path not in sys.path: sys.path.insert(0, project_path) os.chdir(project_path) project_settings = '{}.settings'.format(project) os.environ['DJANGO_SETTINGS_MODULE'] = project_settings try: import django # Sanity django.setup = lambda: False except ImportError: log.error('Found Django settings, but Django is not installed.') return log.warn('Loading Django Settings (Using {}): {}'.format( django.get_version(), filename)) from django.conf import LazySettings installed_apps = set() settings_imports = set() try: settings = LazySettings() settings._setup() for k, v in vars(settings._wrapped).items(): if k not in _excluded_settings and re.match(r'^[A-Z_]+$', k): # log.debug('Scanning Django setting: %s', k) scan_django_settings(v, settings_imports) # Manually scan INSTALLED_APPS since the broad scan won't include # strings without a period in it . for app in getattr(settings, 'INSTALLED_APPS', []): if hasattr(app, '__file__') and getattr(app, '__file__'): imp, _ = utils.import_path_from_file(getattr(app, '__file__')) installed_apps.add(imp) else: installed_apps.add(app) except Exception as e: log.error('Could not load Django settings: %s', e) log.debug('', exc_info=True) return if not installed_apps or not settings_imports: log.error('Got empty settings values from Django settings.') try: from django.apps.registry import apps, Apps, AppRegistryNotReady # Django doesn't like it when the initial instance of `apps` is reused, # but it has to be populated before other instances can be created. if not apps.apps_ready: apps.populate(installed_apps) else: apps = Apps(installed_apps) start = time.time() while True: try: for app in apps.get_app_configs(): installed_apps.add(app.name) except AppRegistryNotReady: if time.time() - start > 10: raise Exception('Bail out of waiting for Django') log.debug('Waiting for apps to load...') continue break except Exception as e: log.debug('Could not use AppConfig: {}'.format(e)) # Restore before sub scans can occur sys.path[:] = old_sys_path os.chdir(cwd) for item in settings_imports: need_scan = item.startswith(_filescan_modules) yield ('django', item, project_path if need_scan else None) for app in installed_apps: need_scan = app.startswith(project) yield ('django', app, project_path if need_scan else None)
class Meta: apps = Apps() app_label = 'migrations' db_table = 'django_migrations'
def test_dangling_references_throw_error(self): new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Publisher(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) publisher = models.ForeignKey(Publisher, models.CASCADE) class Meta: app_label = "migrations" apps = new_apps class Magazine(models.Model): authors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Publisher)) project_state.add_model(ModelState.from_model(Book)) project_state.add_model(ModelState.from_model(Magazine)) self.assertEqual(len(project_state.apps.get_models()), 4) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model(ModelState.from_model(Book)) msg = ( "The field migrations.Book.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Book.publisher was declared with a lazy reference " "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And another with ManyToManyField. project_state = ProjectState() project_state.add_model(ModelState.from_model(Magazine)) msg = ( "The field migrations.Magazine.authors was declared with a lazy reference " "to 'migrations.author\', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Magazine_authors.author was declared with a lazy reference " "to \'migrations.author\', but app 'migrations' doesn't provide model 'author'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And now with multiple models and multiple fields. project_state.add_model(ModelState.from_model(Book)) msg = ( "The field migrations.Book.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Book.publisher was declared with a lazy reference " "to 'migrations.publisher', but app 'migrations' doesn't provide model 'publisher'.\n" "The field migrations.Magazine.authors was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'.\n" "The field migrations.Magazine_authors.author was declared with a lazy reference " "to 'migrations.author', but app 'migrations' doesn't provide model 'author'." ) with self.assertRaisesMessage(ValueError, msg): project_state.apps
def test_field_deconstruction(self): test_apps = Apps() class Foo(PolymorphicModel): foo = PolymorphicTypeField('self', on_delete=models.CASCADE) class Meta: apps = test_apps app_label = 'polymodels' class Bar(models.Model): foo = PolymorphicTypeField('Foo', on_delete=models.CASCADE) foo_null = PolymorphicTypeField(Foo, on_delete=models.CASCADE, null=True) foo_default = PolymorphicTypeField( Foo, on_delete=models.CASCADE, default=get_content_type(Foo).pk) class Meta: apps = test_apps app_label = 'polymodels' def django_version_agnostic(deconstruction): # As of Django 1.9+ on_delete is a required parameter and # doesn't default to models.CASCADE anymore. if django.VERSION >= (1, 9): deconstruction['on_delete'] = models.CASCADE return deconstruction self.assertDeconstructionEqual( Foo._meta.get_field('foo'), ('foo', 'django.db.models.fields.related.ForeignKey', [], django_version_agnostic( { 'to': 'contenttypes.ContentType', 'related_name': '+', 'default': ContentTypeReference('polymodels', 'foo'), }))) self.assertDeconstructionEqual( Bar._meta.get_field('foo'), ('foo', 'django.db.models.fields.related.ForeignKey', [], django_version_agnostic( { 'to': 'contenttypes.ContentType', 'related_name': '+', 'default': ContentTypeReference('polymodels', 'foo'), }))) self.assertDeconstructionEqual( Bar._meta.get_field('foo_null'), ('foo_null', 'django.db.models.fields.related.ForeignKey', [], django_version_agnostic({ 'to': 'contenttypes.ContentType', 'related_name': '+', 'null': True, }))) self.assertDeconstructionEqual( Bar._meta.get_field('foo_default'), ('foo_default', 'django.db.models.fields.related.ForeignKey', [], django_version_agnostic({ 'to': 'contenttypes.ContentType', 'related_name': '+', 'default': get_content_type(Foo).pk, })))
def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=[], rename_fields=[], override_uniques=None): """ Shortcut to transform a model from old_model into new_model """ # Work out the new fields dict / mapping body = dict((f.name, f) for f in model._meta.local_fields) mapping = dict((f.column, f.column) for f in model._meta.local_fields) # If any of the new or altered fields is introducing a new PK, # remove the old one restore_pk_field = None if any(f.primary_key for f in create_fields) or any(n.primary_key for o, n in alter_fields): for name, field in list(body.items()): if field.primary_key: field.primary_key = False restore_pk_field = field if field.auto_created: del body[name] del mapping[field.column] # Add in any created fields for field in create_fields: body[field.name] = field # If there's a default, insert it into the copy map if field.has_default(): mapping[field.column] = self.connection.ops.quote_parameter( field.get_default() ) # Add in any altered fields for (old_field, new_field) in alter_fields: del body[old_field.name] del mapping[old_field.column] body[new_field.name] = new_field mapping[new_field.column] = old_field.column # Remove any deleted fields for field in delete_fields: del body[field.name] del mapping[field.column] # Work inside a new app registry apps = Apps() # Construct a new model for the new state meta_contents = { 'app_label': model._meta.app_label, 'db_table': model._meta.db_table + "__new", 'unique_together': model._meta.unique_together if override_uniques is None else override_uniques, 'apps': apps, } meta = type("Meta", tuple(), meta_contents) body['Meta'] = meta body['__module__'] = model.__module__ temp_model = type(model._meta.object_name, model.__bases__, body) # Create a new table with that format self.create_model(temp_model) # Copy data from the old table field_maps = list(mapping.items()) self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % ( self.quote_name(temp_model._meta.db_table), ', '.join(x for x, y in field_maps), ', '.join(y for x, y in field_maps), self.quote_name(model._meta.db_table), )) # Delete the old table self.delete_model(model) # Rename the new to the old self.alter_db_table(model, temp_model._meta.db_table, model._meta.db_table) # Run deferred SQL on correct table for sql in self.deferred_sql: self.execute(sql.replace(temp_model._meta.db_table, model._meta.db_table)) self.deferred_sql = [] # Fix any PK-removed field if restore_pk_field: restore_pk_field.primary_key = True
from __future__ import unicode_literals from django.apps.registry import Apps from django.db import models # We're testing app registry presence on load, so this is handy. new_apps = Apps(['apps']) class TotallyNormal(models.Model): name = models.CharField(max_length=255) class SoAlternative(models.Model): name = models.CharField(max_length=255) class Meta: apps = new_apps
class ProjectState(object): """ Represents the entire project's overall state. This is the item that is passed around - we do it here rather than at the app level so that cross-app FKs/etc. resolve properly. """ def __init__(self, models=None, real_apps=None): self.models = models or {} self.apps = None # Apps to include from main registry, usually unmigrated ones self.real_apps = real_apps or [] def add_model_state(self, model_state): self.models[(model_state.app_label, model_state.name.lower())] = model_state def clone(self): "Returns an exact copy of this ProjectState" return ProjectState( models=dict((k, v.clone()) for k, v in self.models.items()), real_apps=self.real_apps, ) def render(self, include_real=None): "Turns the project state into actual models in a new Apps" if self.apps is None: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items(): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: # If the lookup failed to something that looks like AUTH_USER_MODEL, # give a better error message about how you can't change it (#22563) extra_message = "" if "%s.%s" % (lookup_model[0], lookup_model[1]) == settings.AUTH_USER_MODEL: extra_message = ( "\nThe missing model matches AUTH_USER_MODEL; if you've changed the value of this" + "\nsetting after making a migration, be aware that this is not supported. If you" + "\nchange AUTH_USER_MODEL you must delete and recreate migrations for its app." ) # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" raise ValueError("Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}{extra_message}".format( field = operations[0][1], model = lookup_model, extra_message = extra_message, )) else: do_pending_lookups(model) return self.apps @classmethod def from_apps(cls, apps): "Takes in an Apps and returns a ProjectState matching it" app_models = {} for model in apps.get_models(): model_state = ModelState.from_model(model) app_models[(model_state.app_label, model_state.name.lower())] = model_state return cls(app_models) def __eq__(self, other): if set(self.models.keys()) != set(other.models.keys()): return False if set(self.real_apps) != set(other.real_apps): return False return all(model == other.models[key] for key, model in self.models.items()) def __ne__(self, other): return not (self == other)
def test_dangling_references_throw_error(self): new_apps = Apps() class Author(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Publisher(models.Model): name = models.TextField() class Meta: app_label = "migrations" apps = new_apps class Book(models.Model): author = models.ForeignKey(Author, models.CASCADE) publisher = models.ForeignKey(Publisher, models.CASCADE) class Meta: app_label = "migrations" apps = new_apps class Magazine(models.Model): authors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps # Make a valid ProjectState and render it project_state = ProjectState() project_state.add_model(ModelState.from_model(Author)) project_state.add_model(ModelState.from_model(Publisher)) project_state.add_model(ModelState.from_model(Book)) project_state.add_model(ModelState.from_model(Magazine)) self.assertEqual(len(project_state.apps.get_models()), 4) # now make an invalid one with a ForeignKey project_state = ProjectState() project_state.add_model(ModelState.from_model(Book)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: migrations.Book.author)\n" " migrations.publisher (referred to by fields: migrations.Book.publisher)" ) with self.assertRaisesMessage(ValueError, msg): project_state.apps # And another with ManyToManyField. project_state = ProjectState() project_state.add_model(ModelState.from_model(Magazine)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: " "migrations.Magazine.authors, migrations.Magazine_authors.author)") with self.assertRaisesMessage(ValueError, msg): project_state.apps # And now with multiple models and multiple fields. project_state.add_model(ModelState.from_model(Book)) msg = ( "Unhandled pending operations for models:\n" " migrations.author (referred to by fields: migrations.Book.author, " "migrations.Magazine.authors, migrations.Magazine_authors.author)\n" " migrations.publisher (referred to by fields: migrations.Book.publisher)" ) with self.assertRaisesMessage(ValueError, msg): project_state.apps
class Meta: # Disable auto loading of this model as we load it on our own apps = Apps() verbose_name = 'úñí©óðé µóðéø' verbose_name_plural = 'úñí©óðé µóðéøß'
def test_create(self): """ Tests making a ProjectState from an Apps """ new_apps = Apps(["migrations"]) class Author(models.Model): name = models.CharField(max_length=255) bio = models.TextField() age = models.IntegerField(blank=True, null=True) class Meta: app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] class AuthorProxy(Author): class Meta: app_label = "migrations" apps = new_apps proxy = True ordering = ["name"] class Book(models.Model): title = models.CharField(max_length=1000) author = models.ForeignKey(Author) contributors = models.ManyToManyField(Author) class Meta: app_label = "migrations" apps = new_apps verbose_name = "tome" db_table = "test_tome" project_state = ProjectState.from_apps(new_apps) author_state = project_state.models['migrations', 'author'] author_proxy_state = project_state.models['migrations', 'authorproxy'] book_state = project_state.models['migrations', 'book'] self.assertEqual(author_state.app_label, "migrations") self.assertEqual(author_state.name, "Author") self.assertEqual([x for x, y in author_state.fields], ["id", "name", "bio", "age"]) self.assertEqual(author_state.fields[1][1].max_length, 255) self.assertEqual(author_state.fields[2][1].null, False) self.assertEqual(author_state.fields[3][1].null, True) self.assertEqual(author_state.options, {"unique_together": {("name", "bio")}}) self.assertEqual(author_state.bases, (models.Model, )) self.assertEqual(book_state.app_label, "migrations") self.assertEqual(book_state.name, "Book") self.assertEqual([x for x, y in book_state.fields], ["id", "title", "author", "contributors"]) self.assertEqual(book_state.fields[1][1].max_length, 1000) self.assertEqual(book_state.fields[2][1].null, False) self.assertEqual(book_state.fields[3][1].__class__.__name__, "ManyToManyField") self.assertEqual(book_state.options, { "verbose_name": "tome", "db_table": "test_tome" }) self.assertEqual(book_state.bases, (models.Model, )) self.assertEqual(author_proxy_state.app_label, "migrations") self.assertEqual(author_proxy_state.name, "AuthorProxy") self.assertEqual(author_proxy_state.fields, []) self.assertEqual(author_proxy_state.options, { "proxy": True, "ordering": ["name"] }) self.assertEqual(author_proxy_state.bases, ("migrations.author", ))
class Meta: # Disable auto loading of this model as we load it on our own apps = Apps()
class Meta: apps = Apps() app_label = "channels" db_table = "django_channel_groups" unique_together = [["group", "channel"]]
def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=[], override_uniques=None): """ Shortcut to transform a model from old_model into new_model """ # Work out the new fields dict / mapping body = dict((f.name, f) for f in model._meta.local_fields) # Since mapping might mix column names and default values, # its values must be already quoted. mapping = dict((f.column, self.quote_name(f.column)) for f in model._meta.local_fields) # This maps field names (not columns) for things like unique_together rename_mapping = {} # If any of the new or altered fields is introducing a new PK, # remove the old one restore_pk_field = None if any(f.primary_key for f in create_fields) or any(n.primary_key for o, n in alter_fields): for name, field in list(body.items()): if field.primary_key: field.primary_key = False restore_pk_field = field if field.auto_created: del body[name] del mapping[field.column] # Add in any created fields for field in create_fields: body[field.name] = field # If there's a default, insert it into the copy map if field.has_default(): mapping[field.column] = self.quote_value( self.effective_default(field)) # Add in any altered fields for (old_field, new_field) in alter_fields: del body[old_field.name] del mapping[old_field.column] body[new_field.name] = new_field mapping[new_field.column] = self.quote_name(old_field.column) rename_mapping[old_field.name] = new_field.name # Remove any deleted fields for field in delete_fields: del body[field.name] del mapping[field.column] # Remove any implicit M2M tables if isinstance( field, ManyToManyField) and field.rel.through._meta.auto_created: return self.delete_model(field.rel.through) # Work inside a new app registry apps = Apps() # Provide isolated instances of the fields to the new model body # Instantiating the new model with an alternate db_table will alter # the internal references of some of the provided fields. body = copy.deepcopy(body) # Work out the new value of unique_together, taking renames into # account if override_uniques is None: override_uniques = [[rename_mapping.get(n, n) for n in unique] for unique in model._meta.unique_together] # Construct a new model for the new state meta_contents = { 'app_label': model._meta.app_label, 'db_table': model._meta.db_table + "__new", 'unique_together': override_uniques, 'apps': apps, } meta = type("Meta", tuple(), meta_contents) body['Meta'] = meta body['__module__'] = model.__module__ temp_model = type(model._meta.object_name, model.__bases__, body) # Create a new table with that format. We remove things from the # deferred SQL that match our table name, too self.deferred_sql = [ x for x in self.deferred_sql if model._meta.db_table not in x ] self.create_model(temp_model) # Copy data from the old table field_maps = list(mapping.items()) self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % ( self.quote_name(temp_model._meta.db_table), ', '.join(self.quote_name(x) for x, y in field_maps), ', '.join(y for x, y in field_maps), self.quote_name(model._meta.db_table), )) # Delete the old table self.delete_model(model, handle_autom2m=False) # Rename the new to the old self.alter_db_table(temp_model, temp_model._meta.db_table, model._meta.db_table) # Run deferred SQL on correct table for sql in self.deferred_sql: self.execute( sql.replace(temp_model._meta.db_table, model._meta.db_table)) self.deferred_sql = [] # Fix any PK-removed field if restore_pk_field: restore_pk_field.primary_key = True
def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None): """ Shortcut to transform a model from old_model into new_model This follows the correct procedure to perform non-rename or column addition operations based on SQLite's documentation https://www.sqlite.org/lang_altertable.html#caution The essential steps are: 1. Create a table with the updated definition called "new__app_model" 2. Copy the data from the existing "app_model" table to the new table 3. Drop the "app_model" table 4. Rename the "new__app_model" table to "app_model" 5. Restore any index of the previous "app_model" table. """ # Self-referential fields must be recreated rather than copied from # the old model to ensure their remote_field.field_name doesn't refer # to an altered field. def is_self_referential(f): return f.is_relation and f.remote_field.model is model # Work out the new fields dict / mapping body = { f.name: f.clone() if is_self_referential(f) else f for f in model._meta.local_concrete_fields } # Since mapping might mix column names and default values, # its values must be already quoted. mapping = { f.column: self.quote_name(f.column) for f in model._meta.local_concrete_fields } # This maps field names (not columns) for things like unique_together rename_mapping = {} # If any of the new or altered fields is introducing a new PK, # remove the old one restore_pk_field = None if getattr(create_field, 'primary_key', False) or ( alter_field and getattr(alter_field[1], 'primary_key', False)): for name, field in list(body.items()): if field.primary_key: field.primary_key = False restore_pk_field = field if field.auto_created: del body[name] del mapping[field.column] # Add in any created fields if create_field: body[create_field.name] = create_field # Choose a default and insert it into the copy map if not create_field.many_to_many and create_field.concrete: mapping[create_field.column] = self.quote_value( self.effective_default(create_field)) # Add in any altered fields if alter_field: old_field, new_field = alter_field body.pop(old_field.name, None) mapping.pop(old_field.column, None) body[new_field.name] = new_field if old_field.null and not new_field.null: case_sql = "coalesce(%(col)s, %(default)s)" % { 'col': self.quote_name(old_field.column), 'default': self.quote_value( self.effective_default(new_field)) } mapping[new_field.column] = case_sql else: mapping[new_field.column] = self.quote_name(old_field.column) rename_mapping[old_field.name] = new_field.name # Remove any deleted fields if delete_field: del body[delete_field.name] del mapping[delete_field.column] # Remove any implicit M2M tables if delete_field.many_to_many and delete_field.remote_field.through._meta.auto_created: return self.delete_model(delete_field.remote_field.through) # Work inside a new app registry apps = Apps() # Work out the new value of unique_together, taking renames into # account unique_together = [[rename_mapping.get(n, n) for n in unique] for unique in model._meta.unique_together] # Work out the new value for index_together, taking renames into # account index_together = [[rename_mapping.get(n, n) for n in index] for index in model._meta.index_together] indexes = model._meta.indexes if delete_field: indexes = [ index for index in indexes if delete_field.name not in index.fields ] constraints = list(model._meta.constraints) # Provide isolated instances of the fields to the new model body so # that the existing model's internals aren't interfered with when # the dummy model is constructed. body_copy = copy.deepcopy(body) # Construct a new model with the new fields to allow self referential # primary key to resolve to. This model won't ever be materialized as a # table and solely exists for foreign key reference resolution purposes. # This wouldn't be required if the schema editor was operating on model # states instead of rendered models. meta_contents = { 'app_label': model._meta.app_label, 'db_table': model._meta.db_table, 'unique_together': unique_together, 'index_together': index_together, 'indexes': indexes, 'constraints': constraints, 'apps': apps, } meta = type("Meta", (), meta_contents) body_copy['Meta'] = meta body_copy['__module__'] = model.__module__ type(model._meta.object_name, model.__bases__, body_copy) # Construct a model with a renamed table name. body_copy = copy.deepcopy(body) meta_contents = { 'app_label': model._meta.app_label, 'db_table': 'new__%s' % strip_quotes(model._meta.db_table), 'unique_together': unique_together, 'index_together': index_together, 'indexes': indexes, 'constraints': constraints, 'apps': apps, } meta = type("Meta", (), meta_contents) body_copy['Meta'] = meta body_copy['__module__'] = model.__module__ new_model = type('New%s' % model._meta.object_name, model.__bases__, body_copy) # Create a new table with the updated schema. self.create_model(new_model) # Copy data from the old table into the new table self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % ( self.quote_name(new_model._meta.db_table), ', '.join(self.quote_name(x) for x in mapping), ', '.join(mapping.values()), self.quote_name(model._meta.db_table), )) # Delete the old table to make way for the new self.delete_model(model, handle_autom2m=False) # Rename the new table to take way for the old self.alter_db_table( new_model, new_model._meta.db_table, model._meta.db_table, disable_constraints=False, ) # Run deferred SQL on correct table for sql in self.deferred_sql: self.execute(sql) self.deferred_sql = [] # Fix any PK-removed field if restore_pk_field: restore_pk_field.primary_key = True
def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None): """ Shortcut to transform a model from old_model into new_model The essential steps are: 1. rename the model's existing table, e.g. "app_model" to "app_model__old" 2. create a table with the updated definition called "app_model" 3. copy the data from the old renamed table to the new table 4. delete the "app_model__old" table """ # Self-referential fields must be recreated rather than copied from # the old model to ensure their remote_field.field_name doesn't refer # to an altered field. def is_self_referential(f): return f.is_relation and f.remote_field.model is model # Work out the new fields dict / mapping body = { f.name: f.clone() if is_self_referential(f) else f for f in model._meta.local_concrete_fields } # Since mapping might mix column names and default values, # its values must be already noted. mapping = { f.column: self.note_name(f.column) for f in model._meta.local_concrete_fields } # This maps field names (not columns) for things like unique_together rename_mapping = {} # If any of the new or altered fields is introducing a new PK, # remove the old one restore_pk_field = None if getattr(create_field, 'primary_key', False) or ( alter_field and getattr(alter_field[1], 'primary_key', False)): for name, field in list(body.items()): if field.primary_key: field.primary_key = False restore_pk_field = field if field.auto_created: del body[name] del mapping[field.column] # Add in any created fields if create_field: body[create_field.name] = create_field # Choose a default and insert it into the copy map if not create_field.many_to_many and create_field.concrete: mapping[create_field.column] = self.note_value( self.effective_default(create_field)) # Add in any altered fields if alter_field: old_field, new_field = alter_field body.pop(old_field.name, None) mapping.pop(old_field.column, None) body[new_field.name] = new_field if old_field.null and not new_field.null: case_sql = "coalesce(%(col)s, %(default)s)" % { 'col': self.note_name(old_field.column), 'default': self.note_value( self.effective_default(new_field)) } mapping[new_field.column] = case_sql else: mapping[new_field.column] = self.note_name(old_field.column) rename_mapping[old_field.name] = new_field.name # Remove any deleted fields if delete_field: del body[delete_field.name] del mapping[delete_field.column] # Remove any implicit M2M tables if delete_field.many_to_many and delete_field.remote_field.through._meta.auto_created: return self.delete_model(delete_field.remote_field.through) # Work inside a new app registry apps = Apps() # Provide isolated instances of the fields to the new model body so # that the existing model's internals aren't interfered with when # the dummy model is constructed. body = copy.deepcopy(body) # Work out the new value of unique_together, taking renames into # account unique_together = [[rename_mapping.get(n, n) for n in unique] for unique in model._meta.unique_together] # Work out the new value for index_together, taking renames into # account index_together = [[rename_mapping.get(n, n) for n in index] for index in model._meta.index_together] indexes = model._meta.indexes if delete_field: indexes = [ index for index in indexes if delete_field.name not in index.fields ] # Construct a new model for the new state meta_contents = { 'app_label': model._meta.app_label, 'db_table': model._meta.db_table, 'unique_together': unique_together, 'index_together': index_together, 'indexes': indexes, 'apps': apps, } meta = type("Meta", tuple(), meta_contents) body['Meta'] = meta body['__module__'] = model.__module__ temp_model = type(model._meta.object_name, model.__bases__, body) # We need to modify model._meta.db_table, but everything explodes # if the change isn't reversed before the end of this method. This # context manager helps us avoid that situation. @contextlib.contextmanager def altered_table_name(model, temporary_table_name): original_table_name = model._meta.db_table model._meta.db_table = temporary_table_name yield model._meta.db_table = original_table_name with altered_table_name(model, model._meta.db_table + "__old"): # Rename the old table to make way for the new self.alter_db_table(model, temp_model._meta.db_table, model._meta.db_table) # Create a new table with the updated schema. We remove things # from the deferred SQL that match our table name, too self.deferred_sql = [ x for x in self.deferred_sql if temp_model._meta.db_table not in x ] self.create_model(temp_model) # Copy data from the old table into the new table field_maps = list(mapping.items()) self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % ( self.note_name(temp_model._meta.db_table), ', '.join(self.note_name(x) for x, y in field_maps), ', '.join(y for x, y in field_maps), self.note_name(model._meta.db_table), )) # Delete the old table self.delete_model(model, handle_autom2m=False) # Run deferred SQL on correct table for sql in self.deferred_sql: self.execute(sql) self.deferred_sql = [] # Fix any PK-removed field if restore_pk_field: restore_pk_field.primary_key = True
def test_wait_for_apps_ready_without_exception(self): app_reg = Apps() app_reg.ready_event.set() thread = mock.MagicMock() thread.is_alive.return_value = True self.assertTrue(self.reloader.wait_for_apps_ready(app_reg, thread))
def render(self, include_real=None, ignore_swappable=False, skip_cache=False): "Turns the project state into actual models in a new Apps" if self.apps is None or skip_cache: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. # FKs/M2Ms from real apps are also not included as they just # mess things up with partial states (due to lack of dependencies) real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append( ModelState.from_model(model, exclude_rels=True)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([ AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels)) ]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError( "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an " "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see " "https://docs.djangoproject.com/en/1.7/topics/migrations/#dependencies " "for more" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items( ): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: app_label = "%s.%s" % (lookup_model[0], lookup_model[1]) if app_label == settings.AUTH_USER_MODEL and ignore_swappable: continue # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" msg = "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}" raise ValueError( msg.format(field=operations[0][1], model=lookup_model)) else: do_pending_lookups(model) try: return self.apps finally: if skip_cache: self.apps = None
class Meta: app_label = "migrations" apps = Apps()
from django.apps.registry import Apps from django.db import models from django.utils.encoding import python_2_unicode_compatible # Because we want to test creation and deletion of these as separate things, # these models are all inserted into a separate Apps so the main test # runner doesn't migrate them. new_apps = Apps() class Author(models.Model): name = models.CharField(max_length=255) height = models.PositiveIntegerField(null=True, blank=True) class Meta: apps = new_apps class AuthorWithM2M(models.Model): name = models.CharField(max_length=255) class Meta: apps = new_apps class Book(models.Model): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) pub_date = models.DateTimeField()
def enable(self): self.old_apps = Options.default_apps apps = Apps(self.installed_apps) setattr(Options, 'default_apps', apps) return apps
class ProjectState(object): """ Represents the entire project's overall state. This is the item that is passed around - we do it here rather than at the app level so that cross-app FKs/etc. resolve properly. """ def __init__(self, models=None): self.models = models or {} self.apps = None def add_model_state(self, model_state): self.models[(model_state.app_label, model_state.name.lower())] = model_state def clone(self): "Returns an exact copy of this ProjectState" return ProjectState( models=dict((k, v.clone()) for k, v in self.models.items()) ) def render(self): "Turns the project state into actual models in a new Apps" if self.apps is None: self.apps = Apps() # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) app_configs = [AppConfigStub(label) for label in sorted(app_labels)] self.apps.populate_apps(app_configs) self.apps.populate_models() # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models return self.apps @classmethod def from_apps(cls, apps): "Takes in an Apps and returns a ProjectState matching it" app_models = {} for model in apps.get_models(): model_state = ModelState.from_model(model) app_models[(model_state.app_label, model_state.name.lower())] = model_state return cls(app_models) def __eq__(self, other): if set(self.models.keys()) != set(other.models.keys()): return False return all(model == other.models[key] for key, model in self.models.items()) def __ne__(self, other): return not (self == other)
class Meta: apps = Apps() app_label = "migrations" db_table = "django_migrations"
def test_singleton_master(self): """ Ensures that only one master registry can exist. """ with self.assertRaises(RuntimeError): Apps(installed_apps=None)
class ProjectState(object): """ Represents the entire project's overall state. This is the item that is passed around - we do it here rather than at the app level so that cross-app FKs/etc. resolve properly. """ def __init__(self, models=None, real_apps=None): self.models = models or {} self.apps = None # Apps to include from main registry, usually unmigrated ones self.real_apps = real_apps or [] def add_model_state(self, model_state): self.models[(model_state.app_label, model_state.name.lower())] = model_state def clone(self): "Returns an exact copy of this ProjectState" return ProjectState( models=dict((k, v.clone()) for k, v in self.models.items()), real_apps=self.real_apps, ) def render(self, include_real=None, ignore_swappable=False): "Turns the project state into actual models in a new Apps" if self.apps is None: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([ AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels)) ]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items( ): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: if "%s.%s" % ( lookup_model[0], lookup_model[1] ) == settings.AUTH_USER_MODEL and ignore_swappable: continue # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" raise ValueError( "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}" .format( field=operations[0][1], model=lookup_model, )) else: do_pending_lookups(model) return self.apps @classmethod def from_apps(cls, apps): "Takes in an Apps and returns a ProjectState matching it" app_models = {} for model in apps.get_models(): model_state = ModelState.from_model(model) app_models[(model_state.app_label, model_state.name.lower())] = model_state return cls(app_models) def __eq__(self, other): if set(self.models.keys()) != set(other.models.keys()): return False if set(self.real_apps) != set(other.real_apps): return False return all(model == other.models[key] for key, model in self.models.items()) def __ne__(self, other): return not (self == other)
def test_add_relations(self): """ #24573 - Adding relations to existing models should reload the referenced models too. """ new_apps = Apps() class A(models.Model): class Meta: app_label = 'something' apps = new_apps class B(A): class Meta: app_label = 'something' apps = new_apps class C(models.Model): class Meta: app_label = 'something' apps = new_apps project_state = ProjectState() project_state.add_model(ModelState.from_model(A)) project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(C)) project_state.apps # We need to work with rendered models old_state = project_state.clone() model_a_old = old_state.apps.get_model('something', 'A') model_b_old = old_state.apps.get_model('something', 'B') model_c_old = old_state.apps.get_model('something', 'C') # Check that the relations between the old models are correct self.assertIs( model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs( model_b_old._meta.get_field('a_ptr').related_model, model_a_old) operation = AddField( 'c', 'to_a', models.OneToOneField( 'something.A', models.CASCADE, related_name='from_c', )) operation.state_forwards('something', project_state) model_a_new = project_state.apps.get_model('something', 'A') model_b_new = project_state.apps.get_model('something', 'B') model_c_new = project_state.apps.get_model('something', 'C') # Check that all models have changed self.assertIsNot(model_a_old, model_a_new) self.assertIsNot(model_b_old, model_b_new) self.assertIsNot(model_c_old, model_c_new) # Check that the relations between the old models still hold self.assertIs( model_a_old._meta.get_field('b').related_model, model_b_old) self.assertIs( model_b_old._meta.get_field('a_ptr').related_model, model_a_old) # Check that the relations between the new models correct self.assertIs( model_a_new._meta.get_field('b').related_model, model_b_new) self.assertIs( model_b_new._meta.get_field('a_ptr').related_model, model_a_new) self.assertIs( model_a_new._meta.get_field('from_c').related_model, model_c_new) self.assertIs( model_c_new._meta.get_field('to_a').related_model, model_a_new)
class ProjectState(object): """ Represents the entire project's overall state. This is the item that is passed around - we do it here rather than at the app level so that cross-app FKs/etc. resolve properly. """ def __init__(self, models=None, real_apps=None): self.models = models or {} self.apps = None # Apps to include from main registry, usually unmigrated ones self.real_apps = real_apps or [] def add_model_state(self, model_state): self.models[(model_state.app_label, model_state.name.lower())] = model_state def clone(self): "Returns an exact copy of this ProjectState" return ProjectState( models=dict((k, v.clone()) for k, v in self.models.items()), real_apps=self.real_apps, ) def render(self, include_real=None, ignore_swappable=False, skip_cache=False): "Turns the project state into actual models in a new Apps" if self.apps is None or skip_cache: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. # FKs/M2Ms from real apps are also not included as they just # mess things up with partial states (due to lack of dependencies) real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model, exclude_rels=True)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r\nThis can happen if you are inheriting models from an app with migrations (e.g. contrib.auth)\n in an app with no migrations; see https://docs.djangoproject.com/en/1.7/topics/migrations/#dependencies for more" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items(): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: if "%s.%s" % (lookup_model[0], lookup_model[1]) == settings.AUTH_USER_MODEL and ignore_swappable: continue # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" raise ValueError("Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}".format( field=operations[0][1], model=lookup_model, )) else: do_pending_lookups(model) try: return self.apps finally: if skip_cache: self.apps = None @classmethod def from_apps(cls, apps): "Takes in an Apps and returns a ProjectState matching it" app_models = {} for model in apps.get_models(include_swapped=True): model_state = ModelState.from_model(model) app_models[(model_state.app_label, model_state.name.lower())] = model_state return cls(app_models) def __eq__(self, other): if set(self.models.keys()) != set(other.models.keys()): return False if set(self.real_apps) != set(other.real_apps): return False return all(model == other.models[key] for key, model in self.models.items()) def __ne__(self, other): return not (self == other)
def render(self, include_real=None, ignore_swappable=False): "Turns the project state into actual models in a new Apps" if self.apps is None: # Any apps in self.real_apps should have all their models included # in the render. We don't use the original model instances as there # are some variables that refer to the Apps object. real_models = [] for app_label in self.real_apps: app = global_apps.get_app_config(app_label) for model in app.get_models(): real_models.append(ModelState.from_model(model)) # Populate the app registry with a stub for each application. app_labels = set(model_state.app_label for model_state in self.models.values()) self.apps = Apps([ AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels)) ]) # We keep trying to render the models in a loop, ignoring invalid # base errors, until the size of the unrendered models doesn't # decrease by at least one, meaning there's a base dependency loop/ # missing base. unrendered_models = list(self.models.values()) + real_models while unrendered_models: new_unrendered_models = [] for model in unrendered_models: try: model.render(self.apps) except InvalidBasesError: new_unrendered_models.append(model) if len(new_unrendered_models) == len(unrendered_models): raise InvalidBasesError("Cannot resolve bases for %r" % new_unrendered_models) unrendered_models = new_unrendered_models # make sure apps has no dangling references if self.apps._pending_lookups: # There's some lookups left. See if we can first resolve them # ourselves - sometimes fields are added after class_prepared is sent for lookup_model, operations in self.apps._pending_lookups.items( ): try: model = self.apps.get_model(lookup_model[0], lookup_model[1]) except LookupError: if "%s.%s" % ( lookup_model[0], lookup_model[1] ) == settings.AUTH_USER_MODEL and ignore_swappable: continue # Raise an error with a best-effort helpful message # (only for the first issue). Error message should look like: # "ValueError: Lookup failed for model referenced by # field migrations.Book.author: migrations.Author" raise ValueError( "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}" .format( field=operations[0][1], model=lookup_model, )) else: do_pending_lookups(model) return self.apps