How to add a new field to a model with new Django migrations? How to add a new field to a model with new Django migrations? django django

How to add a new field to a model with new Django migrations?


To answer your question, with the new migration introduced in Django 1.7, in order to add a new field to a model you can simply add that field to your model and initialize migrations with ./manage.py makemigrations and then run ./manage.py migrate and the new field will be added to your DB.

To avoid dealing with errors for your existing models however, you can use the --fake:

  1. Initialize migrations for your existing models:

    ./manage.py makemigrations myapp
  2. Fake migrations for existing models:

    ./manage.py migrate --fake myapp
  3. Add the new field to myapp.models:

    from django.db import modelsclass MyModel(models.Model):    ... #existing fields    newfield = models.CharField(max_length=100) #new field
  4. Run makemigrations again (this will add a new migration file in migrations folder that add the newfield to db):

    ./manage.py makemigrations myapp
  5. Run migrate again:

    ./manage.py migrate myapp


To be able to do that and have the migration file located in the application where I actually add the field as opposed to having the migration located inside the application to which the model belongs, I had to write my own Migration base class.

If you use contribute_to_class inside the same application as the original model, @nima's answer works perfectly, although I don't see the point of using contribute_to_class then.

Here is the code. It is Django's original code adapted to migrate a model from self.migrated_app instead of self.app_label:

from django.db import migrationsclass Migration(migrations.Migration):  migrated_app = None  def __init__(self, name, app_label):    super(Migration,self).__init__(name, app_label)    if self.migrated_app is None:      self.migrated_app = self.app_label  def mutate_state(self, project_state):    new_state = project_state.clone()    for operation in self.operations:        operation.state_forwards(self.migrated_app, new_state)    return new_state  def apply(self, project_state, schema_editor, collect_sql=False):    for operation in self.operations:      if collect_sql and not operation.reduces_to_sql:        schema_editor.collected_sql.append("--")        schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:")        schema_editor.collected_sql.append("-- %s" % operation.describe())        schema_editor.collected_sql.append("--")        continue      new_state = project_state.clone()      operation.state_forwards(self.migrated_app, new_state)      if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:        with atomic(schema_editor.connection.alias):          operation.database_forwards(self.migrated_app, schema_editor, project_state, new_state)      else:        operation.database_forwards(self.migrated_app, schema_editor, project_state, new_state)      project_state = new_state    return project_state  def unapply(self, project_state, schema_editor, collect_sql=False):    to_run = []    for operation in self.operations:      if collect_sql and not operation.reduces_to_sql:        schema_editor.collected_sql.append("--")        schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE WRITTEN AS SQL:")        schema_editor.collected_sql.append("-- %s" % operation.describe())        schema_editor.collected_sql.append("--")        continue      if not operation.reversible:        raise Migration.IrreversibleError("Operation %s in %s is not reversible" % (operation, self))      new_state = project_state.clone()      operation.state_forwards(self.migrated_app, new_state)      to_run.append((operation, project_state, new_state))      project_state = new_state    to_run.reverse()    for operation, to_state, from_state in to_run:      if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:        with atomic(schema_editor.connection.alias):          operation.database_backwards(self.migrated_app, schema_editor, from_state, to_state)      else:        operation.database_backwards(self.migrated_app, schema_editor, from_state, to_state)    return project_state

With this new Migration class located in base.utils a hand-written migration would look like this. You can also let Django write the migration for you inside the "wrong" application, move the file and update it to use the custom Migration class:

# -*- coding: utf-8 -*-from __future__ import unicode_literalsfrom django.db import models, migrationsfrom base.utils import Migrationimport dynamicsites.fieldsclass Migration(Migration):    dependencies = [        ('sites', '0001_initial'),        ('base', '0001_initial'),    ]    migrated_app = 'sites'    operations = [        migrations.AddField(            model_name='site',            name='folder_name',            field=dynamicsites.fields.FolderNameField(default='', help_text=b"Folder name for this site's files.  The name may only consist of lowercase characters, numbers (0-9), and/or underscores", max_length=64, blank=True),            preserve_default=False,        ),        migrations.AddField(            model_name='site',            name='subdomains',            field=dynamicsites.fields.SubdomainListField(default=(), help_text=b'Comma separated list of subdomains this site supports.  Leave blank to support all subdomains', blank=True),            preserve_default=False,        ),    ]

Custom migration class for Django 1.8

from django.db import migrationsclass Migration(migrations.Migration):  migrated_app = None  def __init__(self, name, app_label):    super(Migration,self).__init__(name, app_label)    if self.migrated_app is None:      self.migrated_app = self.app_label  def __eq__(self, other):    if not isinstance(other, Migration):      if not isinstance(other, migrations.Migration):        return False      return (self.name == other.name) and (self.migrated_app == other.app_label)    return (self.name == other.name) and (self.migrated_app == other.migrated_app)  def __hash__(self):    return hash("%s.%s" % (self.app_label, self.name))  def mutate_state(self, project_state, preserve=True):    new_state = project_state    if preserve:      new_state = project_state.clone()    for operation in self.operations:      operation.state_forwards(self.migrated_app, new_state)    return new_state  def apply(self, project_state, schema_editor, collect_sql=False):    for operation in self.operations:      if collect_sql and not operation.reduces_to_sql:        schema_editor.collected_sql.append("--")        schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE "                                           "WRITTEN AS SQL:")        schema_editor.collected_sql.append("-- %s" % operation.describe())        schema_editor.collected_sql.append("--")        continue      old_state = project_state.clone()      operation.state_forwards(self.migrated_app, project_state)      if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:        with atomic(schema_editor.connection.alias):          operation.database_forwards(self.migrated_app, schema_editor, old_state, project_state)      else:        operation.database_forwards(self.migrated_app, schema_editor, old_state, project_state)    return project_state  def unapply(self, project_state, schema_editor, collect_sql=False):    to_run = []    new_state = project_state    for operation in self.operations:      if not operation.reversible:        raise Migration.IrreversibleError("Operation %s in %s is not reversible" % (operation, self))      new_state = new_state.clone()      old_state = new_state.clone()      operation.state_forwards(self.migrated_app, new_state)      to_run.insert(0, (operation, old_state, new_state))    for operation, to_state, from_state in to_run:      if collect_sql:        if not operation.reduces_to_sql:          schema_editor.collected_sql.append("--")          schema_editor.collected_sql.append("-- MIGRATION NOW PERFORMS OPERATION THAT CANNOT BE "                                             "WRITTEN AS SQL:")          schema_editor.collected_sql.append("-- %s" % operation.describe())          schema_editor.collected_sql.append("--")          continue      if not schema_editor.connection.features.can_rollback_ddl and operation.atomic:        with atomic(schema_editor.connection.alias):          operation.database_backwards(self.migrated_app, schema_editor, from_state, to_state)      else:        operation.database_backwards(self.migrated_app, schema_editor, from_state, to_state)      return project_state


You can create like this:

from django.db.models import CharFieldfrom django.db.models.signals import class_prepareddef add_field(sender, **kwargs):    """    class_prepared signal handler that checks for the model named    MyModel as the sender, and adds a CharField    to it.    """    if sender.__name__ == "MyModel":        field = CharField("New field", max_length=100)        field.contribute_to_class(sender, "new_field")class_prepared.connect(add_field)

See "Django Model Field Injection" for more information.