Пример #1
0
def ensure_test_db(model_entries, app_label='tests',
                   database=DEFAULT_DB_ALIAS):
    """Ensure tables are created and destroyed when running code.

    This will register all necessary models and indexes, provided by the
    caller, and populate them in the database. After the inner context has
    completed, the models and indexes will be destroyed.

    Args:
        model_entries (list of tuple):
            The list of model entries to add to the database. Each entry
            is a tuple containing the model class and the name to register
            for it.

        app_label (unicode, optional):
            The application label for the models to register.

        database (unicode, optional):
            The name of the database to execute on.
    """
    # Set up the initial state of the app cache.
    register_app_models(app_label, model_entries, reset=True)

    # Install the initial tables and indexes.
    execute_transaction(sql_create(evo_test, database),
                        database)

    try:
        yield
    finally:
        # Clean up the database.
        execute_transaction(sql_delete(evo_test, database),
                            database)
    def test_prepare_with_hinted_false(self):
        """Testing EvolveAppTask.prepare with hinted=False"""
        from django_evolution.compat.apps import register_app_models
        register_app_models('tests', [('TestModel', EvolverTestModel)],
                            reset=True)

        evolver = Evolver()
        task = EvolveAppTask(evolver=evolver,
                             app=evo_test,
                             evolutions=[
                                 {
                                     'label':
                                     'my_evolution1',
                                     'mutations': [
                                         ChangeField('TestModel',
                                                     'value',
                                                     max_length=100),
                                     ],
                                 },
                             ])
        task.prepare(hinted=False)

        self.assertTrue(task.evolution_required)
        self.assertTrue(task.can_simulate)
        self.assertEqual('\n'.join(task.sql),
                         self.get_sql_mapping('evolve_app_task'))
        self.assertEqual(len(task.new_evolutions), 1)

        evolution = task.new_evolutions[0]
        self.assertEqual(evolution.app_label, 'tests')
        self.assertEqual(evolution.label, 'my_evolution1')
Пример #3
0
    def test_prepare_with_hinted_false(self):
        """Testing EvolveAppTask.prepare with hinted=False"""
        from django_evolution.compat.apps import register_app_models
        register_app_models('tests', [('TestModel', EvolverTestModel)],
                            reset=True)

        evolver = Evolver()
        task = EvolveAppTask(
            evolver=evolver,
            app=evo_test,
            evolutions=[
                {
                    'label': 'my_evolution1',
                    'mutations': [
                        ChangeField('TestModel', 'value', max_length=100),
                    ],
                },
            ])
        task.prepare(hinted=False)

        self.assertTrue(task.evolution_required)
        self.assertTrue(task.can_simulate)
        self.assertEqual('\n'.join(task.sql),
                         self.get_sql_mapping('evolve_app_task'))
        self.assertEqual(len(task.new_evolutions), 1)

        evolution = task.new_evolutions[0]
        self.assertEqual(evolution.app_label, 'tests')
        self.assertEqual(evolution.label, 'my_evolution1')
Пример #4
0
def ensure_test_db(model_entries, app_label='tests',
                   database=DEFAULT_DB_ALIAS):
    """Ensure tables are created and destroyed when running code.

    This will register all necessary models and indexes, provided by the
    caller, and populate them in the database. After the inner context has
    completed, the models and indexes will be destroyed.

    Args:
        model_entries (list of tuple):
            The list of model entries to add to the database. Each entry
            is a tuple containing the model class and the name to register
            for it.

        app_label (unicode, optional):
            The application label for the models to register.

        database (unicode, optional):
            The name of the database to execute on.
    """
    # Set up the initial state of the app cache.
    register_app_models(app_label, model_entries, reset=True)

    # Install the initial tables and indexes.
    execute_transaction(sql_create(evo_test, database),
                        database)

    try:
        yield
    finally:
        # Clean up the database.
        execute_transaction(sql_delete(evo_test, database),
                            database)
