I'm using the contribute_to_class
method but I don't know how to create the field in the database with new migrations.
问题:
回答1:
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
:
Initialize migrations for your existing models:
./manage.py makemigrations myapp
Fake migrations for existing models:
./manage.py migrate --fake myapp
Add the new field to myapp.models:
from django.db import models class MyModel(models.Model): ... #existing fields newfield = models.CharField(max_length=100) #new field
Run makemigrations again (this will add a new migration file in migrations folder that add the newfield to db):
./manage.py makemigrations myapp
Run migrate again:
./manage.py migrate myapp
回答2:
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 migrations
class 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_literals
from django.db import models, migrations
from base.utils import Migration
import dynamicsites.fields
class 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 migrations
class 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
回答3:
You can create like this:
from django.db.models import CharField
from django.db.models.signals import class_prepared
def 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.