Exemplo n.º 1
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        qn = self.connection.ops.quote_name
        max_name_length = self.connection.ops.max_name_length()
        opts = model._meta
        refs = {}
        models = []

        return AlterTableSQLResult(
            self,
            model,
            pre_sql=self.remove_field_constraints(old_field, opts, models,
                                                  refs),
            alter_table=[
                {
                    'independent': True,
                    'sql': 'RENAME COLUMN %s TO %s'
                           % (truncate_name(qn(old_field.column),
                                            max_name_length),
                              truncate_name(qn(new_field.column),
                                            max_name_length)),
                },
            ],
            post_sql=self.add_primary_key_field_constraints(
                old_field, new_field, models, refs)
        )
Exemplo n.º 2
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        qn = self.connection.ops.quote_name
        max_name_length = self.connection.ops.max_name_length()
        opts = model._meta
        refs = {}
        models = []

        return AlterTableSQLResult(
            self,
            model,
            pre_sql=self.remove_field_constraints(old_field, opts, models,
                                                  refs),
            alter_table=[
                {
                    'independent':
                    True,
                    'sql':
                    'RENAME COLUMN %s TO %s' %
                    (truncate_name(qn(old_field.column), max_name_length),
                     truncate_name(qn(new_field.column), max_name_length)),
                },
            ],
            post_sql=self.add_primary_key_field_constraints(
                old_field, new_field, models, refs))
Exemplo n.º 3
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        qn = self.connection.ops.quote_name
        max_name_length = self.connection.ops.max_name_length()

        sql_result = AlterTableSQLResult(self, model)

        pre_sql, stash = self.stash_field_ref_constraints(model=model,
                                                          replaced_fields={
                                                              old_field:
                                                              new_field,
                                                          })
        sql_result.add_pre_sql(pre_sql)

        sql_result.add_alter_table([{
            'independent':
            True,
            'sql':
            'RENAME COLUMN %s TO %s' %
            (truncate_name(qn(old_field.column), max_name_length),
             truncate_name(qn(new_field.column), max_name_length)),
        }])

        sql_result.add_post_sql(self.restore_field_ref_constraints(stash))

        return sql_result