Пример #5
0
def ensure_test_db(model_entries=[], end_model_entries=None,
                   app_label='tests', database=DEFAULT_DB_ALIAS):
    """Ensure tables are created and destroyed when running code.

    This will register all necessary models and indexes, provided by the
    caller, and populate them in the database. After the inner context has
    completed, the models and indexes will be destroyed.

    Version Changed:
        2.1:
        Added the ``end_model_entries`` argument.

    Args:
        model_entries (list of tuple, optional):
            The list of model entries to add to the database. Each entry
            is a tuple containing the model class and the name to register
            for it.

        end_model_entries (list of tuple, optional):
            The list of model entries to add/replace in the database, once the
            models in ``model_entries`` are written. This allows the model
            state to reflect the ending signature for a test.

            Each entry is a tuple containing the model class and the name to
            register for it.

        app_label (unicode, optional):
            The application label for the models to register.

        database (unicode, optional):
            The name of the database to execute on.
    """
    # Set up the initial state of the app cache.
    if model_entries:
        register_app_models(app_label=app_label,
                            model_infos=model_entries,
                            reset=True)

        # Install the initial tables and indexes.
        execute_test_sql(sql_create_app(app=evo_test,
                                        db_name=database),
                         database=database)

    if end_model_entries:
        # Set the app cache to the end state.
        register_app_models(app_label=app_label,
                            model_infos=end_model_entries,
                            reset=True)

    try:
        yield
    finally:
        # Clean up the database.
        execute_test_sql(sql_delete(evo_test,
                                    db_name=database),
                         database=database)
Пример #6
0
def execute_test_sql(start_sig, end_sig, generate_sql_func, app_label='tests',
                     database=DEFAULT_DB_ALIAS):
    """Execute SQL for a unit test.

    This will register all necessary models and indexes, as defined by the
    starting signature, and populate them in the database. It then sets the
    model state to reflect the ending signature, allowing the unit test to
    perform operations to go from the in-database starting state to the
    new ending signature state.

    The SQL provided by ``generate_sql_func`` will be output to the console,
    to aid in debugging when tests fail.

    Args:
        start_sig (dict):
            The signature for the initial database state, used to generate
            tables and indexes in the database.

        end_sig (dict):
            The signature for the ending database state, reflecting what the
            evolutions will be attempting to evolve to.

        generate_sql_func (callable):
            A function that takes no parameters and returns SQL to execute,
            once the database and app/model states are set up.

        app_label (unicode, optional):
            The application label for any models contained in the signature.

        database (unicode, optional):
            The name of the database to execute on.

    Returns:
        list of unicode:
        The list of executed SQL statements for the test.
    """
    with ensure_test_db(model_entries=six.iteritems(start_sig),
                        app_label=app_label,
                        database=database):
        # Set the app cache to the end state. generate_sql will depend on
        # this state.
        register_app_models(app_label, six.iteritems(end_sig), reset=True)

        # Execute and output the SQL for the test.
        sql = generate_sql_func()
        sql_out = write_sql(sql, database)
        execute_transaction(sql, database)

        return sql_out
Пример #7
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)
Пример #8
0
def execute_test_sql(start_sig, end_sig, generate_sql_func, app_label='tests',
                     database=DEFAULT_DB_ALIAS):
    """Execute SQL for a unit test.

    This will register all necessary models and indexes, as defined by the
    starting signature, and populate them in the database. It then sets the
    model state to reflect the ending signature, allowing the unit test to
    perform operations to go from the in-database starting state to the
    new ending signature state.

    The SQL provided by ``generate_sql_func`` will be output to the console,
    to aid in debugging when tests fail.

    Args:
        start_sig (dict):
            The signature for the initial database state, used to generate
            tables and indexes in the database.

        end_sig (dict):
            The signature for the ending database state, reflecting what the
            evolutions will be attempting to evolve to.

        generate_sql_func (callable):
            A function that takes no parameters and returns SQL to execute,
            once the database and app/model states are set up.

        app_label (unicode, optional):
            The application label for any models contained in the signature.

        database (unicode, optional):
            The name of the database to execute on.

    Returns:
        list of unicode:
        The list of executed SQL statements for the test.
    """
    with ensure_test_db(model_entries=six.iteritems(start_sig),
                        app_label=app_label,
                        database=database):
        # Set the app cache to the end state. generate_sql will depend on
        # this state.
        register_app_models(app_label, six.iteritems(end_sig), reset=True)

        # Execute and output the SQL for the test.
        sql = generate_sql_func()
        sql_out = write_sql(sql, database)
        execute_transaction(sql, database)

        return sql_out
    def test_prepare_with_hinted_true(self):
        """Testing EvolveAppTask.prepare with hinted=True"""
        from django_evolution.compat.apps import register_app_models
        register_app_models('tests', [('TestModel', EvolverTestModel)],
                            reset=True)

        evolver = Evolver(hinted=True)
        task = EvolveAppTask(evolver=evolver, app=evo_test)
        task.prepare(hinted=True)

        self.assertTrue(task.evolution_required)
        self.assertTrue(task.can_simulate)
        self.assertEqual('\n'.join(task.sql),
                         self.get_sql_mapping('evolve_app_task'))
        self.assertEqual(len(task.new_evolutions), 0)
