Beispiel #1
0
    def test_get_app_with_invalid_app_and_emptyok_false(self):
        """Testing get_apps with invalid app and emptyOK=False"""
        if django.VERSION[:2] >= (1, 7):
            message = "No installed app with label 'invalid_app'."
        else:
            message = 'App with label invalid_app could not be found'

        with self.assertRaisesMessage(ImproperlyConfigured, message):
            get_app('invalid_app', emptyOK=False)
    def test_get_app_with_invalid_app_and_emptyok_false(self):
        """Testing get_apps with invalid app and emptyOK=False"""
        if django.VERSION[:2] >= (1, 7):
            message = "No installed app with label 'invalid_app'."
        else:
            message = 'App with label invalid_app could not be found'

        with self.assertRaisesMessage(ImproperlyConfigured, message):
            get_app('invalid_app', emptyOK=False)
Beispiel #3
0
    def ensure_deleted_apps(self, app_labels=None):
        """Ensure an app's models and evolutions aren't in the database.

        Args:
            app_labels (list of unicode, optional):
                An explicit list of app labels to delete. This defaults
                to the built-in list of test app labels.
        """
        if app_labels is None:
            app_labels = self.builtin_test_app_labels

        # Do this one-by-one, since we might have some but not all of the
        # apps in the database.
        sql = []

        for app_label in reversed(app_labels):
            try:
                app = get_app(app_label)
            except Exception:
                # This wasn't registered, so we can skip it.
                continue

            sql += sql_delete(app)

        if sql:
            execute_test_sql(sql)

        Evolution.objects.filter(app_label__in=app_labels).delete()
Beispiel #4
0
    def _add_tasks(self, app_labels):
        """Add tasks to the evolver, based on the command options.

        This will queue up the applications that need to be evolved, and
        queue up the purging of stale applications if requested.

        Args:
            app_labels (list of unicode):
                The list of app labels to evolve. If this is empty, all
                registered apps will be evolved.
        """
        evolver = self.evolver

        if app_labels:
            # The caller wants to evolve specific apps. Queue each one,
            # handling any invalid app labels in the process.
            try:
                for app_label in app_labels:
                    evolver.queue_evolve_app(get_app(app_label))
            except (ImportError, ImproperlyConfigured) as e:
                raise CommandError(
                    _('%s. Are you sure your INSTALLED_APPS setting is '
                      'correct?') % e)
        else:
            # The caller wants the default behavior of evolving all apps
            # with pending evolutions.
            evolver.queue_evolve_all_apps()

        if self.purge:
            # The caller wants to purge all old stale applications that
            # no longer exist.
            #
            # Note that we don't do this by default, since those apps
            # might not be permanently added to the list of installed apps.
            evolver.queue_purge_old_apps()
Beispiel #5
0
    def test_excludes_unchanged_models(self):
        """Testing get_app_pending_mutations excludes mutations for unchanged
        models
        """
        self.ensure_evolution_models()

        latest_version = Version.objects.current_version()
        old_project_sig = latest_version.signature

        # Make a change in the new signature for Evolution, so it will be
        # considered for pending mutations.
        new_project_sig = old_project_sig.clone()
        model_sig = (
            new_project_sig
            .get_app_sig('django_evolution')
            .get_model_sig('Evolution')
        )
        model_sig.get_field_sig('app_label').field_attrs['max_length'] = 500

        # Only the first mutation should match.
        mutations = [
            ChangeField('Evolution', 'app_label', models.CharField,
                        max_length=500),
            AddField('Version', 'new_field', models.BooleanField,
                     default=True),
        ]

        pending_mutations = get_app_pending_mutations(
            app=get_app('django_evolution'),
            old_project_sig=old_project_sig,
            project_sig=new_project_sig,
            mutations=mutations)

        self.assertEqual(pending_mutations, [mutations[0]])
Beispiel #6
0
    def test_with_builtin(self):
        """Testing get_evolution_module with built-in evolutions"""
        from django_evolution.builtin_evolutions import auth_delete_message

        self.assertIs(get_evolution_module(get_app('auth'),
                                           'auth_delete_message'),
                      auth_delete_message)
