App and Model Mutations

Evolutions are composed of one or more mutations, which mutate the state of the app or models. There are several mutations included with Django Evolution, which we’ll take a look at here.

Field Mutations

AddField

AddField is used to add new fields to a table. It takes the following parameters:

class AddField(model_name, field_name, field_type, initial=None, **field_attrs)
Parameters:
  • model_name (str) – The name of the model the field was added to.

  • field_name (str) – The name of the new field.

  • field_type (type) – The field class.

  • initial – The initial value to set for the field. Each row in the table will have this value set once the field is added. It’s required if the field is non-null.

  • field_attrs (dict) – Attributes to pass to the field constructor. Only those that impact the schema of the table are considered (for instance, null=... or max_length=..., but not help_text=....

For example:

from django.db import models
from django_evolution.mutations import AddField


MUTATIONS = [
    AddField('Book', 'publish_date', models.DateTimeField, null=True),
]

ChangeField

ChangeField can make changes to existing fields, altering the attributes (for instance, increasing the maximum length of a CharField).

Note

This cannot be used to change the field type.

It takes the following parameters:

class ChangeField(model_name, field_name, initial=None, **field_attrs)
Parameters:
  • model_name (str) – The name of the model containing the field.

  • field_name (str) – The name of the field to change.

  • field_type

    The new type of field. This must be a subclass of Field.

    This will do its best to change one field type to another, but not all field types can be changed to another type. Some types may be database-specific.

    New in version 2.2.

  • initial – The new initial value to set for the field. If the field previously allowed null values, but null=False is being passed, then this will update all existing rows in the table to have this initial value.

  • field_attrs (dict) – The field attributes to change. Only those that impact the schema of the table are considered (for instance, null=... or max_length=..., but not help_text=....

For example:

from django.db import models
from django_evolution.mutations import ChangeField


MUTATIONS = [
    ChangeField('Book', 'name', max_length=100, null=False),
]

DeleteField

DeleteField will delete a field from the table, erasing its data from all rows. It takes the following parameters:

class DeleteField(model_name, field_name)
Parameters:
  • model_name (str) – The name of the model containing the field to delete.

  • field_name (str) – The name of the field to delete.

For example:

from django.db import models
from django_evolution.mutations import ChangeField


MUTATIONS = [
    ChangeField('Book', 'name', max_length=100, null=False),
]

RenameField

RenameField will rename a field in the table, preserving all stored data. It can also set an explicit column name (in case the name is only changing in the model) or a ManyToManyField table name.

If working with a ManyToManyField, then the parent table won’t actually have a real column backing it. Instead, the relationships are all maintained using the “through” table created by the field. In this case, the db_column value will be ignored, but db_table can be set.

It takes the following parameters:

class RenameField(model_name, old_field_name, new_field_name, db_column=None, db_table=None)
Parameters:
  • model_name (str) – The name of the model containing the field to delete.

  • old_field_name (str) – The old name of the field on the model.

  • new_field_name (str) – The new name of the field on the model.

  • db_column (str) – The explicit name of the column on the table to use. This may be the original column name, if the name is only being changed on the model (which means no database changes may be made).

  • db_table (str) –

    The explicit name of the “through” table to use for a ManyToManyField. If changed, then that table will be renamed. This is ignored for any other types of fields.

    If the table name hasn’t actually changed, then this may not make any changes to the database.

For example:

from django_evolution.mutations import RenameField


MUTATIONS = [
    RenameField('Book', 'isbn_number', 'isbn', column_name='isbn_number'),
    RenameField('Book', 'critics', 'reviewers',
                db_table='book_critics')
]

Model Mutators

ChangeMeta

ChangeMeta can change certain bits of metadata about a model. For example, the indexes or unique-together constraints. It takes the following parameters:

class ChangeMeta(model_name, prop_name, new_value)
Parameters:
  • model_name (str) – The name of the model containing the field to delete.

  • prop_name (str) – The name of the property to change, as documented below.

  • new_value – The new value for the property.

The properties that can be changed depend on the version of Django. They include:

db_table_comment:

A comment to apply to the table’s schema.

This requires Django 4.2 or higher.

Version Added:

2.3

index_together:

Groups of fields that should be indexed together in the database.

This is represented by a list of tuples, each of which groups together multiple field names that should be indexed together in the database.

index_together support requires Django 1.5 or higher. The last versions of Django Evolution to support Django 1.5 was the 0.7.x series.

indexes:

Explicit indexes to create for the model, optionally grouping multiple fields together and optionally naming the index.

This is represented by a list of dictionaries, each of which contain a fields key and an optional name key. Both of these correspond to the matching fields in Django’s Index class.

indexes support requires Django 1.11 or higher.

unique_together:

Groups of fields that together form a unique constraint. Rows in the database cannot repeat the same values for those groups of fields.

This is represented by a list of tuples, each of which groups together multiple field names that should be unique together in the database.

unique_together support is available in all supported versions of Django.

For example:

from django_evolution.mutations import ChangeMeta


MUTATIONS = [
    ChangeMeta('Book', 'index_together', [('name', 'author')]),
]

Changed in version 2.0: Added support for indexes.

DeleteModel

DeleteModel removes a model from the database. It will also remove any “through” models for any of its ManyToManyFields. It takes the following parameters:

class DeleteModel(model_name)
Parameters:

model_name (str) – The name of the model to delete.

For example:

from django_evolution.mutations import DeleteModel


MUTATIONS = [
    DeleteModel('Book'),
]

RenameModel

RenameModel will rename a model and update all relations pointing to that model. It requires an explicit underlying table name, which can be set to the original table name if only the Python-side model name is changing. It takes the following parameters:

class RenameModel(old_model_name, new_model_name, db_table)
Parameters:
  • old_model_name (str) – The old name of the model.

  • new_model_name (str) – The new name of the model.

  • db_table (str) – The explicit name of the underlying table.

For example:

from django_evolution.mutations import RenameModel


MUTATIONS = [
    RenameModel('Critic', 'Reviewer', db_table='books_reviewer'),
]

App Mutators

DeleteApplication

DeleteApplication will remove all the models for an app from the database, erasing all associated data. This mutation takes no parameters.

Note

Make sure that any relation fields from other models to this app’s models have been removed before deleting an app.

In many cases, you may just want to remove the app from your project’s INSTALLED_APPS, and leave the data alone.

For example:

from django_evolution.mutations import DeleteApplication


MUTATIONS = [
    DeleteApplication(),
]

MoveToDjangoMigrations

MoveToDjangoMigrations will tell Django Evolution that any future changes to the app or its models should be handled by Django’s migrations instead evolutions. Any unapplied evolutions will be applied before applying any migrations.

This is a one-way operation. Once an app moves from evolutions to migrations, it cannot move back.

Since an app may have had both evolutions and migrations defined in the tree (in order to work with both systems), this takes a mark_applied= parameter that lists the migrations that should be considered applied by the time this mutation is run. Those migrations will be recorded as applied and skipped.

class MoveToDjangoMigrations(mark_applied=['0001_initial'])
Parameters:

mark_applied (list) – The list of migrations that should be considered applied when running this mutation. This defaults to the 0001_initial migration.

For example:

from django_evolution.mutations import MoveToDjangoMigrations


MUTATIONS = [
    MoveToDjangoMigrations(mark_applied=['0001_initial',
                                         '0002_book_add_isbn']),
]

New in version 2.0.

RenameAppLabel

RenameAppLabel will rename the stored app label for the app, updating all references made in other models. It won’t change indexes or any database state, however.

Django 1.7 moved to an improved concept of app labels that could be customized and were guaranteed to be unique within a project (we’ll call these modern app labels). Django 1.6 and earlier generated app labels based on the app’s module name (legacy app labels).

Because of this, older stored project signatures may have grouped together models from two different apps (both with the same app labels) together. Django Evolution will try to untangle this, but in complicated cases, you may need to supply a list of model names for the app (current and possibly older ones that have been removed). Whether you need to do this is entirely dependent on the structure of your project. Test it in your upgrades.

This takes the following parameters:

class RenameAppLabel(old_app_label, new_app_label, legacy_app_label=None, model_names=None)
Parameters:
  • old_app_label (str) – The old app label that’s being renamed.

  • new_app_label (str) – The new modern app label to rename to.

  • legacy_app_label (str) – The legacy app label for the new app name. This provides compatibility with older versions of Django and helps with transition apps and models.

  • model_names (list) – The list of model names to move out of the old signature and into the new one.

For example:

from django_evolution.mutations import RenameAppLabel


MUTATIONS = [
    RenameAppLabel('admin', 'my_admin', legacy_app_label='admin',
                   model_names=['Report', 'Config']),
]

New in version 2.0.

Other Mutators

SQLMutation

SQLMutation is an advanced mutation used to make arbitrary changes to a database and to the stored project signature. It may be used to make changes that cannot be made by other mutators, such as altering tables not managed by Django, changing a table engine, making metadata changes to the table or database, or modifying the content of rows.

SQL from this mutation cannot be optimized alongside other mutations.

This takes the following parameters:

class SQLMutation(tag, sql, update_func=None)
Parameters:
  • tag (str) – A unique identifier for this SQL mutation within the app.

  • sql (list/str) – A list of SQL statements, or a single SQL statement as a string, to execute. Note that this will be database-dependent.

  • update_func (callable) – A function to call to perform additional operations or update the project signature.

Note

There’s some caveats with providing an update_func.

Django Evolution 2.0 introduced a new form for this function that takes in a django_evolution.mutations.Simulation object, which can be used to access and modify the stored project signature. This is safe to use (well, relatively – try not to blow anything up).

Prior versions supported a function that took two arguments: The app label of the app that’s being evolved, and a serialized dictionary representing the project signature.

If using the legacy style, it’s possible that you can mess up the signature data, since we have to serialize to an older version of the signature and then load from that. Older versions of the signature don’t support all the data that newer versions do, so how well this works is really determined by the types of evolutions that are going to be run.

We strongly recommend updating any SQLMutation calls to use the new-style function format, for safety and future compatibility.

For example:

from django_evolution.mutations import SQLMutation


def _update_signature(simulation):
    pass


MUTATIONS = [
    SQLMutation('set_innodb_engine',
                'ALTER TABLE my_table ENGINE = MYISAM;',
                update_func=_update_signature),
]

Changed in version 2.0: Added the new-style update_func.