mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00: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 these to force registration of checks
|
||||||
import django.core.checks.async_checks # NOQA isort:skip
|
import django.core.checks.async_checks # NOQA isort:skip
|
||||||
import django.core.checks.caches # 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.compatibility.django_4_0 # NOQA isort:skip
|
||||||
import django.core.checks.database # NOQA isort:skip
|
import django.core.checks.database # NOQA isort:skip
|
||||||
import django.core.checks.files # 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"
|
admin = "admin"
|
||||||
async_support = "async_support"
|
async_support = "async_support"
|
||||||
caches = "caches"
|
caches = "caches"
|
||||||
|
commands = "commands"
|
||||||
compatibility = "compatibility"
|
compatibility = "compatibility"
|
||||||
database = "database"
|
database = "database"
|
||||||
files = "files"
|
files = "files"
|
||||||
|
@ -24,6 +24,7 @@ from django.db.migrations.writer import MigrationWriter
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
autodetector = MigrationAutodetector
|
||||||
help = "Creates new migration(s) for apps."
|
help = "Creates new migration(s) for apps."
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
@ -209,7 +210,7 @@ class Command(BaseCommand):
|
|||||||
log=self.log,
|
log=self.log,
|
||||||
)
|
)
|
||||||
# Set up autodetector
|
# Set up autodetector
|
||||||
autodetector = MigrationAutodetector(
|
autodetector = self.autodetector(
|
||||||
loader.project_state(),
|
loader.project_state(),
|
||||||
ProjectState.from_apps(apps),
|
ProjectState.from_apps(apps),
|
||||||
questioner,
|
questioner,
|
||||||
@ -461,7 +462,7 @@ class Command(BaseCommand):
|
|||||||
# If they still want to merge it, then write out an empty
|
# If they still want to merge it, then write out an empty
|
||||||
# file depending on the migrations needing merging.
|
# file depending on the migrations needing merging.
|
||||||
numbers = [
|
numbers = [
|
||||||
MigrationAutodetector.parse_number(migration.name)
|
self.autodetector.parse_number(migration.name)
|
||||||
for migration in merge_migrations
|
for migration in merge_migrations
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
|
@ -15,6 +15,7 @@ from django.utils.text import Truncator
|
|||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
autodetector = MigrationAutodetector
|
||||||
help = (
|
help = (
|
||||||
"Updates database schema. Manages both apps with migrations and those without."
|
"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.")
|
self.stdout.write(" No migrations to apply.")
|
||||||
# If there's changes that aren't in migrations yet, tell them
|
# If there's changes that aren't in migrations yet, tell them
|
||||||
# how to fix it.
|
# how to fix it.
|
||||||
autodetector = MigrationAutodetector(
|
autodetector = self.autodetector(
|
||||||
executor.loader.project_state(),
|
executor.loader.project_state(),
|
||||||
ProjectState.from_apps(apps),
|
ProjectState.from_apps(apps),
|
||||||
)
|
)
|
||||||
|
@ -77,6 +77,7 @@ Django's system checks are organized using the following tags:
|
|||||||
* ``async_support``: Checks asynchronous-related configuration.
|
* ``async_support``: Checks asynchronous-related configuration.
|
||||||
* ``caches``: Checks cache related configuration.
|
* ``caches``: Checks cache related configuration.
|
||||||
* ``compatibility``: Flags potential problems with version upgrades.
|
* ``compatibility``: Flags potential problems with version upgrades.
|
||||||
|
* ``commands``: Checks custom management commands related configuration.
|
||||||
* ``database``: Checks database-related configuration issues. Database checks
|
* ``database``: Checks database-related configuration issues. Database checks
|
||||||
are not run by default because they do more than static code analysis as
|
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
|
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
|
* **models.W047**: ``<database>`` does not support unique constraints with
|
||||||
nulls distinct.
|
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
|
Security
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -230,6 +230,10 @@ Management Commands
|
|||||||
setting the :envvar:`HIDE_PRODUCTION_WARNING` environment variable to
|
setting the :envvar:`HIDE_PRODUCTION_WARNING` environment variable to
|
||||||
``"true"``.
|
``"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
|
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.apps import apps
|
||||||
from django.core.management import CommandError, call_command
|
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 (
|
from django.db import (
|
||||||
ConnectionHandler,
|
ConnectionHandler,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
@ -19,10 +23,11 @@ from django.db import (
|
|||||||
)
|
)
|
||||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
from django.db.backends.utils import truncate_name
|
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.exceptions import InconsistentMigrationHistory
|
||||||
from django.db.migrations.recorder import MigrationRecorder
|
from django.db.migrations.recorder import MigrationRecorder
|
||||||
from django.test import TestCase, override_settings, skipUnlessDBFeature
|
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 import timezone
|
||||||
from django.utils.version import get_docs_version
|
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'."
|
msg = "Cannot find a migration matching 'nonexistent' from app 'migrations'."
|
||||||
with self.assertRaisesMessage(CommandError, msg):
|
with self.assertRaisesMessage(CommandError, msg):
|
||||||
call_command("optimizemigration", "migrations", "nonexistent")
|
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