Beispiel #7
0
    def test_with_applied_move_to_migrations(self):
        """Testing get_app_upgrade_info with applied MoveToDjangoMigrations
        """
        app = get_app('auth')

        self.ensure_evolved_apps([app])
        self.assertIn('auth_move_to_migrations', get_applied_evolutions(app))

        # Check without the evolutions applied.
        upgrade_info = get_app_upgrade_info(app)

        if supports_migrations:
            self.assertTrue(upgrade_info['has_migrations'])
        else:
            self.assertFalse(upgrade_info['has_migrations'])

        self.assertTrue(upgrade_info['applied_migrations'].has_migration_info(
            app_label='auth',
            name='0001_initial'))
        self.assertTrue(upgrade_info['has_evolutions'])
        self.assertEqual(upgrade_info['upgrade_method'],
                         UpgradeMethod.MIGRATIONS)

        # Check with the evolutions applied.
        self.assertEqual(get_app_upgrade_info(app, simulate_applied=True),
                         upgrade_info)
Beispiel #8
0
 def test_with_builtin(self):
     """Testing get_evolution_sequence with built-in evolutions"""
     self.assertEqual(
         get_evolution_sequence(get_app('contenttypes')),
         [
             'contenttypes_unique_together_baseline',
             'contenttypes_move_to_migrations',
         ])
Beispiel #9
0
    def test_with_app(self):
        """Testing get_evolution_module with app-provided evolutions"""
        from django_evolution.tests.evolutions_app.evolutions import \
            first_evolution

        self.assertIs(get_evolution_module(get_app('evolutions_app'),
                                           'first_evolution'),
                      first_evolution)
Beispiel #10
0
    def test_with_project(self):
        """Testing get_evolutions_source with project-provided evolutions"""
        custom_evolutions = {
            'django_evolution.tests.migrations_app':
                'django_evolution.tests.evolutions_app.evolutions',
        }

        with self.settings(CUSTOM_EVOLUTIONS=custom_evolutions):
            self.assertEqual(get_evolutions_source(get_app('migrations_app')),
                             EvolutionsSource.PROJECT)
    def test_queue_evolve_all_apps_with_app_already_queued(self):
        """Testing Evolver.queue_evolve_all_apps with app already queued"""
        evolver = Evolver()
        evolver.queue_evolve_app(get_app('django_evolution'))

        message = '"django_evolution" is already being tracked for evolution'

        with self.assertRaisesMessage(EvolutionTaskAlreadyQueuedError,
                                      message):
            evolver.queue_evolve_all_apps()
    def test_queue_evolve_all_apps_with_app_already_queued(self):
        """Testing Evolver.queue_evolve_all_apps with app already queued"""
        evolver = Evolver()
        evolver.queue_evolve_app(get_app('django_evolution'))

        message = '"django_evolution" is already being tracked for evolution'

        with self.assertRaisesMessage(EvolutionTaskAlreadyQueuedError,
                                      message):
            evolver.queue_evolve_all_apps()
Beispiel #13
0
 def test_without_dependencies(self):
     """Testing get_evolution_app_dependencies without dependencies"""
     self.assertEqual(
         get_evolution_app_dependencies(get_app('evolution_deps_app')),
         {
             'after_evolutions': set(),
             'after_migrations': set(),
             'before_evolutions': set(),
             'before_migrations': set(),
         })
    def test_queue_evolve_app(self):
        """Testing Evolver.queue_evolve_app"""
        app = get_app('django_evolution')

        evolver = Evolver()
        evolver.queue_evolve_app(app)

        tasks = list(evolver.tasks)
        self.assertEqual(len(tasks), 1)
        self.assertIsInstance(tasks[0], EvolveAppTask)
        self.assertIs(tasks[0].app, app)
    def test_queue_evolve_app(self):
        """Testing Evolver.queue_evolve_app"""
        app = get_app('django_evolution')

        evolver = Evolver()
        evolver.queue_evolve_app(app)

        tasks = list(evolver.tasks)
        self.assertEqual(len(tasks), 1)
        self.assertIsInstance(tasks[0], EvolveAppTask)
        self.assertIs(tasks[0].app, app)
Beispiel #16
0
    def test_with_project(self):
        """Testing get_evolutions_path with project-provided evolutions"""
        custom_evolutions = {
            'django_evolution.tests.evolutions_app':
                'django_evolution.tests.evolutions_app.evolutions',
        }

        with self.settings(CUSTOM_EVOLUTIONS=custom_evolutions):
            self.assertEqual(get_evolutions_path(get_app('evolutions_app')),
                             os.path.join(self.base_dir, 'tests',
                                          'evolutions_app', 'evolutions'))
