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'))
Example #4
0
    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 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()
Example #9
0
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
Example #10
0
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)
Example #12
0
    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)
Example #13
0
    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)
Example #14
0
    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