Django creates migrations for Django model changes that do not alter
the database, for example, changes to help text or verbose names. In
most cases when I see a migration for a change I am pretty sure
doesn’t run any SQL, I check my assumption using python manage.py
sqlmigrate <app> <migration_name>
, and if it does not produce any
SQL, then I edit the most recent migration to have touched that
column to match the “changes” Django wants to make. For the most part
that isn’t difficult but it is sometimes annoying. Other people have a
similar opinion and one of them shared the following code on a Slack
channel I am on.
WARNING: I have included the code as it was from the shared file,
but my application had some data migrations with RunPython commands
that invoke related_name
. So in our application, we deleted the code
below that removed attributes in MIGRATION_IGNORE_RELATED_FIELD_ATTRS.
# app/management/commands/__init__.py
"""
Django creates redundant migrations for Django model changes that do not alter the database.
Here we patch Django's migration machinery to ignore attrs.
The management commands `makemigrations` and `migrate` will ignore the attrs defined in:
- MIGRATION_IGNORE_MODEL_ATTRS
- MIGRATION_IGNORE_FIELD_ATTRS
- MIGRATION_IGNORE_FILE_FIELD_ATTRS
- MIGRATION_IGNORE_RELATED_FIELD_ATTRS
This will reduce the number of migrations and therefore speed-up development
"""
import logging
from functools import wraps
from django.db.migrations.operations import AlterModelOptions
from django.db.models import Field, FileField
from django.db.models.fields.related import RelatedField
logger = logging.getLogger(__name__)
MIGRATION_IGNORE_MODEL_ATTRS = ["verbose_name", "verbose_name_plural"]
MIGRATION_IGNORE_FIELD_ATTRS = ["validators", "choices", "help_text", "verbose_name"]
MIGRATION_IGNORE_FILE_FIELD_ATTRS = ["upload_to", "storage"]
MIGRATION_IGNORE_RELATED_FIELD_ATTRS = ["related_name", "related_query_name"]
for attr in MIGRATION_IGNORE_MODEL_ATTRS:
logger.info(f"Model {attr} attr will be ignored.")
for attr in MIGRATION_IGNORE_FIELD_ATTRS:
logger.info(f"Field {attr} attr will be ignored.")
for attr in MIGRATION_IGNORE_FILE_FIELD_ATTRS:
logger.info(f"File field {attr} attr will be ignored.")
# We found we needed the related field info so did not remove these
# for attr in MIGRATION_IGNORE_RELATED_FIELD_ATTRS:
# logger.info(f"Related field {attr} attr will be ignored.")
def patch_ignored_model_attrs(cls):
for attr in MIGRATION_IGNORE_MODEL_ATTRS:
cls.ALTER_OPTION_KEYS.remove(attr)
def patch_field_deconstruct(old_func):
@wraps(old_func)
def deconstruct_with_ignored_attrs(self):
name, path, args, kwargs = old_func(self)
for attr in MIGRATION_IGNORE_FIELD_ATTRS:
kwargs.pop(attr, None)
return name, path, args, kwargs
return deconstruct_with_ignored_attrs
def patch_file_field_deconstruct(old_func):
@wraps(old_func)
def deconstruct_with_ignored_attrs(self):
name, path, args, kwargs = old_func(self)
for attr in MIGRATION_IGNORE_FILE_FIELD_ATTRS:
kwargs.pop(attr, None)
return name, path, args, kwargs
return deconstruct_with_ignored_attrs
def patch_related_field_deconstruct(old_func):
@wraps(old_func)
def deconstruct_with_ignored_attrs(self):
name, path, args, kwargs = old_func(self)
for attr in MIGRATION_IGNORE_RELATED_FIELD_ATTRS:
kwargs.pop(attr, None)
return name, path, args, kwargs
return deconstruct_with_ignored_attrs
Field.deconstruct = patch_field_deconstruct(Field.deconstruct)
FileField.deconstruct = patch_file_field_deconstruct(FileField.deconstruct)
# We found we needed the related field info so did not remove these fields
# RelatedField.deconstruct = patch_related_field_deconstruct(RelatedField.deconstruct)
patch_ignored_model_attrs(AlterModelOptions)
And now, create override files for the two manage commands we need to load our patches: migrate and makemigrations.
# app/management/commands/makemigrations.py
"""
Override of Django makemigrations. When we use this version, we
will load the __init__ file above that patches models.Field.
"""
from django.core.management.commands.makemigrations import Command # noqa
# app/management/commands/migrate.py
"""
Override of Django migrate. When we use this version, we
will load the __init__ file above that patches models.Field.
"""
from django.core.management.commands.migrate import Command # noqa