Beispiel #17
0
    def test_with_project(self):
        """Testing get_evolution_sequence with project-provided evolutions"""
        new_settings = {
            'CUSTOM_EVOLUTIONS': {
                'django_evolution.tests.migrations_app':
                    'django_evolution.tests.evolutions_app.evolutions',
            },
        }

        with self.settings(DJANGO_EVOLUTION=new_settings):
            self.assertEqual(get_evolution_sequence(get_app('migrations_app')),
                             ['first_evolution', 'second_evolution'])
Beispiel #18
0
def _on_app_models_updated(app, using=DEFAULT_DB_ALIAS, **kwargs):
    """Handler for when an app's models were updated.

    This is called in response to a syncdb or migrate operation for an app.
    The very first time this is called for Django Evolution's app, this will
    set up the current project version to contain the full database signature,
    and to populate the list of evolutions with all currently-registered ones.

    This is only done when we're not actively evolving the database. That
    means it will only be called if we're running unit tests or in reaction
    to some other process that emits the signals (such as the flush management
    command).

    Args:
        app (module):
            The app models module that was updated.

        using (str, optional):
            The database being updated.

        **kwargs (dict):
            Additional keyword arguments provided by the signal handler for
            the syncdb or migrate operation.
    """
    global _django_evolution_app

    if _django_evolution_app is None:
        _django_evolution_app = get_app('django_evolution')

    if (_evolve_lock > 0 or
        app is not _django_evolution_app or
        Version.objects.using(using).exists()):
        return

    evolver = Evolver(database_name=using)

    version = evolver.version
    version.signature = evolver.target_project_sig
    version.save(using=using)

    evolutions = []

    for app in get_apps():
        app_label = get_app_label(app)

        evolutions += [
            Evolution(app_label=app_label,
                      label=evolution_label,
                      version=version)
            for evolution_label in get_evolution_sequence(app)
        ]

    Evolution.objects.using(using).bulk_create(evolutions)
Beispiel #19
0
    def test_with_project_deprecated_setting(self):
        """Testing get_evolution_sequence with project-provided evolutions
        and deprecated settings.CUSTOM_EVOLUTIONS
        """
        custom_evolutions = {
            'django_evolution.tests.migrations_app':
                'django_evolution.tests.evolutions_app.evolutions',
        }

        with self.settings(CUSTOM_EVOLUTIONS=custom_evolutions):
            self.assertEqual(get_evolution_sequence(get_app('migrations_app')),
                             ['first_evolution', 'second_evolution'])
    def test_queue_evolve_app_after_prepared(self):
        """Testing Evolver.queue_evolve_app after tasks were already prepared
        """
        evolver = Evolver()

        # Force preparation of tasks.
        list(evolver.tasks)

        message = ('Evolution tasks have already been prepared. New tasks '
                   'cannot be added.')

        with self.assertRaisesMessage(QueueEvolverTaskError, message):
            evolver.queue_evolve_app(get_app('django_evolution'))
Beispiel #21
0
    def test_with_project_deprecated_setting(self):
        """Testing get_evolutions_module_name with project-provided evolutions
        and deprecated settings.CUSTOM_EVOLUTIONS
        """
        custom_evolutions = {
            'django_evolution':
                'django_evolution.tests.evolutions_app.evolutions',
        }

        with self.settings(CUSTOM_EVOLUTIONS=custom_evolutions):
            self.assertEqual(
                get_evolutions_module_name(get_app('django_evolution')),
                'django_evolution.tests.evolutions_app.evolutions')
