Skip to content

Commit

Permalink
Adds check to forbid auto-names for migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Feb 25, 2020
1 parent 51e94d9 commit 2ab3a8e
Show file tree
Hide file tree
Showing 14 changed files with 794 additions and 166 deletions.
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Allows to test `django` schema and data migrations
- Allows to test both forward and rollback migrations
- Allows to test the migrations order
- Allows to test migration names
- Fully typed with annotations and checked with `mypy`, [PEP561 compatible](https://www.python.org/dev/peps/pep-0561/)
- Easy to start: has lots of docs, tests, and tutorials

Expand Down Expand Up @@ -136,7 +137,7 @@ assert nodes_to_tuples(main_migrations) == [
('main_app', '0001_initial'),
('main_app', '0002_someitem_is_clean'),
('other_app', '0001_initial'),
('main_app', '0003_auto_20191119_2125'),
('main_app', '0003_update_is_clean'),
('main_app', '0004_auto_20191119_2125'),
('other_app', '0002_auto_20191120_2230'),
]
Expand Down Expand Up @@ -193,7 +194,7 @@ class TestDirectMigration(MigratorTestCase):
"""This class is used to test direct migrations."""

migrate_from = ('main_app', '0002_someitem_is_clean')
migrate_to = ('main_app', '0003_auto_20191119_2125')
migrate_to = ('main_app', '0003_update_is_clean')

def prepare(self):
"""Prepare some data before the migration."""
Expand All @@ -210,13 +211,47 @@ class TestDirectMigration(MigratorTestCase):
```


## Testing migration names

`django` generates migration names for you when you run `makemigrations`.
And these names are bad ([read more](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/) about why it is bad)!
Just look at this: `0004_auto_20191119_2125.py`

What does this migration do? What changes does it have?

One can also pass `--name` attribute when creating migrations, but it is easy to forget.

We offer an automated solution: `django` check
that produces a warning for each badly named migration.

Add our check into your `INSTALLED_APPS`:

```python
INSTALLED_APPS = [
# ...

# Our custom check:
'django_test_migrations.contrib.django_checks.AutoNames',
]
```

And then in your CI run:

```bash
python manage.py check --deploy --fail-level WARNING
```

This way you will be safe from wrong names in your migrations.


## Credits

This project is based on work of other awesome people:

- [@asfaltboy](https://gist.github.com/asfaltboy/b3e6f9b5d95af8ba2cc46f2ba6eae5e2)
- [@blueyed](https://gist.github.com/blueyed/4fb0a807104551f103e6)
- [@fernandogrd](https://gist.github.com/blueyed/4fb0a807104551f103e6#gistcomment-1546191)
- [@@adamchainz](https://adamj.eu/tech/2020/02/24/how-to-disallow-auto-named-django-migrations/)

## License

Expand Down
3 changes: 3 additions & 0 deletions django_test_app/django_test_app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
'django.contrib.messages',
'django.contrib.staticfiles',

# Our custom check:
'django_test_migrations.contrib.django_checks.AutoNames',

# Custom:
'main_app',
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 2.2.7 on 2019-11-19 21:25

"""
This migration is named incorrectly.
We use it as a test for wrong autonames.
Please, do not rename it!
"""

from django.db import migrations, models


class Migration(migrations.Migration):
"""Removes the default value from ``is_clean`` from ``SomeItem``."""

dependencies = [
('main_app', '0003_auto_20191119_2125'),
('main_app', '0003_update_is_clean'),
]

operations = [
Expand Down
46 changes: 46 additions & 0 deletions django_test_migrations/autonames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-

from fnmatch import fnmatch
from typing import List, Set, Tuple

from django.conf import settings
from django.core.checks import CheckMessage, Warning
from django.db.migrations.loader import MigrationLoader
from typing_extensions import Final

#: We use this value as a unique identifier of this check.
CHECK_NAME: Final = 'django_test_migrations.autonames'

#: Settings name for this check to ignore some migrations.
_SETTINGS_NAME: Final = 'DTM_IGNORED_MIGRATIONS'


def check_migration_names(*args, **kwargs) -> List[CheckMessage]:
"""Finds automatic names in available migrations."""
loader = MigrationLoader(None, ignore_no_migrations=True)
loader.load_disk()

ignored_migrations: Set[Tuple[str, str]] = getattr(
settings, _SETTINGS_NAME, set(),
)

messages = []
for app_label, migration_name in loader.disk_migrations.keys():
if (app_label, migration_name) in ignored_migrations:
continue

if fnmatch(migration_name, '????_auto_*'):
messages.append(
Warning(
'Migration {0}.{1} has an automatic name.'.format(
app_label, migration_name,
),
hint=(
'Rename the migration to describe its contents, ' +
"or if it's from a third party app, add to " +
_SETTINGS_NAME
),
id='{0}.E001'.format(CHECK_NAME),
),
)
return messages
22 changes: 22 additions & 0 deletions django_test_migrations/contrib/django_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-

from django.apps import AppConfig
from django.core import checks

from django_test_migrations.autonames import CHECK_NAME, check_migration_names


class AutoNames(AppConfig):
"""
Class to install this check into ``INSTALLED_APPS`` in ``django``.
See:
https://docs.djangoproject.com/en/3.0/ref/applications/
"""

name = CHECK_NAME

def ready(self):
"""That's how we register our check when apps are ready."""
checks.register(checks.Tags.compatibility)(check_migration_names)

0 comments on commit 2ab3a8e

Please sign in to comment.