177 lines
7.2 KiB
Python
177 lines
7.2 KiB
Python
"""
|
|
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}") |