mirror of
https://github.com/django/django.git
synced 2024-11-21 19:09:18 +01:00
Fixed #35656 -- Added an autodetector attribute to the makemigrations and migrate commands.
This commit is contained in:
parent
dc626fbe3a
commit
06bf06a911
@ -16,6 +16,7 @@ from .registry import Tags, register, run_checks, tag_exists
|
||||
# Import these to force registration of checks
|
||||
import django.core.checks.async_checks # NOQA isort:skip
|
||||
import django.core.checks.caches # NOQA isort:skip
|
||||
import django.core.checks.commands # NOQA isort:skip
|
||||
import django.core.checks.compatibility.django_4_0 # NOQA isort:skip
|
||||
import django.core.checks.database # NOQA isort:skip
|
||||
import django.core.checks.files # NOQA isort:skip
|
||||
|
28
django/core/checks/commands.py
Normal file
28
django/core/checks/commands.py
Normal file
@ -0,0 +1,28 @@
|
||||
from django.core.checks import Error, Tags, register
|
||||
|
||||
|
||||
@register(Tags.commands)
|
||||
def migrate_and_makemigrations_autodetector(**kwargs):
|
||||
from django.core.management import get_commands, load_command_class
|
||||
|
||||
commands = get_commands()
|
||||
|
||||
make_migrations = load_command_class(commands["makemigrations"], "makemigrations")
|
||||
migrate = load_command_class(commands["migrate"], "migrate")
|
||||
|
||||
if make_migrations.autodetector is not migrate.autodetector:
|
||||
return [
|
||||
Error(
|
||||
"The migrate and makemigrations commands must have the same "
|
||||
"autodetector.",
|
||||
hint=(
|
||||
f"makemigrations.Command.autodetector is "
|
||||
f"{make_migrations.autodetector.__name__}, but "
|
||||
f"migrate.Command.autodetector is "
|
||||
f"{migrate.autodetector.__name__}."
|
||||
),
|
||||
id="commands.E001",
|
||||
)
|
||||
]
|
||||
|
||||
return []
|
@ -12,6 +12,7 @@ class Tags:
|
||||
admin = "admin"
|
||||
async_support = "async_support"
|
||||
caches = "caches"
|
||||
commands = "commands"
|
||||
compatibility = "compatibility"
|
||||
database = "database"
|
||||
files = "files"
|
||||
|
@ -24,6 +24,7 @@ from django.db.migrations.writer import MigrationWriter
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
autodetector = MigrationAutodetector
|
||||
help = "Creates new migration(s) for apps."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
@ -209,7 +210,7 @@ class Command(BaseCommand):
|
||||
log=self.log,
|
||||
)
|
||||
# Set up autodetector
|
||||
autodetector = MigrationAutodetector(
|
||||
autodetector = self.autodetector(
|
||||
loader.project_state(),
|
||||
ProjectState.from_apps(apps),
|
||||
questioner,
|
||||
@ -461,7 +462,7 @@ class Command(BaseCommand):
|
||||
# If they still want to merge it, then write out an empty
|
||||
# file depending on the migrations needing merging.
|
||||
numbers = [
|
||||
MigrationAutodetector.parse_number(migration.name)
|
||||
self.autodetector.parse_number(migration.name)
|
||||
for migration in merge_migrations
|
||||
]
|
||||
try:
|
||||
|
@ -15,6 +15,7 @@ from django.utils.text import Truncator
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
autodetector = MigrationAutodetector
|
||||
help = (
|
||||
"Updates database schema. Manages both apps with migrations and those without."
|
||||
)
|
||||
@ -329,7 +330,7 @@ class Command(BaseCommand):
|
||||
self.stdout.write(" No migrations to apply.")
|
||||
# If there's changes that aren't in migrations yet, tell them
|
||||
# how to fix it.
|
||||
autodetector = MigrationAutodetector(
|
||||
autodetector = self.autodetector(
|
||||
executor.loader.project_state(),
|
||||
ProjectState.from_apps(apps),
|
||||
)
|
||||
|
@ -77,6 +77,7 @@ Django's system checks are organized using the following tags:
|
||||
* ``async_support``: Checks asynchronous-related configuration.
|
||||
* ``caches``: Checks cache related configuration.
|
||||
* ``compatibility``: Flags potential problems with version upgrades.
|
||||
* ``commands``: Checks custom management commands related configuration.
|
||||
* ``database``: Checks database-related configuration issues. Database checks
|
||||
are not run by default because they do more than static code analysis as
|
||||
regular checks do. They are only run by the :djadmin:`migrate` command or if
|
||||
@ -428,6 +429,14 @@ Models
|
||||
* **models.W047**: ``<database>`` does not support unique constraints with
|
||||
nulls distinct.
|
||||
|
||||
Management Commands
|
||||
-------------------
|
||||
|
||||
The following checks verify custom management commands are correctly configured:
|
||||
|
||||
* **commands.E001**: The ``migrate`` and ``makemigrations`` commands must have
|
||||
the same ``autodetector``.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
|
@ -230,6 +230,10 @@ Management Commands
|
||||
setting the :envvar:`HIDE_PRODUCTION_WARNING` environment variable to
|
||||
``"true"``.
|
||||
|
||||
* The :djadmin:`makemigrations` and :djadmin:`migrate` commands have a new
|
||||
``Command.autodetector`` attribute for subclasses to override in order to use
|
||||
a custom autodetector class.
|
||||
|
||||
Migrations
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
from django.core.management.commands.makemigrations import (
|
||||
Command as MakeMigrationsCommand,
|
||||
)
|
||||
|
||||
|
||||
class Command(MakeMigrationsCommand):
|
||||
autodetector = int
|
25
tests/check_framework/test_commands.py
Normal file
25
tests/check_framework/test_commands.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.core import checks
|
||||
from django.core.checks import Error
|
||||
from django.test import SimpleTestCase
|
||||
from django.test.utils import isolate_apps, override_settings, override_system_checks
|
||||
|
||||
|
||||
@isolate_apps("check_framework.custom_commands_app", attr_name="apps")
|
||||
@override_settings(INSTALLED_APPS=["check_framework.custom_commands_app"])
|
||||
@override_system_checks([checks.commands.migrate_and_makemigrations_autodetector])
|
||||
class CommandCheckTests(SimpleTestCase):
|
||||
def test_migrate_and_makemigrations_autodetector_different(self):
|
||||
expected_error = Error(
|
||||
"The migrate and makemigrations commands must have the same "
|
||||
"autodetector.",
|
||||
hint=(
|
||||
"makemigrations.Command.autodetector is int, but "
|
||||
"migrate.Command.autodetector is MigrationAutodetector."
|
||||
),
|
||||
id="commands.E001",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
checks.run_checks(app_configs=self.apps.get_app_configs()),
|
||||
[expected_error],
|
||||
)
|
@ -9,6 +9,10 @@ from unittest import mock
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.management import CommandError, call_command
|
||||
from django.core.management.commands.makemigrations import (
|
||||
Command as MakeMigrationsCommand,
|
||||
)
|
||||
from django.core.management.commands.migrate import Command as MigrateCommand
|
||||
from django.db import (
|
||||
ConnectionHandler,
|
||||
DatabaseError,
|
||||
@ -19,10 +23,11 @@ from django.db import (
|
||||
)
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.backends.utils import truncate_name
|
||||
from django.db.migrations.autodetector import MigrationAutodetector
|
||||
from django.db.migrations.exceptions import InconsistentMigrationHistory
|
||||
from django.db.migrations.recorder import MigrationRecorder
|
||||
from django.test import TestCase, override_settings, skipUnlessDBFeature
|
||||
from django.test.utils import captured_stdout, extend_sys_path
|
||||
from django.test.utils import captured_stdout, extend_sys_path, isolate_apps
|
||||
from django.utils import timezone
|
||||
from django.utils.version import get_docs_version
|
||||
|
||||
@ -3296,3 +3301,59 @@ class OptimizeMigrationTests(MigrationTestBase):
|
||||
msg = "Cannot find a migration matching 'nonexistent' from app 'migrations'."
|
||||
with self.assertRaisesMessage(CommandError, msg):
|
||||
call_command("optimizemigration", "migrations", "nonexistent")
|
||||
|
||||
|
||||
class CustomMigrationCommandTests(MigrationTestBase):
|
||||
@override_settings(
|
||||
MIGRATION_MODULES={"migrations": "migrations.test_migrations"},
|
||||
INSTALLED_APPS=["migrations.migrations_test_apps.migrated_app"],
|
||||
)
|
||||
@isolate_apps("migrations.migrations_test_apps.migrated_app")
|
||||
def test_makemigrations_custom_autodetector(self):
|
||||
class CustomAutodetector(MigrationAutodetector):
|
||||
def changes(self, *args, **kwargs):
|
||||
return []
|
||||
|
||||
class CustomMakeMigrationsCommand(MakeMigrationsCommand):
|
||||
autodetector = CustomAutodetector
|
||||
|
||||
class NewModel(models.Model):
|
||||
class Meta:
|
||||
app_label = "migrated_app"
|
||||
|
||||
out = io.StringIO()
|
||||
command = CustomMakeMigrationsCommand(stdout=out)
|
||||
call_command(command, "migrated_app", stdout=out)
|
||||
self.assertIn("No changes detected", out.getvalue())
|
||||
|
||||
@override_settings(INSTALLED_APPS=["migrations.migrations_test_apps.migrated_app"])
|
||||
@isolate_apps("migrations.migrations_test_apps.migrated_app")
|
||||
def test_migrate_custom_autodetector(self):
|
||||
class CustomAutodetector(MigrationAutodetector):
|
||||
def changes(self, *args, **kwargs):
|
||||
return []
|
||||
|
||||
class CustomMigrateCommand(MigrateCommand):
|
||||
autodetector = CustomAutodetector
|
||||
|
||||
class NewModel(models.Model):
|
||||
class Meta:
|
||||
app_label = "migrated_app"
|
||||
|
||||
out = io.StringIO()
|
||||
command = CustomMigrateCommand(stdout=out)
|
||||
|
||||
out = io.StringIO()
|
||||
try:
|
||||
call_command(command, verbosity=0)
|
||||
call_command(command, stdout=out, no_color=True)
|
||||
command_stdout = out.getvalue().lower()
|
||||
self.assertEqual(
|
||||
"operations to perform:\n"
|
||||
" apply all migrations: migrated_app\n"
|
||||
"running migrations:\n"
|
||||
" no migrations to apply.\n",
|
||||
command_stdout,
|
||||
)
|
||||
finally:
|
||||
call_command(command, "migrated_app", "zero", verbosity=0)
|
||||
|
Loading…
Reference in New Issue
Block a user