Django renaming model field without ALTER TABLE on the database - python

I'm trying to rename a model field name without any impact on the DB using db_colum.
My model before:
class Foo(models.Model)::
old_name = models.CharField()
My model after:
class Foo(models.Model)::
new_name = models.CharField(db_column="old_name")
I generated a migration and Django guessed that I have renamed the field. The migration looks like:
class Migration(migrations.Migration):
dependencies = [
("fooapp", "0001_bar"),
]
operations = [
migrations.RenameField(
model_name="foo", old_name="old_name", new_name="new_name",
),
migrations.AlterField(
model_name="foo",
name="new_name",
field=models.CharField(db_column="old_name"),
),
]
Everything is working fine. I try the migration and it is ok. But if I take a look at the SQL generated (with ./manage.py sqlmigrate), I see:
--
-- Rename field old_name on foo to new_name
--
ALTER TABLE "fooapp_foo" RENAME COLUMN "old_name" TO "new_name";
--
-- Alter field new_name on foo
--
ALTER TABLE "fooapp_foo" RENAME COLUMN "new_name" TO "old_name";
I don't get why the migration does that instead of doing nothing. Is there a way to avoid that?

It does seem that Django migration engine has produced unnecessary migration commands/steps ( should be reported as Bug )
In meantime you could edit migration to avoid these queries from being executed by making custom RunSQL replacement for these migration operations
Something in a line of
migrations.RunSQL(
migrations.RunSQL.noop,
state_operations=[
migrations.RenameField(
model_name="foo", old_name="old_name", new_name="new_name",
),
migrations.AlterField(
model_name="foo",
name="new_name",
field=models.CharField(db_column="old_name"),
),
],
)

Related

Django cannot delete custom field class; makemigrations throws AttributeError

Lead-up:
I subclassed django.db.models.fields.CharField. Then used that custom field called myapp.models.QueryStringField in a model and made my migrations and migrated successfully. Then I changed my mind and decided to replace it with a normal CharField in my model, which I did (again with successfull migration).
Problem:
When I then deleted the QueryStringField class entirely from myapp.models and did makemigrations, it threw the following error (last lines shown here):
File "C:\...\migrations\00....py", line 17, in Migration
field=myapp.models.QueryStringField(max_length=255),
AttributeError: module 'myapp.models' has no attribute 'QueryStringField'
What can I do to fix this? I understand that this is technically correct, since the migration references a class that is not present, but surely this can be solved somehow. I am a little nervous about just deleting migration files.
You can not just delete a field class, that class is referenced by the migration files.
You should change the migration files where the QueryStringField is involved. You can inspect the migration where you changed the field, and remove that part of the migration, so:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('someapp', '1234_some_name'),
]
operations = [
# migrations.AlterField(
# model_name='mymodel',
# name='myfield',
# field=models.QueryStringField(
# # ...
# ),
# ),
]
as well as in the migration where you change it back:
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('someapp', '5678_some_other_name'),
]
operations = [
# migrations.AlterField(
# model_name='mymodel',
# name='myfield',
# field=models.CharField(
# # ...
# ),
# ),
]
After you removed these changes (and all changes that thus work with the QueryStringField), you can safely remove it.

How can I add a new field in model when I already have a datamigration in Django?

