def test_evolve_with_hinted(self): """Testing Evolver.evolve with hinting""" model_sig = ModelSignature.from_model(EvolverTestModel) model_sig.get_field_sig('value').field_attrs['max_length'] = 50 app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) orig_version = Version.objects.current_version() orig_version.signature.add_app_sig(app_sig) orig_version.save() with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): evolver = Evolver(hinted=True) evolver.queue_evolve_app(evo_test) evolver.evolve() self.assertTrue(evolver.evolved) version = Version.objects.current_version() self.assertNotEqual(version, orig_version) self.assertTrue(version.is_hinted()) model_sig = ( version.signature .get_app_sig('tests') .get_model_sig('TestModel') ) self.assertEqual( model_sig.get_field_sig('value').field_attrs['max_length'], 100)
def test_diff_evolutions(self): """Testing Evolver.diff_evolutions""" version = Version.objects.current_version() model_sig = ( version.signature .get_app_sig('django_evolution') .get_model_sig('Evolution') ) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver() evolver.queue_evolve_all_apps() diff = evolver.diff_evolutions() self.assertFalse(diff.is_empty()) self.assertEqual( diff.changed, { 'django_evolution': { 'changed': { 'Evolution': { 'changed': { 'label': ['max_length'], }, }, }, }, })
def test_diff_evolutions(self): """Testing Evolver.diff_evolutions""" version = Version.objects.current_version() model_sig = (version.signature.get_app_sig( 'django_evolution').get_model_sig('Evolution')) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver() evolver.queue_evolve_all_apps() diff = evolver.diff_evolutions() self.assertFalse(diff.is_empty()) self.assertEqual( diff.changed, { 'django_evolution': { 'changed': { 'Evolution': { 'changed': { 'label': ['max_length'], }, }, }, }, })
def test_evolve(self): """Testing Evolver.evolve""" model_sig = ModelSignature.from_model(EvolverTestModel) model_sig.get_field_sig('value').field_attrs['max_length'] = 50 app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) orig_version = Version.objects.current_version() orig_version.signature.add_app_sig(app_sig) orig_version.save() with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): evolver = Evolver() evolver.queue_task(EvolveAppTask( evolver=evolver, app=evo_test, evolutions=[ { 'label': 'my_evolution1', 'mutations': [ ChangeField('TestModel', 'value', max_length=200), ], }, { 'label': 'my_evolution2', 'mutations': [ AddField('TestModel', 'new_field', models.BooleanField, null=True), ], }, ])) evolver.evolve() self.assertTrue(evolver.evolved) version = Version.objects.current_version() self.assertNotEqual(version, orig_version) self.assertFalse(version.is_hinted()) evolutions = list(version.evolutions.all()) self.assertEqual(len(evolutions), 2) self.assertEqual(evolutions[0].app_label, 'tests') self.assertEqual(evolutions[0].label, 'my_evolution1') self.assertEqual(evolutions[1].app_label, 'tests') self.assertEqual(evolutions[1].label, 'my_evolution2') model_sig = ( version.signature .get_app_sig('tests') .get_model_sig('TestModel') ) self.assertEqual( model_sig.get_field_sig('value').field_attrs['max_length'], 200) self.assertIsNotNone(model_sig.get_field_sig('new_field'))
def test_queue_evolve_all_apps_with_app_already_queued(self): """Testing Evolver.queue_evolve_all_apps with app already queued""" evolver = Evolver() evolver.queue_evolve_app(get_app('django_evolution')) message = '"django_evolution" is already being tracked for evolution' with self.assertRaisesMessage(EvolutionTaskAlreadyQueuedError, message): evolver.queue_evolve_all_apps()
def test_queue_evolve_app(self): """Testing Evolver.queue_evolve_app""" app = get_app('django_evolution') evolver = Evolver() evolver.queue_evolve_app(app) tasks = list(evolver.tasks) self.assertEqual(len(tasks), 1) self.assertIsInstance(tasks[0], EvolveAppTask) self.assertIs(tasks[0].app, app)
def test_queue_purge_app(self): """Testing Evolver.queue_purge_app""" version = Version.objects.current_version() version.signature.add_app_sig(AppSignature(app_id='old_app')) version.save() evolver = Evolver() evolver.queue_purge_app('old_app') tasks = list(evolver.tasks) self.assertEqual(len(tasks), 1) self.assertIsInstance(tasks[0], PurgeAppTask) self.assertEqual(tasks[0].app_label, 'old_app')
def test_diff_evolutions_with_hinted_true(self): """Testing Evolver.diff_evolutions with hinting""" version = Version.objects.current_version() model_sig = (version.signature.get_app_sig( 'django_evolution').get_model_sig('Evolution')) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver(hinted=True) evolver.queue_evolve_all_apps() diff = evolver.diff_evolutions() self.assertTrue(diff.is_empty())
def test_queue_evolve_app_after_prepared(self): """Testing Evolver.queue_evolve_app after tasks were already prepared """ evolver = Evolver() # Force preparation of tasks. list(evolver.tasks) message = ('Evolution tasks have already been prepared. New tasks ' 'cannot be added.') with self.assertRaisesMessage(QueueEvolverTaskError, message): evolver.queue_evolve_app(get_app('django_evolution'))
def test_queue_evolve_all_apps(self): """Testing Evolver.queue_evolve_all_apps""" evolver = Evolver() evolver.queue_evolve_all_apps() apps = get_apps() tasks = list(evolver.tasks) self.assertGreater(len(apps), 0) self.assertEqual(len(tasks), len(apps)) for app, task in zip(apps, tasks): self.assertIsInstance(task, EvolveAppTask) self.assertIs(task.app, app)
def test_queue_purge_app_with_already_queued(self): """Testing Evolver.queue_purge_app with app purge already queued""" version = Version.objects.current_version() version.signature.add_app_sig(AppSignature(app_id='old_app')) version.save() evolver = Evolver() evolver.queue_purge_app('old_app') message = '"old_app" is already being tracked for purging' with self.assertRaisesMessage(EvolutionTaskAlreadyQueuedError, message): evolver.queue_purge_app('old_app')
def test_queue_evolve_app_after_prepared(self): """Testing Evolver.queue_evolve_app after tasks were already prepared """ evolver = Evolver() # Force preparation of tasks. list(evolver.tasks) message = ( 'Evolution tasks have already been prepared. New tasks ' 'cannot be added.' ) with self.assertRaisesMessage(QueueEvolverTaskError, message): evolver.queue_evolve_app(get_app('django_evolution'))
def test_diff_evolutions_with_hinted_true(self): """Testing Evolver.diff_evolutions with hinting""" version = Version.objects.current_version() model_sig = ( version.signature .get_app_sig('django_evolution') .get_model_sig('Evolution') ) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver(hinted=True) evolver.queue_evolve_all_apps() diff = evolver.diff_evolutions() self.assertTrue(diff.is_empty())
def test_execute(self): """Testing PurgeAppTask.execute""" task = PurgeAppTask(evolver=Evolver(), app_label='tests') task.prepare() with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): task.execute(connections['default'].cursor())
def test_prepare_with_hinted_false(self): """Testing EvolveAppTask.prepare with hinted=False""" from django_evolution.compat.apps import register_app_models register_app_models('tests', [('TestModel', EvolverTestModel)], reset=True) evolver = Evolver() task = EvolveAppTask(evolver=evolver, app=evo_test, evolutions=[ { 'label': 'my_evolution1', 'mutations': [ ChangeField('TestModel', 'value', max_length=100), ], }, ]) task.prepare(hinted=False) self.assertTrue(task.evolution_required) self.assertTrue(task.can_simulate) self.assertEqual('\n'.join(task.sql), self.get_sql_mapping('evolve_app_task')) self.assertEqual(len(task.new_evolutions), 1) evolution = task.new_evolutions[0] self.assertEqual(evolution.app_label, 'tests') self.assertEqual(evolution.label, 'my_evolution1')
def test_get_evolution_content(self): """Testing EvolveAppTask.get_evolution_content""" evolver = Evolver() task = EvolveAppTask(evolver=evolver, app=evo_test, evolutions=[ { 'label': 'my_evolution1', 'mutations': [ ChangeField('TestModel', 'value', max_length=100), ], }, ]) task.prepare(hinted=False) content = task.get_evolution_content() self.assertEqual( content, "from __future__ import unicode_literals\n" "\n" "from django_evolution.mutations import ChangeField\n" "\n" "\n" "MUTATIONS = [\n" " ChangeField('TestModel', 'value', initial=None," " max_length=100),\n" "]")
def test_queue_purge_app_after_prepared(self): """Testing Evolver.queue_purge_app after tasks were already prepared""" version = Version.objects.current_version() version.signature.add_app_sig(AppSignature(app_id='old_app')) version.save() evolver = Evolver() # Force preparation of tasks. list(evolver.tasks) message = ('Evolution tasks have already been prepared. New tasks ' 'cannot be added.') with self.assertRaisesMessage(QueueEvolverTaskError, message): evolver.queue_purge_app('old_app')
def test_iter_evolution_content(self): """Testing Evolver.iter_evolution_content""" version = Version.objects.current_version() model_sig = (version.signature.get_app_sig( 'django_evolution').get_model_sig('Evolution')) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver(hinted=True) evolver.queue_evolve_all_apps() evolver.queue_task(DummyTask('dummy', evolver)) content = list(evolver.iter_evolution_content()) self.assertEqual(len(content), 1) self.assertIsInstance(content[0][0], EvolveAppTask) self.assertEqual( content[0][1], "from __future__ import unicode_literals\n" "\n" "from django_evolution.mutations import ChangeField\n" "\n" "\n" "MUTATIONS = [\n" " ChangeField('Evolution', 'label', initial=None," " max_length=100),\n" "]")
def test_init(self): """Testing Evolver.__init__""" self.assertEqual(Version.objects.count(), 1) version = Version.objects.get() evolver = Evolver() self.assertEqual(evolver.project_sig, version.signature) self.assertTrue(evolver.initial_diff.is_empty()) self.assertEqual(list(evolver.tasks), [])
def test_queue_purge_app_after_prepared(self): """Testing Evolver.queue_purge_app after tasks were already prepared""" version = Version.objects.current_version() version.signature.add_app_sig(AppSignature(app_id='old_app')) version.save() evolver = Evolver() # Force preparation of tasks. list(evolver.tasks) message = ( 'Evolution tasks have already been prepared. New tasks ' 'cannot be added.' ) with self.assertRaisesMessage(QueueEvolverTaskError, message): evolver.queue_purge_app('old_app')
def test_prepare(self): """Testing PurgeAppTask.prepare""" task = PurgeAppTask(evolver=Evolver(), app_label='tests') task.prepare() self.assertTrue(task.evolution_required) self.assertEqual(task.new_evolutions, []) self.assertTrue(task.can_simulate) self.assertEqual('\n'.join(task.sql), self.get_sql_mapping('purge_app_task'))
def _on_app_models_updated(app, using=DEFAULT_DB_ALIAS, **kwargs): """Handler for when an app's models were updated. This is called in response to a syncdb or migrate operation for an app. The very first time this is called for Django Evolution's app, this will set up the current project version to contain the full database signature, and to populate the list of evolutions with all currently-registered ones. This is only done when we're not actively evolving the database. That means it will only be called if we're running unit tests or in reaction to some other process that emits the signals (such as the flush management command). Args: app (module): The app models module that was updated. using (str, optional): The database being updated. **kwargs (dict): Additional keyword arguments provided by the signal handler for the syncdb or migrate operation. """ global _django_evolution_app if _django_evolution_app is None: _django_evolution_app = get_app('django_evolution') if (_evolve_lock > 0 or app is not _django_evolution_app or Version.objects.using(using).exists()): return evolver = Evolver(database_name=using) version = evolver.version version.signature = evolver.target_project_sig version.save(using=using) evolutions = [] for app in get_apps(): app_label = get_app_label(app) evolutions += [ Evolution(app_label=app_label, label=evolution_label, version=version) for evolution_label in get_evolution_sequence(app) ] Evolution.objects.using(using).bulk_create(evolutions)
def test_evolve_with_hinted(self): """Testing Evolver.evolve with hinting""" model_sig = ModelSignature.from_model(EvolverTestModel) model_sig.get_field_sig('value').field_attrs['max_length'] = 50 app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) orig_version = Version.objects.current_version() orig_version.signature.add_app_sig(app_sig) orig_version.save() with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): evolver = Evolver(hinted=True) evolver.queue_evolve_app(evo_test) evolver.evolve() self.assertTrue(evolver.evolved) version = Version.objects.current_version() self.assertNotEqual(version, orig_version) self.assertTrue(version.is_hinted()) model_sig = ( version.signature.get_app_sig('tests').get_model_sig('TestModel')) self.assertEqual( model_sig.get_field_sig('value').field_attrs['max_length'], 100)
def test_iter_evolution_content(self): """Testing Evolver.iter_evolution_content""" version = Version.objects.current_version() model_sig = ( version.signature .get_app_sig('django_evolution') .get_model_sig('Evolution') ) model_sig.get_field_sig('label').field_attrs['max_length'] = 50 version.save() evolver = Evolver(hinted=True) evolver.queue_evolve_all_apps() evolver.queue_task(DummyTask('dummy', evolver)) content = list(evolver.iter_evolution_content()) self.assertEqual(len(content), 1) self.assertIsInstance(content[0][0], EvolveAppTask) self.assertEqual( content[0][1], "from __future__ import unicode_literals\n" "\n" "from django_evolution.mutations import ChangeField\n" "\n" "\n" "MUTATIONS = [\n" " ChangeField('Evolution', 'label', initial=None," " max_length=100),\n" "]")
def test_evolve(self): """Testing Evolver.evolve""" model_sig = ModelSignature.from_model(EvolverTestModel) model_sig.get_field_sig('value').field_attrs['max_length'] = 50 app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) orig_version = Version.objects.current_version() orig_version.signature.add_app_sig(app_sig) orig_version.save() with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): evolver = Evolver() evolver.queue_task( EvolveAppTask(evolver=evolver, app=evo_test, evolutions=[ { 'label': 'my_evolution1', 'mutations': [ ChangeField('TestModel', 'value', max_length=200), ], }, { 'label': 'my_evolution2', 'mutations': [ AddField('TestModel', 'new_field', models.BooleanField, null=True), ], }, ])) evolver.evolve() self.assertTrue(evolver.evolved) version = Version.objects.current_version() self.assertNotEqual(version, orig_version) self.assertFalse(version.is_hinted()) evolutions = list(version.evolutions.all()) self.assertEqual(len(evolutions), 2) self.assertEqual(evolutions[0].app_label, 'tests') self.assertEqual(evolutions[0].label, 'my_evolution1') self.assertEqual(evolutions[1].app_label, 'tests') self.assertEqual(evolutions[1].label, 'my_evolution2') model_sig = ( version.signature.get_app_sig('tests').get_model_sig('TestModel')) self.assertEqual( model_sig.get_field_sig('value').field_attrs['max_length'], 200) self.assertIsNotNone(model_sig.get_field_sig('new_field'))
def test_prepare_with_hinted_true(self): """Testing EvolveAppTask.prepare with hinted=True""" from django_evolution.compat.apps import register_app_models register_app_models('tests', [('TestModel', EvolverTestModel)], reset=True) evolver = Evolver(hinted=True) task = EvolveAppTask(evolver=evolver, app=evo_test) task.prepare(hinted=True) self.assertTrue(task.evolution_required) self.assertTrue(task.can_simulate) self.assertEqual('\n'.join(task.sql), self.get_sql_mapping('evolve_app_task')) self.assertEqual(len(task.new_evolutions), 0)
def test_get_evolution_required_with_any_true(self): """Testing Evolver.get_evolution_required with any tasks having evolution_required=True """ evolver = Evolver() task1 = DummyTask('dummy1', evolver) task1.evolution_required = False evolver.queue_task(task1) task2 = DummyTask('dummy2', evolver) task2.evolution_required = True evolver.queue_task(task2) self.assertFalse(evolver.can_simulate())
def test_can_simulate_with_all_can_simulate_true_evolution_true(self): """Testing Evolver.can_simulate with all tasks having can_simulate=True """ evolver = Evolver() task1 = DummyTask('dummy1', evolver) task1.can_simulate = True task1.evolution_required = True evolver.queue_task(task1) task2 = DummyTask('dummy2', evolver) task2.can_simulate = True task2.evolution_required = True evolver.queue_task(task2) self.assertTrue(evolver.can_simulate())
def ensure_evolved_apps(self, apps): """Ensure an app's models and evolutions are applied to the database. Args: apps (list of module): The app modules to evolve. """ evolver = Evolver() for app in apps: evolver.queue_evolve_app(app) evolver.evolve()
def test_execute(self): """Testing EvolveAppTask.execute""" saw = set() @receiver(applying_evolution) def _on_applying(sender, **kwargs): saw.add('on_applying') self.assertIs(sender, evolver) self.assertIs(kwargs['task'], task) @receiver(applied_evolution) def _on_applied(sender, **kwargs): saw.add('on_applied') self.assertIs(sender, evolver) self.assertIs(kwargs['task'], task) evolver = Evolver() task = EvolveAppTask(evolver=evolver, app=evo_test, evolutions=[ { 'label': 'my_evolution1', 'mutations': [ ChangeField('TestModel', 'value', max_length=100), ], }, ]) task.prepare(hinted=False) with ensure_test_db(model_entries=[('TestModel', EvolverTestModel)]): task.execute(connections['default'].cursor()) self.assertEqual(saw, set(['on_applying', 'on_applied']))
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))
class Command(BaseCommand): """Manages and applies evolutions to the database.""" help = 'Manage evolutions to the database schema based on model changes.' args = '<appname appname ...>' requires_model_validation = False 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 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 _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 _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 _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.')) 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 _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 _display_compiled_sql(self): """Display the compiled SQL for the evolution run. This will output the SQL that would be executed based on the options passed to the command. """ database_name = self.evolver.database_name with SQLExecutor(database=database_name) as executor: for i, task in enumerate(self.evolver.tasks): if task.sql: if i > 0: self.stdout.write('\n') self.stdout.write('-- %s\n' % task) for statement in executor.run_sql(task.sql, capture=True): self.stdout.write('%s\n' % statement) 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 _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') def _wrap_paragraphs(self, text): """Wrap a block of text into paragraphs. This will take paragraphs worth of text and wrap them to fit in a standard terminal width, helping provide more readable output. Args: text (unicode): The text to wrap. Returns: unicode: The wrapped text. """ return '\n'.join( textwrap.fill(paragraph) for paragraph in text.splitlines())
def test_queue_purge_old_apps_without_old_apps(self): """Testing Evolver.queue_purge_old_apps without old apps""" evolver = Evolver() evolver.queue_purge_old_apps() self.assertEqual(list(evolver.tasks), [])
def ensure_evolution_models(self): """Create the Evolution and Version models if missing.""" Evolver() assert Version.objects.exists()
class Command(BaseCommand): """Manages and applies evolutions to the database.""" help = 'Manage evolutions to the database schema based on model changes.' args = '<appname appname ...>' requires_model_validation = False 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 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 getattr(settings, 'DJANGO_EVOLUTION_ENABLED', True): 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.')) try: self.evolver = Evolver(database_name=database_name, hinted=hint) # 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 evolution required.\n')) elif execute: if not interactive or self._confirm_execute(): self._perform_evolution() else: self.stderr.write(_('Evolution 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 evolution 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 _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 _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.can_evolve: 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 _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.')) 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 evolution. This will alter ' 'tables and data currently in the "%s" database, and may ' 'result in IRREVERSABLE DATA LOSS. Evolutions should be ' '*thoroughly* reviewed prior to execution.\n' '\n' 'MAKE A BACKUP OF YOUR DATABASE BEFORE YOU CONTINUE!\n' '\n' 'Are you sure you want to execute the evolutions?\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 _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, **kwargs): self.stdout.write( _('Applying database evolution for %s...\n') % task.app_label) if verbosity > 1: @receiver(applied_evolution, sender=evolver) def _on_applied_evolution(task, **kwargs): self.stdout.write( _('Successfully applied database evolution for %s.\n') % task.app_label) 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: self.stdout.write(_('The evolution was successful!\n')) def _display_compiled_sql(self): """Display the compiled SQL for the evolution run. This will output the SQL that would be executed based on the options passed to the command. """ database_name = self.evolver.database_name for i, task in enumerate(self.evolver.tasks): if task.sql: if i > 0: self.stdout.write('\n') self.stdout.write('-- %s\n' % task) write_sql(task.sql, database_name) 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 _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') def _wrap_paragraphs(self, text): """Wrap a block of text into paragraphs. This will take paragraphs worth of text and wrap them to fit in a standard terminal width, helping provide more readable output. Args: text (unicode): The text to wrap. Returns: unicode: The wrapped text. """ return '\n'.join( textwrap.fill(paragraph) for paragraph in text.splitlines() )
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 getattr(settings, 'DJANGO_EVOLUTION_ENABLED', True): 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.')) try: self.evolver = Evolver(database_name=database_name, hinted=hint) # 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 evolution required.\n')) elif execute: if not interactive or self._confirm_execute(): self._perform_evolution() else: self.stderr.write(_('Evolution 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 evolution 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 test_init_with_no_baseline(self): """Testing Evolver.__init__ with no baselines""" Version.objects.all().delete() with self.assertRaises(EvolutionBaselineMissingError): Evolver()