def simulate(self, simulation): """Simulate the mutation. This will alter the database schema to add the specified field. Args: simulation (Simulation): The state for the simulation. Raises: django_evolution.errors.SimulationFailure: The simulation failed. The reason is in the exception's message. """ model_sig = simulation.get_model_sig(self.model_name) if model_sig.get_field_sig(self.field_name) is not None: simulation.fail('A field with this name already exists.') if (not issubclass(self.field_type, models.ManyToManyField) and not self.field_attrs.get('null') and self.initial is None): simulation.fail('A non-null initial value must be specified in ' 'the mutation.') field_attrs = self.field_attrs.copy() related_model = field_attrs.pop('related_model', None) field_sig = FieldSignature(field_name=self.field_name, field_type=self.field_type, field_attrs=field_attrs, related_model=related_model) model_sig.add_field_sig(field_sig)
def update_third(simulation): model_sig = simulation.get_model_sig('TestModel') model_sig.add_field_sig(FieldSignature( field_name='added_field3', field_type=models.IntegerField, field_attrs={ 'null': True, }))
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 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