""" Management command to create migrations for translatable field changes """ from django.core.management.base import BaseCommand from django.core.management import call_command from django.apps import apps from django.db import models from django.db.migrations.writer import MigrationWriter from django.db.migrations import Migration from django.utils.translation import get_language import os from ...operations import ConvertTranslatableField from ...fields import TranslatableMixin class Command(BaseCommand): help = 'Create migrations for translatable field changes (translate=True/False)' def add_arguments(self, parser): parser.add_argument( '--app', type=str, help='Specific app to check for translatable changes' ) parser.add_argument( '--language', type=str, default=None, help='Language to use when converting from translatable to non-translatable (default: current language)' ) parser.add_argument( '--dry-run', action='store_true', help='Show what would be created without actually creating migrations' ) def handle(self, *args, **options): app_label = options.get('app') language = options.get('language') or get_language() or 'en' dry_run = options.get('dry_run', False) if app_label: apps_to_check = [apps.get_app_config(app_label)] else: apps_to_check = apps.get_app_configs() changes_found = False for app_config in apps_to_check: changes = self.detect_translatable_changes(app_config) if changes: changes_found = True self.stdout.write( self.style.SUCCESS(f'Found translatable changes in {app_config.label}:') ) for model_name, field_changes in changes.items(): for field_name, change in field_changes.items(): from_trans = change['from_translatable'] to_trans = change['to_translatable'] direction = "translatable" if to_trans else "non-translatable" self.stdout.write(f" {model_name}.{field_name} -> {direction}") if not dry_run: self.create_migration(app_config, model_name, field_name, from_trans, to_trans, language) if not changes_found: self.stdout.write(self.style.WARNING('No translatable field changes detected.')) elif not dry_run: self.stdout.write( self.style.SUCCESS('Migration files created successfully.') ) self.stdout.write('Run "python manage.py migrate" to apply the changes.') def detect_translatable_changes(self, app_config): """Detect changes in translatable field settings""" changes = {} # Get current migration state try: from django.db.migrations.loader import MigrationLoader loader = MigrationLoader(None) # Get the latest migration state for this app if app_config.label in loader.graph.nodes: project_state = loader.project_state() for model_name, model in app_config.get_models(): model_name = model_name.__name__ # Check current model fields for field in model._meta.get_fields(): if isinstance(field, TranslatableMixin): current_translatable = field.translatable # Try to get the field from the migration state try: migration_model = project_state.models.get( (app_config.label, model_name.lower()) ) if migration_model: migration_field = migration_model.fields.get(field.name) if migration_field: # Check if translatable setting changed old_translatable = getattr(migration_field, 'translatable', True) if old_translatable != current_translatable: if model_name not in changes: changes[model_name] = {} changes[model_name][field.name] = { 'from_translatable': old_translatable, 'to_translatable': current_translatable } except (KeyError, AttributeError): # Field doesn't exist in migrations yet, skip pass except Exception as e: self.stdout.write( self.style.WARNING(f'Could not detect changes in {app_config.label}: {e}') ) return changes def create_migration(self, app_config, model_name, field_name, from_translatable, to_translatable, language): """Create a migration file with the conversion operation""" # Create the operation operation = ConvertTranslatableField( model_name=model_name.lower(), field_name=field_name, from_translatable=from_translatable, to_translatable=to_translatable, language=language ) # Create migration migration = Migration( f"convert_{field_name}_translatable", app_config.label ) migration.operations = [operation] # Find migrations directory migrations_dir = os.path.join(app_config.path, 'migrations') if not os.path.exists(migrations_dir): os.makedirs(migrations_dir) # Generate migration file writer = MigrationWriter(migration) migration_string = writer.as_string() # Find next migration number existing_migrations = [ f for f in os.listdir(migrations_dir) if f.endswith('.py') and f[0].isdigit() ] if existing_migrations: numbers = [int(f.split('_')[0]) for f in existing_migrations if f.split('_')[0].isdigit()] next_number = max(numbers) + 1 if numbers else 1 else: next_number = 1 filename = f"{next_number:04d}_convert_{field_name}_translatable.py" filepath = os.path.join(migrations_dir, filename) with open(filepath, 'w') as f: f.write(migration_string) self.stdout.write(f"Created migration: {filepath}")