Beispiel #22
0
 def test_with_dependencies_and_custom_evolutions(self):
     """Testing get_evolution_dependencies with dependencies and custom
     evolutions
     """
     self.assertEqual(
         get_evolution_dependencies(
             app=get_app('evolution_deps_app'),
             evolution_label='test_custom_evolution',
             custom_evolutions=[
                 {
                     'label': 'test_custom_evolution',
                     'after_evolutions': [
                         ('other_app1', 'other_evolution1'),
                         ('other_app1', 'other_evolution2'),
                     ],
                     'after_migrations': [
                         ('other_app2', '0001_migration'),
                         ('other_app2', '0002_migration'),
                     ],
                     'before_evolutions': [
                         ('other_app3', 'other_evolution3'),
                         ('other_app3', 'other_evolution4'),
                     ],
                     'before_migrations': [
                         ('other_app4', '0003_migration'),
                         ('other_app4', '0004_migration'),
                     ],
                     'mutations': [
                         AddField('MyModel', 'new_field',
                                  models.BooleanField),
                     ],
                 },
             ]),
         {
             'after_evolutions': {
                 ('other_app1', 'other_evolution1'),
                 ('other_app1', 'other_evolution2'),
             },
             'after_migrations': {
                 ('other_app2', '0001_migration'),
                 ('other_app2', '0002_migration'),
             },
             'before_evolutions': {
                 ('other_app3', 'other_evolution3'),
                 ('other_app3', 'other_evolution4'),
             },
             'before_migrations': {
                 ('other_app4', '0003_migration'),
                 ('other_app4', '0004_migration'),
             },
         })
Beispiel #23
0
    def test_with_project(self):
        """Testing get_evolution_module with project-provided evolutions"""
        from django_evolution.tests.evolutions_app.evolutions import \
            first_evolution

        custom_evolutions = {
            'django_evolution':
                'django_evolution.tests.evolutions_app.evolutions',
        }

        with self.settings(CUSTOM_EVOLUTIONS=custom_evolutions):
            self.assertIs(get_evolution_module(get_app('django_evolution'),
                                               'first_evolution'),
                          first_evolution)
Beispiel #24
0
    def test_with_project(self):
        """Testing get_evolutions_module_name with project-provided evolutions
        """
        new_settings = {
            'CUSTOM_EVOLUTIONS': {
                'django_evolution':
                    'django_evolution.tests.evolutions_app.evolutions',
            },
        }

        with self.settings(DJANGO_EVOLUTION=new_settings):
            self.assertEqual(
                get_evolutions_module_name(get_app('django_evolution')),
                'django_evolution.tests.evolutions_app.evolutions')
Beispiel #25
0
    def test_with_project(self):
        """Testing get_evolutions_module with project-provided evolutions"""
        from django_evolution.tests.evolutions_app import evolutions

        new_settings = {
            'CUSTOM_EVOLUTIONS': {
                'django_evolution':
                    'django_evolution.tests.evolutions_app.evolutions',
            },
        }

        with self.settings(DJANGO_EVOLUTION=new_settings):
            self.assertIs(get_evolutions_module(get_app('django_evolution')),
                          evolutions)
Beispiel #26
0
def replace_models(database_state, apps_to_models):
    """Temporarily replace existing models with new definitions.

    This allows a unit test to replace some previously-defined models, backing
    up the old ones and restoring them once the operations are complete.

    Args:
        database_state (django_evolution.db.state.DatabaseState):
            The currently-computed database state.

        apps_to_models (dict):
            A mapping of app labels to lists of tuples. Each tuple contains:

            1. The model class name to register (which can be different from
               the real class name).
            2. The model class.

    Context:
        The replaced models will be available in Django.
    """
    old_models_info = []

    for app_label, app_models in six.iteritems(apps_to_models):
        app = get_app(app_label)
        old_models_info.append((app_label, get_models(app)))

        # Needed in Django 1.6 to ensure the model isn't filtered out in
        # our own get_models() calls.
        for name, app_model in app_models:
            app_model.__module__ = app.__name__

    try:
        for app_label, app_models in six.iteritems(apps_to_models):
            register_models(database_state=database_state,
                            models=app_models,
                            new_app_label=app_label)

        yield
    finally:
        unregister_test_models()

        for app_label, old_models in old_models_info:
            register_app_models(
                app_label,
                [
                    (model._meta.object_name.lower(), model)
                    for model in old_models
                ],
                reset=True)