Пример #10
0
    def test_prepare_with_hinted_true(self):
        """Testing EvolveAppTask.prepare with hinted=True"""
        from django_evolution.compat.apps import register_app_models
        register_app_models('tests', [('TestModel', EvolverTestModel)],
                            reset=True)

        evolver = Evolver(hinted=True)
        task = EvolveAppTask(evolver=evolver,
                             app=evo_test)
        task.prepare(hinted=True)

        self.assertTrue(task.evolution_required)
        self.assertTrue(task.can_simulate)
        self.assertEqual('\n'.join(task.sql),
                         self.get_sql_mapping('evolve_app_task'))
        self.assertEqual(len(task.new_evolutions), 0)
Пример #11
0
def register_models(database_state, models, register_indexes=False,
                    new_app_label='tests', db_name='default', app=evo_test):
    """Register models for testing purposes.

    Args:
        database_state (django_evolution.db.state.DatabaseState):
            The database state to populate with model information.

        models (list of django.db.models.Model):
            The models to register.

        register_indexes (bool, optional):
            Whether indexes should be registered for any models. Defaults to
            ``False``.

        new_app_label (str, optional):
            The label for the test app. Defaults to "tests".

        db_name (str, optional):
            The name of the database connection. Defaults to "default".

        app (module, optional):
            The application module for the test models.

    Returns:
        collections.OrderedDict:
        A dictionary of registered models. The keys are model names, and
        the values are the models.
    """
    app_cache = OrderedDict()
    evolver = EvolutionOperationsMulti(db_name, database_state).get_evolver()

    db_connection = connections[db_name or DEFAULT_DB_ALIAS]
    max_name_length = db_connection.ops.max_name_length()

    for new_object_name, model in reversed(models):
        # Grab some state from the model's meta instance. Some of this will
        # be original state that we'll keep around to help us unregister old
        # values and compute new ones.
        meta = model._meta

        orig_app_label = meta.app_label
        orig_db_table = meta.db_table
        orig_object_name = meta.object_name
        orig_model_name = get_model_name(model)

        # Find out if the table name being used is a custom table name, or
        # one generated by Django.
        new_model_name = new_object_name.lower()
        new_db_table = orig_db_table

        generated_db_table = truncate_name(
            '%s_%s' % (orig_app_label, orig_model_name),
            max_name_length)

        if orig_db_table == generated_db_table:
            # It was a generated one, so replace it with a version containing
            # the new model and app names.
            new_db_table = truncate_name('%s_%s' % (new_app_label,
                                                    new_model_name),
                                         max_name_length)
            meta.db_table = new_db_table

        # Set the new app/model names back on the meta instance.
        meta.app_label = new_app_label
        meta.object_name = new_object_name
        set_model_name(model, new_model_name)

        # Add an entry for the table in the database state, if it's not
        # already there.
        if not database_state.has_table(new_db_table):
            database_state.add_table(new_db_table)

        if register_indexes:
            # Now that we definitely have an entry, store the indexes for
            # all the fields in the database state, so that other operations
            # can look up the index names.
            for field in meta.local_fields:
                if field.db_index or field.unique:
                    new_index_name = create_index_name(
                        db_connection,
                        new_db_table,
                        field_names=[field.name],
                        col_names=[field.column],
                        unique=field.unique)

                    database_state.add_index(
                        index_name=new_index_name,
                        table_name=new_db_table,
                        columns=[field.column],
                        unique=field.unique)

            for field_names in meta.unique_together:
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_name(
                    db_connection,
                    new_db_table,
                    field_names=field_names,
                    unique=True)

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields],
                    unique=True)

            for field_names in getattr(meta, 'index_together', []):
                # Django >= 1.5
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_together_name(
                    db_connection,
                    new_db_table,
                    field_names=[field.name for field in fields])

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields])

            if getattr(meta, 'indexes', None):
                # Django >= 1.11
                for index, orig_index in zip(meta.indexes,
                                             meta.original_attrs['indexes']):
                    if not orig_index.name:
                        # The name was auto-generated. We'll need to generate
                        # it again for the new table name.
                        index.set_name_with_model(model)

                    fields = evolver.get_fields_for_names(
                        model, index.fields, allow_sort_prefixes=True)
                    database_state.add_index(
                        index_name=index.name,
                        table_name=new_db_table,
                        columns=[field.column for field in fields])

        # ManyToManyFields have their own tables, which will also need to be
        # renamed. Go through each of them and figure out what changes need
        # to be made.
        for field in meta.local_many_to_many:
            through = get_remote_field(field).through

            if not through:
                continue

            through_meta = through._meta
            through_orig_model_name = get_model_name(through)
            through_new_model_name = through_orig_model_name

            # Find out if the through table name is a custom table name, or
            # one generated by Django.
            generated_db_table = truncate_name(
                '%s_%s' % (orig_db_table, field.name),
                max_name_length)

            if through_meta.db_table == generated_db_table:
                # This is an auto-generated table name. Start changing the
                # state for it.
                assert through_meta.app_label == orig_app_label
                through_meta.app_label = new_app_label

                # Transform the 'through' table information only if we've
                # transformed the parent db_table.
                if new_db_table != orig_db_table:
                    through_meta.db_table = truncate_name(
                        '%s_%s' % (new_db_table, field.name),
                        max_name_length)

                    through_meta.object_name = \
                        through_meta.object_name.replace(orig_object_name,
                                                         new_object_name)

                    through_new_model_name = \
                        through_orig_model_name.replace(orig_model_name,
                                                        new_model_name)
                    set_model_name(through, through_new_model_name)

            # Change each of the columns for the fields on the
            # ManyToManyField's model to reflect the new model names.
            for through_field in through._meta.local_fields:
                through_remote_field = get_remote_field(through_field)

                if (through_remote_field and
                    get_remote_field_model(through_remote_field)):
                    column = through_field.column

                    if (column.startswith((orig_model_name,
                                           'to_%s' % orig_model_name,
                                           'from_%s' % orig_model_name))):
                        # This is a field that references one end of the
                        # relation or another. Update the model naem in the
                        # field's column.
                        through_field.column = column.replace(orig_model_name,
                                                              new_model_name)

            # Replace the entry in the models cache for the through table,
            # removing the old name and adding the new one.
            if through_orig_model_name in all_models[orig_app_label]:
                unregister_app_model(orig_app_label, through_orig_model_name)

            app_cache[through_new_model_name] = through
            register_app_models(new_app_label,
                                [(through_new_model_name, through)])

        # Unregister with the old model name and register the new one.
        if orig_model_name in all_models[orig_app_label]:
            unregister_app_model(orig_app_label, orig_model_name)

        register_app_models(new_app_label, [(new_model_name, model)])
        app_cache[new_model_name] = model

    # If the app hasn't yet been registered, do that now.
    if not is_app_registered(app):
        register_app(new_app_label, app)

    return app_cache
