Using @Ernest code, I've written a manage_custom.py for pending migrations. You can get the list of pending migrations also migrate those pending migrations (only), hence saving your time.
manage_custom.py
__author__ = "Parag Tyagi"
# set environment
import os
import sys
import django
sys.path.append('../')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
django.setup()
from django.core.management import execute_from_command_line
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.executor import MigrationExecutor
class Migration(object):
"""
A custom manage.py file for managing pending migrations (only)
"""
def __init__(self, migrate_per_migration_id=False):
"""
:param migrate_per_migration_id: Setting this to `True` will migrate each pending migration of any
particular app individually. `False` will migrate the whole app at a time.
You can add more arguments (viz. showmigrations, migrate) by defining the argument with prefix as 'ARGV_'
and create its functionality accordingly.
"""
self.ARG_PREFIX = 'ARGV_'
self.MIGRATE_PER_MIGRATION_ID = migrate_per_migration_id
self.ARGV_showmigrations = False
self.ARGV_migrate = False
@staticmethod
def get_pending_migrations(database):
"""
:param database: Database alias
:return: List of pending migrations
"""
connection = connections[database]
connection.prepare_database()
executor = MigrationExecutor(connection)
targets = executor.loader.graph.leaf_nodes()
return executor.migration_plan(targets)
def check_arguments(self, args):
"""
Method for checking arguments passed while running the command
:param args: Dictionary of arguments passed while running the script file
:return: Set the argument variable ('ARGV_<argument>') to True if found else terminate the script
"""
required_args = filter(None, [var.split(self.ARG_PREFIX)[1] if var.startswith(self.ARG_PREFIX)
else None for var in self.__dict__.keys()])
if any(k in args for k in required_args):
for arg in required_args:
if arg in args:
setattr(self, '{}{}'.format(self.ARG_PREFIX, arg), True)
break
else:
print ("Please pass argument: {}"
"\ne.g. python manage_custom.py {}".format(required_args, required_args[0]))
sys.exit()
def do_migration(self):
"""
Migrates all the pending migrations (if any)
"""
pending_migrations = self.get_pending_migrations(DEFAULT_DB_ALIAS)
if pending_migrations:
done_app = []
for mig in pending_migrations:
app, migration_id = str(mig[0]).split('.')
commands = ['manage.py', 'migrate'] + ([app, migration_id] if self.MIGRATE_PER_MIGRATION_ID else [app])
if self.ARGV_migrate and (app not in done_app or self.MIGRATE_PER_MIGRATION_ID):
execute_from_command_line(commands)
done_app.append(app)
elif self.ARGV_showmigrations:
print (str(mig[0]))
else:
print ("No pending migrations")
if __name__ == '__main__':
args = sys.argv
migration = Migration()
migration.check_arguments(args)
migration.do_migration()
Usage:
# below command will show all pending migrations
python manage_custom.py showmigrations
# below command will migrate all pending migrations
python manage_custom.py migrate
PS: Please setup environment as per your project structure.
./manage.py showmigrations #check which already-made migrations have been applied or not
(or: ./manage.py showmigrations someApp #for specific app alone)
./manage.py makemigrations --dry-run #check for migrations to be made
(or: ./manage.py makemigrations someApp --dry-run #for specific app alone)
./manage.py makemigrations #make the migrations
(or: ./manage.py makemigrations someApp #for specific app alone)
./manage.py showmigrations #check which already-made migrations have been applied or not
(or: ./manage.py showmigrations someApp #for specific app alone)
./manage.py sqlmigrate someApp 0001 #view SQL changes for specific app & migration
./manage.py showmigrations #check which already-made migrations have been applied or not
(or: ./manage.py showmigrations someApp #for specific app alone)
./manage.py makemigrations --dry-run #check for migrations to be made
(or: ./manage.py makemigrations someApp --dry-run #for specific app alone)
PS: ./manage.py migrate someApp zero #unapply all migrations for specific app
Here is my Python soloution to get some information about the migration-states:
from io import StringIO # for Python 2 use from StringIO import StringIO
from django.core.management import call_command
def get_migration_state():
result = []
out = StringIO()
call_command('showmigrations', format="plan", stdout=out)
out.seek(0)
for line in out.readlines():
status, name = line.rsplit(' ', 1)
result.append((status.strip() == '[X]', name.strip()))
return result
if there are no pending changes in the Models requiring a migration to be created
python manage.py migrate --plan
expected output would be:
'Planned operations: No planned migration operations.'
You can use it in python script with call_command and develop a way to check for the expected output. If there are any pending makemigrations of migrate calls, the output will be different from the expected and you can understand that something is missing.
I'm running this at a CI/CD pipeline with very good results.