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_signature_save(self): """Testing Version.signature field serializes JSON-encoded v2 signatures """ project_sig = ProjectSignature() project_sig.add_app_sig(AppSignature('app1')) project_sig.add_app_sig(AppSignature('app2')) version = Version.objects.create(signature=project_sig) raw_signature = (Version.objects.filter( pk=version.pk).values_list('signature'))[0][0] self.assertTrue(raw_signature.startswith('json!')) sig_data = json.loads(raw_signature[len('json!'):]) self.assertEqual( sig_data, { '__version__': 2, 'apps': { 'app1': { 'legacy_app_label': 'app1', 'models': {}, }, 'app2': { 'legacy_app_label': 'app2', 'models': {}, }, }, })
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_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_enable_extension_evolve_with_pending_evolutions(self): """Testing ExtensionManager.enable_extension evolves database models when pending evolutions found """ from django_evolution.models import Version from django_evolution.signature import AppSignature, ModelSignature self.spy_on(Evolver.evolve, owner=Evolver) class TestExtensionWithApps(Extension): apps = [ 'djblets.extensions.tests.apps.evolve_tests', ] # We need to set some initial state in the database for the model and # for the evolution history. connection.cursor().execute( 'CREATE TABLE evolve_tests_testevolveextensionmodel (' ' id INTEGER PRIMARY KEY AUTOINCREMENT,' ' test_field VARCHAR(16) NOT NULL' ')') from djblets.extensions.tests.apps.evolve_tests.models import \ TestEvolveExtensionModel latest_version = Version.objects.current_version() model_sig = ModelSignature.from_model(TestEvolveExtensionModel) model_sig.remove_field_sig('new_field') app_sig = AppSignature(app_id='evolve_tests') app_sig.add_model_sig(model_sig) latest_version.signature.add_app_sig(app_sig) latest_version.save() # We can now enable the extension, which will perform an evolution. extension = self.setup_extension(TestExtensionWithApps) self.assertTrue(Evolver.evolve.called) # We should be able to create entries and query them. TestEvolveExtensionModel.objects.create(test_field='test') self.assertEqual(TestEvolveExtensionModel.objects.count(), 1) # We're now going to shut down and re-enable, but with a different # version of the model. This should trigger an evolution sequence. self.manager.disable_extension(extension.id) self.manager.enable_extension(extension.id) TestEvolveExtensionModel.objects.create(test_field='test', new_field=100) self.assertEqual(TestEvolveExtensionModel.objects.count(), 2) obj = TestEvolveExtensionModel.objects.get(pk=2) self.assertEqual(obj.new_field, 100)
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 setUp(self): super(PurgeAppTaskTests, self).setUp() app_sig = AppSignature(app_id='tests') app_sig.add_model_sig( ModelSignature(model_name='TestModel', table_name='tests_testmodel')) version = Version.objects.current_version() version.signature.add_app_sig(app_sig) version.save()
def setUp(self): super(PurgeAppTaskTests, self).setUp() app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(ModelSignature( model_name='TestModel', table_name='tests_testmodel')) version = Version.objects.current_version() version.signature.add_app_sig(app_sig) version.save()
def setUp(self): super(EvolveAppTaskTests, self).setUp() 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) version = Version.objects.current_version() version.signature.add_app_sig(app_sig) version.save()
def test_queue_purge_old_apps(self): """Testing Evolver.queue_purge_old_apps""" version = Version.objects.current_version() version.signature.add_app_sig(AppSignature(app_id='old_app1')) version.signature.add_app_sig(AppSignature(app_id='old_app2')) version.save() evolver = Evolver() evolver.queue_purge_old_apps() tasks = list(evolver.tasks) self.assertEqual(len(tasks), 2) self.assertIsInstance(tasks[0], PurgeAppTask) self.assertIsInstance(tasks[1], PurgeAppTask) self.assertEqual(tasks[0].app_label, 'old_app1') self.assertEqual(tasks[1].app_label, 'old_app2')
def test_enable_extension_evolve_with_applied_evolutions(self): """Testing ExtensionManager.enable_extension evolves database models when all evolutions are already applied """ from django_evolution.models import Evolution, Version from django_evolution.signature import AppSignature, ModelSignature self.spy_on(Evolver.evolve, owner=Evolver) class TestExtensionWithApps(Extension): apps = [ 'djblets.extensions.tests.apps.evolve_tests', ] # We need to set some initial state in the database for the model and # for the evolution history. connection.cursor().execute( 'CREATE TABLE evolve_tests_testevolveextensionmodel (' ' id INTEGER PRIMARY KEY AUTOINCREMENT,' ' test_field VARCHAR(16) NOT NULL' ')') from djblets.extensions.tests.apps.model_tests.models import \ TestExtensionModel latest_version = Version.objects.current_version() signature = latest_version.signature.clone() model_sig = ModelSignature.from_model(TestExtensionModel) model_sig.model_name = 'TestEvolveExtensionModel' model_sig.table_name = 'evolve_tests_testevolveextensionmodel' app_sig = AppSignature(app_id='evolve_tests') app_sig.add_model_sig(model_sig) signature.add_app_sig(app_sig) version = Version.objects.create(signature=signature) Evolution.objects.create(version=version, app_label='evolve_tests', label='add_new_field') # We can now enable the extension, which will perform an evolution. self.setup_extension(TestExtensionWithApps) self.assertFalse(Evolver.evolve.called)
def test_from_app_sig(self): """Testing MigrationList.from_app_sig""" app_sig = AppSignature( app_id='tests', applied_migrations=['0001_initial', '0002_stuff']) migration_list = MigrationList.from_app_sig(app_sig) self.assertTrue(migration_list.has_migration_info(app_label='tests', name='0001_initial')) self.assertTrue(migration_list.has_migration_info(app_label='tests', name='0002_stuff'))
def test_with_bad_field(self): """Testing ChangeField with field not in signature""" mutation = ChangeField('TestModel', 'char_field1') model_sig = ModelSignature(model_name='TestModel', table_name='tests_testmodel') app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) project_sig = ProjectSignature() project_sig.add_app_sig(app_sig) message = ('Cannot change the field "char_field1" on model ' '"tests.TestModel". The field could not be found in the ' 'signature.') with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
def test_with_bad_field(self): """Testing RenameField with field not in signature""" mutation = RenameField('TestModel', 'char_field1', 'char_field2') model_sig = ModelSignature(model_name='TestModel', table_name='tests_testmodel') app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) project_sig = ProjectSignature() project_sig.add_app_sig(app_sig) message = ( 'Cannot rename the field "char_field1" on model ' '"tests.TestModel". The field could not be found in the ' 'signature.' ) with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
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_with_bad_model(self): """Testing DeleteModel with model not in signature""" mutation = DeleteModel('TestModel') project_sig = ProjectSignature() project_sig.add_app_sig(AppSignature(app_id='tests')) message = ( 'Cannot delete the model "tests.TestModel". The model could ' 'not be found in the signature.') with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
def test_with_bad_model(self): """Testing AddField with model not in signature""" mutation = AddField('TestModel', 'char_field1', models.CharField) project_sig = ProjectSignature() project_sig.add_app_sig(AppSignature(app_id='tests')) message = ( 'Cannot add the field "char_field1" to model "tests.TestModel". ' 'The model could not be found in the signature.') with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
def test_with_bad_field(self): """Testing AddField with field already in signature""" mutation = AddField('TestModel', 'char_field1', models.CharField) model_sig = ModelSignature(model_name='TestModel', table_name='tests_testmodel') model_sig.add_field_sig( FieldSignature(field_name='char_field1', field_type=models.CharField)) app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) project_sig = ProjectSignature() project_sig.add_app_sig(app_sig) message = ( 'Cannot add the field "char_field1" to model "tests.TestModel". ' 'A field with this name already exists.') with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
def test_with_bad_field(self): """Testing AddField with field already in signature""" mutation = AddField('TestModel', 'char_field1', models.CharField) model_sig = ModelSignature(model_name='TestModel', table_name='tests_testmodel') model_sig.add_field_sig(FieldSignature(field_name='char_field1', field_type=models.CharField)) app_sig = AppSignature(app_id='tests') app_sig.add_model_sig(model_sig) project_sig = ProjectSignature() project_sig.add_app_sig(app_sig) message = ( 'Cannot add the field "char_field1" to model "tests.TestModel". ' 'A field with this name already exists.' ) with self.assertRaisesMessage(SimulationFailure, message): mutation.run_simulation(app_label='tests', project_sig=project_sig, database_state=None)
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 create_test_project_sig(models, app_label='tests', version=1): """Return a dummy project signature for the given models. Args: models (list of django.db.models.Model): The list of models for the project signature. app_label (unicode, optional): The application label that will contain the models. version (int, optional): The signature version to use for the project signature. Returns: dict: The new project signature. """ app_sig = AppSignature(app_id=app_label) project_sig = ProjectSignature() project_sig.add_app_sig(app_sig) for full_name, model in models: parts = full_name.split('.') if len(parts) == 1: app_sig.add_model_sig(ModelSignature.from_model(model)) else: model_app_label, model_name = parts model_app_sig = project_sig.get_app_sig(model_app_label) if model_app_sig is None: model_app_sig = AppSignature(app_id=model_app_label) project_sig.add_app_sig(model_app_sig) model_app_sig.add_model_sig(ModelSignature.from_model(model)) return project_sig
def simulate(self, simulation): """Simulate the mutation. This will alter the signature to make any changes needed for the application's evolution storage. """ old_app_label = self.old_app_label new_app_label = self.new_app_label model_names = self.model_names project_sig = simulation.project_sig old_app_sig = project_sig.get_app_sig(old_app_label, required=True) # Begin building the new AppSignature. For at least a short time, both # the old and new will exist, as we begin moving some or all of the old # to the new. The old will only be removed if it's empty after the # rename (so that we don't get rid of anything if there's two apps # sharing the same old app ID in the signature). new_app_sig = AppSignature(app_id=new_app_label, legacy_app_label=self.legacy_app_label, upgrade_method=UpgradeMethod.EVOLUTIONS) project_sig.add_app_sig(new_app_sig) if model_names is None: # Move over every single model listed under the app's signature. model_sigs = [ model_sig for model_sig in old_app_sig.model_sigs ] else: # Move over only the requested models, in case the app signature # has the contents of two separate apps merged. Each will be # validated by way of simulation.get_model_sig. model_sigs = [ simulation.get_model_sig(model_name) for model_name in model_names ] # Copy over the models. for model_sig in model_sigs: old_app_sig.remove_model_sig(model_sig.model_name) new_app_sig.add_model_sig(model_sig) if old_app_sig.is_empty(): # The old app is now empty. We can remove the signature. project_sig.remove_app_sig(old_app_sig.app_id) # Update the simulation to refer to the new label. simulation.app_label = new_app_label # Go through the model signatures and update any that have a # related_model property referencing the old app label. for cur_app_sig in project_sig.app_sigs: for cur_model_sig in cur_app_sig.model_sigs: for cur_field_sig in cur_model_sig.field_sigs: if cur_field_sig.related_model: parts = cur_field_sig.related_model.split('.', 1)[1] if parts[0] == old_app_label: cur_field_sig.related_model = \ '%s.%s' % (new_app_label, parts[1])
def __init__(self, hinted=False, verbosity=0, interactive=False, database_name=DEFAULT_DB_ALIAS): """Initialize the evolver. Args: hinted (bool, optional): Whether to operate against hinted evolutions. This may result in changes to the database without there being any accompanying evolution files backing those changes. verbosity (int, optional): The verbosity level for any output. This is passed along to signal emissions. interactive (bool, optional): Whether the evolution operations are being performed in a way that allows interactivity on the command line. This is passed along to signal emissions. database_name (unicode, optional): The name of the database to evolve. Raises: django_evolution.errors.EvolutionBaselineMissingError: An initial baseline for the project was not yet installed. This is due to ``syncdb``/``migrate`` not having been run. """ self.database_name = database_name self.hinted = hinted self.verbosity = verbosity self.interactive = interactive self.evolved = False self.initial_diff = None self.project_sig = None self.version = None self.installed_new_database = False self.connection = connections[database_name] if hasattr(self.connection, 'prepare_database'): # Django >= 1.8 self.connection.prepare_database() self.database_state = DatabaseState(self.database_name) self.target_project_sig = \ ProjectSignature.from_database(database_name) self._tasks_by_class = OrderedDict() self._tasks_by_id = OrderedDict() self._tasks_prepared = False latest_version = None if self.database_state.has_model(Version): try: latest_version = \ Version.objects.current_version(using=database_name) except Version.DoesNotExist: # We'll populate this next. pass if latest_version is None: # Either the models aren't yet synced to the database, or we # don't have a saved project signature, so let's set these up. self.installed_new_database = True self.project_sig = ProjectSignature() app = get_app('django_evolution') task = EvolveAppTask(evolver=self, app=app) task.prepare(hinted=False) with self.sql_executor() as sql_executor: task.execute(sql_executor=sql_executor, create_models_now=True) self.database_state.rescan_tables() app_sig = AppSignature.from_app(app=app, database=database_name) self.project_sig.add_app_sig(app_sig) # Let's make completely sure that we've only found the models # we expect. This is mostly for the benefit of unit tests. model_names = set(model_sig.model_name for model_sig in app_sig.model_sigs) expected_model_names = set(['Evolution', 'Version']) assert model_names == expected_model_names, ( 'Unexpected models found for django_evolution app: %s' % ', '.join(model_names - expected_model_names)) self._save_project_sig(new_evolutions=task.new_evolutions) latest_version = self.version self.project_sig = latest_version.signature self.initial_diff = Diff(self.project_sig, self.target_project_sig)