def queue_task(self, task): """Queue a task to run during evolution. This should only be directly called if working with custom tasks. Otherwise, use a more specific queue method. Args: task (BaseEvolutionTask): The task to queue. Raises: django_evolution.errors.EvolutionTaskAlreadyQueuedError: A purge of this app was already queued. django_evolution.errors.QueueEvolverTaskError: Error queueing a non-duplicate task. Tasks may have already been prepared and finalized. """ assert task.id if self._tasks_prepared: raise QueueEvolverTaskError( _('Evolution tasks have already been prepared. New tasks ' 'cannot be added.')) if task.id in self._tasks_by_id: raise EvolutionTaskAlreadyQueuedError( _('A task with ID "%s" is already queued.') % task.id) self._tasks_by_id[task.id] = task self._tasks_by_class.setdefault(type(task), []).append(task)
def handle(self, *args, **options): """Handle the command. This will validate the arguments and run through the evolution process. Args: *args (list of unicode): Positional arguments passed on the command line. **options (dict): Options parsed by the argument parser. Raises: django.core.management.base.CommandError: Arguments were invalid or something went wrong. Details are in the message. """ if not has_migrate: raise CommandError( _('migrate is not available on this version of Django. ' 'Use `syncdb` instead.')) if not django_evolution_settings.ENABLED: # Run the original migrate command. return super(Command, self).handle(*args, **options) if options.get('migration_name'): raise CommandError( _('The migrate command cannot apply a specific migration ' 'name when Django Evolution is in use. Set ' '`DJANGO_EVOLUTION_ENABLED = False` in your settings.py ' 'to use the original migrate command.')) if options.get('fake'): raise CommandError( _('The migrate command cannot use --fake when Django ' 'Evolution is in use. Set ' '`DJANGO_EVOLUTION_ENABLED = False` in your settings.py ' 'to use the original migrate command.')) app_labels = [] if options.get('app_label'): app_labels.append(options.get('app_label')) call_command('evolve', *app_labels, verbosity=options.get('verbosity'), interactive=options.get('interactive'), database=options.get('database'), execute=True)
def _on_creating_models(app_label, model_names, **kwargs): if verbosity > 2: message = ( _('Creating new database models for %(app_label)s ' '(%(model_names)s)...\n') % { 'app_label': app_label, 'model_names': ', '.join(model_names), }) else: message = (_('Creating new database models for ' '%(app_label)s...\n') % { 'app_label': app_label, }) self.stdout.write(message)
def add_arguments(self, parser): """Add arguments to the command. Args: parser (object): The argument parser to add to. """ parser.add_argument('args', metavar='APP_LABEL', nargs='*', help=_('One or more app labels to evolve.')) parser.add_argument( '--noinput', action='store_false', dest='interactive', default=True, help=_('Automatically says yes to any prompts. When used with ' '--execute, this will apply evolutions without first ' 'asking for confirmation.')) parser.add_argument( '--hint', action='store_true', dest='hint', default=False, help=_('Display sample evolutions covering any new changes made ' 'to models since the last evolution.')) parser.add_argument( '--purge', action='store_true', dest='purge', default=False, help=_('Purge deleted applications from the evolution history.')) parser.add_argument('--sql', action='store_true', dest='compile_sql', default=False, help=_('Display the evolutions as SQL.')) parser.add_argument( '-w', '--write', metavar='EVOLUTION_NAME', action='store', dest='write_evolution_name', default=None, help=_('Write the generated evolutions to files with the given ' 'evolution name in each affected app\'s "evolutions" ' 'paths.')) parser.add_argument('-x', '--execute', action='store_true', dest='execute', default=False, help=_('Apply evolutions to the database.')) parser.add_argument( '--database', action='store', dest='database', help=_('Specify the database containing models to synchronize.'))
def _add_tasks(self, app_labels): """Add tasks to the evolver, based on the command options. This will queue up the applications that need to be evolved, and queue up the purging of stale applications if requested. Args: app_labels (list of unicode): The list of app labels to evolve. If this is empty, all registered apps will be evolved. """ evolver = self.evolver if app_labels: # The caller wants to evolve specific apps. Queue each one, # handling any invalid app labels in the process. try: for app_label in app_labels: evolver.queue_evolve_app(get_app(app_label)) except (ImportError, ImproperlyConfigured) as e: raise CommandError( _('%s. Are you sure your INSTALLED_APPS setting is ' 'correct?') % e) else: # The caller wants the default behavior of evolving all apps # with pending evolutions. evolver.queue_evolve_all_apps() if self.purge: # The caller wants to purge all old stale applications that # no longer exist. # # Note that we don't do this by default, since those apps # might not be permanently added to the list of installed apps. evolver.queue_purge_old_apps()
def _on_applying_migration(migration, **kwargs): self.stdout.write( _('Applying database migration %(migration_name)s for ' '%(app_label)s...\n') % { 'app_label': migration.app_label, 'migration_name': migration.name, })
def execute(self, cursor=None, sql_executor=None, **kwargs): """Execute the task. This will delete any tables owned by the application. Args: cursor (django.db.backends.util.CursorWrapper, unused): The legacy database cursor. This is no longer used. sql_executor (django_evolution.utils.sql.SQLExecutor, optional): The SQL executor used to run any SQL on the database. Raises: django_evolution.errors.EvolutionExecutionError: The evolution task failed. Details are in the error. """ assert sql_executor if self.evolution_required: try: sql_executor.run_sql(self.sql, execute=True) except Exception as e: raise EvolutionExecutionError( _('Error purging app "%s": %s') % (self.app_label, e), app_label=self.app_label, detailed_error=six.text_type(e), last_sql_statement=getattr(e, 'last_sql_statement'))
def _on_applied_migration(migration, **kwargs): self.stdout.write( _('Successfully applied database migration ' '%(migration_name)s for %(app_label)s.\n') % { 'app_label': migration.app_label, 'migration_name': migration.name, })
def _on_created_models(app_label, model_names, **kwargs): if verbosity > 2: message = ( _('Successfully created new database models for ' '%(app_label)s (%(model_names)s).\n') % { 'app_label': app_label, 'model_names': ', '.join(model_names), }) else: message = ( _('Successfully created new database models for ' '%(app_label)s.\n') % { 'app_label': app_label, }) self.stdout.write(message)
def handle(self, *args, **options): """Handle the command. This will validate the arguments and run through the evolution process. Args: *args (list of unicode): Positional arguments passed on the command line. **options (dict): Options parsed by the argument parser. Raises: django.core.management.base.CommandError: Arguments were invalid or something went wrong. Details are in the message. """ if not has_syncdb: raise CommandError( _('syncdb is not available on this version of Django. ' 'Use `migrate` instead.')) if not django_evolution_settings.ENABLED: # Run the original syncdb command. return super(Command, self).handle(*args, **options) call_command('evolve', verbosity=options.get('verbosity'), interactive=options.get('interactive'), database=options.get('database'), execute=True)
def _confirm_execute(self): """Prompt the user to confirm execution of an evolution. This will warn the user of the risks of evolving the database and to recommend a backup. It will then prompt for confirmation, returning the result. Returns: bool: ``True`` if the user confirmed the execution. ``False`` if the execution should be cancelled. """ prompt = self._wrap_paragraphs( _('You have requested a database upgrade. This will alter ' 'tables and data currently in the "%s" database, and may ' 'result in IRREVERSABLE DATA LOSS. Upgrades should be ' '*thoroughly* reviewed and tested prior to execution.\n' '\n' 'MAKE A BACKUP OF YOUR DATABASE BEFORE YOU CONTINUE!\n' '\n' 'Are you sure you want to execute the database upgrade?\n' '\n' 'Type "yes" to continue, or "no" to cancel:') % self.evolver.database_name) # Note that we must append a space here, rather than above, since the # paragraph wrapping logic will strip trailing whitespace. return input('%s ' % prompt).lower() == 'yes'
def _display_extra_task_details(self): """Display some informative state about queued tasks. This will list any applications that are already up-tp-date, and list whether or not any applications need to be purged. """ # List all applications that appear up-to-date. for task in self.evolver.tasks: if (isinstance(task, EvolveAppTask) and not task.evolution_required): self.stdout.write( _('Application "%s" is up-to-date\n') % task.app_label) if self.purge and not self.active_purge_tasks: # List whether there are any applications that need to be # purged. self.stdout.write(_('No applications need to be purged.\n'))
def _on_applying_evolution(task, evolutions, **kwargs): if verbosity > 2: message = ( _('Applying database evolutions for %(app_label)s ' '(%(evolution_labels)s)...\n') % { 'app_label': task.app_label, 'evolution_labels': ', '.join(evolution.label for evolution in evolutions), }) else: message = (_('Applying database evolutions for ' '%(app_label)s...\n') % { 'app_label': task.app_label, }) self.stdout.write(message)
def _on_applied_evolution(task, evolutions, **kwargs): if verbosity > 2: message = ( _('Successfully applied database evolutions for ' '%(app_label)s (%(evolution_labels)s).\n') % { 'app_label': task.app_label, 'evolution_labels': ', '.join(evolution.label for evolution in evolutions), }) else: message = ( _('Successfully applied database evolutions for ' '%(app_label)s.\n') % { 'app_label': task.app_label, }) self.stdout.write(message)
def add_arguments(self, parser): """Add arguments to the command. Args: parser (object): The argument parser to add to. """ parser.add_argument( 'args', metavar='APP_LABEL', nargs='*', help=_('One or more app labels to list evolutions for.'))
def __call__(self): """Handle calls on this object. This will raise an exception stating that the evolution cannot be performed. Raises: django_evolution.errors.EvolutionException: An error stating that an explicit initial value must be provided in place of this object. """ raise EvolutionException( _('Cannot use hinted evolution: Mutation requires a ' 'user-specified value.'))
def __call__(self): """Handle calls on this object. This will raise an exception stating that the evolution cannot be performed. Raises: django_evolution.errors.EvolutionException: An error stating that an explicit initial value must be provided in place of this object. """ raise EvolutionException( _('Cannot use hinted evolution: AddField or ChangeField mutation ' 'for "%s.%s" in "%s" requires user-specified initial value.') % (self.model_name, self.field_name, self.app_label))
def evolve(self): """Perform the evolution. This will run through all queued tasks and attempt to apply them in a database transaction, tracking each new batch of evolutions as the tasks finish. This can only be called once per evolver instance. Raises: django_evolution.errors.EvolutionException: Something went wrong during the evolution process. Details are in the error message. Note that a more specific exception may be raised. django_evolution.errors.EvolutionExecutionError: A specific evolution task failed. Details are in the error. """ if self.evolved: raise EvolutionException( _('Evolver.evolve() has already been run once. It cannot be ' 'run again.')) self._prepare_tasks() evolving.send(sender=self) try: new_evolutions = [] for task_cls, tasks in six.iteritems(self._tasks_by_class): # Perform the evolution for the app. This is responsible # for raising any exceptions. task_cls.execute_tasks(evolver=self, tasks=tasks) for task in tasks: new_evolutions += task.new_evolutions # Things may have changed, so rescan the database. self.database_state.rescan_tables() self._save_project_sig(new_evolutions=new_evolutions) self.evolved = True except Exception as e: evolving_failed.send(sender=self, exception=e) raise evolved.send(sender=self)
def _display_available_purges(self): """Display the apps that can be purged.""" purge_tasks = self.active_purge_tasks if purge_tasks: self.stdout.write( ngettext('The following application can be purged:', 'The following applications can be purged:', len(purge_tasks))) self.stdout.write('\n') for purge_task in purge_tasks: self.stdout.write(' * %s\n' % purge_task.app_label) self.stdout.write('\n') elif self.verbosity > 1: self.stdout.write(_('No applications need to be purged.\n'))
def queue_purge_app(self, app_label): """Queue the purging of a Django app. Args: app_label (unicode): The label of the app to purge. Raises: django_evolution.errors.EvolutionTaskAlreadyQueuedError: A purge of this app was already queued. django_evolution.errors.QueueEvolverTaskError: Error queueing a non-duplicate task. Tasks may have already been prepared and finalized. """ try: self.queue_task(PurgeAppTask(evolver=self, app_label=app_label)) except EvolutionTaskAlreadyQueuedError: raise EvolutionTaskAlreadyQueuedError( _('"%s" is already being tracked for purging') % app_label)
def queue_evolve_app(self, app): """Queue an evolution of a registered Django app. Args: app (module): The Django app to queue an evolution for. Raises: django_evolution.errors.EvolutionTaskAlreadyQueuedError: An evolution for this app was already queued. django_evolution.errors.QueueEvolverTaskError: Error queueing a non-duplicate task. Tasks may have already been prepared and finalized. """ try: self.queue_task(EvolveAppTask(self, app)) except EvolutionTaskAlreadyQueuedError: raise EvolutionTaskAlreadyQueuedError( _('"%s" is already being tracked for evolution') % get_app_label(app))
def add_arguments(self, parser): """Add arguments to the command. Args: parser (object): The argument parser to add to. """ parser.add_argument('args', metavar='EVOLUTION_LABEL', nargs='+', help=_('One or more evolution labels to wipe.')) parser.add_argument( '--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.') parser.add_argument( '--app-label', action='store', dest='app_label', help='The app label the evolution label applies to.')
def _save_project_sig(self, new_evolutions): """Save the project signature and any new evolutions. This will serialize the current modified project signature to the database and write any new evolutions, attaching them to the current project version. This can be called many times for one evolver instance. After the first time, the version already saved will simply be updated. Args: new_evolutions (list of django_evolution.models.Evolution): The list of new evolutions to save to the database. Raises: django_evolution.errors.EvolutionExecutionError: There was an error saving to the database. """ version = self.version if version is None: version = Version(signature=self.project_sig) self.version = version try: version.save(using=self.database_name) if new_evolutions: for evolution in new_evolutions: evolution.version = version Evolution.objects.using( self.database_name).bulk_create(new_evolutions) except Exception as e: raise EvolutionExecutionError( _('Error saving new evolution version information: %s') % e, detailed_error=six.text_type(e))
def handle(self, *app_labels, **options): """Handle the command. This will validate the arguments and run through the evolution process. Args: app_labels (list of unicode): The app labels to evolve. options (dict): Options parsed by the argument parser. Raises: django.core.management.base.CommandError: Arguments were invalid or something went wrong. Details are in the message. """ if not django_evolution_settings.ENABLED: raise CommandError( _('Django Evolution is disabled for this project. ' 'Evolutions cannot be manually run.')) self.purge = options['purge'] self.verbosity = int(options['verbosity']) hint = options['hint'] compile_sql = options['compile_sql'] database_name = options['database'] or DEFAULT_DB_ALIAS execute = options['execute'] interactive = options['interactive'] write_evolution_name = options['write_evolution_name'] if app_labels and self.execute: raise CommandError( _('Cannot specify an application name when executing ' 'evolutions.')) if write_evolution_name and not hint: raise CommandError(_('--write cannot be used without --hint.')) import_management_modules() try: self.evolver = Evolver(database_name=database_name, hinted=hint, verbosity=self.verbosity, interactive=interactive) # Figure out what tasks we need to add to the evolver. This # must be done before we check any state (as that will finalize # the task list). self._add_tasks(app_labels) # Calculate some information we may need later. self.active_purge_tasks = [ task for task in self.evolver.tasks if isinstance(task, PurgeAppTask) and len(task.sql) > 0 ] # Display any additional information on the evolution process # the caller may be interested in. if self.verbosity > 1: self._display_extra_task_details() # Simulate the evolutions to make sure that they'll get us to the # target database state. This will raise a CommandError with # helpful information if the evolutions don't get us there, or # if one or more evolutions couldn't be simulated. simulated = self._check_simulation() if not self.evolver.get_evolution_required(): if self.verbosity > 0: self.stdout.write(_('No database upgrade required.\n')) elif execute: if not interactive or self._confirm_execute(): self._perform_evolution() else: self.stderr.write(_('Database upgrade cancelled.\n')) elif compile_sql: self._display_compiled_sql() else: # Be helpful and list any applications that can be purged, # and then show any evolution content that may be useful to # the user. self._display_available_purges() self._generate_evolution_contents(write_evolution_name) if simulated: if self.verbosity > 0: self.stdout.write(_('Trial upgrade successful!\n')) if not self.evolver.hinted and self.verbosity > 0: self.stdout.write( _('Run `./manage.py evolve --execute` to apply ' 'the evolution.\n')) except EvolutionException as e: raise CommandError(six.text_type(e))
def _generate_evolution_contents(self, evolution_label=None): """Generate the contents of evolution files or hinted evolutions. This will grab the contents of either the stored evolution files or hinted evolutions (if using ``--hint``) and write them to the console or to generated evolution files (if using ``--write``). Args: evolution_label (unicode, optional): The label used as a base for any generated filenames. If provided, the filenames will be written to the appropriate evolution directories, with a ``.py`` appended. Raises: django.core.management.base.CommandError: An evolution file couldn't be written. Details are in the error message. """ evolution_contents = self.evolver.iter_evolution_content() if evolution_label: # We're writing the hinted evolution files to disk. Notify the user # and begin writing. verbosity = self.verbosity if verbosity > 0: self.stdout.write('\n%s\n\n' % self._wrap_paragraphs( _('The following evolution files were written. Verify the ' 'contents and add them to the SEQUENCE lists in each ' '__init__.py.'))) for task, content in evolution_contents: assert hasattr(task, 'app') dirname = get_evolutions_path(task.app) filename = os.path.join(dirname, '%s.py' % evolution_label) if not os.path.exists(dirname): try: os.mkdir(dirname, 0o755) except IOError as e: raise CommandError( _('Unable to create evolutions directory "%s": %s') % (dirname, e)) try: with open(filename, 'w') as fp: fp.write(content.strip()) fp.write('\n') except Exception as e: raise CommandError( _('Unable to write evolution file "%s": %s') % (filename, e)) if verbosity > 0: self.stdout.write(' * %s\n' % os.path.relpath(filename)) else: # We're just going to output the hint content. for i, (task, content) in enumerate(evolution_contents): assert hasattr(task, 'app_label') self.stdout.write('#----- Evolution for %s\n' % task.app_label) self.stdout.write(content.strip()) self.stdout.write('#----------------------\n') self.stdout.write('\n')
class SignatureField(models.TextField): """A field for loading and storing project signatures. This will handle deserializing any project signatures stored in the database, converting them into a :py:class:`~django_evolution.signatures.ProjectSignature`, and then writing a serialized version back to the database. """ description = _('Signature') def contribute_to_class(self, cls, name): """Perform operations when added to a class. This will listen for when an instance is constructed in order to perform some initial work. Args: cls (type): The model class. name (str): The name of the field. """ super(SignatureField, self).contribute_to_class(cls, name) post_init.connect(self._post_init, sender=cls) def value_to_string(self, obj): """Return a serialized string value from the field. Args: obj (django.db.models.Model): The model instance. Returns: unicode: The serialized string contents. """ return self._dumps(self.value_from_object(obj)) def to_python(self, value): """Return a ProjectSignature value from the field contents. Args: value (object): The current value assigned to the field. This might be serialized string content or a :py:class:`~django_evolution.signatures.ProjectSignature` instance. Returns: django_evolution.signatures.ProjectSignature: The project signature stored in the field. Raises: django.core.exceptions.ValidationError: The field contents are of an unexpected type. """ if not value: return ProjectSignature() elif isinstance(value, six.string_types): if value.startswith('json!'): loaded_value = json.loads(value[len('json!'):], object_pairs_hook=OrderedDict) else: loaded_value = pickle_loads(value) return ProjectSignature.deserialize(loaded_value) elif isinstance(value, ProjectSignature): return value else: raise ValidationError('Unsupported serialized signature type %s' % type(value), code='invalid', params={ 'value': value, }) def get_prep_value(self, value): """Return a prepared Python value to work with. This simply wraps :py:meth:`to_python`. Args: value (object): The current value assigned to the field. This might be serialized string content or a :py:class:`~django_evolution.signatures.ProjectSignature` instance. Returns: django_evolution.signatures.ProjectSignature: The project signature stored in the field. Raises: django.core.exceptions.ValidationError: The field contents are of an unexpected type. """ return self.to_python(value) def get_db_prep_value(self, value, connection, prepared=False): """Return a prepared value for use in database operations. Args: value (object): The current value assigned to the field. This might be serialized string content or a :py:class:`~django_evolution.signatures.ProjectSignature` instance. connection (django.db.backends.base.BaseDatabaseWrapper): The database connection to operate on. prepared (bool, optional): Whether the value is already prepared for Python. Returns: unicode: The value prepared for database operations. """ if not prepared: value = self.get_prep_value(value) return self._dumps(value) def _post_init(self, instance, **kwargs): """Handle the construction of a model instance. This will ensure the value set on the field is a valid :py:class:`~django_evolution.signatures.ProjectSignature` object. Args: instance (django.db.models.Model): The model instance being constructed. **kwargs (dict, unused): Additional keyword arguments from the signal. """ setattr(instance, self.attname, self.to_python(self.value_from_object(instance))) def _dumps(self, data): """Serialize the project signature to a string. Args: data (object): The signature data to dump. This might be serialized string content or a :py:class:`~django_evolution.signatures.ProjectSignature` instance. Returns: unicode: The project signature stored in the field. Raises: TypeError: The data provided was not of a supported type. """ if isinstance(data, six.string_types): return data elif isinstance(data, ProjectSignature): serialized_data = data.serialize() sig_version = serialized_data['__version__'] if sig_version >= 2: return 'json!%s' % json.dumps(serialized_data) else: return pickle_dumps(serialized_data) else: raise TypeError('Unsupported signature type %s' % type(data))
def _perform_evolution(self): """Perform the evolution. This will perform the evolution, based on the options passed to this command. Progress on the evolution will be printed to the console. Raises: django.core.management.base.CommandError: The evolution failed. """ evolver = self.evolver verbosity = self.verbosity if verbosity > 0: @receiver(applying_evolution, sender=evolver) def _on_applying_evolution(task, evolutions, **kwargs): if verbosity > 2: message = ( _('Applying database evolutions for %(app_label)s ' '(%(evolution_labels)s)...\n') % { 'app_label': task.app_label, 'evolution_labels': ', '.join(evolution.label for evolution in evolutions), }) else: message = (_('Applying database evolutions for ' '%(app_label)s...\n') % { 'app_label': task.app_label, }) self.stdout.write(message) @receiver(applying_migration, sender=evolver) def _on_applying_migration(migration, **kwargs): self.stdout.write( _('Applying database migration %(migration_name)s for ' '%(app_label)s...\n') % { 'app_label': migration.app_label, 'migration_name': migration.name, }) @receiver(creating_models, sender=evolver) def _on_creating_models(app_label, model_names, **kwargs): if verbosity > 2: message = ( _('Creating new database models for %(app_label)s ' '(%(model_names)s)...\n') % { 'app_label': app_label, 'model_names': ', '.join(model_names), }) else: message = (_('Creating new database models for ' '%(app_label)s...\n') % { 'app_label': app_label, }) self.stdout.write(message) if verbosity > 1: @receiver(applied_evolution, sender=evolver) def _on_applied_evolution(task, evolutions, **kwargs): if verbosity > 2: message = ( _('Successfully applied database evolutions for ' '%(app_label)s (%(evolution_labels)s).\n') % { 'app_label': task.app_label, 'evolution_labels': ', '.join(evolution.label for evolution in evolutions), }) else: message = ( _('Successfully applied database evolutions for ' '%(app_label)s.\n') % { 'app_label': task.app_label, }) self.stdout.write(message) @receiver(applied_migration, sender=evolver) def _on_applied_migration(migration, **kwargs): self.stdout.write( _('Successfully applied database migration ' '%(migration_name)s for %(app_label)s.\n') % { 'app_label': migration.app_label, 'migration_name': migration.name, }) @receiver(created_models, sender=evolver) def _on_created_models(app_label, model_names, **kwargs): if verbosity > 2: message = ( _('Successfully created new database models for ' '%(app_label)s (%(model_names)s).\n') % { 'app_label': app_label, 'model_names': ', '.join(model_names), }) else: message = ( _('Successfully created new database models for ' '%(app_label)s.\n') % { 'app_label': app_label, }) self.stdout.write(message) self.stdout.write('\n%s\n\n' % self._wrap_paragraphs( _('This may take a while. Please be patient, and DO NOT ' 'cancel the upgrade!'))) try: evolver.evolve() except EvolutionException as e: self.stderr.write('%s\n' % e) if getattr(e, 'last_sql_statement', None): self.stderr.write( _('The SQL statement that failed was: %s\n') % (e.last_sql_statement, )) raise CommandError(six.text_type(e)) if verbosity > 0: if evolver.installed_new_database: self.stdout.write(_('The database creation was successful!\n')) else: self.stdout.write(_('The database upgrade was successful!\n'))
def _check_simulation(self): """Check the results of a simulation. This will check first if a simulation could even occur (based on whether there are raw SQL mutations that are going to be applied). If a simulation did occur, information on the simulation results and the resulting signature diff will be displayed. If a simulation either could not be performed, or was performed and succeeded, a result will be returned so that the caller can perform additional operations based on that state. If a simulation could be performed but failed, this will immediately terminate the command with an error message. Returns: bool: ```True`` if the simulation was successful and all changes were resolved. ``False`` if a simulation could not be performed due to raw SQL mutations. Raises: django.core.management.base.CommandError: A simulation was performed, but changes could not be resolved. """ if not self.evolver.can_simulate(): self.stdout.write( self.style.NOTICE( _('Evolution could not be simulated, possibly due ' 'to raw SQL mutations\n'))) return False diff = self.evolver.diff_evolutions() if diff.is_empty(ignore_apps=not self.purge): return True if self.evolver.hinted: self.stderr.write( self._wrap_paragraphs( _('Your models contain changes that Django Evolution ' 'cannot resolve automatically.\n' '\n' 'This is probably due to a currently unimplemented ' 'mutation type. You will need to manually construct a ' 'mutation to resolve the remaining changes.'))) else: self.stderr.write( self._wrap_paragraphs( _('The stored evolutions do not completely resolve ' 'all model changes.\n' '\n' 'Run `./manage.py evolve --hint` to see a ' 'suggestion for the changes required.'))) self.stdout.write('\n\n') self.stdout.write( self._wrap_paragraphs( _('The following are the changes that could not be resolved:')) ) self.stdout.write('\n%s\n' % diff) raise CommandError( _('Your models contain changes that Django Evolution cannot ' 'resolve automatically.'))