Beispiel #27
0
 def test_with_move_to_django_migrations(self):
     """Testing get_evolution_dependencies with MoveToDjangoMigrations
     mutation
     """
     self.assertEqual(
         get_evolution_dependencies(get_app('admin'),
                                    'admin_move_to_migrations'),
         {
             'after_evolutions': set(),
             'after_migrations': {
                 ('admin', '0001_initial'),
             },
             'before_evolutions': set(),
             'before_migrations': set(),
         })
    def test_queue_evolve_app_after_prepared(self):
        """Testing Evolver.queue_evolve_app after tasks were already prepared
        """
        evolver = Evolver()

        # Force preparation of tasks.
        list(evolver.tasks)

        message = (
            'Evolution tasks have already been prepared. New tasks '
            'cannot be added.'
        )

        with self.assertRaisesMessage(QueueEvolverTaskError, message):
            evolver.queue_evolve_app(get_app('django_evolution'))
Beispiel #29
0
    def test_with_rename_model(self):
        """Testing get_app_pending_mutations always includes RenameModel
        mutations
        """
        self.ensure_evolution_models()

        mutations = [
            RenameModel('Version', 'ProjectVersion',
                        db_table='django_project_version'),
        ]

        pending_mutations = get_app_pending_mutations(
            app=get_app('django_evolution'),
            mutations=mutations)

        self.assertEqual(pending_mutations, mutations)
Beispiel #30
0
    def test_with_migrations(self):
        """Testing get_app_upgrade_info with migrations only"""
        app = get_app('migrations_app')

        upgrade_info = get_app_upgrade_info(app)

        self.assertEqual(
            upgrade_info,
            {
                'applied_migrations': None,
                'has_evolutions': False,
                'has_migrations': True,
                'upgrade_method': UpgradeMethod.MIGRATIONS,
            })
        self.assertEqual(
            get_app_upgrade_info(app, simulate_applied=True),
            upgrade_info)
Beispiel #31
0
 def test_with_dependencies(self):
     """Testing get_evolution_app_dependencies with dependencies"""
     self.assertEqual(
         get_evolution_app_dependencies(get_app('app_deps_app')),
         {
             'after_evolutions': {
                 'evolutions_app',
                 ('evolutions_app', 'first_evolution'),
             },
             'after_migrations': {
                 ('migrations_app', '0001_initial'),
             },
             'before_evolutions': {
                 'evolutions_app2',
                 ('evolutions_app2', 'second_evolution'),
             },
             'before_migrations': {
                 ('migrations_app2', '0002_add_field'),
             },
         })
Beispiel #32
0
 def test_without_dependencies(self):
     """Testing get_evolution_dependencies without dependencies"""
     self.assertEqual(
         get_evolution_dependencies(
             app=get_app('app_deps_app'),
             evolution_label='test_evolution',
             custom_evolutions=[
                 {
                     'label': 'test_custom_evolution',
                     'mutations': [
                         AddField('MyModel', 'new_field',
                                  models.BooleanField),
                     ],
                 },
             ]),
         {
             'after_evolutions': set(),
             'after_migrations': set(),
             'before_evolutions': set(),
             'before_migrations': set(),
         })
Beispiel #33
0
    def test_with_unregistered_app_sig(self):
        """Testing get_app_pending_mutations with unregistered app signature
        """
        self.ensure_evolution_models()

        latest_version = Version.objects.current_version()
        self.assertIsNone(
            latest_version.signature.get_app_sig('evolutions_app'))

        mutations = [
            ChangeField('EvolutionsAppTestModel', 'char_field',
                        models.CharField, max_length=10),
            AddField('EvolutionsAppTestModel', 'new_field',
                     models.IntegerField, default=42,
                     null=True),
        ]

        pending_mutations = get_app_pending_mutations(
            app=get_app('evolutions_app'),
            mutations=mutations)

        self.assertEqual(pending_mutations, mutations)
Beispiel #34
0
    def test_excludes_new_models(self):
        """Testing get_app_pending_mutations excludes mutations for new models
        """
        self.ensure_evolution_models()

        latest_version = Version.objects.current_version()
        old_project_sig = latest_version.signature

        # Remove the model signature from the old project signature.
        old_project_sig.get_app_sig('django_evolution').remove_model_sig(
            'Evolution')

        # Make a change in the new signature for Evolution, so it will be
        # considered for pending mutations.
        new_project_sig = old_project_sig.clone()

        model_sig = (
            new_project_sig
            .get_app_sig('django_evolution')
            .get_model_sig('Version')
        )
        model_sig.get_field_sig('when').field_attrs['null'] = False

        # Only the first mutation should match.
        mutations = [
            ChangeField('Evolution', 'field1', models.CharField,
                        max_length=500),
            ChangeField('Version', 'when', models.DateTimeField,
                        null=True),
        ]

        pending_mutations = get_app_pending_mutations(
            app=get_app('django_evolution'),
            old_project_sig=old_project_sig,
            project_sig=new_project_sig,
            mutations=mutations)

        self.assertEqual(pending_mutations, [mutations[1]])
