def handle(self, app_label, **options): app_config = apps.get_app_config(app_label) if not is_first_party_app_config(app_config): raise CommandError(f"{app_label!r} is not a first-party app.") migration_details = MigrationDetails(app_label) max_migration_txt = migration_details.dir / "max_migration.txt" if not max_migration_txt.exists(): raise CommandError( f"{app_label} does not have a max_migration.txt.") migration_names = find_migration_names( max_migration_txt.read_text().splitlines()) if migration_names is None: raise CommandError( f"{app_label}'s max_migration.txt does not seem to contain a" + " merge conflict.") merged_migration_name, rebased_migration_name = migration_names if merged_migration_name not in migration_details.names: raise CommandError( f"Parsed {merged_migration_name!r} as the already-merged" + f" migration name from {app_label}'s max_migration.txt, but" + " this migration does not exist.") if rebased_migration_name not in migration_details.names: raise CommandError( f"Parsed {rebased_migration_name!r} as the rebased migration" + f" name from {app_label}'s max_migration.txt, but this" + " migration does not exist.") rebased_migration_filename = "{}.py".format(rebased_migration_name) rebased_migration_path = migration_details.dir / rebased_migration_filename if not rebased_migration_path.exists(): raise CommandError( f"Detected {rebased_migration_filename!r} as the rebased" + " migration filename, but it does not exist.") if migration_applied(app_label, rebased_migration_name): raise CommandError( f"Detected {rebased_migration_name} as the rebased migration," + " but it is applied to the local database. Undo the rebase," + " reverse the migration, and try again.") content = rebased_migration_path.read_text() split_result = re.split( r"(?<=dependencies = )(\[.*?\])", content, maxsplit=1, flags=re.DOTALL, ) if len(split_result) != 3: raise CommandError("Could not find dependencies = [...] in" + f" {rebased_migration_filename!r}") before_deps, deps, after_deps = split_result try: dependencies_module = ast.parse(deps) except SyntaxError: raise CommandError( f"Encountered a SyntaxError trying to parse 'dependencies = {deps}'." ) dependencies = dependencies_module.body[0].value new_dependencies = ast.List(elts=[]) num_this_app_dependencies = 0 for dependency in dependencies.elts: # Skip swappable_dependency calls, other dynamically defined # dependencies, and bad definitions if (not isinstance(dependency, (ast.Tuple, ast.List)) or len(dependency.elts) != 2 or not all(is_ast_constant_str(el) for el in dependency.elts)): new_dependencies.elts.append(dependency) continue dependency_app_label = get_ast_constant_str_value( dependency.elts[0]) if dependency_app_label == app_label: num_this_app_dependencies += 1 new_dependencies.elts.append( ast.Tuple(elts=[ make_ast_constant_str(app_label), make_ast_constant_str(merged_migration_name), ])) else: new_dependencies.elts.append(dependency) if num_this_app_dependencies != 1: raise CommandError( f"Cannot edit {rebased_migration_filename!r} since it has " + f"{num_this_app_dependencies} dependencies within " + f"{app_label}.") new_content = before_deps + ast_unparse(new_dependencies) + after_deps merged_number, _merged_rest = merged_migration_name.split("_", 1) _rebased_number, rebased_rest = rebased_migration_name.split("_", 1) new_number = int(merged_number) + 1 new_name = str(new_number).zfill(4) + "_" + rebased_rest new_path_parts = rebased_migration_path.parts[:-1] + ( f"{new_name}.py", ) new_path = Path(*new_path_parts) rebased_migration_path.rename(new_path) new_path.write_text(new_content) max_migration_txt.write_text(f"{new_name}\n") self.stdout.write( f"Renamed {rebased_migration_path.parts[-1]} to {new_path.parts[-1]}," + " updated its dependencies, and updated max_migration.txt.")
def test_named_by_app_config_path(self): app_config = apps.get_app_config("testapp") assert is_first_party_app_config(app_config)
def test_empty(self): app_config = apps.get_app_config("testapp") assert not is_first_party_app_config(app_config)
def handle(self, app_label, **options): app_config = apps.get_app_config(app_label) if not is_first_party_app_config(app_config): raise CommandError(f"{app_label!r} is not a first-party app.") migration_details = MigrationDetails(app_label) max_migration_txt = migration_details.dir / "max_migration.txt" if not max_migration_txt.exists(): raise CommandError(f"{app_label} does not have a max_migration.txt.") migration_names = find_migration_names( max_migration_txt.read_text().splitlines() ) if migration_names is None: raise CommandError( f"{app_label}'s max_migration.txt does not seem to contain a" + " merge conflict." ) merged_migration_name, rebased_migration_name = migration_names if merged_migration_name not in migration_details.names: raise CommandError( f"Parsed {merged_migration_name!r} as the already-merged" + f" migration name from {app_label}'s max_migration.txt, but" + " this migration does not exist." ) if rebased_migration_name not in migration_details.names: raise CommandError( f"Parsed {rebased_migration_name!r} as the rebased migration" + f" name from {app_label}'s max_migration.txt, but this" + " migration does not exist." ) rebased_migration_filename = "{}.py".format(rebased_migration_name) rebased_migration_path = migration_details.dir / rebased_migration_filename if not rebased_migration_path.exists(): raise CommandError( f"Detected {rebased_migration_filename!r} as the rebased" + " migration filename, but it does not exist." ) if migration_applied(app_label, rebased_migration_name): raise CommandError( f"Detected {rebased_migration_name} as the rebased migration," + " but it is applied to the local database. Undo the rebase," + " reverse the migration, and try again." ) content = rebased_migration_path.read_text() split_result = re.split( r"(?<=dependencies = )(\[.*?\])", content, maxsplit=1, flags=re.DOTALL, ) if len(split_result) != 3: raise CommandError( "Could not find dependencies = [...] in" + f" {rebased_migration_filename!r}" ) before_deps, deps, after_deps = split_result try: dependencies = ast.literal_eval(deps) except SyntaxError: raise CommandError( f"Encountered a SyntaxError trying to parse 'dependencies = {deps}'." ) num_this_app_dependencies = len([d for d in dependencies if d[0] == app_label]) if num_this_app_dependencies != 1: raise CommandError( f"Cannot edit {rebased_migration_filename!r} since it has two" + f" dependencies within {app_label}." ) new_dependencies = [] for dependency_app_label, migration_name in dependencies: if dependency_app_label == app_label: new_dependencies.append((app_label, merged_migration_name)) else: new_dependencies.append((dependency_app_label, migration_name)) new_content = before_deps + repr(new_dependencies) + after_deps merged_number, _merged_rest = merged_migration_name.split("_", 1) _rebased_number, rebased_rest = rebased_migration_name.split("_", 1) new_number = int(merged_number) + 1 new_name = str(new_number).zfill(4) + "_" + rebased_rest new_path_parts = rebased_migration_path.parts[:-1] + (f"{new_name}.py",) new_path = Path(*new_path_parts) rebased_migration_path.rename(new_path) new_path.write_text(new_content) max_migration_txt.write_text(f"{new_name}\n") self.stdout.write( f"Renamed {rebased_migration_path.parts[-1]} to {new_path.parts[-1]}," + " updated its dependencies, and updated max_migration.txt." )