I have a django projects that has 2 apps and each app runs on a different DB (lets call them default and DB2).
I have created a router to manage the data and added the router to my settings.py
When I run migrate I get Applying analytics.0001_initial... OK but in the default DB the only thing that gets updated is the django_migrations table showing that the app analytics has been migrated with 0001, and in the DB2 the table itself isn’t even created at all.
Going to the django shell and trying to do obj.objects.all() gives me table DB2.table_name does not exist
I also verified in sql DB2 exists but doesn’t have any table from what I created
My router:
class DB2Router(object):
"""
A router for apps that connect directly to the DB2 database.
This router allows apps to directly update the DB2 database rather
than using raw SQL as in the past. The router forces the use of the DB2
database for any app in the "apps" list.
"""
db = "DB2"
apps = ["analytics"]
def db_for_read(self, model, **hints):
if model._meta.app_label in self.apps:
return self.db
return None
def db_for_write(self, model, **hints):
if model._meta.app_label in self.apps:
return self.db
return None
def allow_relation(self, obj1, obj2, **hints):
# Allow any relation between two models that are both in the same app.
if (obj1._meta.app_label in self.apps) and (obj2._meta.app_label in self.apps):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label in self.apps:
return db == self.db
return None
My model:
class PartnerImpression(models.Model):
""" used to track impressions on widgets through our partners """
partner = models.CharField(max_length=1024)
referer = models.CharField(default="N/A", max_length=2048)
created = models.DateTimeField(auto_now_add=True, blank=True)
The migration:
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='PartnerImpression',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('partner', models.CharField(max_length=1024)),
('referer', models.CharField(default='N/A', max_length=2048)),
('created', models.DateTimeField(auto_now_add=True)),
],
),
]
DB2 exists and has an empty django_migrations table.
Defining the database in the manage.py migrate --database=DB2 command is not an option since I run a CI process on dozens of servers and can't run this manually on all of them so the command needs to stay without arguments.
I don't want any raw SQL in my migrations
I have also found this: Django migrations with multiple databases but I don't know if anything has changed since 2016, and also hoping for a way to get it running without the database option
By default migrate takes --database value as default (DEFAULT_DB_ALIAS), if not provided; hence applies all the migrations for that database. If you want to use what Django provides out-of-the-box, you have to use --database mentioning which database to operate on. If for some reason, you can't use that (like you mentioned), you can create a custom management command e.g. migrate_all to apply all your migrations to all databases at once.
An example approach below with only showing how the handle method can look like, you can take inspiration from migrate:
migrate_all.py:
from importlib import import_module
from django.apps import apps
from django.db import connections
class Command(BaseCommand):
help = "Updates all database schemas."
def add_arguments(self, parser):
# Add arguments if you want
pass
def handle(self, *args, **options):
# Import the 'management' module within each installed app, to register
# dispatcher events.
for app_config in apps.get_app_configs():
if module_has_submodule(app_config.module, "management"):
import_module('.management', app_config.name)
# Iterate over and operate on each of the databases
for connection in connections.databases:
# https://github.com/django/django/blob/master/django/core/management/commands/migrate.py#L84
connection.prepare_database()
...
...
After getting each connection from connections.databases, follow the operations from migrations for each.
Related
I have 'default' and 'secondary' databases. Why, even though it is stated:
makemigrations always creates migrations for model changes, but if allow_migrate() returns False, any migration operations for the model_name will be silently skipped when running migrate on the db. Changing the behavior of allow_migrate() for models that already have migrations may result in broken foreign keys, extra tables, or missing tables. When makemigrations verifies the migration history, it skips databases where no app is allowed to migrate.
when running ./manage.py migrate --database=secondary I receive all the migrations listed as OK and django_migrations table existing in the 'secondary' database, instead of no migrations and no trace for them. Is it a Django design decision or I messed up the routing?
class PrimaryRouter:
"""Primary router allowing ORM operations only on default database"""
# NOTE: https://docs.djangoproject.com/en/3.1/topics/db/multi-db/#an-example
def db_for_read(self, model, **hints):
return 'default'
def db_for_write(self, model, **hints):
return 'default'
def allow_relation(self, obj1, obj2, **hints):
"""
Relations between objects are allowed if both objects are
in the primary/replica pool.
"""
db_set = {'default'}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
All non-auth models end up in this pool.
"""
return db == 'default'
# settings
DATABASE_ROUTERS = ['app.routers.PrimaryRouter', ]
Yes this was intentional as documented
makemigrations always creates migrations for model changes, but if
allow_migrate() returns False, any migration operations for the
model_name will be silently skipped
So that would mean that only migration operations will be skipped
Is there any way in Django to populate the database with multiple records during the migration, or after it, except for the manual method, or restore the backup.
For example:
I have a model with services, which after the creation of the database should already have 3 entries, because it is a binder.
How do I implement this in Django 2.x?
From django documentation on Data migrations
Django can’t automatically generate data migrations for you, as it
does with schema migrations, but it’s not very hard to write them.
Migration files in Django are made up of Operations, and the main
operation you use for data migrations is RunPython.
Example
from django.db import migrations
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model('yourappname', 'Person')
for person in Person.objects.all():
person.name = '%s %s' % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
Django 1.8 - I wrote a new model and I want to create a migration for it. I don't want the other models in the app to be created by my migration since they're proxies.
I tried making sure all the other models have class Meta: managed = False, this didn't stop them from showing up in my migration file.
In my db router, I tried to make use of allow_migrate but again, all the models showed up as "Created" in my migration file.
def allow_migrate(self, db, app_label, model_name=None, **hints):
if db == 'a123admin_rw' and app_label == 'article' and model_name == 'articlestat':
return True
elif db == 'a123admin_rw':
return False
return None
What should I be doing to ensure only my model gets a migration when I run makemigrations?
Thanks to the comment that was posted on here, I worked through it. Here's my final report: http://learnedandhacked.blogspot.ca/2016/02/a-story-of-migrating-new-model-in-app.html
I am trying to use a migrations.RunSQL Django migration to run some arbitrary SQL code. I would like to run this migration for only certain db backends (for example only for postgres).
I would think to use something like this but I don't see the DB connection info in the Migration class.
Here is how I solved the problem since I couldn't get RunSQL to work inside RunPython. Thankfully, the schema_editor object has an execute() method.
def forwards(apps, schema_editor):
if not schema_editor.connection.vendor.startswith('postgres'):
logger.info('Database vendor: {}'.format(schema_editor.connection.vendor))
logger.info('Skipping migration without attempting to ADD CONSTRAINT')
return
schema_editor.execute('ALTER TABLE my_table ADD CONSTRAINT my_constraint (my_field != \'NaN\';)')
def backwards(apps, schema_editor):
if not schema_editor.connection.vendor.startswith('postgres'):
logger.info('Database vendor: {}'.format(schema_editor.connection.vendor))
logger.info('Skipping migration without attempting to DROP CONSTRAINT')
return
schema_editor.execute('ALTER TABLE my_table DROP CONSTRAINT my_constraint;')
class Migration(migrations.Migration):
dependencies = [
...
]
operations = [
migrations.RunPython(forwards, backwards, atomic=True)
]
I just had the same need. I had to edit a migration that set the initial value of a sequence, which works on postgres but not sqlite. Here's how I wrapped the RunSQL inside a RunPython, following the documentation that Daniel linked to.
from django.db import migrations
def forwards(apps, schema_editor):
if not schema_editor.connection.vendor == 'postgres':
return
migrations.RunSQL(
"alter sequence api_consumer_id_seq restart with 1000500;")
class Migration(migrations.Migration):
dependencies = [
('api', '0043_auto_20160416_2313'),
]
operations = [
migrations.RunPython(forwards),
]
I solved a similar problem today -- needing to perform a migration to create a new model, but only for a postgres DB -- and I found this question. However, Matthew's answer did not help me. In fact, I'm not sure it works at all. That is because the line with migrations.RunSQL(...) does not actually run SQL; it creates a new object of type RunSQL which is a Command, and then immediately discards it.
Here's how I ended up solving the problem, in case anyone tries to search for "django conditional migration" in the future:
from __future__ import unicode_literals
import django.contrib.postgres.fields
from django.db import migrations, models
class PostgresOnlyCreateModel(migrations.CreateModel):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor.startswith("postgres"):
super(PostgresOnlyCreateModel, self).database_forwards(app_label, schema_editor, from_state, to_state)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
if schema_editor.connection.vendor.startswith("postgres"):
super(PostgresOnlyCreateModel, self).database_backwards(app_label, schema_editor, from_state, to_state)
class Migration(migrations.Migration):
dependencies = [
...whatever...
]
operations = [
PostgresOnlyCreateModel(
name='...whatever...',
fields=[...whatever...],
),
]
That information is not provided in the Migration class, it is provided in the schema_editor attribute passed to a RunPython operation. See the documentation for some examples on using this.
Another option is to have the actual sql depend on db.connection.vendor:
from django.db import connection
CONCURRENTLY = "CONCURRENTLY" if connection.vendor == "postgres" else ""
SQL = f"CREATE INDEX {CONCURRENTLY}..."
At that point you can just use migrations.RunSQL, which is handy, particularly if you use the state_operations argument.
If you want to apply migrations depending on app or model, I think the best solution is using django database router.
First define a database router class:
from django.db import connections
class PgOnlyMigrateRouter:
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label == 'pgonly_app' or model_name == 'pgonly_model':
return connections[db].vendor.startswith("postgres")
return None
Then in your setting.py file, add this line:
DATABASE_ROUTERS = ['path.to.PgOnlyMigrateRouter']
As you see, this works for all migrations in specified model or app, not just a single migration operation.
I have several django applications:
INSTALLED_APPS = (
'geonode.exposure',
'geonode.isc_viewer',
'geonode.geodetic',
'geonode.observations',
'geonode.ged4gem',
I need to manage all of them except one with syncdb.
How can I get syncdb to intentionally skip the geonode.exposure application?
Update:
I did not describe the full configuration, please allow me to go into more detail:
I am using south to manage db migrations and fixtures for all the apps except exposure.
The exposure app is accessing an external database and is using a router to do so (this is why I want it to be skipped by syncdb).
My router settings look like this:
class GedRouter(object):
def db_for_read(self, model, **hints):
"Point all operations on ged models to 'geddb'"
if model._meta.app_label == 'exposure':
return 'geddb'
return 'default'
def allow_syncdb(self, db, model):
if db == 'geddb' or model._meta.app_label == "ged":
return False # we're not using syncdb on our legacy database
else: # but all other models/databases are fine
return True
Is south not respecting the allow_syncdb method? is south running syncbd on the exposure app because I do not have a migration for it?
You can use managed = False in the model's Meta class. This way, syncdb won't create the app's tables. More information on the documentation.
There is a model meta option "managed", for more info check django documentation:
https://docs.djangoproject.com/en/dev/ref/models/options/#managed
Ok, this is not what your asking directly, but please consider using South http://south.aeracode.org
You can decided which apps to include which version of the model to migrate etc. Sounds like you need a solution here.