I have a migrations come from the work of another person, and I'm in on a code base controlled by the version too. I created a migration file after adding a new field but the problem is in the data migration (0003_datamigration_initial_service_configuration.py)that is created before through the function get_or_create () is executed in the head of the list while my migration file data in the end I should not change a migration already developed.
This is my list of migrations
0001_initial.py
0002_datamigration_initial_users_list.py
0003_datamigration_initial_mymodel.py
...
0015.addnewfield
0003_datamigration_initial_mymodel.py:
from __future__ import unicode_literals
from django.db import migrations
from ..models import MyModel
def create_initial_mymodel(apps, schema_editor):
current_product_type, created = MyModel.objects.get_or_create()
class Migration(migrations.Migration):
dependencies = [
('myapp', '0002_datamigration_initial_users_list'),
]
operations = [
migrations.RunPython(create_initial_mymodel),
]
Rhe error is no such column named new_field
How can I fix the problem without editing any data migrations?
You should use apps.get_model to get the MyModel model, instead of importing it directly.
def create_initial_mymodel_forward(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
current_product_type, created = MyModel.objects.get_or_create()
Using apps.get_model will load a historical version of the model with the correct fields.

Django : Migration of polymorphic models back to a single base class

Let's suppose I have a polymorphic model and I want to get rid of it.
class AnswerBase(models.Model):
question = models.ForeignKey(Question, related_name="answers")
response = models.ForeignKey(Response, related_name="answers")
class AnswerText(AnswerBase):
body = models.TextField(blank=True, null=True)
class AnswerInteger(AnswerBase):
body = models.IntegerField(blank=True, null=True)
When I want to get all the answers I can never access "body" or I need to try to get the instance of a sub-class by trial and error.
# Query set of answerBase, no access to body
AnswerBase.objects.all()
question = Question.objects.get(pk=1)
# Query set of answerBase, no access to body (even with django-polymorphic)
question.answers.all()
I don't want to use django-polymorphic because of performances, because it does not seem to work for foreignKey relation, and because I don't want my model to be too complicated. So I want this polymorphic architecture to become this simplified one :
class Answer(models.Model):
question = models.ForeignKey(Question, related_name="answers")
response = models.ForeignKey(Response, related_name="answers")
body = models.TextField(blank=True, null=True)
The migrations cannot be created automatically, it would delete all older answers in the database. I've read the Schema Editor documentation but it does not seem there is a buildin to migrate a model to something that already exists. So I want to create my own operation to save the AnswerText and AnswerInteger as an Answer then delete AnswerText and AnswerInteger. I'm hoping I won't have to write SQL directly, but maybe that's the only solution ? My migration file looks like this. I created an Operation called MigrateAnswer :
from myapp.migrations import MigrateAnswer
class Migration(migrations.Migration):
operations = [
migrations.RenameModel("AnswerBase", "Answer"),
migrations.AddField(
model_name='answer',
name='body',
field=models.TextField(blank=True, null=True),
),
MigrateAnswer("AnswerInteger"),
MigrateAnswer("AnswerText"),
migrations.DeleteModel(name='AnswerInteger',),
migrations.DeleteModel(name='AnswerText',),
]
So what I want to do in MigrateAnswer is to migrate the value for an old model (AnswerInteger and AnswerText) to the base class (now named Answer, previousely AnswerBase). Here's my operation class :
from django.db.migrations.operations.base import Operation
class MigrateAnswer(Operation):
reversible = False
def __init__(self, model_name):
self.old_name = model_name
def database_forwards(self, app_label, schema_editor, from_state,
to_state):
new_model = to_state.apps.get_model(app_label, "Answer")
old_model = from_state.apps.get_model(app_label, self.old_name)
for field in old_model._meta.local_fields:
# loop on "question", "reponse" and "body"
# schema_editor.alter_field() Alter a field on a single model
# schema_editor.add_field() + remove_field() Does not permit
# to migrate the value from the old field to the new one
pass
So my question is : Is it possible to do this wihout using "execute" (ie : without writing SQL). If so what should I do in the for loop of my Operation ?
Thanks in advance !
There is no need to write an Operations class; data migrations can be done simply with a RunPython call, as the docs show.
Within that function you can use perfectly normal model instance methods; since you know the fields you want to move the data for, there is no need to get them via meta lookups.
However you will need to temporarily call the new body field a different name, so it doesn't conflict with the old fields on the subclasses; you can rename it back at the end and delete the child classes because the value will be in the base class.
def migrate_answers(apps, schema_editor):
classes = []
classes_str = ['AnswerText', 'AnswerInteger']
for class_name in classes_str:
classes.append(apps.get_model('survey', class_name))
for class_ in classes:
for answer in class_.objects.all():
answer.new_body = answer.body
answer.save()
operations = [
migrations.AddField(
model_name='answerbase',
name='new_body',
field=models.TextField(blank=True, null=True),
),
migrations.RunPython(migrate_answers),
migrations.DeleteModel(name='AnswerInteger',),
migrations.DeleteModel(name='AnswerText',),
migrations.RenameField('AnswerBase', 'new_body', 'body'),
migrations.RenameModel("AnswerBase", "Answer"),
]
You could create an empty migration for the app you want to do these modifications and use the migrations.RunPython Class to execute custom python functions.
Inside these functions you can have access to your models
The Django ORM that you can do data manipulation.
Pure python, no raw SQL.

How do I populate the Django table MyApp_user_groups in a yaml fixture?

My question is very similar to How do I populate the Django table django_site in a yaml fixture? , but I need some help on this.
I create a group in auth_group table in my YAML fixture:
- model: auth.Group
pk: 1
fields:
name: admin
In my MySql I have a Django table named: MyApp_user_groups, where MyApp is the name of the Django app; this table is automatically created by Django. This table should connect MyApp.User model (created by me) to the Groups model of Django.
Using a YAML fixture, I want to add a tuple in that table (for the purpose of add a user to a group automatically when I run syncdb), but I receive the following error:
DeserializationError: Invalid model identifier: MyApp.User_Groups
So the code is
- model: MyApp.User_Groups
pk: 1
fields:
user_id: 2
group_id: 1
I'm a newbie with Django.
Any ideas?
If you use Django >= 1.7 (with migrations) you could (and should) use Data Migrations.
https://docs.djangoproject.com/en/1.7/topics/migrations/#data-migrations
def create_group(apps, schema_editor):
Group = apps.get_model("auth", "Group")
g = Group.objects.create("Your group name")
UserGroup = apps.get_model("MyApp", "User_Group")
UserGroup.objects.create(user_id=2, group_id=g.id)
class Migration(migrations.Migration):
dependencies = [
('MyApp', '0001_initial'),
('auth','0001_initial'),
]
operations = [
migrations.RunPython(create_group),
]

How to reference generated permissions in Django 1.7 migrations

I'm trying to create a auth.Group with permissions automaticly with migrations. My problem is that when I run migrate on empty database the migration that tries to attach permission to the group can't find the permission. If I target an earlier migration so that migrate exits without an error the permissions appear into the database and after that the migration code can find the permission. So what can I do so that the migration can reference a permission which is created in an earlier migration when the migrations are run back to back?
def load_data(apps, schema_editor):
Permission = apps.get_model('auth', 'Permission')
Group = apps.get_model('auth', 'Group')
can_add = Permission.objects.get(codename='add_game')
developers = Group.objects.create(name='Developer')
developers.permissions.add(can_add)
developers.save()
class Migration(migrations.Migration):
dependencies = [
('myApp', '0004_game'),
]
operations = [
migrations.RunPython(load_data),
]
The game model is created in an earlier migration. This code causes always an error stating that Permission matching query does not exist when I run it with other migrations on an empty database.
I'm using python 3.4 with django 1.7.2
Oh. 4 years later...
To create permissions Django uses post_migrate signal.
Therefore, when running all migrations at a time, permissions do not yet exist.
Therefore, you can take out your function, for example, in the management command.
However, you can still do it like this:
from django.contrib.auth.management import create_permissions
APPS = [
...your app labels
]
def create_applications_permissions():
for app in APPS:
app_config = django_apps.get_app_config(app)
create_permissions(app_config)
def load_data(apps, schema_editor):
create_applications_permissions()
Permission = apps.get_model('auth', 'Permission')
Group = apps.get_model('auth', 'Group')
can_add = Permission.objects.get(codename='add_game')
developers = Group.objects.create(name='Developer')
developers.permissions.add(can_add)
developers.save()
class Migration(migrations.Migration):
dependencies = [
('myApp', '0004_game'),
]
operations = [
migrations.RunPython(load_data),
]
And to create permissions do not use apps passed to the migration. Will not pass the check in create_permissions:
if not app_config.models_module:
return
But you have to be careful.
I hope someone will be useful.

Categories

Resources