Exemplo n.º 4
0
def generate_unique_constraint_name(table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        table (str):
            The table name.

        col_names (list of str):
            The list of column names for the constraint.

    Returns:
        The expected constraint name for this version of Django.
    """
    if django.VERSION[:2] >= (1, 7):
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)
        name = '_%s%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        name = digest(connection, col_names)

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Exemplo n.º 5
0
    def get_default_index_name(self, table_name, field):
        """Return a default index name for the database.

        This will return an index name for the given field that matches what
        the database or Django database backend would automatically generate
        when marking a field as indexed or unique.

        This can be overridden by subclasses if the database or Django
        database backend provides different values.

        Args:
            table_name (str):
                The name of the table for the index.

            field (django.db.models.Field):
                The field for the index.

        Returns:
            str:
            The name of the index.
        """
        assert field.unique or field.db_index

        if field.unique:
            return truncate_name(field.column,
                                 self.connection.ops.max_name_length())
        elif field.db_index:
            return create_index_name(self.connection, table_name,
                                     field_names=[field.name],
                                     col_names=[field.column])
        else:
            # This won't be reached, due to the assert above.
            raise NotImplementedError
Exemplo n.º 6
0
    def record_index(self,
                     model,
                     fields,
                     use_constraint_name=False,
                     index_name=None,
                     unique=False):
        """Records an index in the database signature.

        This is a convenience to record an index in the database signature
        for future lookups. It can take an index name, or it can generate
        a constraint name if that's to be used.
        """
        if not index_name and use_constraint_name:
            index_name = truncate_name(
                '%s_%s_key' % (model._meta.db_table, fields[0].column),
                self.connection.ops.max_name_length())

        assert index_name or not unique

        add_index_to_database_sig(self,
                                  self.database_sig,
                                  model,
                                  fields,
                                  index_name=index_name,
                                  unique=unique)
Exemplo n.º 7
0
    def get_default_index_name(self, table_name, field):
        """Return a default index name for the database.

        This will return an index name for the given field that matches what
        the database or Django database backend would automatically generate
        when marking a field as indexed or unique.

        This can be overridden by subclasses if the database or Django
        database backend provides different values.

        Args:
            table_name (str):
                The name of the table for the index.

            field (django.db.models.Field):
                The field for the index.

        Returns:
            str:
            The name of the index.
        """
        assert field.unique or field.db_index

        if field.unique:
            return truncate_name(field.column,
                                 self.connection.ops.max_name_length())
        elif field.db_index:
            return create_index_name(self.connection,
                                     table_name,
                                     field_names=[field.name],
                                     col_names=[field.column])
        else:
            # This won't be reached, due to the assert above.
            raise NotImplementedError
Exemplo n.º 8
0
    def rename_table(self, model, old_db_tablename, db_tablename):
        sql_result = SQLResult()

        if old_db_tablename == db_tablename:
            # No Operation
            return sql_result

        max_name_length = self.connection.ops.max_name_length()

        refs = {}
        models = []

        for field in model._meta.local_many_to_many:
            remote_field = get_remote_field(field)

            if (remote_field and remote_field.through and
                    remote_field.through._meta.db_table == old_db_tablename):

                through = remote_field.through

                for m2m_field in through._meta.local_fields:
                    remote_m2m_field = get_remote_field(m2m_field)

                    if remote_m2m_field:
                        remote_m2m_field_model = get_remote_field_model(
                            remote_m2m_field)

                        if remote_m2m_field_model == model:
                            models.append(remote_m2m_field_model)
                            refs.setdefault(remote_m2m_field_model, []).append(
                                (through, m2m_field))

        remove_refs = refs.copy()

        if self.supports_constraints:
            for relto in models:
                sql_result.add_pre_sql(
                    sql_delete_constraints(self.connection, relto,
                                           remove_refs))

        sql_result.add(
            self.get_rename_table_sql(model, old_db_tablename, db_tablename))

        for relto in models:
            for rel_class, f in refs[relto]:
                if rel_class._meta.db_table == old_db_tablename:
                    rel_class._meta.db_table = db_tablename

                rel_class._meta.db_table = \
                    truncate_name(rel_class._meta.db_table,
                                  max_name_length)

            if self.supports_constraints:
                sql_result.add_post_sql(
                    sql_add_constraints(self.connection, relto, refs))

        return sql_result
Exemplo n.º 9
0
    def rename_table(self, model, old_db_tablename, db_tablename):
        sql_result = SQLResult()

        if old_db_tablename == db_tablename:
            # No Operation
            return sql_result

        max_name_length = self.connection.ops.max_name_length()

        refs = {}
        models = []

        for field in model._meta.local_many_to_many:
            if (field.rel and
                field.rel.through and
                field.rel.through._meta.db_table == old_db_tablename):

                through = field.rel.through

                for m2m_field in through._meta.local_fields:
                    if m2m_field.rel and m2m_field.rel.to == model:
                        models.append(m2m_field.rel.to)
                        refs.setdefault(m2m_field.rel.to, []).append(
                            (through, m2m_field))

        remove_refs = refs.copy()

        if self.supports_constraints:
            for relto in models:
                sql_result.add_pre_sql(sql_delete_constraints(
                    self.connection, relto, remove_refs))

        sql_result.add(self.get_rename_table_sql(
            model, old_db_tablename, db_tablename))

        for relto in models:
            for rel_class, f in refs[relto]:
                if rel_class._meta.db_table == old_db_tablename:
                    rel_class._meta.db_table = db_tablename

                rel_class._meta.db_table = \
                    truncate_name(rel_class._meta.db_table,
                                  max_name_length)

            if self.supports_constraints:
                sql_result.add_post_sql(sql_add_constraints(
                    self.connection, relto, refs))

        return sql_result
Exemplo n.º 10
0
    def get_new_constraint_name(self, table_name, column):
        """Return a newly-generated constraint name.

        Args:
            table_name (unicode):
                The name of the table.

            column (unicode):
                The name of the column.

        Returns:
            unicode:
            The new constraint name.
        """
        return truncate_name('%s_%s_key' % (table_name, column),
                             self.connection.ops.max_name_length())
Exemplo n.º 11
0
    def record_index(self, model, fields, use_constraint_name=False,
                     index_name=None, unique=False):
        """Records an index in the database signature.

        This is a convenience to record an index in the database signature
        for future lookups. It can take an index name, or it can generate
        a constraint name if that's to be used.
        """
        if not index_name and use_constraint_name:
            index_name = truncate_name(
                '%s_%s_key' % (model._meta.db_table, fields[0].column),
                self.connection.ops.max_name_length())

        assert index_name or not unique

        add_index_to_database_sig(self, self.database_sig, model, fields,
                                  index_name=index_name, unique=unique)
Exemplo n.º 12
0
    def get_default_index_name(self, table_name, field):
        """Return a default index name for the database.

        This will return an index name for the given field that matches what
        the database or Django database backend would automatically generate
        when marking a field as indexed or unique.

        This can be overridden by subclasses if the database or Django
        database backend provides different values.

        Args:
            table_name (str):
                The name of the table for the index.

            field (django.db.models.Field):
                The field for the index.

        Returns:
            str:
            The name of the index.
        """
        if django.VERSION[:2] >= (1, 7):
            # On Django 1.7+, the default behavior for the index name is used.
            return super(EvolutionOperations,
                         self).get_default_index_name(table_name, field)
        else:
            # On Django < 1.7, a custom form of index name is used.
            assert field.unique or field.db_index

            if field.unique:
                index_name = '%s_%s_key' % (table_name, field.column)
            elif field.db_index:
                index_name = '%s_%s' % (table_name, field.column)

            return truncate_name(index_name,
                                 self.connection.ops.max_name_length())
Exemplo n.º 13
0
    def get_default_index_name(self, table_name, field):
        """Return a default index name for the database.

        This will return an index name for the given field that matches what
        the database or Django database backend would automatically generate
        when marking a field as indexed or unique.

        This can be overridden by subclasses if the database or Django
        database backend provides different values.

        Args:
            table_name (str):
                The name of the table for the index.

            field (django.db.models.Field):
                The field for the index.

        Returns:
            str:
            The name of the index.
        """
        if django.VERSION[:2] >= (1, 7):
            # On Django 1.7+, the default behavior for the index name is used.
            return super(EvolutionOperations, self).get_default_index_name(
                table_name, field)
        else:
            # On Django < 1.7, a custom form of index name is used.
            assert field.unique or field.db_index

            if field.unique:
                index_name = '%s_%s_key' % (table_name, field.column)
            elif field.db_index:
                index_name = '%s_%s' % (table_name, field.column)

            return truncate_name(index_name,
                                 self.connection.ops.max_name_length())
Exemplo n.º 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
Exemplo n.º 15
0
def generate_unique_constraint_name(connection, table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The table name.

        col_names (list of unicode):
            The list of column names for the constraint.

    Returns:
        unicode:
        The expected constraint name for this version of Django.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        suffix = '%s_uniq' % index_unique_name
        col_names_part = '_'.join(col_names)
        full_name = '%s_%s_%s' % (table, col_names_part, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (table[:part_lengths],
                                      col_names_part[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # Django versions >= 1.7 all use roughly the same format for unique
        # constraint index names, but starting in Django 1.11, the format
        # changed slightly. In 1.7 through 1.10, the name contained only the
        # first column (if specifying more than one), but in 1.11, that
        # changed to contain all column names (for unique_together).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        name = '_%s_%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        # Convert each of the field names to Python's native string format,
        # which is what the default name would normally be in.
        name = digest(connection, [
            str(col_name)
            for col_name in col_names
        ])

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Exemplo n.º 16
0
def _register_models(database_sig, app_label='tests', db_name='default',
                     app=evo_test, *models, **kwargs):
    """Register models for testing purposes.

    Args:
        database_sig (dict):
            The database signature to populate with model information.

        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.

        *models (tuple):
            The models to register.

        **kwargs (dict):
            Additional keyword arguments. This supports:

            ``register_indexes``:
                Specifies whether indexes should be registered for any
                models. Defaults to ``False``.

    Returns:
        collections.OrderedDict:
        A dictionary of registered models. The keys are model names, and
        the values are the models.
    """
    django_evolution_models = all_models['django_evolution']

    app_cache = OrderedDict()
    evolver = EvolutionOperationsMulti(db_name, database_sig).get_evolver()
    register_indexes = kwargs.get('register_indexes', False)

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

    for name, model in reversed(models):
        orig_model_name = get_model_name(model)

        if orig_model_name in django_evolution_models:
            unregister_app_model('django_evolution', orig_model_name)

        orig_db_table = model._meta.db_table
        orig_object_name = model._meta.object_name

        generated_db_table = truncate_name(
            '%s_%s' % (model._meta.app_label, orig_model_name),
            max_name_length)

        if orig_db_table.startswith(generated_db_table):
            model._meta.db_table = '%s_%s' % (app_label, name.lower())

        model._meta.db_table = truncate_name(model._meta.db_table,
                                             max_name_length)
        model._meta.app_label = app_label
        model._meta.object_name = name
        model_name = name.lower()
        set_model_name(model, model_name)

        # Add an entry for the table in database_sig, if it's not already
        # there.
        if model._meta.db_table not in database_sig:
            database_sig[model._meta.db_table] = \
                signature.create_empty_database_table_sig()

        if register_indexes:
            # Now that we definitely have an entry, store the indexes for
            # all the fields in database_sig, so that other operations can
            # look up the index names.
            for field in model._meta.local_fields:
                if field.db_index or field.unique:
                    index_name = create_index_name(
                        my_connection,
                        model._meta.db_table,
                        field_names=[field.name],
                        col_names=[field.column],
                        unique=field.unique)

                    signature.add_index_to_database_sig(
                        evolver, database_sig, model, [field],
                        index_name=index_name,
                        unique=field.unique)

            for field_names in model._meta.unique_together:
                index_name = create_index_name(
                    my_connection,
                    model._meta.db_table,
                    field_names=field_names,
                    unique=True)

                signature.add_index_to_database_sig(
                    evolver, database_sig, model,
                    evolver.get_fields_for_names(model, field_names),
                    index_name=index_name,
                    unique=True)

            for field_names in getattr(model._meta, 'index_together', []):
                fields = evolver.get_fields_for_names(model, field_names)
                index_name = create_index_name(
                    my_connection,
                    model._meta.db_table,
                    field_names=[field.name for field in fields],
                    col_names=[field.column for field in fields])

                signature.add_index_to_database_sig(
                    evolver, database_sig, model,
                    fields,
                    index_name=index_name)

        # Register the model with the app.
        add_app_test_model(model, app_label=app_label)

        for field in model._meta.local_many_to_many:
            if not field.rel.through:
                continue

            through = field.rel.through

            generated_db_table = truncate_name(
                '%s_%s' % (orig_db_table, field.name),
                max_name_length)

            if through._meta.db_table == generated_db_table:
                through._meta.app_label = app_label

                # Transform the 'through' table information only
                # if we've transformed the parent db_table.
                if model._meta.db_table != orig_db_table:
                    through._meta.db_table = \
                        '%s_%s' % (model._meta.db_table, field.name)

                    through._meta.object_name = \
                        through._meta.object_name.replace(
                            orig_object_name,
                            model._meta.object_name)

                    set_model_name(
                        through,
                        get_model_name(through).replace(orig_model_name,
                                                        model_name))

            through._meta.db_table = \
                truncate_name(through._meta.db_table, max_name_length)

            for field in through._meta.local_fields:
                if field.rel and field.rel.to:
                    column = field.column

                    if (column.startswith(orig_model_name) or
                        column.startswith('to_%s' % orig_model_name) or
                        column.startswith('from_%s' % orig_model_name)):

                        field.column = column.replace(
                            orig_model_name,
                            get_model_name(model))

            through_model_name = get_model_name(through)

            if through_model_name in django_evolution_models:
                unregister_app_model('django_evolution', through_model_name)

            app_cache[through_model_name] = through
            add_app_test_model(through, app_label=app_label)

        app_cache[model_name] = model

    if not is_app_registered(app):
        register_app(app_label, app)

    return app_cache
Exemplo n.º 17
0
def generate_index_name(db_type, table, col_names, field_names=None,
                        index_together=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        db_type (str):
            The database type for the index. Currently, only "postgres"
            does anything special.

        table (str):
            The name of the table the index refers to.

        col_names (str or list of str):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

    Returns:
        str:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s%s_idx' % (col_names[0], index_unique_name)
    elif db_type == 'postgres' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        name = digest(connection, field_names or col_names)
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())
Exemplo n.º 18
0
def generate_unique_constraint_name(connection, table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The table name.

        col_names (list of unicode):
            The list of column names for the constraint.

    Returns:
        unicode:
        The expected constraint name for this version of Django.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        suffix = '%s_uniq' % index_unique_name
        col_names_part = '_'.join(col_names)
        full_name = '%s_%s_%s' % (table, col_names_part, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (table[:part_lengths],
                                      col_names_part[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # Django versions >= 1.7 all use roughly the same format for unique
        # constraint index names, but starting in Django 1.11, the format
        # changed slightly. In 1.7 through 1.10, the name contained only the
        # first column (if specifying more than one), but in 1.11, that
        # changed to contain all column names (for unique_together).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        name = '_%s_%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        # Convert each of the field names to Python's native string format,
        # which is what the default name would normally be in.
        name = digest(connection, [
            str(col_name)
            for col_name in col_names
        ])

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Exemplo n.º 19
0
def generate_index_name(connection, table, col_names, field_names=None,
                        index_together=False, model_meta_indexes=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table the index refers to.

        col_names (unicode or list of unicode):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

        model_meta_indexes (bool, optional):
            The index comes from a
            :py:class:`django.db.models.Options.indexes` entry.

    Returns:
        unicode:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 11):
        # Django 1.11+ changed the index format again, this time to include
        # all relevant column names in the plain text part of the index
        # (instead of just in the hash). Like with 1.7 through 1.10, the
        # index_together entries have a "_idx" suffix. However, there's
        # otherwise no difference in format between those and single-column
        # indexes.
        #
        # It's also worth noting that with the introduction of
        # Model._meta.indexes, there's *another* new index format. It's
        # similar, but different enough, and needs to be handled specially.
        if model_meta_indexes:
            name = '%s_%s' % (
                col_names[0][:7],
                digest(connection, *([table] + col_names + ['idx']))[:6],
            )
            table = table[:11]
        else:
            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s' % ('_'.join(col_names), index_unique_name)

        if model_meta_indexes or index_together:
            name = '%s_idx' % name
    elif django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s_idx' % (col_names[0], index_unique_name)
    elif connection.vendor == 'postgresql' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        #
        # We convert each of the field names to Python's native string
        # format, which is what the default name would normally be in.
        name = digest(connection, [
            str(field_name)
            for field_name in (field_names or col_names)
        ])
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())
Exemplo n.º 20
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
Exemplo n.º 21
0
def generate_index_name(connection, table, col_names, field_names=None,
                        index_together=False, model_meta_indexes=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table the index refers to.

        col_names (unicode or list of unicode):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

        model_meta_indexes (bool, optional):
            The index comes from a
            :py:class:`django.db.models.Options.indexes` entry.

    Returns:
        unicode:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 11):
        # Django 1.11+ changed the index format again, this time to include
        # all relevant column names in the plain text part of the index
        # (instead of just in the hash). Like with 1.7 through 1.10, the
        # index_together entries have a "_idx" suffix. However, there's
        # otherwise no difference in format between those and single-column
        # indexes.
        #
        # It's also worth noting that with the introduction of
        # Model._meta.indexes, there's *another* new index format. It's
        # similar, but different enough, and needs to be handled specially.
        if model_meta_indexes:
            name = '%s_%s' % (
                col_names[0][:7],
                digest(connection, *([table] + col_names + ['idx']))[:6],
            )
            table = table[:11]
        else:
            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s' % ('_'.join(col_names), index_unique_name)

        if model_meta_indexes or index_together:
            name = '%s_idx' % name
    elif django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s_idx' % (col_names[0], index_unique_name)
    elif connection.vendor == 'postgresql' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        #
        # We convert each of the field names to Python's native string
        # format, which is what the default name would normally be in.
        name = digest(connection, [
            str(field_name)
            for field_name in (field_names or col_names)
        ])
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())