Beispiel #35
0
    def _add_tasks(self, app_labels):
        """Add tasks to the evolver, based on the command options.

        This will queue up the applications that need to be evolved, and
        queue up the purging of stale applications if requested.

        Args:
            app_labels (list of unicode):
                The list of app labels to evolve. If this is empty, all
                registered apps will be evolved.
        """
        evolver = self.evolver

        if app_labels:
            # The caller wants to evolve specific apps. Queue each one,
            # handling any invalid app labels in the process.
            try:
                for app_label in app_labels:
                    evolver.queue_evolve_app(get_app(app_label))
            except (ImportError, ImproperlyConfigured) as e:
                raise CommandError(
                    _('%s. Are you sure your INSTALLED_APPS setting is '
                      'correct?')
                    % e)
        else:
            # The caller wants the default behavior of evolving all apps
            # with pending evolutions.
            evolver.queue_evolve_all_apps()

        if self.purge:
            # The caller wants to purge all old stale applications that
            # no longer exist.
            #
            # Note that we don't do this by default, since those apps
            # might not be permanently added to the list of installed apps.
            evolver.queue_purge_old_apps()
 def test_get_app_with_valid_and_has_model(self):
     """Testing get_apps with valid app containing models"""
     self.assertIsNotNone(get_app('django_evolution'))
 def test_get_app_with_valid_no_models_and_emptyok_true(self):
     """Testing get_apps with valid app without models and emptyOK=True"""
     self.assertIsNone(get_app('no_models_app', emptyOK=True))
    def test_get_app_with_valid_no_models_and_emptyok_false(self):
        """Testing get_apps with valid app without models and emptyOK=False"""
        message = 'App with label no_models_app is missing a models.py module.'

        with self.assertRaisesMessage(ImproperlyConfigured, message):
            get_app('no_models_app', emptyOK=False)
Beispiel #39
0
def _on_app_models_updated(app, verbosity=1, using=DEFAULT_DB_ALIAS, **kwargs):
    """Handler for when an app's models were updated.

    This is called in response to a syncdb or migrate operation for an app.
    It will install baselines for any new models, record the changes in the
    evolution history, and notify the user if any of the changes require an
    evolution.

    Args:
        app (module):
            The app models module that was updated.

        verbosity (int, optional):
            The verbosity used to control output. This will have been provided
            by the syncdb or migrate command.

        using (str, optional):
            The database being updated.

        **kwargs (dict):
            Additional keyword arguments provided by the signal handler for
            the syncdb or migrate operation.
    """
    project_sig = ProjectSignature.from_database(using)

    try:
        latest_version = Version.objects.current_version(using=using)
    except Version.DoesNotExist:
        # We need to create a baseline version.
        if verbosity > 0:
            print("Installing baseline version")

        latest_version = Version(signature=project_sig)
        latest_version.save(using=using)

        for a in get_apps():
            _install_baseline(app=a,
                              latest_version=latest_version,
                              using=using,
                              verbosity=verbosity)

    unapplied = get_unapplied_evolutions(app, using)

    if unapplied:
        print(style.NOTICE('There are unapplied evolutions for %s.'
                           % get_app_label(app)))

    # Evolutions are checked over the entire project, so we only need to check
    # once. We do this check when Django Evolutions itself is synchronized.

    if app is get_app('django_evolution'):
        old_project_sig = latest_version.signature

        # If any models or apps have been added, a baseline must be set
        # for those new models
        changed = False
        new_apps = []

        for new_app_sig in project_sig.app_sigs:
            app_id = new_app_sig.app_id
            old_app_sig = old_project_sig.get_app_sig(app_id)

            if old_app_sig is None:
                # App has been added
                old_project_sig.add_app_sig(new_app_sig.clone())
                new_apps.append(app_id)
                changed = True
            else:
                for new_model_sig in new_app_sig.model_sigs:
                    model_name = new_model_sig.model_name

                    old_model_sig = old_app_sig.get_model_sig(model_name)

                    if old_model_sig is None:
                        # Model has been added
                        old_app_sig.add_model_sig(
                            project_sig
                            .get_app_sig(app_id)
                            .get_model_sig(model_name)
                            .clone())
                        changed = True

        if changed:
            if verbosity > 0:
                print("Adding baseline version for new models")

            latest_version = Version(signature=old_project_sig)
            latest_version.save(using=using)

            for app_name in new_apps:
                app = get_app(app_name, True)

                if app:
                    _install_baseline(app=app,
                                      latest_version=latest_version,
                                      using=using,
                                      verbosity=verbosity)

        # TODO: Model introspection step goes here.
        # # If the current database state doesn't match the last
        # # saved signature (as reported by latest_version),
        # # then we need to update the Evolution table.
        # actual_sig = introspect_project_sig()
        # acutal = pickle.dumps(actual_sig)
        # if actual != latest_version.signature:
        #     nudge = Version(signature=actual)
        #     nudge.save()
        #     latest_version = nudge

        diff = Diff(old_project_sig, project_sig)

        if not diff.is_empty():
            print(style.NOTICE(
                'Project signature has changed - an evolution is required'))

            if verbosity > 1:
                print(diff)
