From 9fec6ac9ce8b07c52e5665cf2078226b3f6027bc Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Sat, 27 Jan 2024 10:04:11 -0500 Subject: [PATCH 01/11] Fixes bug with nested operations --- django_squash/db/migrations/utils.py | 7 +++++++ django_squash/db/migrations/writer.py | 4 ++-- .../migrations/run_python_noop/0001_initial.py | 9 ++++----- tests/test_migrations.py | 2 +- tests/test_utils.py | 14 ++++++++++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/django_squash/db/migrations/utils.py b/django_squash/db/migrations/utils.py index b0bd668..263b4ba 100644 --- a/django_squash/db/migrations/utils.py +++ b/django_squash/db/migrations/utils.py @@ -6,6 +6,8 @@ import pkgutil import types from collections import defaultdict +from django.db import migrations +from django.utils.module_loading import import_string def file_hash(file_path): @@ -48,6 +50,11 @@ def function(self, func): raise ValueError("func cannot be part of an instance") name = original_name = func.__qualname__ + if "." in name: + parent_name, actual_name = name.rsplit(".", 1) + parent = getattr(import_string(func.__module__), parent_name) + if issubclass(parent, migrations.Migration): + name = name = original_name = actual_name already_accounted = func in self.functions if already_accounted: return self.functions[func] diff --git a/django_squash/db/migrations/writer.py b/django_squash/db/migrations/writer.py index 91e235c..d102d96 100644 --- a/django_squash/db/migrations/writer.py +++ b/django_squash/db/migrations/writer.py @@ -197,10 +197,10 @@ def get_kwargs(self): for operation in self.migration.operations: if isinstance(operation, dj_migrations.RunPython): if not utils.is_code_in_site_packages(operation.code.__original_module__): - functions.append(self.extract_function(operation.code)) + functions.append(textwrap.dedent(self.extract_function(operation.code))) if operation.reverse_code: if not utils.is_code_in_site_packages(operation.reverse_code.__original_module__): - functions.append(self.extract_function(operation.reverse_code)) + functions.append(textwrap.dedent(self.extract_function(operation.reverse_code))) elif isinstance(operation, dj_migrations.RunSQL): variables.append(self.template_variable % (operation.sql.name, operation.sql.value)) if operation.reverse_sql: diff --git a/tests/app/tests/migrations/run_python_noop/0001_initial.py b/tests/app/tests/migrations/run_python_noop/0001_initial.py index eb046fd..beca342 100644 --- a/tests/app/tests/migrations/run_python_noop/0001_initial.py +++ b/tests/app/tests/migrations/run_python_noop/0001_initial.py @@ -12,17 +12,16 @@ def same_name(apps, schema_editor): return -def same_name_2(apps, schema_editor): - # original function 2 - return - - class Migration(migrations.Migration): initial = True dependencies = [] + def same_name_2(apps, schema_editor): + # original function 2 + return + operations = [ migrations.RunPython(same_name, migrations.RunPython.noop), migrations.RunPython(noop, OtherRunPython.noop), diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 4b7e8c0..ed4b44d 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -18,7 +18,7 @@ def load_migration_module(path): spec.loader.exec_module(module) except Exception as e: with open(path) as f: - raise type(e)("Error loading module file containing:\n\n%s" % f.read()) from e + raise type(e)(f"{e}.\nError loading module file containing:\n\n{f.read()}") from e return module diff --git a/tests/test_utils.py b/tests/test_utils.py index 7268b2e..8ea0b21 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,4 @@ +from django.db import migrations import pytest from django_squash.db.migrations import utils @@ -47,6 +48,14 @@ def func(self): return 5 +class D(migrations.Migration): + def func(self): + return 6 + + def func2(apps, schema_editor): + return 61 + + def test_is_code_in_site_packages(): assert utils.is_code_in_site_packages(django.get_version.__module__) path = django_squash.db.migrations.utils.is_code_in_site_packages.__module__ @@ -82,6 +91,9 @@ def test_unique_function_names_errors(): with pytest.raises(ValueError): names.function(C().func) + with pytest.raises(ValueError): + names.function(D.func) + def test_unique_function_names(): uniq1 = utils.UniqueVariableName() @@ -104,6 +116,7 @@ def test_unique_function_names(): assert uniq1("A.func") == "A.func_2" assert uniq1.function(A.func) == "A.func" assert uniq1.function(A().func) == "A.func" + assert uniq1.function(D.func2) == "func2_5" assert uniq2.function(func2_impostor) == "func2" assert uniq2.function(func2_impostor) == "func2" @@ -115,3 +128,4 @@ def test_unique_function_names(): assert uniq2.function(func2_impostor) == "func2" assert uniq2.function(func2) == "func2_2" assert uniq2.function(func2) == "func2_2" + assert uniq2.function(D.func2) == "func2_5" From 5de644acdc8d3106ef298eb83d4da95f17dcb665 Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Sat, 27 Jan 2024 12:03:09 -0500 Subject: [PATCH 02/11] Fixes issue with test app cache --- tests/conftest.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ff59e59..825867e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +from collections import defaultdict import pytest import io import os @@ -10,6 +11,9 @@ from django.core.management import call_command from django.test.utils import extend_sys_path from django.utils.module_loading import module_dir +from django.conf import settings as django_settings + +INSTALLED_APPS = django_settings.INSTALLED_APPS.copy() @pytest.fixture @@ -72,17 +76,18 @@ def _clean_model(monkeypatch): Django registers models in the apps cache, this is a helper to remove them, otherwise django throws warnings that this model already exists. """ - mock_register_model = mock.Mock(wraps=apps.register_model) - monkeypatch.setattr(apps, "register_model", mock_register_model) yield - for call in mock_register_model.call_args_list: - app_label, model = call.args - model_name = model._meta.model_name - app_models = apps.all_models[app_label] - app_models.pop(model_name) - apps.clear_cache() + # clear apps cache + apps.all_models = defaultdict(dict) + apps.app_configs = {} + apps.stored_app_configs = [] + apps.apps_ready = apps.models_ready = apps.ready = False + apps.loading = False + apps._pending_operations = defaultdict(list) + apps.clear_cache() + apps.populate(INSTALLED_APPS) @pytest.fixture From fded28f8cc6bbad215345b60aa7dd6e75a52921b Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Sun, 28 Jan 2024 15:11:13 -0500 Subject: [PATCH 03/11] all kinds of broken --- django_squash/db/migrations/writer.py | 3 +- .../management/commands/squash_migrations.py | 7 +- .../app/tests/migrations/xxx/0001_initial.py | 44 +++++++ tests/app/tests/migrations/xxx/0002_auto.py | 60 +++++++++ tests/app/tests/migrations/xxx/__init__.py | 0 tests/conftest.py | 121 +++++++++++++----- tests/test_migrations.py | 71 +++++++++- 7 files changed, 261 insertions(+), 45 deletions(-) create mode 100644 tests/app/tests/migrations/xxx/0001_initial.py create mode 100644 tests/app/tests/migrations/xxx/0002_auto.py create mode 100644 tests/app/tests/migrations/xxx/__init__.py diff --git a/django_squash/db/migrations/writer.py b/django_squash/db/migrations/writer.py index d102d96..e35859b 100644 --- a/django_squash/db/migrations/writer.py +++ b/django_squash/db/migrations/writer.py @@ -35,7 +35,7 @@ def check_django_migration_hash(): raise Warning(messsage) -check_django_migration_hash() +# check_django_migration_hash() class ReplacementMigrationWriter(dj_writer.MigrationWriter): @@ -77,6 +77,7 @@ def get_kwargs(self): # pragma: no cover # Format dependencies and write out swappable dependencies right dependencies = [] + import ipdb; print('\a'); ipdb.sset_trace() for dependency in self.migration.dependencies: if dependency[0] == "__setting__": dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1]) diff --git a/django_squash/management/commands/squash_migrations.py b/django_squash/management/commands/squash_migrations.py index d044fd1..dc82139 100644 --- a/django_squash/management/commands/squash_migrations.py +++ b/django_squash/management/commands/squash_migrations.py @@ -2,11 +2,12 @@ import os from django.apps import apps +from django.conf import settings from django.core.management.base import BaseCommand, CommandError, no_translations from django.db.migrations.loader import MigrationLoader from django.db.migrations.state import ProjectState -from django_squash import settings +from django_squash import settings as app_settings from django_squash.db.migrations import serializer from django_squash.db.migrations.autodetector import SquashMigrationAutodetector from django_squash.db.migrations.loader import SquashMigrationLoader @@ -21,7 +22,7 @@ def add_arguments(self, parser): "--ignore-app", action="append", nargs="*", - default=settings.DJANGO_SQUASH_IGNORE_APPS, + default=app_settings.DJANGO_SQUASH_IGNORE_APPS, help="Ignore app name from quashing, ensure that there is nothing dependent on these apps. " "(default: %(default)s)", ) @@ -33,7 +34,7 @@ def add_arguments(self, parser): ) parser.add_argument( "--squashed-name", - default=settings.DJANGO_SQUASH_MIGRATION_NAME, + default=app_settings.DJANGO_SQUASH_MIGRATION_NAME, help="Sets the name of the new squashed migration. Also accepted are the standard datetime parse " 'variables such as "%%Y%%m%%d". (default: "%(default)s" -> "xxxx_%(default)s")', ) diff --git a/tests/app/tests/migrations/xxx/0001_initial.py b/tests/app/tests/migrations/xxx/0001_initial.py new file mode 100644 index 0000000..01d24e6 --- /dev/null +++ b/tests/app/tests/migrations/xxx/0001_initial.py @@ -0,0 +1,44 @@ +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("app3", "0001_inital"), + ] + + operations = [ + migrations.CreateModel( + name="TranscodeJob", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("job_id", models.CharField(max_length=50, unique=True)), + ( + "status", + models.CharField( + choices=[ + ("S", "Submitted"), + ("P", "Progressing"), + ("C", "Complete"), + ("X", "Canceled"), + ("E", "Error"), + ("U", "Unknown"), + ], + default="S", + max_length=1, + ), + ), + ("initial_response_data", models.TextField()), + ("final_response_data", models.TextField()), + ("video", models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="app3.Person")), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/tests/app/tests/migrations/xxx/0002_auto.py b/tests/app/tests/migrations/xxx/0002_auto.py new file mode 100644 index 0000000..f759b46 --- /dev/null +++ b/tests/app/tests/migrations/xxx/0002_auto.py @@ -0,0 +1,60 @@ +import django.contrib.contenttypes.models +import django.db.models.deletion + +from django.db import migrations, models +from django.db.models import F + + +class Migration(migrations.Migration): + + dependencies = [ + ("app", "0001_initial"), + ] + + def forwards_func(apps, schema_editor): + return + + operations = [ + migrations.AddField( + model_name="transcodejob", + name="content_type", + field=models.IntegerField(null=True, verbose_name=django.contrib.contenttypes.models.ContentType), + ), + migrations.AddField( + model_name="transcodejob", + name="object_id", + field=models.BigIntegerField(null=True), + ), + migrations.AlterField( + model_name="transcodejob", + name="job_id", + field=models.CharField(max_length=50, null=True, unique=True), + ), + migrations.AlterField( + model_name="transcodejob", + name="status", + field=models.CharField( + choices=[ + ("R", "Ready"), + ("S", "Submitted"), + ("P", "Progressing"), + ("C", "Complete"), + ("X", "Canceled"), + ("E", "Error"), + ("U", "Unknown"), + ], + default="S", + max_length=1, + ), + ), + migrations.AlterField( + model_name="transcodejob", + name="video", + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to="app3.Person"), + ), + migrations.AlterIndexTogether( + name="transcodejob", + index_together=set([("content_type", "object_id")]), + ), + migrations.RunPython(forwards_func, reverse_code=migrations.RunPython.noop), + ] diff --git a/tests/app/tests/migrations/xxx/__init__.py b/tests/app/tests/migrations/xxx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index 825867e..9e49bfa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,18 @@ from collections import defaultdict +from contextlib import ExitStack import pytest import io import os +import sys import shutil +from types import ModuleType import tempfile from importlib import import_module -from unittest import mock +from django.utils.module_loading import import_string from django.apps import apps from django.core.management import call_command -from django.test.utils import extend_sys_path +from django.test.utils import extend_sys_path, isolate_apps from django.utils.module_loading import module_dir from django.conf import settings as django_settings @@ -17,12 +20,12 @@ @pytest.fixture -def migration_app_dir(request, settings): +def migration_app_dir(request, isolated_apps, settings): yield from _migration_app_dir("temporary_migration_module", request, settings) @pytest.fixture -def migration_app2_dir(request, settings): +def migration_app2_dir(request, isolated_apps, settings): yield from _migration_app_dir("temporary_migration_module2", request, settings) @@ -45,49 +48,99 @@ def _migration_app_dir(marker_name, request, settings): module = mark.kwargs.get("module") join = mark.kwargs.get("join") or False - with tempfile.TemporaryDirectory() as temp_dir: - target_dir = tempfile.mkdtemp(dir=temp_dir) - with open(os.path.join(target_dir, "__init__.py"), "w"): - pass - target_migrations_dir = os.path.join(target_dir, "migrations") + # with tempfile.TemporaryDirectory() as temp_dir: + # target_dir = tempfile.mkdtemp(dir=temp_dir) + # with open(os.path.join(target_dir, "__init__.py"), "w"): + # pass + # target_migrations_dir = os.path.join(target_dir, "migrations") - if module is None: - module = apps.get_app_config(app_label).name + ".migrations" + # if module is None: + # module = apps.get_app_config(app_label).name + ".migrations" - try: - source_migrations_dir = module_dir(import_module(module)) - except (ImportError, ValueError): - pass - else: - shutil.copytree(source_migrations_dir, target_migrations_dir) + # try: + # source_migrations_dir = module_dir(import_module(module)) + # except (ImportError, ValueError): + # pass + # else: + # shutil.copytree(source_migrations_dir, target_migrations_dir) + + # with extend_sys_path(temp_dir): + # new_module = os.path.basename(target_dir) + ".migrations" + # modules = {app_label: new_module} + # if join: + # modules.update(settings.MIGRATION_MODULES) + # settings.MIGRATION_MODULES = modules + # yield target_migrations_dir + + source_module_path = module_dir(import_module(module)) + target_module = import_module(settings.MIGRATION_MODULES[app_label]) + target_module_path = module_dir(target_module) + shutil.rmtree(target_module_path) + shutil.copytree(source_module_path, target_module_path) + yield target_module_path - with extend_sys_path(temp_dir): - new_module = os.path.basename(target_dir) + ".migrations" - modules = {app_label: new_module} - if join: - modules.update(settings.MIGRATION_MODULES) - settings.MIGRATION_MODULES = modules - yield target_migrations_dir + +# @pytest.fixture(autouse=True) +# def _set_missing_migration_app_dir(settings): +# for settings.INSTALLED_APPS @pytest.fixture(autouse=True) -def _clean_model(monkeypatch): +def isolated_apps(settings, monkeypatch): """ Django registers models in the apps cache, this is a helper to remove them, otherwise django throws warnings that this model already exists. """ + with ExitStack() as stack, isolate_apps(*INSTALLED_APPS) as new_apps: + monkeypatch.setattr("django_squash.management.commands.squash_migrations.apps", new_apps) + monkeypatch.setattr("django.test.utils.apps", new_apps) + monkeypatch.setattr("django.db.models.base.apps", new_apps) + monkeypatch.setattr("django.contrib.auth.django_apps", new_apps) + monkeypatch.setattr("django.db.migrations.loader.apps", new_apps) + monkeypatch.setattr("django.db.migrations.writer.apps", new_apps) + + temp_dir = tempfile.TemporaryDirectory() + stack.enter_context(temp_dir) + stack.enter_context(extend_sys_path(temp_dir.name)) + with open(os.path.join(temp_dir.name, "__init__.py"), "w"): + pass + + for app_label in INSTALLED_APPS: + target_dir = tempfile.mkdtemp(prefix=f"{app_label}_", dir=temp_dir.name) + with open(os.path.join(target_dir, "__init__.py"), "w"): + pass + migration_path = os.path.join(target_dir, "migrations") + os.mkdir(migration_path) + with open(os.path.join(migration_path, "__init__.py"), "w"): + pass + module_name = f"{os.path.basename(target_dir)}.migrations" + + settings.MIGRATION_MODULES[app_label] = module_name + stack.enter_context(extend_sys_path(target_dir)) + + yield new_apps + + return + # from unittest import mock + # mock_ = mock.Mock() + # monkeypatch.setattr("django.apps.registry.apps.get_model", mock_) + + # # Reset App variables + # apps.all_models = defaultdict(dict) + # apps.app_configs = {} + # apps.stored_app_configs = [] + # apps.apps_ready = apps.models_ready = apps.ready = False + # apps.loading = False + # apps._pending_operations = defaultdict(list) + # # Start fresh + # apps.populate(INSTALLED_APPS) + + import itertools + print(list(itertools.chain.from_iterable(apps.all_models.values()))) yield - # clear apps cache - apps.all_models = defaultdict(dict) - apps.app_configs = {} - apps.stored_app_configs = [] - apps.apps_ready = apps.models_ready = apps.ready = False - apps.loading = False - apps._pending_operations = defaultdict(list) - apps.clear_cache() - apps.populate(INSTALLED_APPS) + print(list(itertools.chain.from_iterable(apps.all_models.values()))) @pytest.fixture diff --git a/tests/test_migrations.py b/tests/test_migrations.py index ed4b44d..70feea4 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -65,13 +65,14 @@ def traverse_node(nodes, looking_for): @pytest.mark.temporary_migration_module(module="app.tests.migrations.elidable", app_label="app") -def test_squashing_elidable_migration_simple(migration_app_dir, call_squash_migrations): +def test_squashing_elidable_migration_simple(settings, isolated_apps, migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" + apps = isolated_apps call_squash_migrations() @@ -191,7 +192,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.simple", app_label="app") @pytest.mark.temporary_migration_module2(module="app2.tests.migrations.foreign_key", app_label="app2", join=True) -def test_squashing_migration_simple(migration_app_dir, migration_app2_dir, call_squash_migrations): +def test_squashing_migration_simple(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() @@ -199,6 +200,7 @@ class Person(models.Model): class Meta: app_label = "app" + apps = isolated_apps class Address(models.Model): person = models.ForeignKey("app.Person", on_delete=models.deletion.CASCADE) @@ -211,6 +213,7 @@ class Address(models.Model): class Meta: app_label = "app2" + apps = isolated_apps call_squash_migrations() @@ -232,13 +235,14 @@ class Meta: @pytest.mark.temporary_migration_module(module="app.test_empty", app_label="app") -def test_squashing_migration_empty(call_squash_migrations): +def test_squashing_migration_empty(isolated_apps, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" + apps = isolated_apps with pytest.raises(CommandError) as error: call_squash_migrations() @@ -304,13 +308,14 @@ def test_only_argument_with_invalid_apps(call_squash_migrations, monkeypatch): @pytest.mark.temporary_migration_module(module="app.tests.migrations.elidable", app_label="app") -def test_simple_delete_squashing_migrations_noop(migration_app_dir, call_squash_migrations): +def test_simple_delete_squashing_migrations_noop(isolated_apps, migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" + apps = isolated_apps call_squash_migrations() @@ -326,13 +331,14 @@ class Meta: @pytest.mark.temporary_migration_module(module="app.tests.migrations.delete_replaced", app_label="app") -def test_simple_delete_squashing_migrations(migration_app_dir, call_squash_migrations): +def test_simple_delete_squashing_migrations(isolated_apps, migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" + apps = isolated_apps original_app_squash = load_migration_module(os.path.join(migration_app_dir, "0004_squashed.py")) assert original_app_squash.Migration.replaces == [ @@ -381,7 +387,7 @@ def test_empty_models_migrations(migration_app_dir, call_squash_migrations): @pytest.mark.temporary_migration_module(module="app.tests.migrations.incorrect_name", app_label="app") -def test_squashing_migration_incorrect_name(migration_app_dir, call_squash_migrations): +def test_squashing_migration_incorrect_name(isolated_apps, migration_app_dir, call_squash_migrations): """ If the app has incorrect migration numbers like: `app/migrations/initial.py` instead of `0001_initial.py` it should not fail. Same goes for bad formats all around. @@ -393,6 +399,7 @@ class Person(models.Model): class Meta: app_label = "app" + apps = isolated_apps call_squash_migrations() @@ -482,7 +489,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.swappable_dependency", app_label="app") -def test_swappable_dependency_migrations(migration_app_dir, settings, call_squash_migrations): +def test_swappable_dependency_migrations(isolated_apps, migration_app_dir, settings, call_squash_migrations): settings.INSTALLED_APPS += [ "django.contrib.auth", "django.contrib.contenttypes", @@ -494,6 +501,7 @@ class UserProfile(models.Model): class Meta: app_label = "app" + apps = isolated_apps call_squash_migrations() files_in_app = sorted(file for file in os.listdir(migration_app_dir) if file.endswith(".py")) @@ -537,3 +545,52 @@ class Migration(migrations.Migration): """ # noqa ) assert pretty_extract_piece(app_squash, "") == expected + + +@pytest.mark.temporary_migration_module(module="app.tests.migrations.xxx", app_label="app") +@pytest.mark.temporary_migration_module2(module="app3.tests.migrations.moved", app_label="app3") +def test_nested(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations, settings): + settings.INSTALLED_APPS += [ + "django.contrib.contenttypes", + ] + + from django.contrib.contenttypes.models import ContentType + + class TranscodeJob(models.Model): + + STATUS_CHOICES = ( + ("R", "Ready"), + ("S", "Submitted"), + ("P", "Progressing"), + ("C", "Complete"), + ("X", "Canceled"), + ("E", "Error"), + ("U", "Unknown"), + ) + + content_type = models.IntegerField(ContentType, null=True) + object_id = models.BigIntegerField(null=True) + # content_object = common.fields.ShardGenericForeignKey("content_type", "object_id") + + video = models.OneToOneField("app3.Person", on_delete=models.CASCADE, null=True) + job_id = models.CharField(max_length=50, unique=True, null=True) + status = models.CharField(max_length=1, choices=STATUS_CHOICES, default="S") + initial_response_data = models.TextField() + final_response_data = models.TextField() + + class Meta: + index_together = [ + ["content_type", "object_id"], + ] + app_label = "app" + apps = isolated_apps + + class Person(models.Model): + name = models.CharField(max_length=10) + dob = models.DateField() + + class Meta: + app_label = "app3" + apps = isolated_apps + + call_squash_migrations() From 5d354011b1c12b822e61b24d182cea1b71b127af Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 29 Jan 2024 03:22:42 -0500 Subject: [PATCH 04/11] WIP --- django_squash/db/migrations/utils.py | 1 + tests/app/tests/migrations/xxx/0001_initial.py | 2 +- tests/app/tests/migrations/xxx/0002_auto.py | 1 - tests/conftest.py | 15 +++++++-------- tests/test_utils.py | 3 +-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/django_squash/db/migrations/utils.py b/django_squash/db/migrations/utils.py index 39a1c4f..37bfa82 100644 --- a/django_squash/db/migrations/utils.py +++ b/django_squash/db/migrations/utils.py @@ -7,6 +7,7 @@ import sysconfig import types from collections import defaultdict + from django.db import migrations from django.utils.module_loading import import_string diff --git a/tests/app/tests/migrations/xxx/0001_initial.py b/tests/app/tests/migrations/xxx/0001_initial.py index 01d24e6..3a3ab21 100644 --- a/tests/app/tests/migrations/xxx/0001_initial.py +++ b/tests/app/tests/migrations/xxx/0001_initial.py @@ -1,5 +1,5 @@ -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/tests/app/tests/migrations/xxx/0002_auto.py b/tests/app/tests/migrations/xxx/0002_auto.py index f759b46..37c5ecd 100644 --- a/tests/app/tests/migrations/xxx/0002_auto.py +++ b/tests/app/tests/migrations/xxx/0002_auto.py @@ -1,6 +1,5 @@ import django.contrib.contenttypes.models import django.db.models.deletion - from django.db import migrations, models from django.db.models import F diff --git a/tests/conftest.py b/tests/conftest.py index b134303..acca70f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,21 +1,19 @@ -from collections import defaultdict -from contextlib import ExitStack -import pytest import io import os -import sys import shutil -from types import ModuleType +import sys import tempfile +from collections import defaultdict +from contextlib import ExitStack from importlib import import_module -from django.utils.module_loading import import_string +from types import ModuleType import pytest from django.apps import apps +from django.conf import settings as django_settings from django.core.management import call_command from django.test.utils import extend_sys_path, isolate_apps -from django.utils.module_loading import module_dir -from django.conf import settings as django_settings +from django.utils.module_loading import import_string, module_dir INSTALLED_APPS = django_settings.INSTALLED_APPS.copy() @@ -137,6 +135,7 @@ def isolated_apps(settings, monkeypatch): # apps.populate(INSTALLED_APPS) import itertools + print(list(itertools.chain.from_iterable(apps.all_models.values()))) yield diff --git a/tests/test_utils.py b/tests/test_utils.py index 54391a4..5ad0d9b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,8 @@ -from django.db import migrations -import pytest import tempfile import django import pytest +from django.db import migrations import django_squash from django_squash.db.migrations import utils From 84896472c70feaf18baf8b3fd1d42e37c3ffee43 Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 29 Jan 2024 03:27:38 -0500 Subject: [PATCH 05/11] No need to do this anymore --- django_squash/db/migrations/writer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django_squash/db/migrations/writer.py b/django_squash/db/migrations/writer.py index e589ea4..d4f9b32 100644 --- a/django_squash/db/migrations/writer.py +++ b/django_squash/db/migrations/writer.py @@ -36,7 +36,7 @@ def check_django_migration_hash(): warnings.warn(messsage, Warning) -# check_django_migration_hash() +check_django_migration_hash() class ReplacementMigrationWriter(dj_writer.MigrationWriter): @@ -78,7 +78,6 @@ def get_kwargs(self): # pragma: no cover # Format dependencies and write out swappable dependencies right dependencies = [] - import ipdb; print('\a'); ipdb.sset_trace() for dependency in self.migration.dependencies: if dependency[0] == "__setting__": dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1]) From eaee979d7e1a5852183e9cb1e17a0392c7185add Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 29 Jan 2024 03:29:11 -0500 Subject: [PATCH 06/11] no need to do this until the app isolation is not working --- tests/test_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_migrations.py b/tests/test_migrations.py index c2a141f..c9d444e 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -549,7 +549,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.xxx", app_label="app") @pytest.mark.temporary_migration_module2(module="app3.tests.migrations.moved", app_label="app3") -def test_nested(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations, settings): +def xtest_nested(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations, settings): settings.INSTALLED_APPS += [ "django.contrib.contenttypes", ] From c97164192778dfb11fb079936328bb6df79a95d7 Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 29 Jan 2024 08:24:32 -0500 Subject: [PATCH 07/11] Removes import --- django_squash/management/commands/squash_migrations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django_squash/management/commands/squash_migrations.py b/django_squash/management/commands/squash_migrations.py index dc82139..3ab9044 100644 --- a/django_squash/management/commands/squash_migrations.py +++ b/django_squash/management/commands/squash_migrations.py @@ -2,7 +2,6 @@ import os from django.apps import apps -from django.conf import settings from django.core.management.base import BaseCommand, CommandError, no_translations from django.db.migrations.loader import MigrationLoader from django.db.migrations.state import ProjectState From 86a01bf7e07576b9941f7ebed959c11719395dfb Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 4 Mar 2024 05:32:18 -0500 Subject: [PATCH 08/11] Everything passes --- tests/conftest.py | 80 ++++++++++++++++++++++++++++++++++------ tests/settings.py | 2 + tests/test_migrations.py | 74 +++++++++++++++++++++++++++++++++---- 3 files changed, 137 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index acca70f..36e4263 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,9 @@ from django.apps import apps from django.conf import settings as django_settings from django.core.management import call_command -from django.test.utils import extend_sys_path, isolate_apps +from django.test.utils import extend_sys_path +from django.db.models.options import Options +from django.apps.registry import Apps from django.utils.module_loading import import_string, module_dir INSTALLED_APPS = django_settings.INSTALLED_APPS.copy() @@ -83,20 +85,76 @@ def _migration_app_dir(marker_name, request, settings): # def _set_missing_migration_app_dir(settings): # for settings.INSTALLED_APPS +class BidirectionalProxy: + def __init__(self, primary_target, secondary_target, *attrs_to_proxy): + super().__setattr__('_primary_target', primary_target) + super().__setattr__('_secondary_target', secondary_target) + super().__setattr__('_attrs_to_proxy', set(attrs_to_proxy)) + + def __getattr__(self, name): + if name in self._attrs_to_proxy: + return getattr(self._primary_target, name) + else: + return super().__getattribute__(name) + + def __setattr__(self, name, value): + if name in self._attrs_to_proxy: + setattr(self._primary_target, name, value) + setattr(self._secondary_target, name, value) + else: + super().__setattr__(name, value) + + def __delattr__(self, name): + if name in self._attrs_to_proxy: + delattr(self._primary_target, name) + delattr(self._secondary_target, name) + else: + super().__delattr__(name) + @pytest.fixture(autouse=True) -def isolated_apps(settings, monkeypatch): +def isolated_apps(settings, monkeypatch, request): """ Django registers models in the apps cache, this is a helper to remove them, otherwise django throws warnings that this model already exists. """ - with ExitStack() as stack, isolate_apps(*INSTALLED_APPS) as new_apps: - monkeypatch.setattr("django_squash.management.commands.squash_migrations.apps", new_apps) - monkeypatch.setattr("django.test.utils.apps", new_apps) - monkeypatch.setattr("django.db.models.base.apps", new_apps) - monkeypatch.setattr("django.contrib.auth.django_apps", new_apps) - monkeypatch.setattr("django.db.migrations.loader.apps", new_apps) - monkeypatch.setattr("django.db.migrations.writer.apps", new_apps) + with ExitStack() as stack: #, isolate_apps(*INSTALLED_APPS) as new_apps: + # monkeypatch.setattr("django_squash.management.commands.squash_migrations.apps", new_apps) + # monkeypatch.setattr("django.test.utils.apps", new_apps) + # monkeypatch.setattr("django.db.models.base.apps", new_apps) + # monkeypatch.setattr("django.contrib.auth.django_apps", new_apps) + # monkeypatch.setattr("django.db.migrations.loader.apps", new_apps) + # monkeypatch.setattr("django.db.migrations.writer.apps", new_apps) + + # new_apps = Apps() + # print('models', Options.default_apps.all_models) + # print('app_configs', Options.default_apps.app_configs) + # print('stored_app_configs', Options.default_apps.stored_app_configs) + original_apps = Options.default_apps + original_all_models = original_apps.all_models + original_app_configs = original_apps.app_configs + new_all_models = defaultdict(dict) + new_app_configs = {} + + # monkeypatch.setattr(Options, "default_apps", new_apps) + # monkeypatch.setattr("django.apps.apps", new_apps.all_models) + monkeypatch.setattr("django.apps.apps.all_models", new_all_models) + monkeypatch.setattr("django.apps.apps.app_configs", new_app_configs) + monkeypatch.setattr("django.apps.apps.stored_app_configs", []) + monkeypatch.setattr("django.apps.apps.apps_ready", False) + monkeypatch.setattr("django.apps.apps.models_ready", False) + monkeypatch.setattr("django.apps.apps.ready", False) + monkeypatch.setattr("django.apps.apps.loading", False) + monkeypatch.setattr("django.apps.apps._pending_operations", defaultdict(list)) + installed_app = settings.INSTALLED_APPS.copy() + _installed_app = installed_app.copy() + _installed_app.remove('django.contrib.auth') + _installed_app.remove('django.contrib.contenttypes') + original_apps.populate(_installed_app) + + for app_label in {'auth', 'contenttypes'}: + new_all_models[app_label] = original_all_models[app_label] + new_app_configs[app_label] = original_app_configs[app_label] temp_dir = tempfile.TemporaryDirectory() stack.enter_context(temp_dir) @@ -104,7 +162,7 @@ def isolated_apps(settings, monkeypatch): with open(os.path.join(temp_dir.name, "__init__.py"), "w"): pass - for app_label in INSTALLED_APPS: + for app_label in installed_app: target_dir = tempfile.mkdtemp(prefix=f"{app_label}_", dir=temp_dir.name) with open(os.path.join(target_dir, "__init__.py"), "w"): pass @@ -117,7 +175,7 @@ def isolated_apps(settings, monkeypatch): settings.MIGRATION_MODULES[app_label] = module_name stack.enter_context(extend_sys_path(target_dir)) - yield new_apps + yield original_apps return # from unittest import mock diff --git a/tests/settings.py b/tests/settings.py index b49154e..9c10878 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -31,6 +31,8 @@ # Application definition INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", "django_squash", "app", "app2", diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 19cbfe3..208dc13 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -291,7 +291,8 @@ def test_only_argument(call_squash_migrations, settings, monkeypatch): ) assert str(error.value) == "There are no migrations to squash." assert mock_squash.called - assert set(mock_squash.call_args[1]["ignore_apps"]) == set(settings.INSTALLED_APPS) - {"app2", "app"} + installed_apps = {full_app.rsplit('.')[-1] for full_app in settings.INSTALLED_APPS} + assert set(mock_squash.call_args[1]["ignore_apps"]) == installed_apps - {"app2", "app"} @pytest.mark.temporary_migration_module(module="app.test_empty", app_label="app") @@ -496,12 +497,69 @@ class Migration(migrations.Migration): assert pretty_extract_piece(app_squash, "") == expected +# def test_test(settings, monkeypatch): +# installed_app = settings.INSTALLED_APPS +# # + [ +# # "django.contrib.auth", +# # "django.contrib.contenttypes", +# # ] +# from django.test.utils import isolate_apps +# from django.db.models.options import Options +# from django.apps.registry import Apps + +# new_apps = Apps() +# monkeypatch.setattr(Options, "default_apps", new_apps) +# monkeypatch.setattr("django.apps.apps.all_models", new_apps.all_models) +# monkeypatch.setattr("django.apps.apps.app_configs", new_apps.app_configs) +# monkeypatch.setattr("django.apps.apps.stored_app_configs", new_apps.stored_app_configs) +# monkeypatch.setattr("django.apps.apps.apps_ready", new_apps.apps_ready) +# monkeypatch.setattr("django.apps.apps.loading", new_apps.loading) +# monkeypatch.setattr("django.apps.apps.models_ready", new_apps.models_ready) +# monkeypatch.setattr("django.apps.apps._pending_operations", new_apps._pending_operations) +# new_apps.populate(installed_app) + +# class Person(models.Model): +# name = models.CharField(max_length=10) +# dob = models.DateField() + +# class Meta: +# app_label = "app" +# apps = new_apps + +# new_apps = Apps() +# monkeypatch.setattr(Options, "default_apps", new_apps) +# monkeypatch.setattr("django.apps.apps.all_models", new_apps.all_models) +# monkeypatch.setattr("django.apps.apps.app_configs", new_apps.app_configs) +# monkeypatch.setattr("django.apps.apps.stored_app_configs", new_apps.stored_app_configs) +# monkeypatch.setattr("django.apps.apps.apps_ready", new_apps.apps_ready) +# monkeypatch.setattr("django.apps.apps.loading", new_apps.loading) +# monkeypatch.setattr("django.apps.apps.models_ready", new_apps.models_ready) +# monkeypatch.setattr("django.apps.apps._pending_operations", new_apps._pending_operations) +# new_apps.populate(installed_app) + +# class Person(models.Model): +# name = models.CharField(max_length=10) +# dob = models.DateField() + +# class Meta: +# app_label = "app" +# apps = new_apps + +# # class UserProfile(models.Model): +# # user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) +# # dob = models.DateField() + +# # class Meta: +# # app_label = "app" + + + @pytest.mark.temporary_migration_module(module="app.tests.migrations.swappable_dependency", app_label="app") def test_swappable_dependency_migrations(isolated_apps, migration_app_dir, settings, call_squash_migrations): - settings.INSTALLED_APPS += [ - "django.contrib.auth", - "django.contrib.contenttypes", - ] + # settings.INSTALLED_APPS += [ + # "django.contrib.auth", + # "django.contrib.contenttypes", + # ] class UserProfile(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) @@ -558,9 +616,9 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.xxx", app_label="app") @pytest.mark.temporary_migration_module2(module="app3.tests.migrations.moved", app_label="app3") def xtest_nested(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations, settings): - settings.INSTALLED_APPS += [ - "django.contrib.contenttypes", - ] + # settings.INSTALLED_APPS += [ + # "django.contrib.contenttypes", + # ] from django.contrib.contenttypes.models import ContentType From a3eae61baa6553d2c450d2f94b5aaab143719b96 Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 4 Mar 2024 05:49:03 -0500 Subject: [PATCH 09/11] Clean up --- tests/app/tests/migrations/xxx/0002_auto.py | 1 - tests/conftest.py | 109 ++------------------ tests/test_migrations.py | 100 ++---------------- 3 files changed, 18 insertions(+), 192 deletions(-) diff --git a/tests/app/tests/migrations/xxx/0002_auto.py b/tests/app/tests/migrations/xxx/0002_auto.py index 37c5ecd..3ea7f8b 100644 --- a/tests/app/tests/migrations/xxx/0002_auto.py +++ b/tests/app/tests/migrations/xxx/0002_auto.py @@ -1,7 +1,6 @@ import django.contrib.contenttypes.models import django.db.models.deletion from django.db import migrations, models -from django.db.models import F class Migration(migrations.Migration): diff --git a/tests/conftest.py b/tests/conftest.py index 36e4263..94c64e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,21 +1,17 @@ import io import os import shutil -import sys import tempfile from collections import defaultdict from contextlib import ExitStack from importlib import import_module -from types import ModuleType import pytest -from django.apps import apps from django.conf import settings as django_settings from django.core.management import call_command -from django.test.utils import extend_sys_path from django.db.models.options import Options -from django.apps.registry import Apps -from django.utils.module_loading import import_string, module_dir +from django.test.utils import extend_sys_path +from django.utils.module_loading import module_dir INSTALLED_APPS = django_settings.INSTALLED_APPS.copy() @@ -49,30 +45,6 @@ def _migration_app_dir(marker_name, request, settings): module = mark.kwargs.get("module") join = mark.kwargs.get("join") or False - # with tempfile.TemporaryDirectory() as temp_dir: - # target_dir = tempfile.mkdtemp(dir=temp_dir) - # with open(os.path.join(target_dir, "__init__.py"), "w"): - # pass - # target_migrations_dir = os.path.join(target_dir, "migrations") - - # if module is None: - # module = apps.get_app_config(app_label).name + ".migrations" - - # try: - # source_migrations_dir = module_dir(import_module(module)) - # except (ImportError, ValueError): - # pass - # else: - # shutil.copytree(source_migrations_dir, target_migrations_dir) - - # with extend_sys_path(temp_dir): - # new_module = os.path.basename(target_dir) + ".migrations" - # modules = {app_label: new_module} - # if join: - # modules.update(settings.MIGRATION_MODULES) - # settings.MIGRATION_MODULES = modules - # yield target_migrations_dir - source_module_path = module_dir(import_module(module)) target_module = import_module(settings.MIGRATION_MODULES[app_label]) target_module_path = module_dir(target_module) @@ -81,63 +53,19 @@ def _migration_app_dir(marker_name, request, settings): yield target_module_path -# @pytest.fixture(autouse=True) -# def _set_missing_migration_app_dir(settings): -# for settings.INSTALLED_APPS - -class BidirectionalProxy: - def __init__(self, primary_target, secondary_target, *attrs_to_proxy): - super().__setattr__('_primary_target', primary_target) - super().__setattr__('_secondary_target', secondary_target) - super().__setattr__('_attrs_to_proxy', set(attrs_to_proxy)) - - def __getattr__(self, name): - if name in self._attrs_to_proxy: - return getattr(self._primary_target, name) - else: - return super().__getattribute__(name) - - def __setattr__(self, name, value): - if name in self._attrs_to_proxy: - setattr(self._primary_target, name, value) - setattr(self._secondary_target, name, value) - else: - super().__setattr__(name, value) - - def __delattr__(self, name): - if name in self._attrs_to_proxy: - delattr(self._primary_target, name) - delattr(self._secondary_target, name) - else: - super().__delattr__(name) - - @pytest.fixture(autouse=True) -def isolated_apps(settings, monkeypatch, request): +def isolated_apps(settings, monkeypatch): """ Django registers models in the apps cache, this is a helper to remove them, otherwise django throws warnings that this model already exists. """ - with ExitStack() as stack: #, isolate_apps(*INSTALLED_APPS) as new_apps: - # monkeypatch.setattr("django_squash.management.commands.squash_migrations.apps", new_apps) - # monkeypatch.setattr("django.test.utils.apps", new_apps) - # monkeypatch.setattr("django.db.models.base.apps", new_apps) - # monkeypatch.setattr("django.contrib.auth.django_apps", new_apps) - # monkeypatch.setattr("django.db.migrations.loader.apps", new_apps) - # monkeypatch.setattr("django.db.migrations.writer.apps", new_apps) - - # new_apps = Apps() - # print('models', Options.default_apps.all_models) - # print('app_configs', Options.default_apps.app_configs) - # print('stored_app_configs', Options.default_apps.stored_app_configs) + with ExitStack() as stack: original_apps = Options.default_apps original_all_models = original_apps.all_models original_app_configs = original_apps.app_configs new_all_models = defaultdict(dict) new_app_configs = {} - # monkeypatch.setattr(Options, "default_apps", new_apps) - # monkeypatch.setattr("django.apps.apps", new_apps.all_models) monkeypatch.setattr("django.apps.apps.all_models", new_all_models) monkeypatch.setattr("django.apps.apps.app_configs", new_app_configs) monkeypatch.setattr("django.apps.apps.stored_app_configs", []) @@ -148,11 +76,11 @@ def isolated_apps(settings, monkeypatch, request): monkeypatch.setattr("django.apps.apps._pending_operations", defaultdict(list)) installed_app = settings.INSTALLED_APPS.copy() _installed_app = installed_app.copy() - _installed_app.remove('django.contrib.auth') - _installed_app.remove('django.contrib.contenttypes') + _installed_app.remove("django.contrib.auth") + _installed_app.remove("django.contrib.contenttypes") original_apps.populate(_installed_app) - for app_label in {'auth', 'contenttypes'}: + for app_label in {"auth", "contenttypes"}: new_all_models[app_label] = original_all_models[app_label] new_app_configs[app_label] = original_app_configs[app_label] @@ -177,29 +105,6 @@ def isolated_apps(settings, monkeypatch, request): yield original_apps - return - # from unittest import mock - # mock_ = mock.Mock() - # monkeypatch.setattr("django.apps.registry.apps.get_model", mock_) - - # # Reset App variables - # apps.all_models = defaultdict(dict) - # apps.app_configs = {} - # apps.stored_app_configs = [] - # apps.apps_ready = apps.models_ready = apps.ready = False - # apps.loading = False - # apps._pending_operations = defaultdict(list) - # # Start fresh - # apps.populate(INSTALLED_APPS) - - import itertools - - print(list(itertools.chain.from_iterable(apps.all_models.values()))) - - yield - - print(list(itertools.chain.from_iterable(apps.all_models.values()))) - @pytest.fixture def call_squash_migrations(): diff --git a/tests/test_migrations.py b/tests/test_migrations.py index 208dc13..b4df073 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -65,14 +65,13 @@ def traverse_node(nodes, looking_for): @pytest.mark.temporary_migration_module(module="app.tests.migrations.elidable", app_label="app") -def test_squashing_elidable_migration_simple(settings, isolated_apps, migration_app_dir, call_squash_migrations): +def test_squashing_elidable_migration_simple(migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" - apps = isolated_apps call_squash_migrations() @@ -186,7 +185,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.simple", app_label="app") @pytest.mark.temporary_migration_module2(module="app2.tests.migrations.foreign_key", app_label="app2", join=True) -def test_squashing_migration_simple(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations): +def test_squashing_migration_simple(migration_app_dir, migration_app2_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() @@ -194,7 +193,6 @@ class Person(models.Model): class Meta: app_label = "app" - apps = isolated_apps class Address(models.Model): person = models.ForeignKey("app.Person", on_delete=models.deletion.CASCADE) @@ -207,7 +205,6 @@ class Address(models.Model): class Meta: app_label = "app2" - apps = isolated_apps call_squash_migrations() @@ -229,14 +226,13 @@ class Meta: @pytest.mark.temporary_migration_module(module="app.test_empty", app_label="app") -def test_squashing_migration_empty(isolated_apps, call_squash_migrations): +def test_squashing_migration_empty(call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" - apps = isolated_apps with pytest.raises(CommandError) as error: call_squash_migrations() @@ -291,7 +287,7 @@ def test_only_argument(call_squash_migrations, settings, monkeypatch): ) assert str(error.value) == "There are no migrations to squash." assert mock_squash.called - installed_apps = {full_app.rsplit('.')[-1] for full_app in settings.INSTALLED_APPS} + installed_apps = {full_app.rsplit(".")[-1] for full_app in settings.INSTALLED_APPS} assert set(mock_squash.call_args[1]["ignore_apps"]) == installed_apps - {"app2", "app"} @@ -311,14 +307,13 @@ def test_only_argument_with_invalid_apps(call_squash_migrations, monkeypatch): @pytest.mark.temporary_migration_module(module="app.tests.migrations.elidable", app_label="app") -def test_simple_delete_squashing_migrations_noop(isolated_apps, migration_app_dir, call_squash_migrations): +def test_simple_delete_squashing_migrations_noop(migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" - apps = isolated_apps call_squash_migrations() @@ -334,14 +329,13 @@ class Meta: @pytest.mark.temporary_migration_module(module="app.tests.migrations.delete_replaced", app_label="app") -def test_simple_delete_squashing_migrations(isolated_apps, migration_app_dir, call_squash_migrations): +def test_simple_delete_squashing_migrations(migration_app_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) dob = models.DateField() class Meta: app_label = "app" - apps = isolated_apps original_app_squash = load_migration_module(os.path.join(migration_app_dir, "0004_squashed.py")) assert original_app_squash.Migration.replaces == [ @@ -390,7 +384,7 @@ def test_empty_models_migrations(migration_app_dir, call_squash_migrations): @pytest.mark.temporary_migration_module(module="app.tests.migrations.incorrect_name", app_label="app") -def test_squashing_migration_incorrect_name(isolated_apps, migration_app_dir, call_squash_migrations): +def test_squashing_migration_incorrect_name(migration_app_dir, call_squash_migrations): """ If the app has incorrect migration numbers like: `app/migrations/initial.py` instead of `0001_initial.py` it should not fail. Same goes for bad formats all around. @@ -402,7 +396,6 @@ class Person(models.Model): class Meta: app_label = "app" - apps = isolated_apps call_squash_migrations() @@ -497,77 +490,14 @@ class Migration(migrations.Migration): assert pretty_extract_piece(app_squash, "") == expected -# def test_test(settings, monkeypatch): -# installed_app = settings.INSTALLED_APPS -# # + [ -# # "django.contrib.auth", -# # "django.contrib.contenttypes", -# # ] -# from django.test.utils import isolate_apps -# from django.db.models.options import Options -# from django.apps.registry import Apps - -# new_apps = Apps() -# monkeypatch.setattr(Options, "default_apps", new_apps) -# monkeypatch.setattr("django.apps.apps.all_models", new_apps.all_models) -# monkeypatch.setattr("django.apps.apps.app_configs", new_apps.app_configs) -# monkeypatch.setattr("django.apps.apps.stored_app_configs", new_apps.stored_app_configs) -# monkeypatch.setattr("django.apps.apps.apps_ready", new_apps.apps_ready) -# monkeypatch.setattr("django.apps.apps.loading", new_apps.loading) -# monkeypatch.setattr("django.apps.apps.models_ready", new_apps.models_ready) -# monkeypatch.setattr("django.apps.apps._pending_operations", new_apps._pending_operations) -# new_apps.populate(installed_app) - -# class Person(models.Model): -# name = models.CharField(max_length=10) -# dob = models.DateField() - -# class Meta: -# app_label = "app" -# apps = new_apps - -# new_apps = Apps() -# monkeypatch.setattr(Options, "default_apps", new_apps) -# monkeypatch.setattr("django.apps.apps.all_models", new_apps.all_models) -# monkeypatch.setattr("django.apps.apps.app_configs", new_apps.app_configs) -# monkeypatch.setattr("django.apps.apps.stored_app_configs", new_apps.stored_app_configs) -# monkeypatch.setattr("django.apps.apps.apps_ready", new_apps.apps_ready) -# monkeypatch.setattr("django.apps.apps.loading", new_apps.loading) -# monkeypatch.setattr("django.apps.apps.models_ready", new_apps.models_ready) -# monkeypatch.setattr("django.apps.apps._pending_operations", new_apps._pending_operations) -# new_apps.populate(installed_app) - -# class Person(models.Model): -# name = models.CharField(max_length=10) -# dob = models.DateField() - -# class Meta: -# app_label = "app" -# apps = new_apps - -# # class UserProfile(models.Model): -# # user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) -# # dob = models.DateField() - -# # class Meta: -# # app_label = "app" - - - @pytest.mark.temporary_migration_module(module="app.tests.migrations.swappable_dependency", app_label="app") -def test_swappable_dependency_migrations(isolated_apps, migration_app_dir, settings, call_squash_migrations): - # settings.INSTALLED_APPS += [ - # "django.contrib.auth", - # "django.contrib.contenttypes", - # ] - +def test_swappable_dependency_migrations(migration_app_dir, settings, call_squash_migrations): class UserProfile(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) dob = models.DateField() class Meta: app_label = "app" - apps = isolated_apps call_squash_migrations() files_in_app = sorted(file for file in os.listdir(migration_app_dir) if file.endswith(".py")) @@ -615,11 +545,8 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.xxx", app_label="app") @pytest.mark.temporary_migration_module2(module="app3.tests.migrations.moved", app_label="app3") -def xtest_nested(isolated_apps, migration_app_dir, migration_app2_dir, call_squash_migrations, settings): - # settings.INSTALLED_APPS += [ - # "django.contrib.contenttypes", - # ] - +def xtest_nested(migration_app_dir, migration_app2_dir, call_squash_migrations, settings): + from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType class TranscodeJob(models.Model): @@ -636,7 +563,7 @@ class TranscodeJob(models.Model): content_type = models.IntegerField(ContentType, null=True) object_id = models.BigIntegerField(null=True) - # content_object = common.fields.ShardGenericForeignKey("content_type", "object_id") + content_object = GenericForeignKey("content_type", "object_id") video = models.OneToOneField("app3.Person", on_delete=models.CASCADE, null=True) job_id = models.CharField(max_length=50, unique=True, null=True) @@ -645,11 +572,7 @@ class TranscodeJob(models.Model): final_response_data = models.TextField() class Meta: - index_together = [ - ["content_type", "object_id"], - ] app_label = "app" - apps = isolated_apps class Person(models.Model): name = models.CharField(max_length=10) @@ -657,6 +580,5 @@ class Person(models.Model): class Meta: app_label = "app3" - apps = isolated_apps call_squash_migrations() From 6f52734303fce29fc847eba00d39ab0a2b67e8f7 Mon Sep 17 00:00:00 2001 From: kingbuzzman Date: Mon, 4 Mar 2024 05:51:50 -0500 Subject: [PATCH 10/11] No need to join --- tests/conftest.py | 1 - tests/test_migrations.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 94c64e8..c788fa4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -43,7 +43,6 @@ def _migration_app_dir(marker_name, request, settings): app_label = mark.kwargs["app_label"] module = mark.kwargs.get("module") - join = mark.kwargs.get("join") or False source_module_path = module_dir(import_module(module)) target_module = import_module(settings.MIGRATION_MODULES[app_label]) diff --git a/tests/test_migrations.py b/tests/test_migrations.py index b4df073..dad25c0 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -184,7 +184,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.simple", app_label="app") -@pytest.mark.temporary_migration_module2(module="app2.tests.migrations.foreign_key", app_label="app2", join=True) +@pytest.mark.temporary_migration_module2(module="app2.tests.migrations.foreign_key", app_label="app2") def test_squashing_migration_simple(migration_app_dir, migration_app2_dir, call_squash_migrations): class Person(models.Model): name = models.CharField(max_length=10) From b096ba4be5a3606d749b2135eca8d1c40d9b7df4 Mon Sep 17 00:00:00 2001 From: Javier Buzzi Date: Mon, 4 Mar 2024 05:58:42 -0500 Subject: [PATCH 11/11] Update test_migrations.py --- tests/test_migrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_migrations.py b/tests/test_migrations.py index dad25c0..db24958 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -545,7 +545,7 @@ class Migration(migrations.Migration): @pytest.mark.temporary_migration_module(module="app.tests.migrations.xxx", app_label="app") @pytest.mark.temporary_migration_module2(module="app3.tests.migrations.moved", app_label="app3") -def xtest_nested(migration_app_dir, migration_app2_dir, call_squash_migrations, settings): +def test_nested(migration_app_dir, migration_app2_dir, call_squash_migrations, settings): from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType