from django_evolution.tests.utils import test_sql_mapping tests = r""" # The AddField tests will aim to test the following usecases: # Field resulting in a new database column. # Field resulting in a new database column with a non-default name. # Field resulting in a new database column in a table with a non-default name. # Primary key field. # Indexed field # Unique field. # Null field # # Foreign Key field. # M2M field between models with default table names. # M2M field between models with non-default table names. # M2M field between self >>> from datetime import datetime >>> from django.db import models >>> from django_evolution.mutations import AddField, DeleteField >>> from django_evolution.tests.utils import test_proj_sig, execute_test_sql, register_models, deregister_models >>> from django_evolution.diff import Diff >>> from django_evolution import signature >>> from django_evolution import models as test_app >>> import copy >>> class AddSequenceFieldInitial(object): ... def __init__(self, suffix): ... self.suffix = suffix ... ... def __call__(self): ... from django.db import connection ... qn = connection.ops.quote_name ... return qn('int_field') >>> class AddAnchor1(models.Model): ... value = models.IntegerField() >>> class AddAnchor2(models.Model): ... value = models.IntegerField() ... class Meta: ... db_table = 'custom_add_anchor_table' >>> class AddBaseModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() >>> class CustomTableModel(models.Model): ... value = models.IntegerField() ... alt_value = models.CharField(max_length=20) ... class Meta: ... db_table = 'custom_table_name' # Store the base signatures >>> anchors = ( ... ('AddAnchor1', AddAnchor1), ... ('AddAnchor2', AddAnchor2) ... ) >>> custom_model = ('CustomTableModel', CustomTableModel) >>> custom = register_models(custom_model) >>> custom_table_sig = test_proj_sig(custom_model) >>> test_model = ('TestModel', AddBaseModel) >>> start = register_models(*anchors) >>> start.update(register_models(test_model)) >>> start_sig = test_proj_sig(test_model, *anchors) # Add non-null field with non-callable initial value >>> class AddNonNullColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField() >>> end = register_models(('TestModel', AddNonNullColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddNonNullColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] #AddNonNullColumnModel ["AddField('TestModel', 'added_field', models.IntegerField, initial=<>)"] # Evolution won't run as-is >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) Traceback (most recent call last): ... EvolutionException: Cannot use hinted evolution: AddField or ChangeField mutation for 'TestModel.added_field' in 'tests' requires user-specified initial value. # First try without an initial value. This will fail >>> evolution = [AddField('TestModel', 'added_field', models.IntegerField)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) Traceback (most recent call last): ... SimulationFailure: Cannot create new column 'added_field' on 'tests.TestModel' without a non-null initial value. # Now try with an explicitly null initial value. This will also fail >>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=None)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) Traceback (most recent call last): ... SimulationFailure: Cannot create new column 'added_field' on 'tests.TestModel' without a non-null initial value. # Now try with a good initial value >>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=1)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddNonNullNonCallableColumnModel %(AddNonNullNonCallableColumnModel)s # Now try with a good callable initial value >>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=AddSequenceFieldInitial('AddNonNullCallableColumnModel'))] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddNonNullCallableColumnModel %(AddNonNullCallableColumnModel)s # Add nullable column with initial data >>> class AddNullColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField(null=True) >>> end = register_models(('TestModel',AddNullColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddNullColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] #AddNullColumnModel ["AddField('TestModel', 'added_field', models.IntegerField, null=True)"] >>> evolution = [AddField('TestModel', 'added_field', models.IntegerField, initial=1, null=True)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddNullColumnWithInitialColumnModel %(AddNullColumnWithInitialColumnModel)s # Add a field that requires string-form initial data >>> class AddStringColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.CharField(max_length=10) >>> end = register_models(('TestModel',AddStringColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddStringColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] # AddStringColumnModel ["AddField('TestModel', 'added_field', models.CharField, initial=<>, max_length=10)"] >>> evolution = [AddField('TestModel', 'added_field', models.CharField, initial="abc's xyz", max_length=10)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddStringColumnModel %(AddStringColumnModel)s # Add a string field that allows empty strings as initial values >>> class AddBlankStringColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.CharField(max_length=10, blank=True) >>> end = register_models(('TestModel',AddBlankStringColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddBlankStringColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] # AddBlankStringColumnModel ["AddField('TestModel', 'added_field', models.CharField, initial='', max_length=10)"] # Add a field that requires date-form initial data >>> class AddDateColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.DateTimeField() >>> end = register_models(('TestModel',AddDateColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddDateColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] # AddDateColumnModel ["AddField('TestModel', 'added_field', models.DateTimeField, initial=<>)"] >>> new_date = datetime(2007,12,13,16,42,0) >>> evolution = [AddField('TestModel', 'added_field', models.DateTimeField, initial=new_date)] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in evolution: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddDateColumnModel %(AddDateColumnModel)s # Add column with default value >>> class AddDefaultColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField(default=42) >>> end = register_models(('TestModel',AddDefaultColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddDefaultColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] #AddDefaultColumnModel ["AddField('TestModel', 'added_field', models.IntegerField, initial=42)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddDefaultColumnModel %(AddDefaultColumnModel)s # Add column with an empty string as the default value >>> class AddEmptyStringDefaultColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.CharField(max_length=20, default='') >>> end = register_models(('TestModel',AddEmptyStringDefaultColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddEmptyStringDefaultColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] #AddEmptyStringDefaultColumnModel ["AddField('TestModel', 'added_field', models.CharField, initial=u'', max_length=20)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddEmptyStringDefaultColumnModel %(AddEmptyStringDefaultColumnModel)s # Null field >>> class AddNullColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField(null=True) >>> end = register_models(('TestModel', AddNullColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel', AddNullColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] #AddNullColumnModel ["AddField('TestModel', 'added_field', models.IntegerField, null=True)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddNullColumnModel %(AddNullColumnModel)s # Field resulting in a new database column with a non-default name. >>> class NonDefaultColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... add_field = models.IntegerField(db_column='non-default_column', null=True) >>> end = register_models(('TestModel',NonDefaultColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',NonDefaultColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'add_field', models.IntegerField, null=True, db_column='non-default_column')"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #NonDefaultColumnModel %(NonDefaultColumnModel)s # Field resulting in a new database column in a table with a non-default name. >>> class AddColumnCustomTableModel(models.Model): ... value = models.IntegerField() ... alt_value = models.CharField(max_length=20) ... added_field = models.IntegerField(null=True) ... class Meta: ... db_table = 'custom_table_name' >>> end = register_models(('CustomTableModel',AddColumnCustomTableModel)) >>> end_sig = test_proj_sig(('CustomTableModel',AddColumnCustomTableModel)) >>> d = Diff(custom_table_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('CustomTableModel', 'added_field', models.IntegerField, null=True)"] >>> test_sig = copy.deepcopy(custom_table_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(custom, end, test_sql) #AddColumnCustomTableModel %(AddColumnCustomTableModel)s # Add Primary key field. # Delete of old Primary Key is prohibited. >>> class AddPrimaryKeyModel(models.Model): ... my_primary_key = models.AutoField(primary_key=True) ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() >>> end = register_models(('TestModel', AddPrimaryKeyModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddPrimaryKeyModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'my_primary_key', models.AutoField, initial=<>, primary_key=True)", "DeleteField('TestModel', 'id')"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in [AddField('TestModel', 'my_primary_key', models.AutoField, initial=AddSequenceFieldInitial('AddPrimaryKeyModel'), primary_key=True), DeleteField('TestModel', 'id')]: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) Traceback (most recent call last): ... SimulationFailure: Cannot delete a primary key. # Indexed field >>> class AddIndexedColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... add_field = models.IntegerField(db_index=True, null=True) >>> end = register_models(('TestModel',AddIndexedColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddIndexedColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'add_field', models.IntegerField, null=True, db_index=True)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql, debug=False) #AddIndexedColumnModel %(AddIndexedColumnModel)s # Unique field. >>> class AddUniqueColumnModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField(unique=True, null=True) >>> end = register_models(('TestModel',AddUniqueColumnModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddUniqueColumnModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.IntegerField, unique=True, null=True)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddUniqueColumnModel %(AddUniqueColumnModel)s # Unique indexed field. >>> class AddUniqueIndexedModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.IntegerField(unique=True, db_index=True, null=True) >>> end = register_models(('TestModel',AddUniqueIndexedModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddUniqueIndexedModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.IntegerField, unique=True, null=True, db_index=True)"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddUniqueIndexedModel %(AddUniqueIndexedModel)s Foreign Key field. >>> class AddForeignKeyModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.ForeignKey(AddAnchor1, null=True) >>> end = register_models(('TestModel',AddForeignKeyModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddForeignKeyModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.ForeignKey, null=True, related_model='tests.AddAnchor1')"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddForeignKeyModel %(AddForeignKeyModel)s # M2M field between models with default table names. >>> class AddM2MDatabaseTableModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.ManyToManyField(AddAnchor1) >>> end = register_models(('TestModel',AddM2MDatabaseTableModel), *anchors) >>> end_sig = test_proj_sig(('TestModel',AddM2MDatabaseTableModel), *anchors) >>> end_sig['tests'][AddAnchor1.__name__] = signature.create_model_sig(AddAnchor1) >>> anchor_sig = copy.deepcopy(start_sig) >>> anchor_sig['tests'][AddAnchor1.__name__] = signature.create_model_sig(AddAnchor1) >>> d = Diff(anchor_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.AddAnchor1')"] >>> test_sig = copy.deepcopy(anchor_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddManyToManyDatabaseTableModel %(AddManyToManyDatabaseTableModel)s # M2M field between models with non-default table names. >>> class AddM2MNonDefaultDatabaseTableModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.ManyToManyField(AddAnchor2) >>> end = register_models(('TestModel', AddM2MNonDefaultDatabaseTableModel), *anchors) >>> end_sig = test_proj_sig(('TestModel', AddM2MNonDefaultDatabaseTableModel), *anchors) >>> end_sig['tests'][AddAnchor2.__name__] = signature.create_model_sig(AddAnchor2) >>> anchor_sig = copy.deepcopy(start_sig) >>> anchor_sig['tests'][AddAnchor2.__name__] = signature.create_model_sig(AddAnchor2) >>> d = Diff(anchor_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.AddAnchor2')"] >>> test_sig = copy.deepcopy(anchor_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddManyToManyNonDefaultDatabaseTableModel %(AddManyToManyNonDefaultDatabaseTableModel)s # M2M field between self # Need to find a better way to do this. >>> class AddM2MSelfDatabaseTableModel(models.Model): ... char_field = models.CharField(max_length=20) ... int_field = models.IntegerField() ... added_field = models.ManyToManyField('AddM2MSelfDatabaseTableModel') >>> end = register_models(('TestModel', AddM2MSelfDatabaseTableModel), *anchors) >>> end_sig = test_proj_sig(('TestModel', AddM2MSelfDatabaseTableModel), *anchors) >>> d = Diff(start_sig, end_sig) >>> print [str(e) for e in d.evolution()['tests']] ["AddField('TestModel', 'added_field', models.ManyToManyField, related_model='tests.TestModel')"] >>> test_sig = copy.deepcopy(start_sig) >>> test_sql = [] >>> for mutation in d.evolution()['tests']: ... test_sql.extend(mutation.mutate('tests', test_sig)) ... mutation.simulate('tests', test_sig) >>> Diff(test_sig, end_sig).is_empty() True >>> execute_test_sql(start, end, test_sql) #AddManyToManySelf %(AddManyToManySelf)s # Clean up after the applications that were installed >>> deregister_models() """ % test_sql_mapping('add_field')