Source code for translations.management.commands.synctranslations

"""
This module contains the synctranslations command for the Translations app.
"""

import sys

from django.core.management.base import (
    BaseCommand, CommandError,
)
from django.apps import apps
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType

from translations.models import Translation, Translatable


__docformat__ = 'restructuredtext'


[docs]class Command(BaseCommand): """ The command which synchronizes the translations with the apps models configurations. """ help = 'Synchronize the translations with the apps models configurations.'
[docs] def execute(self, *args, **options): """Execute the `Command` with `BaseCommand` arguments.""" self.stdin = options.get('stdin', sys.stdin) # Used for testing return super(Command, self).execute(*args, **options)
[docs] def add_arguments(self, parser): """ Add the arguments that the `Command` accepts on an `ArgumentParser`. """ parser.add_argument( 'args', metavar='app_label', nargs='*', help=( 'Specify the app label(s) to synchronize the translations for.' ), ) parser.add_argument( '--noinput', '--no-input', action='store_false', dest='interactive', help='Tells Django to NOT prompt the user for input of any kind.', )
[docs] def get_content_types(self, *app_labels): r"""Return the `ContentType`\ s in some apps or all of them.""" if app_labels: query = Q() for app_label in app_labels: try: apps.get_app_config(app_label) except LookupError: raise CommandError( "App '{}' is not found.".format(app_label) ) else: query |= Q(app_label=app_label) content_types = ContentType.objects.filter(query) else: content_types = ContentType.objects.all() return content_types
[docs] def get_obsolete_translations(self, content_types): r"""Return the obsolete translations of some `ContentType`\ s.""" if content_types: query = Q() for content_type in content_types: model = content_type.model_class() if issubclass(model, Translatable): trans_fields = model._get_translatable_fields_names() model_query = ( Q(content_type=content_type) & ~Q(field__in=trans_fields) ) else: model_query = Q(content_type=content_type) query |= model_query obsolete_translations = Translation.objects.filter(query) else: obsolete_translations = Translation.objects.none() return obsolete_translations
[docs] def log_obsolete_translations(self, obsolete_translations): """Log the details of some obsolete translations.""" if self.verbosity >= 1: self.stdout.write('Looking for obsolete translations...') if obsolete_translations: changes = {} for translation in obsolete_translations: app = apps.get_app_config( translation.content_type.app_label ) app_name = app.name model = translation.content_type.model_class() model_name = model.__name__ changes.setdefault(app_name, {}) changes[app_name].setdefault(model_name, set()) changes[app_name][model_name].add(translation.field) self.stdout.write( 'Obsolete translations found for the specified fields:' ) for app_name, models in sorted( changes.items(), key=lambda x: x[0]): self.stdout.write('- App: {}'.format(app_name)) for model_name, fields in sorted( models.items(), key=lambda x: x[0]): self.stdout.write(' - Model: {}'.format(model_name)) for field in sorted( fields, key=lambda x: x[0]): self.stdout.write(' - Field: {}'.format(field)) self.stdout.write( self.style.WARNING( 'Obsolete translations will be deleted in the ' 'synchronization process.' ) ) else: self.stdout.write('No obsolete translations found.')
[docs] def ask_yes_no(self, message, default=None): """Ask the user for yes or no with a message and a default value.""" answer = None while answer is None: value = input(message) # default if default is not None and value == '': value = default # yes or no? value = value.lower() if value in ['y', 'yes', True]: answer = True elif value in ['n', 'no', False]: answer = False else: answer = None return answer
[docs] def should_run_synchronization(self): """Return whether to run the synchronization or not.""" run = None if self.interactive: if hasattr(self.stdin, 'isatty') and not self.stdin.isatty(): self.stderr.write( "Synchronization failed due to not running in a TTY." ) self.stderr.write( "If you are sure about synchronization you can run " "it with the '--no-input' flag." ) sys.exit(1) else: try: run = self.ask_yes_no( ( 'Are you sure you want to synchronize the ' 'translations? [Y/n] ' ), default='Y' ) except KeyboardInterrupt: self.stdout.write('\n') # move to the next line of stdin self.stdout.write('\n') # move another line for division self.stderr.write("Operation cancelled.") sys.exit(1) else: run = True return run
[docs] def handle(self, *app_labels, **options): """Run the `Command` with the configured arguments.""" # get arguments self.verbosity = options['verbosity'] self.interactive = options['interactive'] # collect all the models which will be affected content_types = self.get_content_types(*app_labels) # handle obsolete translations obsolete_translations = self.get_obsolete_translations(content_types) self.log_obsolete_translations(obsolete_translations) # divide initializing synchronization with asking for synchronization self.stdout.write('\n') if obsolete_translations: # ask user if they are sure that they want to synchronize run_synchronization = self.should_run_synchronization() # divide asking for synchronization with actual synchronization self.stdout.write('\n') if run_synchronization: obsolete_translations.delete() else: self.stdout.write( 'Synchronization cancelled.' ) return self.stdout.write( self.style.SUCCESS( 'Synchronization successful.' ) )