So about a year ago I started a project and like all new developers I didn't really focus too much on the structure, however now I am further along with Django it has started to appear that my project layout mainly my models are horrible in structure.
I have models mainly held in a single app and really most of these models should be in their own individual apps, I did try and resolve this and move them with south however I found it tricky and really difficult due to foreign keys ect.
However due to Django 1.7 and built in support for migrations is there a better way to do this now?
This can be done fairly easily using migrations.SeparateDatabaseAndState. Basically, we use a database operation to rename the table concurrently with two state operations to remove the model from one app's history and create it in another's.
Remove from old app
python manage.py makemigrations old_app --empty
In the migration:
class Migration(migrations.Migration):
dependencies = []
database_operations = [
migrations.AlterModelTable('TheModel', 'newapp_themodel')
]
state_operations = [
migrations.DeleteModel('TheModel')
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations)
]
Add to new app
First, copy the model to the new app's model.py, then:
python manage.py makemigrations new_app
This will generate a migration with a naive CreateModel operation as the sole operation. Wrap that in a SeparateDatabaseAndState operation such that we don't try to recreate the table. Also include the prior migration as a dependency:
class Migration(migrations.Migration):
dependencies = [
('old_app', 'above_migration')
]
state_operations = [
migrations.CreateModel(
name='TheModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
'db_table': 'newapp_themodel',
},
bases=(models.Model,),
)
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]
I am removing the old answer as may result in data loss. As ozan mentioned, we can create 2 migrations one in each app. The comments below this post refer to my old answer.
First migration to remove model from 1st app.
$ python manage.py makemigrations old_app --empty
Edit migration file to include these operations.
class Migration(migrations.Migration):
database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]
state_operations = [migrations.DeleteModel('TheModel')]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations)
]
Second migration which depends on first migration and create the new table in 2nd app. After moving model code to 2nd app
$ python manage.py makemigrations new_app
and edit migration file to something like this.
class Migration(migrations.Migration):
dependencies = [
('old_app', 'above_migration')
]
state_operations = [
migrations.CreateModel(
name='TheModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
'db_table': 'newapp_themodel',
},
bases=(models.Model,),
)
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]
I encountered the same problem.
Ozan's answer helped me a lot but unfortunately was not enough. Indeed I had several ForeignKey linking to the model I wanted to move. After some headache I found the solution so decided to post it to solve people time.
You need 2 more steps:
Before doing anything, change all your ForeignKey linking to TheModel into Integerfield. Then run python manage.py makemigrations
After doing Ozan's steps, re-convert your foreign keys: put back ForeignKey(TheModel)instead of IntegerField(). Then make the migrations again (python manage.py makemigrations). You can then migrate and it should work (python manage.py migrate)
Hope it helps. Of course test it in local before trying in production to avoid bad suprises :)
How I did it (tested on Django==1.8, with postgres, so probably also 1.7)
Situation
app1.YourModel
but you want it to go to:
app2.YourModel
Copy YourModel (the code) from app1 to app2.
add this to app2.YourModel:
Class Meta:
db_table = 'app1_yourmodel'
$ python manage.py makemigrations app2
A new migration (e.g. 0009_auto_something.py) is made in app2 with a migrations.CreateModel() statement, move this statement to the initial migration of app2 (e.g. 0001_initial.py) (it will be just like it always have been there). And now remove the created migration = 0009_auto_something.py
Just as you act, like app2.YourModel always has been there, now remove the existence of app1.YourModel from your migrations. Meaning: comment out the CreateModel statements, and every adjustment or datamigration you used after that.
And of course, every reference to app1.YourModel has to be changed to app2.YourModel through your project. Also, don't forget that all possible foreign keys to app1.YourModel in migrations have to be changed to app2.YourModel
Now if you do $ python manage.py migrate, nothing has changed, also when you do $ python manage.py makemigrations, nothing new has been detected.
Now the finishing touch: remove the Class Meta from app2.YourModel and do $ python manage.py makemigrations app2 && python manage.py migrate app2 (if you look into this migration you'll see something like this:)
migrations.AlterModelTable(
name='yourmodel',
table=None,
),
table=None, means it will take the default table-name, which in this case will be app2_yourmodel.
DONE, with data saved.
P.S during the migration it will see that that content_type app1.yourmodel has been removed and can be deleted. You can say yes to that but only if you don't use it. In case you heavily depend on it to have FKs to that content-type be intact, don't answer yes or no yet, but go into the db that time manually, and remove the contentype app2.yourmodel, and rename the contenttype app1.yourmodel to app2.yourmodel, and then continue by answering no.
I get nervous hand-coding migrations (as is required by Ozan's answer) so the following combines Ozan's and Michael's strategies to minimize the amount of hand-coding required:
Before moving any models, make sure you're working with a clean baseline by running makemigrations.
Move the code for the Model from app1 to app2
As recommended by #Michael, we point the new model to the old database table using the db_table Meta option on the "new" model:
class Meta:
db_table = 'app1_yourmodel'
Run makemigrations. This will generate CreateModel in app2 and DeleteModel in app1. Technically, these migrations refer to the exact same table and would remove (including all data) and re-create the table.
In reality, we don't want (or need) to do anything to the table. We just need Django to believe that the change has been made. Per #Ozan's answer, the state_operations flag in SeparateDatabaseAndState does this. So we wrap all of the migrations entries IN BOTH MIGRATIONS FILES with SeparateDatabaseAndState(state_operations=[...]). For example,
operations = [
...
migrations.DeleteModel(
name='YourModel',
),
...
]
becomes
operations = [
migrations.SeparateDatabaseAndState(state_operations=[
...
migrations.DeleteModel(
name='YourModel',
),
...
])
]
You also need to make sure the new "virtual" CreateModel migration depends on any migration that actually created or altered the original table. For example, if your new migrations are app2.migrations.0004_auto_<date> (for the Create) and app1.migrations.0007_auto_<date> (for the Delete), the simplest thing to do is:
Open app1.migrations.0007_auto_<date> and copy its app1 dependency (e.g. ('app1', '0006...'),). This is the "immediately prior" migration in app1 and should include dependencies on all of the actual model building logic.
Open app2.migrations.0004_auto_<date> and add the dependency you just copied to its dependencies list.
If you have ForeignKey relationship(s) to the model you're moving, the above may not work. This happens because:
Dependencies are not automatically created for the ForeignKey changes
We do not want to wrap the ForeignKey changes in state_operations so we need to ensure they are separate from the table operations.
NOTE: Django 2.2 added a warning (models.E028) that breaks this method. You may be able to work around it with managed=False but I have not tested it.
The "minimum" set of operations differ depending on the situation, but the following procedure should work for most/all ForeignKey migrations:
COPY the model from app1 to app2, set db_table, but DON'T change any FK references.
Run makemigrations and wrap all app2 migration in state_operations (see above)
As above, add a dependency in the app2 CreateTable to the latest app1 migration
Point all of the FK references to the new model. If you aren't using string references, move the old model to the bottom of models.py (DON'T remove it) so it doesn't compete with the imported class.
Run makemigrations but DON'T wrap anything in state_operations (the FK changes should actually happen). Add a dependency in all the ForeignKey migrations (i.e. AlterField) to the CreateTable migration in app2 (you'll need this list for the next step so keep track of them). For example:
Find the migration that includes the CreateModel e.g. app2.migrations.0002_auto_<date> and copy the name of that migration.
Find all migrations that have a ForeignKey to that model (e.g. by searching app2.YourModel to find migrations like:
class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='relatedmodel',
name='fieldname',
field=models.ForeignKey(... to='app2.YourModel'),
),
]
Add the CreateModel migration as as a dependency:
class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
('app2', '0002_auto_<date>'),
]
Remove the models from app1
Run makemigrations and wrap the app1 migration in state_operations.
Add a dependency to all of the ForeignKey migrations (i.e. AlterField) from the previous step (may include migrations in app1 and app2).
When I built these migrations, the DeleteTable already depended on the AlterField migrations so I didn't need to manually enforce it (i.e. Alter before Delete).
At this point, Django is good to go. The new model points to the old table and Django's migrations have convinced it that everything has been relocated appropriately. The big caveat (from #Michael's answer) is that a new ContentType is created for the new model. If you link (e.g. by ForeignKey) to content types, you'll need to create a migration to update the ContentType table.
I wanted to cleanup after myself (Meta options and table names) so I used the following procedure (from #Michael):
Remove the db_table Meta entry
Run makemigrations again to generate the database rename
Edit this last migration and make sure it depends on the DeleteTable migration. It doesn't seem like it should be necessary as the Delete should be purely logical, but I've run into errors (e.g. app1_yourmodel doesn't exist) if I don't.
Another hacky alternative if the data is not big or too complicated, but still important to maintain, is to:
Get data fixtures using manage.py dumpdata
Proceed to model changes and migrations properly, without relating the changes
Global replace the fixtures from the old model and app names to the new
Load data using manage.py loaddata
You can try the following (untested):
move the model from src_app to dest_app
migrate dest_app; make sure the schema migration depends on the latest src_app migration (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files)
add a data migration to dest_app, that copies all data from src_app
migrate src_app; make sure the schema migration depends on the latest (data) migration of dest_app -- that is: the migration of step 3
Note that you will be copying the whole table, instead of moving it, but that way both apps don't have to touch a table that belongs to the other app, which I think is more important.
Copied from my answer at https://stackoverflow.com/a/47392970/8971048
In case you need to move the model and you don't have access to the app anymore (or you don't want the access), you can create a new Operation and consider to create a new model only if the migrated model does not exist.
In this example I am passing 'MyModel' from old_app to myapp.
class MigrateOrCreateTable(migrations.CreateModel):
def __init__(self, source_table, dst_table, *args, **kwargs):
super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
self.source_table = source_table
self.dst_table = dst_table
def database_forwards(self, app_label, schema_editor, from_state, to_state):
table_exists = self.source_table in schema_editor.connection.introspection.table_names()
if table_exists:
with schema_editor.connection.cursor() as cursor:
cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
else:
return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)
class Migration(migrations.Migration):
dependencies = [
('myapp', '0002_some_migration'),
]
operations = [
MigrateOrCreateTable(
source_table='old_app_mymodel',
dst_table='myapp_mymodel',
name='MyModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=18))
],
),
]
This is tested roughly, so do not forget to backup your DB!!!
For example, there are two apps: src_app and dst_app, we want to move model MoveMe from src_app to dst_app.
Create empty migrations for both apps:
python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app
Let's assume, that new migrations are XXX1_src_app_new and XXX1_dst_app_new, previuos top migrations are XXX0_src_app_old and XXX0_dst_app_old.
Add an operation that renames table for MoveMe model and renames its app_label in ProjectState to XXX1_dst_app_new. Do not forget to add dependency on XXX0_src_app_old migration. The resulting XXX1_dst_app_new migration is:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):
def __init__(self, name, old_app_label):
self.name = name
self.old_app_label = old_app_label
def state_forwards(self, app_label, state):
# Get all of the related objects we need to repoint
apps = state.render(skip_cache=True)
model = apps.get_model(self.old_app_label, self.name)
related_objects = model._meta.get_all_related_objects()
related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
# Rename the model
state.models[app_label, self.name.lower()] = state.models.pop(
(self.old_app_label, self.name.lower())
)
state.models[app_label, self.name.lower()].app_label = app_label
for model_state in state.models.values():
try:
i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
except ValueError:
pass
# Repoint the FKs and M2Ms pointing to us
for related_object in (related_objects + related_m2m_objects):
# Use the new related key for self referential related objects.
if related_object.model == model:
related_key = (app_label, self.name.lower())
else:
related_key = (
related_object.model._meta.app_label,
related_object.model._meta.object_name.lower(),
)
new_fields = []
for name, field in state.models[related_key].fields:
if name == related_object.field.name:
field = field.clone()
field.rel.to = "%s.%s" % (app_label, self.name)
new_fields.append((name, field))
state.models[related_key].fields = new_fields
def database_forwards(self, app_label, schema_editor, from_state, to_state):
old_apps = from_state.render()
new_apps = to_state.render()
old_model = old_apps.get_model(self.old_app_label, self.name)
new_model = new_apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
# Move the main table
schema_editor.alter_db_table(
new_model,
old_model._meta.db_table,
new_model._meta.db_table,
)
# Alter the fields pointing to us
related_objects = old_model._meta.get_all_related_objects()
related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
for related_object in (related_objects + related_m2m_objects):
if related_object.model == old_model:
model = new_model
related_key = (app_label, self.name.lower())
else:
model = related_object.model
related_key = (
related_object.model._meta.app_label,
related_object.model._meta.object_name.lower(),
)
to_field = new_apps.get_model(
*related_key
)._meta.get_field_by_name(related_object.field.name)[0]
schema_editor.alter_field(
model,
related_object.field,
to_field,
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
self.old_app_label, app_label = app_label, self.old_app_label
self.database_forwards(app_label, schema_editor, from_state, to_state)
app_label, self.old_app_label = self.old_app_label, app_label
def describe(self):
return "Move %s from %s" % (self.name, self.old_app_label)
class Migration(migrations.Migration):
dependencies = [
('dst_app', 'XXX0_dst_app_old'),
('src_app', 'XXX0_src_app_old'),
]
operations = [
MoveModelFromOtherApp('MoveMe', 'src_app'),
]
Add dependency on XXX1_dst_app_new to XXX1_src_app_new. XXX1_src_app_new is no-op migration that is needed to make sure that future src_app migrations will be executed after XXX1_dst_app_new.
Move MoveMe from src_app/models.py to dst_app/models.py. Then run:
python manage.py migrate
That's all!
Lets say you are moving model TheModel from app_a to app_b.
An alternate solution is to alter the existing migrations by hand. The idea is that each time you see an operation altering TheModel in app_a's migrations, you copy that operation to the end of app_b's initial migration. And each time you see a reference 'app_a.TheModel' in app_a's migrations, you change it to 'app_b.TheModel'.
I just did this for an existing project, where I wanted to extract a certain model to an reusable app. The procedure went smoothly. I guess things would be much harder if there were references from app_b to app_a. Also, I had a manually defined Meta.db_table for my model which might have helped.
Notably you will end up with altered migration history. This doesn't matter, even if you have a database with the original migrations applied. If both the original and the rewritten migrations end up with the same database schema, then such rewrite should be OK.
change the names of old models to ‘model_name_old’
makemigrations
make new models named ‘model_name_new’ with identical relationships on the related models
(eg. user model now has user.blog_old and user.blog_new)
makemigrations
write a custom migration that migrates all the data to the new model tables
test the hell out of these migrations by comparing backups with new db copies before and after running the migrations
when all is satisfactory, delete the old models
makemigrations
change the new models to the correct name ‘model_name_new’ -> ‘model_name’
test the whole slew of migrations on a staging server
take your production site down for a few minutes in order to run all migrations without users interfering
Do this individually for each model that needs to be moved.
I wouldn’t suggest doing what the other answer says by changing to integers and back to foreign keys
There is a chance that new foreign keys will be different and rows may have different IDs after the migrations and I didn’t want to run any risk of mismatching ids when switching back to foreign keys.
Related
Recently I renamed a Django model and its parent folder:
from
input_source_type/
models.py
to
event_source_type/
models.py
The models.py contains the InputSourceType which is also renamed to EventSourceType.
Another model in (in the system folder) refers to to this model in its migration (0001_initial.py):
class Migration(migrations.Migration):
initial = True
dependencies = [
('admin_input_source_type', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Systm',
...
When I run python manage.py makemigrations I got
django.db.migrations.exceptions.NodeNotFoundError: Migration admin_system.0001_initial dependencies reference nonexistent parent node ('admin_input_source_type', '0001_initial')
which is correct as I don't the admin_input_source_type anymore.
I don't want to change the migration manually, what would be the Django way in this scenario?
Thanks!
This link should help, but I would recommend creating a new app to avoid many sensitive issues.
TL;DR If I have a Django model for which I create a migration, I can run the migration and create the table in the DB, then I can proceed to reference the model in the code. Now, when I merge the code into master, it will both contain the migration files and the code that references the Django model. When I pull this onto a new machine (e.g. staging/prod), I will want to run the migrations in this new environment so as to have an updated DB. correct? This operation seems impossible though because the code that references the model will want the table to already exist even to just perform the migration itself, otherwise the migration command fails. How to solve this issue?
The Problem
Whenever I create a new model class inside products/models.py (products is the name of my Django app), I cannot reference such model (e.g. Product model) in any method or class before having made and run the migrations. Otherwise, if I reference the new model in the code and then I run any of the following commands/procedures, I receive the OperationalError: no such table error:
python manage.py runserver
python manage.py makemigrations [appname]
python manage.py migrate
python manage.py showmigrations
Delete all existing migrations and try to regenerate them with python manage.py makemigrations [appname]
Delete current db.sqlite3 file; delete all existing migrations and try to regenerate them with python manage.py makemigrations [appname]
The error looks like this:
...
File "/Users/my_user/Work/django_proj_1/config/urls.py", line 21, in <module>
path('', include('products.urls')),
...
File "/Users/my_user/Work/django_proj_1/products/urls.py", line 1, in <module>
from products.api_views import ProductList3
File "/Users/my_user/Work/django_proj_1/products/api_views.py", line 7, in <module>
class ProductList3(ListAPIView):
File "/Users/my_user/Work/django_proj_1/products/api_views.py", line 8, in ProductList3
queryset = get_products()
File "/Users/my_user/Work/django_proj_1/products/data_layer/product_data_layer.py", line 4, in get_products
all_prods = list(Product.objects.values())
...
File "/Users/my_user/.local/share/virtualenvs/django_proj_1-a2O6RBaf/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 413, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table: products_product
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Real Question
This behavior may seem normal, but if you think about it, how would a developer make a release in production where a new model is created and also used in the code simultaneously (given that running migrations will fail if you referenced the created model into the code)?
I found similar questions (which I linked below), but none of them seems actually relevant for a real-world large scale production project. The only 'solution' in the similar questions is to:
comment out the code (all functions in the layer/chain) that end up interacting with the new model that does not exist in the DB yet
(make and) run the migrations
revert the comments
This seems pretty manual and not feasible for real-world production applications. I can understand if the issue was for local development, in which I can
create a model,
quickly make and run the migrations
and then start coding and use that model.
However, how is it possible to go about this in a real-world production scenario? By this I mean, how is it possible to release a single PR of code where I create a model as well as use the model in the code and make sure I can successfully run the migration on the production machine upon release of such code?
More Context of my problem
Let's assume I have the following app in Django called products with, among others, the following files:
...
|_products/ # one of the Django apps in the project
|
|_ models.py # where I defined the Product model
|_ data_layer/ # a python module to separate all DB logic
| |_ __init__.py
| |_ product_data_layer.py # file where I put the function that references the Product model
|_ api_views.py # file where I call a function from the data layer
|_ urls.py # file where I create an API endpoint that references api_views.py
|
...
Code inside the above files:
# products/urls.py content
from products.api_views import ProductList3 # THORWS ERROR
from django.urls import path
urlpatterns = [
path('api/v1/products', ProductList3)
]
# products/api_views.py content
from rest_framework.generics import ListAPIView
from products.serializers import GenericProductSerializer
from products.data_layer.product_data_layer import get_products
class ProductList3(ListAPIView): # THORWS ERROR
queryset = get_products() # THORWS ERROR
serializer_class = GenericProductSerializer
# products/data_layer/product_data_layer.py content
from products.models import Product
def get_products():
all_prods = list(Product.objects.values()) # THORWS ERROR (main problem)
# all the logic I need
return all_prods
# products/models.py content
from django.db import models
class Product(models.Model):
product_name = models.CharField(max_length=100)
product_description = models.TextField('Main Product Description')
def __str__(self):
return self.product_name
Similar questions
The following questions are similar questions that I looked at, but besides some dev-process solutions, I found no substantial answer on how to tackle this issue once for all.
django migration no such table 1 - unclear as to how not to execute code on import
django migration no such table 2 - no answers as of 18/03/2021
django migration no such table 3 - does not work in my case
You write in your class declaration:
queryset = get_products()
And this function has this line:
all_prods = list(Product.objects.values())
What happens here? Anything written directly inside a class is executed when the class is being created. If it were a method of the class then that would not be evaluated. So get_products() is called when the class is being created. Next list(Product.objects.values()) is called where calling list on the queryset forces the query to be evaluated. Now obviously you are making migrations and the tables don't exist yet giving you an error.
Firstly this code of yours is bad design even if there are changes to the table Product your view will still only display the products that were gotten in the first query made when the server was started up. This is obviously unintended and not what you want to do.
What is the solution? Obviously your query should reside somewhere where it is actually meant to be. Mostly in Django with classes that get a queryset there is a method named get_queryset that does this task, so you should be overriding that:
class ProductList3(ListAPIView):
serializer_class = GenericProductSerializer
def get_queryset(self):
return get_products() # More sensible to write it here.
# If concerned about overriding something you can use the below which would be equivalent
# self.queryset = get_products()
# return super().get_queryset()
I had a Django model that looked like the following:
class Foobar(models.Model):
baz = models.CharField(max_length=12, default=some_func)
some_func existed in a file /someproj/utils/utils.py
This was fine, and all was well. I created my migration for this model and worked as expected. Roughly the migration looked like:
from __future__ import unicode_literals
from django.db import migrations, models
import someproj.utils.utils
class Migration(migrations.Migration):
dependencies = [("someproj", "0008_auto_20180928_0002")]
operations = [
migrations.CreateModel(
name="Foobar",
fields=[
(
"baz",
models.CharField(
default=someproj.utils.utils.some_func,
max_length=12,
serialize=False,
),
),
],
)
]
Then I later realized I wanted to rename some_func to something else. I renamed the function in utils.py, and of course now the migration fails as some_func no longer exists. If I modify the migration by hand that works, but the rule of thumb that was explained to me is you (almost) never edit a migration by hand.
What is a way to accommodate this change? Is it you have to edit the migration by hand? Wouldn't that be problematic if I had to run an older version of the code (ie say I had to checkout a previous commit to a point in time before the rename)?
If you only have 1 database, this should work. If you have more than 1, I'm not sure.
Start by writing your method to handle the default of the field:
def rewrite_field_default(self):
for object in Foobar.objects.all()
object.baz = some_func
object.save()
Make an empty migration:
python3 manage.py makemigrations --empty app
Then you can import that into a migration.
operations = [
migrations.RunPython(rewrite_field_default)
]
Then run your migrations as usual.
If it doens't seem to work, let me know.
I have a new django project with no migrations before, therefore I want to create my first migration which inserts some rows into a table. This is what I have yet:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def create_types(apps, schema_editor):
Type = apps.get_model("core", "Type")
type = Type()
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.RunPython(create_types)
]
I have an application core which has model named Type, and I want to create few types and set attributes for them, and all of them save into database. How to create a new object of Type model here?
Also other question, if this is very first migrator can I leave the dependencies empty? The file I created with this tutorial https://docs.djangoproject.com/en/1.8/topics/migrations/#data-migrations named it 0001_initial.py
EDIT: solved, creating model like this works, my overseight.
Type = apps.get_model("core", "Type")
type = Type()
type.prop = '..'
type.save()
The pattern I follow is to start with an initial migration:
> python manage.py makemigrations
This creates the migrations to build your table.
Next, I create an empty migration for the data:
> python manage.py makemigrations --empty core
Then I edit the newly created migration file with the data migration using the RunPython function. The pattern in the docs is a little different compared to the other answer here:
https://docs.djangoproject.com/en/1.11/ref/migration-operations/
def create_types(apps, schema_editor):
db_alias = schema_editor.connection.alias
Type = apps.get_model("core", "Type")
Type.objects.using(db_alias).create(property=value)
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.RunPython(create_types),
]
Firstly, this can't be the first migration, since you need one to actually create your Type table. You at least need to depend on that.
Secondly, once you have the Type model, you treat it just as you would in normal Django code; instantiate it, set its attributes, save it, whatever.
I'm using south to manage migrations and I've hit a corner. Basically I have the following setup:
App1:
class A(models.Model):
# bunch of attributes
App2:
class B(models.Models):
instance_a = models.OneToOneField(A, null=True, blank=True,
editable=False)
Now, I want to go from this to this:
App1:
class A(models.Model):
instance_b = models.ForeignKey(B, null=True, blank=True)
App2:
class B(models.Models):
# other attributes
My main issue is that I can't loose data. So basically at the end of the migration(s) all objects A that mapped previously to objects B should keep that mapping. As an example, if object A with id 7 was mapped to object B with id 8, by the end of this procedure this mapping should be retained.
I tried several things from schema migrations mixed with temporary place holders and data migrations. However I end up always in the same place, which is by the time the data migration is performed I no longer have the previous relations in order to access the correct attributes. So for example, B.instance_a is no longer available.
I would like your opinion on two things:
First, is this viable at all using just south migrations.
Second, how shall I proceed.
Thanks
Finally after some time I got a procedure with django-south that might help others. The key was in south's depends_on feature (http://south.aeracode.org/wiki/Dependencies). I did it in 4 steps:
First:
Create a placeholder for the values of the foreign key in model A.
So model A becomes:
class A(models.Model):
instance_b_placeholder = models.ForeignKey(A, null=True, blank=True)
Now just run manage.py schemamigration app1 --auto.
Second
Create a datamigration so we can copy the values. The goal is to have the data duplicated in the db and later on rename the attributes and delete the old ones. Issue manage.py datamigration app1 update_fields. I chose to keep the datamigration in app1. If you don't do this just make sure it runs after the previous migration.
Here's the datamigration coded:
# Forwards:
for b in orm['app2.B'].objects.filter(instance_b__isnull=False):
b.instance_a.instance_b_placeholder = b
b.instance_a.save()
# Backwards:
for r in orm['app1.A'].objects.filter(instance_b_placeholder__isnull=False):
r.instance_b_placeholder.instance_a = r
r.instance_b_placeholder.save()
Third:
Delete the field instance_b from model B and be sure to make the migration run after the one created in the previous step.
Model B becomes:
class B(models.Model):
# etc...
Issue manage.py schemamigration app2 --auto and edit the migration adding the previous migration to depends_on:
depends_on = (
("app1", "<migration_number>_update_fields"),
)
Forth step:
Rename the place holder. This is achieved by changing the name in the code and editing the migration. Editing is necessary because south tends to delete and add a new column, but we only want it to rename the column.
This migration should run in last place, so I made it dependent on the previous one.
Here's the code:
depends_on = (
("app2", "<previous_migration_here>"),
)
# Forwards:
db.rename_column('app1_a', 'instance_b_placeholder_id', 'instance_b_id')
# Backwards:
db.rename_column('app1_a', 'instance_b_id', 'instance_b_placeholder_id')
So that's it. I don't know if there are so many other ways to do it, but at least this helped me.