This commit is contained in:
Holger Sielaff
2025-08-02 20:08:33 +02:00
commit 79c68169f6
47 changed files with 4880 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
"""
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}")