def evolution(app, created_models, verbosity=1, **kwargs):
    """
    A hook into syncdb's post_syncdb signal, that is used to notify the user
    if a model evolution is necessary.
    """
    default_db = DEFAULT_DB_ALIAS

    db = kwargs.get('db', default_db)
    proj_sig = create_project_sig(db)
    signature = pickle.dumps(proj_sig)

    using_args = {
        'using': db,
    }

    try:
        latest_version = \
            django_evolution.Version.objects.current_version(using=db)
    except django_evolution.Version.DoesNotExist:
        # We need to create a baseline version.
        if verbosity > 0:
            print "Installing baseline version"

        latest_version = django_evolution.Version(signature=signature)
        latest_version.save(**using_args)

        for a in get_apps():
            install_baseline(a, latest_version, using_args, verbosity)

    unapplied = get_unapplied_evolutions(app, db)

    if unapplied:
        print style.NOTICE('There are unapplied evolutions for %s.'
                           % app.__name__.split('.')[-2])

    # Evolutions are checked over the entire project, so we only need to check
    # once. We do this check when Django Evolutions itself is synchronized.
    if app == django_evolution:
        old_proj_sig = pickle.loads(str(latest_version.signature))

        # If any models or apps have been added, a baseline must be set
        # for those new models
        changed = False
        new_apps = []

        for app_name, new_app_sig in proj_sig.items():
            if app_name == '__version__':
                # Ignore the __version__ tag
                continue

            old_app_sig = old_proj_sig.get(app_name, None)

            if old_app_sig is None:
                # App has been added
                old_proj_sig[app_name] = proj_sig[app_name]
                new_apps.append(app_name)
                changed = True
            else:
                for model_name, new_model_sig in new_app_sig.items():
                    old_model_sig = old_app_sig.get(model_name, None)

                    if old_model_sig is None:
                        # Model has been added
                        old_proj_sig[app_name][model_name] = \
                            proj_sig[app_name][model_name]
                        changed = True

        if changed:
            if verbosity > 0:
                print "Adding baseline version for new models"

            latest_version = \
                django_evolution.Version(signature=pickle.dumps(old_proj_sig))
            latest_version.save(**using_args)

            for app_name in new_apps:
                app = get_app(app_name, True)

                if app:
                    install_baseline(app, latest_version, using_args,
                                     verbosity)

        # TODO: Model introspection step goes here.
        # # If the current database state doesn't match the last
        # # saved signature (as reported by latest_version),
        # # then we need to update the Evolution table.
        # actual_sig = introspect_project_sig()
        # acutal = pickle.dumps(actual_sig)
        # if actual != latest_version.signature:
        #     nudge = Version(signature=actual)
        #     nudge.save()
        #     latest_version = nudge

        diff = Diff(old_proj_sig, proj_sig)

        if not diff.is_empty():
            print style.NOTICE(
                'Project signature has changed - an evolution is required')

            if verbosity > 1:
                old_proj_sig = pickle.loads(str(latest_version.signature))
                print diff