Пример #12
0
def add_app_test_model(model, app_label):
    """Adds a model to the Django test models registry."""
    register_app_models(app_label,
                        [(model._meta.object_name.lower(), model)])
Пример #13
0
def set_app_test_models(models, app_label):
    """Sets the list of models in the Django test models registry."""
    register_app_models(app_label, models.items(), reset=True)
Пример #14
0
def register_models(database_state, models, register_indexes=False,
                    new_app_label='tests', db_name='default', app=evo_test):
    """Register models for testing purposes.

    Args:
        database_state (django_evolution.db.state.DatabaseState):
            The database state to populate with model information.

        models (list of django.db.models.Model):
            The models to register.

        register_indexes (bool, optional):
            Whether indexes should be registered for any models. Defaults to
            ``False``.

        new_app_label (str, optional):
            The label for the test app. Defaults to "tests".

        db_name (str, optional):
            The name of the database connection. Defaults to "default".

        app (module, optional):
            The application module for the test models.

    Returns:
        collections.OrderedDict:
        A dictionary of registered models. The keys are model names, and
        the values are the models.
    """
    app_cache = OrderedDict()
    evolver = EvolutionOperationsMulti(db_name, database_state).get_evolver()

    db_connection = connections[db_name or DEFAULT_DB_ALIAS]
    max_name_length = db_connection.ops.max_name_length()

    for new_object_name, model in reversed(models):
        # Grab some state from the model's meta instance. Some of this will
        # be original state that we'll keep around to help us unregister old
        # values and compute new ones.
        meta = model._meta

        orig_app_label = meta.app_label
        orig_db_table = meta.db_table
        orig_object_name = meta.object_name
        orig_model_name = get_model_name(model)

        # Find out if the table name being used is a custom table name, or
        # one generated by Django.
        new_model_name = new_object_name.lower()
        new_db_table = orig_db_table

        generated_db_table = truncate_name(
            '%s_%s' % (orig_app_label, orig_model_name),
            max_name_length)

        if orig_db_table == generated_db_table:
            # It was a generated one, so replace it with a version containing
            # the new model and app names.
            new_db_table = truncate_name('%s_%s' % (new_app_label,
                                                    new_model_name),
                                         max_name_length)
            meta.db_table = new_db_table

        # Set the new app/model names back on the meta instance.
        meta.app_label = new_app_label
        meta.object_name = new_object_name
        set_model_name(model, new_model_name)

        # Add an entry for the table in the database state, if it's not
        # already there.
        if not database_state.has_table(new_db_table):
            database_state.add_table(new_db_table)

        if register_indexes:
            # Now that we definitely have an entry, store the indexes for
            # all the fields in the database state, so that other operations
            # can look up the index names.
            for field in meta.local_fields:
                if field.db_index or field.unique:
                    new_index_name = create_index_name(
                        db_connection,
                        new_db_table,
                        field_names=[field.name],
                        col_names=[field.column],
                        unique=field.unique)

                    database_state.add_index(
                        index_name=new_index_name,
                        table_name=new_db_table,
                        columns=[field.column],
                        unique=field.unique)

            for field_names in meta.unique_together:
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_name(
                    db_connection,
                    new_db_table,
                    field_names=field_names,
                    unique=True)

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields],
                    unique=True)

            for field_names in getattr(meta, 'index_together', []):
                # Django >= 1.5
                fields = evolver.get_fields_for_names(model, field_names)
                new_index_name = create_index_together_name(
                    db_connection,
                    new_db_table,
                    field_names=[field.name for field in fields])

                database_state.add_index(
                    index_name=new_index_name,
                    table_name=new_db_table,
                    columns=[field.column for field in fields])

            if getattr(meta, 'indexes', None):
                # Django >= 1.11
                for index, orig_index in zip(meta.indexes,
                                             meta.original_attrs['indexes']):
                    if not orig_index.name:
                        # The name was auto-generated. We'll need to generate
                        # it again for the new table name.
                        index.set_name_with_model(model)

                    fields = evolver.get_fields_for_names(
                        model, index.fields, allow_sort_prefixes=True)
                    database_state.add_index(
                        index_name=index.name,
                        table_name=new_db_table,
                        columns=[field.column for field in fields])

        # ManyToManyFields have their own tables, which will also need to be
        # renamed. Go through each of them and figure out what changes need
        # to be made.
        for field in meta.local_many_to_many:
            through = get_remote_field(field).through

            if not through:
                continue

            through_meta = through._meta
            through_orig_model_name = get_model_name(through)
            through_new_model_name = through_orig_model_name

            # Find out if the through table name is a custom table name, or
            # one generated by Django.
            generated_db_table = truncate_name(
                '%s_%s' % (orig_db_table, field.name),
                max_name_length)

            if through_meta.db_table == generated_db_table:
                # This is an auto-generated table name. Start changing the
                # state for it.
                assert through_meta.app_label == orig_app_label
                through_meta.app_label = new_app_label

                # Transform the 'through' table information only if we've
                # transformed the parent db_table.
                if new_db_table != orig_db_table:
                    through_meta.db_table = truncate_name(
                        '%s_%s' % (new_db_table, field.name),
                        max_name_length)

                    through_meta.object_name = \
                        through_meta.object_name.replace(orig_object_name,
                                                         new_object_name)

                    through_new_model_name = \
                        through_orig_model_name.replace(orig_model_name,
                                                        new_model_name)
                    set_model_name(through, through_new_model_name)

            # Change each of the columns for the fields on the
            # ManyToManyField's model to reflect the new model names.
            for through_field in through._meta.local_fields:
                through_remote_field = get_remote_field(through_field)

                if (through_remote_field and
                    get_remote_field_model(through_remote_field)):
                    column = through_field.column

                    if (column.startswith((orig_model_name,
                                           'to_%s' % orig_model_name,
                                           'from_%s' % orig_model_name))):
                        # This is a field that references one end of the
                        # relation or another. Update the model naem in the
                        # field's column.
                        through_field.column = column.replace(orig_model_name,
                                                              new_model_name)

            # Replace the entry in the models cache for the through table,
            # removing the old name and adding the new one.
            if through_orig_model_name in all_models[orig_app_label]:
                unregister_app_model(orig_app_label, through_orig_model_name)

            app_cache[through_new_model_name] = through
            register_app_models(new_app_label,
                                [(through_new_model_name, through)])

        # Unregister with the old model name and register the new one.
        if orig_model_name in all_models[orig_app_label]:
            unregister_app_model(orig_app_label, orig_model_name)

        register_app_models(new_app_label, [(new_model_name, model)])
        app_cache[new_model_name] = model

    # If the app hasn't yet been registered, do that now.
    if not is_app_registered(app):
        register_app(new_app_label, app)

    return app_cache