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_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(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 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 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_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_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 create_field(project_sig, field_name, field_type, field_attrs, parent_model, related_model=None): """Create a Django field instance for the given signature data. This creates a field in a way that's compatible with a variety of versions of Django. It takes in data such as the field's name and attributes and creates an instance that can be used like any field found on a model. Args: field_name (unicode): The name of the field. field_type (cls): The class for the type of field being constructed. This must be a subclass of :py:class:`django.db.models.Field`. field_attrs (dict): Attributes to set on the field. parent_model (cls): The parent model that would own this field. This must be a subclass of :py:class:`django.db.models.Model`. related_model (unicode, optional): The full class path to a model this relates to. This requires a :py:class:`django.db.models.ForeignKey` field type. Returns: django.db.models.Field: A new field instance matching the provided data. """ # Convert to the standard string format for each version of Python, to # simulate what the format would be for the default name. field_name = str(field_name) assert 'related_model' not in field_attrs, \ ('related_model cannot be passed in field_attrs when calling ' 'create_field(). Pass the related_model parameter instead.') if related_model: related_app_name, related_model_name = related_model.split('.') related_model_sig = (project_sig.get_app_sig( related_app_name, required=True).get_model_sig(related_model_name, required=True)) to = MockModel(project_sig=project_sig, app_name=related_app_name, model_name=related_model_name, model_sig=related_model_sig, stub=True) if (issubclass(field_type, models.ForeignKey) and hasattr(models, 'CASCADE') and 'on_delete' not in field_attrs): # Starting in Django 2.0, on_delete is a requirement for # ForeignKeys. If not provided in the signature, we want to # default this to CASCADE, which is the value that Django # previously defaulted to. field_attrs = dict({ 'on_delete': models.CASCADE, }, **field_attrs) field = field_type(to, name=field_name, **field_attrs) else: field = field_type(name=field_name, **field_attrs) if (issubclass(field_type, models.ManyToManyField) and parent_model is not None): # Starting in Django 1.2, a ManyToManyField must have a through # model defined. This will be set internally to an auto-created # model if one isn't specified. We have to fake that model. through_model = field_attrs.get('through_model') through_model_sig = None if through_model: through_app_name, through_model_name = through_model.split('.') through_model_sig = (project_sig.get_app_sig( through_app_name).get_model_sig(through_model_name)) elif hasattr(field, '_get_m2m_attr'): # Django >= 1.2 remote_field = get_remote_field(field) remote_field_model = get_remote_field_model(remote_field) to_field_name = remote_field_model._meta.object_name.lower() if (remote_field_model == RECURSIVE_RELATIONSHIP_CONSTANT or to_field_name == parent_model._meta.object_name.lower()): from_field_name = 'from_%s' % to_field_name to_field_name = 'to_%s' % to_field_name else: from_field_name = parent_model._meta.object_name.lower() # This corresponds to the signature in # related.create_many_to_many_intermediary_model through_app_name = parent_model.app_name through_model_name = '%s_%s' % (parent_model._meta.object_name, field.name), through_model_sig = ModelSignature( model_name=through_model_name, table_name=field._get_m2m_db_table(parent_model._meta), pk_column='id', unique_together=[(from_field_name, to_field_name)]) # 'id' field through_model_sig.add_field_sig( FieldSignature(field_name='id', field_type=models.AutoField, field_attrs={ 'primary_key': True, })) # 'from' field through_model_sig.add_field_sig( FieldSignature( field_name=from_field_name, field_type=models.ForeignKey, field_attrs={ 'related_name': '%s+' % through_model_name, }, related_model='%s.%s' % (parent_model.app_name, parent_model._meta.object_name))) # 'to' field through_model_sig.add_field_sig( FieldSignature(field_name=to_field_name, field_type=models.ForeignKey, field_attrs={ 'related_name': '%s+' % through_model_name, }, related_model=related_model)) field.auto_created = True if through_model_sig: through = MockModel(project_sig=project_sig, app_name=through_app_name, model_name=through_model_name, model_sig=through_model_sig, auto_created=not through_model, managed=not through_model) get_remote_field(field).through = through field.m2m_db_table = curry(field._get_m2m_db_table, parent_model._meta) field.set_attributes_from_rel() field.set_attributes_from_name(field_name) # Needed in Django >= 1.7, for index building. field.model = parent_model return field