def get_hint_params(self): """Return parameters for the mutation's hinted evolution. Returns: list of unicode: A list of parameter strings to pass to the mutation's constructor in a hinted evolution. """ if self.prop_name in ('index_together', 'unique_together'): # Make sure these always appear as lists and not tuples, for # compatibility. norm_value = list(self.new_value) elif self.prop_name == 'constraints': # Django >= 2.2 norm_value = [ OrderedDict(sorted(six.iteritems(constraint_data), key=lambda pair: pair[0])) for constraint_data in self.new_value ] elif self.prop_name == 'indexes': # Django >= 1.11 norm_value = [ OrderedDict(sorted(six.iteritems(index_data), key=lambda pair: pair[0])) for index_data in self.new_value ] else: norm_value = self.new_value return [ self.serialize_value(self.model_name), self.serialize_value(self.prop_name), self.serialize_value(norm_value), ]
def __init__(self, project_sig, app_name, model_name, model_sig, managed=False, auto_created=False): """Initialize the meta instance. Args: project_sig (django_evolution.signature.ProjectSignature): The project's schema signature. app_name (unicode): The name of the Django application owning the model. model_name (unicode): The name of the model. model_sig (dict): The model's schema signature. managed (bool, optional): Whether this represents a model managed internally by Django, rather than a developer-created model. auto_created (bool, optional): Whether this represents an auto-created model (such as an intermediary many-to-many model). """ self.object_name = model_name self.app_label = app_name self.meta = { 'auto_created': auto_created, 'db_table': model_sig.table_name, 'db_tablespace': model_sig.db_tablespace, 'has_auto_field': None, 'index_together': model_sig.index_together, 'indexes': [], 'managed': managed, 'order_with_respect_to': None, 'pk_column': model_sig.pk_column, 'swapped': False, 'unique_together': model_sig.unique_together, } if hasattr(models, 'Index'): self.meta['indexes'] = [ models.Index(name=index_sig.name, fields=index_sig.fields) for index_sig in model_sig.index_sigs ] self._fields = OrderedDict() self._many_to_many = OrderedDict() self.abstract = False self.managed = True self.proxy = False self._model_sig = model_sig self._project_sig = project_sig
def register_app_models(app_label, model_infos, reset=False): """Register one or more models to a given app. These will add onto the list of existing models. Args: app_label (str): The label of the app to register the models on. model_info (list); A list of pairs of ``(model name, model class)`` to register. reset (bool, optional): If set, the old list will be overwritten with the new list. """ if app_label not in all_models: # This isn't really needed for Django 1.7+ (which uses defaultdict # with OrderedDict), but it's needed for earlier versions, so do it # explicitly. all_models[app_label] = OrderedDict() model_dict = all_models[app_label] if reset: model_dict.clear() for model_name, model in model_infos: model_dict[model_name] = model clear_app_cache()
def __init__(self, proj_sig, app_name, model_name, model_sig): self.object_name = model_name self.app_label = app_name self.meta = { 'order_with_respect_to': None, 'has_auto_field': None, 'db_tablespace': None, 'swapped': False, 'index_together': [], } self.meta.update(model_sig['meta']) self._fields = OrderedDict() self._many_to_many = OrderedDict() self.abstract = False self.managed = True self.proxy = False self._model_sig = model_sig self._proj_sig = proj_sig
def _test_proj_sig(app_label, *models, **kwargs): "Generate a dummy project signature based around a single model" version = kwargs.get('version', 1) proj_sig = { app_label: OrderedDict(), '__version__': version, } # Compute the project siguature for full_name, model in models: parts = full_name.split('.') if len(parts) == 1: name = parts[0] app = app_label else: app, name = parts proj_sig.setdefault(app, OrderedDict())[name] = \ signature.create_model_sig(model) return proj_sig
def __init__(self, original_project_sig, target_project_sig): """Initialize the object. Args: original_project_sig (django_evolution.signature.ProjectSignature): The original project signature for the diff. target_project_sig (django_evolution.signature.ProjectSignature): The target project signature for the diff. """ assert isinstance(original_project_sig, ProjectSignature), \ 'original_project_sig must be a ProjectSignature instance' assert isinstance(target_project_sig, ProjectSignature), \ 'target_project_sig must be a ProjectSignature instance' self.original_project_sig = original_project_sig self.target_project_sig = target_project_sig diff = target_project_sig.diff(original_project_sig) self.changed = diff.get('changed', OrderedDict()) self.deleted = diff.get('deleted', OrderedDict())
def __new__(cls, *args, **kwargs): """Construct an instance of the class. Args: *args (tuple): Positional arguments to pass to the constructor. **kwargs (dict): Keyword arguments to pass to the constructor. Returns: collections.OrderedDict: The new instance. """ return OrderedDict.__new__(cls, *args, **kwargs)
def create_app_sig(app, database): """ Creates a dictionary representation of the models in a given app. Only those attributes that are interesting from a schema-evolution perspective are included. """ app_sig = OrderedDict() for model in get_models(app): # Only include those models that can be synced. # # On Django 1.7 and up, we need to check if the model allows for # migrations (using allow_migrate_model). # # On older versions of Django, we check if the model allows for # synchronization to the database (allow_syncdb). if ((hasattr(router, 'allow_syncdb') and router.allow_syncdb(database, model.__class__)) or (hasattr(router, 'allow_migrate_model') and router.allow_migrate_model(database, model))): app_sig[model._meta.object_name] = create_model_sig(model) return app_sig
def register_models(database_state, models, register_indexes=False, new_app_label='tests', db_name='default', app=evo_test): """Register models for testing purposes. Args: database_state (django_evolution.db.state.DatabaseState): The database state to populate with model information. models (list of django.db.models.Model): The models to register. register_indexes (bool, optional): Whether indexes should be registered for any models. Defaults to ``False``. new_app_label (str, optional): The label for the test app. Defaults to "tests". db_name (str, optional): The name of the database connection. Defaults to "default". app (module, optional): The application module for the test models. Returns: collections.OrderedDict: A dictionary of registered models. The keys are model names, and the values are the models. """ app_cache = OrderedDict() evolver = EvolutionOperationsMulti(db_name, database_state).get_evolver() db_connection = connections[db_name or DEFAULT_DB_ALIAS] max_name_length = db_connection.ops.max_name_length() for new_object_name, model in reversed(models): # Grab some state from the model's meta instance. Some of this will # be original state that we'll keep around to help us unregister old # values and compute new ones. meta = model._meta orig_app_label = meta.app_label orig_db_table = meta.db_table orig_object_name = meta.object_name orig_model_name = get_model_name(model) # Find out if the table name being used is a custom table name, or # one generated by Django. new_model_name = new_object_name.lower() new_db_table = orig_db_table generated_db_table = truncate_name( '%s_%s' % (orig_app_label, orig_model_name), max_name_length) if orig_db_table == generated_db_table: # It was a generated one, so replace it with a version containing # the new model and app names. new_db_table = truncate_name('%s_%s' % (new_app_label, new_model_name), max_name_length) meta.db_table = new_db_table # Set the new app/model names back on the meta instance. meta.app_label = new_app_label meta.object_name = new_object_name set_model_name(model, new_model_name) # Add an entry for the table in the database state, if it's not # already there. if not database_state.has_table(new_db_table): database_state.add_table(new_db_table) if register_indexes: # Now that we definitely have an entry, store the indexes for # all the fields in the database state, so that other operations # can look up the index names. for field in meta.local_fields: if field.db_index or field.unique: new_index_name = create_index_name( db_connection, new_db_table, field_names=[field.name], col_names=[field.column], unique=field.unique) database_state.add_index( index_name=new_index_name, table_name=new_db_table, columns=[field.column], unique=field.unique) for field_names in meta.unique_together: fields = evolver.get_fields_for_names(model, field_names) new_index_name = create_index_name( db_connection, new_db_table, field_names=field_names, unique=True) database_state.add_index( index_name=new_index_name, table_name=new_db_table, columns=[field.column for field in fields], unique=True) for field_names in getattr(meta, 'index_together', []): # Django >= 1.5 fields = evolver.get_fields_for_names(model, field_names) new_index_name = create_index_together_name( db_connection, new_db_table, field_names=[field.name for field in fields]) database_state.add_index( index_name=new_index_name, table_name=new_db_table, columns=[field.column for field in fields]) if getattr(meta, 'indexes', None): # Django >= 1.11 for index, orig_index in zip(meta.indexes, meta.original_attrs['indexes']): if not orig_index.name: # The name was auto-generated. We'll need to generate # it again for the new table name. index.set_name_with_model(model) fields = evolver.get_fields_for_names( model, index.fields, allow_sort_prefixes=True) database_state.add_index( index_name=index.name, table_name=new_db_table, columns=[field.column for field in fields]) # ManyToManyFields have their own tables, which will also need to be # renamed. Go through each of them and figure out what changes need # to be made. for field in meta.local_many_to_many: through = get_remote_field(field).through if not through: continue through_meta = through._meta through_orig_model_name = get_model_name(through) through_new_model_name = through_orig_model_name # Find out if the through table name is a custom table name, or # one generated by Django. generated_db_table = truncate_name( '%s_%s' % (orig_db_table, field.name), max_name_length) if through_meta.db_table == generated_db_table: # This is an auto-generated table name. Start changing the # state for it. assert through_meta.app_label == orig_app_label through_meta.app_label = new_app_label # Transform the 'through' table information only if we've # transformed the parent db_table. if new_db_table != orig_db_table: through_meta.db_table = truncate_name( '%s_%s' % (new_db_table, field.name), max_name_length) through_meta.object_name = \ through_meta.object_name.replace(orig_object_name, new_object_name) through_new_model_name = \ through_orig_model_name.replace(orig_model_name, new_model_name) set_model_name(through, through_new_model_name) # Change each of the columns for the fields on the # ManyToManyField's model to reflect the new model names. for through_field in through._meta.local_fields: through_remote_field = get_remote_field(through_field) if (through_remote_field and get_remote_field_model(through_remote_field)): column = through_field.column if (column.startswith((orig_model_name, 'to_%s' % orig_model_name, 'from_%s' % orig_model_name))): # This is a field that references one end of the # relation or another. Update the model naem in the # field's column. through_field.column = column.replace(orig_model_name, new_model_name) # Replace the entry in the models cache for the through table, # removing the old name and adding the new one. if through_orig_model_name in all_models[orig_app_label]: unregister_app_model(orig_app_label, through_orig_model_name) app_cache[through_new_model_name] = through register_app_models(new_app_label, [(through_new_model_name, through)]) # Unregister with the old model name and register the new one. if orig_model_name in all_models[orig_app_label]: unregister_app_model(orig_app_label, orig_model_name) register_app_models(new_app_label, [(new_model_name, model)]) app_cache[new_model_name] = model # If the app hasn't yet been registered, do that now. if not is_app_registered(app): register_app(new_app_label, app) return app_cache
def evolution(self): """Return the mutations needed for resolving the diff. This will attempt to return a hinted evolution, consisting of a series of mutations for each affected application. These mutations will convert the database from the original to the target signatures. Returns: collections.OrderedDict: An ordered dictionary of mutations. Each key is an application label, and each value is a list of mutations for the application. """ if self._mutations is not None: return self._mutations mutations = OrderedDict() for app_label, app_changes in six.iteritems(self.changed): app_sig = self.target_project_sig.get_app_sig(app_label) model_changes = app_changes.get('changed', {}) app_mutations = [] for model_name, model_change in six.iteritems(model_changes): model_sig = app_sig.get_model_sig(model_name) # Process the list of added fields for the model. for field_name in model_change.get('added', {}): field_sig = model_sig.get_field_sig(field_name) field_type = field_sig.field_type add_params = field_sig.field_attrs.copy() add_params['field_type'] = field_type if (not issubclass(field_type, models.ManyToManyField) and not field_sig.get_attr_value('null')): # This field requires an initial value. Inject either # a suitable initial value or a placeholder that must # be filled in by the developer. add_params['initial'] = \ self._get_initial_value(app_label=app_label, model_name=model_name, field_name=field_name) if field_sig.related_model: add_params['related_model'] = field_sig.related_model app_mutations.append(AddField( model_name=model_name, field_name=field_name, **add_params)) # Process the list of deleted fields for the model. app_mutations += [ DeleteField(model_name=model_name, field_name=field_name) for field_name in model_change.get('deleted', []) ] # Process the list of changed fields for the model. field_changes = model_change.get('changed', {}) for field_name, field_change in six.iteritems(field_changes): field_sig = model_sig.get_field_sig(field_name) changed_attrs = OrderedDict() field_type_changed = 'field_type' in field_change if field_type_changed: # If the field type changes, we're doing a hard # reset on the attributes. We won't be showing the # difference between any other attributes on here. changed_attrs['field_type'] = field_sig.field_type changed_attrs.update(field_sig.field_attrs) else: changed_attrs.update( (attr, field_sig.get_attr_value(attr)) for attr in field_change ) if ('null' in field_change and not field_sig.get_attr_value('null') and not issubclass(field_sig.field_type, models.ManyToManyField)): # The field no longer allows null values, meaning an # initial value is required. Inject either a suitable # initial value or a placeholder that must be filled # in by the developer. changed_attrs['initial'] = \ self._get_initial_value(app_label=app_label, model_name=model_name, field_name=field_name) if 'related_model' in field_change: changed_attrs['related_model'] = \ field_sig.related_model app_mutations.append(ChangeField( model_name=model_name, field_name=field_name, **changed_attrs)) # Process the Meta attribute changes for the model. meta_changed = model_change.get('meta_changed', []) # Check if the Meta.constraints property has any changes. # They'll all be assembled into a single ChangeMeta. if 'constraints' in meta_changed: app_mutations.append(ChangeMeta( model_name=model_name, prop_name='constraints', new_value=[ dict({ 'type': constraint_sig.type, 'name': constraint_sig.name, }, **constraint_sig.attrs) for constraint_sig in model_sig.constraint_sigs ])) # Check if the Meta.indexes property has any changes. # They'll all be assembled into a single ChangeMeta. if 'indexes' in meta_changed: change_meta_indexes = [] for index_sig in model_sig.index_sigs: change_meta_index = index_sig.attrs.copy() if index_sig.expressions: change_meta_index['expressions'] = \ index_sig.expressions if index_sig.fields: change_meta_index['fields'] = index_sig.fields if index_sig.name: change_meta_index['name'] = index_sig.name change_meta_indexes.append(change_meta_index) app_mutations.append(ChangeMeta( model_name=model_name, prop_name='indexes', new_value=change_meta_indexes)) # Check Meta.index_together and Meta.unique_together. app_mutations += [ ChangeMeta(model_name=model_name, prop_name=prop_name, new_value=getattr(model_sig, prop_name) or []) for prop_name in ('index_together', 'unique_together') if prop_name in meta_changed ] # Process the list of deleted models for the application. app_mutations += [ DeleteModel(model_name=model_name) for model_name in app_changes.get('deleted', {}) ] # See if any important details about the app have changed. meta_changed = app_changes.get('meta_changed', {}) app_label_changed = meta_changed.get('app_id', {}) legacy_app_label_changed = meta_changed.get('legacy_app_label', {}) if app_label_changed or legacy_app_label_changed: app_mutations.append(RenameAppLabel( app_label_changed.get('old', app_sig.app_id), app_label_changed.get('new', app_sig.app_id), legacy_app_label=legacy_app_label_changed.get( 'new', app_sig.legacy_app_label))) if app_mutations: mutations[app_label] = app_mutations self._mutations = mutations return mutations
def _register_models(database_sig, app_label='tests', db_name='default', app=evo_test, *models, **kwargs): """Register models for testing purposes. Args: database_sig (dict): The database signature to populate with model information. app_label (str, optional): The label for the test app. Defaults to "tests". db_name (str, optional): The name of the database connection. Defaults to "default". app (module, optional): The application module for the test models. *models (tuple): The models to register. **kwargs (dict): Additional keyword arguments. This supports: ``register_indexes``: Specifies whether indexes should be registered for any models. Defaults to ``False``. Returns: collections.OrderedDict: A dictionary of registered models. The keys are model names, and the values are the models. """ django_evolution_models = all_models['django_evolution'] app_cache = OrderedDict() evolver = EvolutionOperationsMulti(db_name, database_sig).get_evolver() register_indexes = kwargs.get('register_indexes', False) my_connection = connections[db_name or DEFAULT_DB_ALIAS] max_name_length = my_connection.ops.max_name_length() for name, model in reversed(models): orig_model_name = get_model_name(model) if orig_model_name in django_evolution_models: unregister_app_model('django_evolution', orig_model_name) orig_db_table = model._meta.db_table orig_object_name = model._meta.object_name generated_db_table = truncate_name( '%s_%s' % (model._meta.app_label, orig_model_name), max_name_length) if orig_db_table.startswith(generated_db_table): model._meta.db_table = '%s_%s' % (app_label, name.lower()) model._meta.db_table = truncate_name(model._meta.db_table, max_name_length) model._meta.app_label = app_label model._meta.object_name = name model_name = name.lower() set_model_name(model, model_name) # Add an entry for the table in database_sig, if it's not already # there. if model._meta.db_table not in database_sig: database_sig[model._meta.db_table] = \ signature.create_empty_database_table_sig() if register_indexes: # Now that we definitely have an entry, store the indexes for # all the fields in database_sig, so that other operations can # look up the index names. for field in model._meta.local_fields: if field.db_index or field.unique: index_name = create_index_name( my_connection, model._meta.db_table, field_names=[field.name], col_names=[field.column], unique=field.unique) signature.add_index_to_database_sig( evolver, database_sig, model, [field], index_name=index_name, unique=field.unique) for field_names in model._meta.unique_together: index_name = create_index_name( my_connection, model._meta.db_table, field_names=field_names, unique=True) signature.add_index_to_database_sig( evolver, database_sig, model, evolver.get_fields_for_names(model, field_names), index_name=index_name, unique=True) for field_names in getattr(model._meta, 'index_together', []): fields = evolver.get_fields_for_names(model, field_names) index_name = create_index_name( my_connection, model._meta.db_table, field_names=[field.name for field in fields], col_names=[field.column for field in fields]) signature.add_index_to_database_sig( evolver, database_sig, model, fields, index_name=index_name) # Register the model with the app. add_app_test_model(model, app_label=app_label) for field in model._meta.local_many_to_many: if not field.rel.through: continue through = field.rel.through generated_db_table = truncate_name( '%s_%s' % (orig_db_table, field.name), max_name_length) if through._meta.db_table == generated_db_table: through._meta.app_label = app_label # Transform the 'through' table information only # if we've transformed the parent db_table. if model._meta.db_table != orig_db_table: through._meta.db_table = \ '%s_%s' % (model._meta.db_table, field.name) through._meta.object_name = \ through._meta.object_name.replace( orig_object_name, model._meta.object_name) set_model_name( through, get_model_name(through).replace(orig_model_name, model_name)) through._meta.db_table = \ truncate_name(through._meta.db_table, max_name_length) for field in through._meta.local_fields: if field.rel and field.rel.to: column = field.column if (column.startswith(orig_model_name) or column.startswith('to_%s' % orig_model_name) or column.startswith('from_%s' % orig_model_name)): field.column = column.replace( orig_model_name, get_model_name(model)) through_model_name = get_model_name(through) if through_model_name in django_evolution_models: unregister_app_model('django_evolution', through_model_name) app_cache[through_model_name] = through add_app_test_model(through, app_label=app_label) app_cache[model_name] = model if not is_app_registered(app): register_app(app_label, app) return app_cache
def create_field(proj_sig, field_name, field_type, field_attrs, parent_model): """ Create an instance of a field from a field signature. This is useful for accessing all the database property mechanisms built into fields. """ # related_model isn't a valid field attribute, so it must be removed # prior to instantiating the field, but it must be restored # to keep the signature consistent. related_model = field_attrs.pop('related_model', None) if related_model: related_app_name, related_model_name = related_model.split('.') related_model_sig = proj_sig[related_app_name][related_model_name] to = MockModel(proj_sig, related_app_name, related_model_name, related_model_sig, stub=True) field = field_type(to, name=field_name, **field_attrs) field_attrs['related_model'] = related_model else: field = field_type(name=field_name, **field_attrs) if field_type is 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', None) through_model_sig = None if through_model: through_app_name, through_model_name = through_model.split('.') through_model_sig = proj_sig[through_app_name][through_model_name] elif hasattr(field, '_get_m2m_attr'): # Django >= 1.2 to = field.rel.to._meta.object_name.lower() if (field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == parent_model._meta.object_name.lower()): from_ = 'from_%s' % to to = 'to_%s' % to else: from_ = 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 = '%s.%s' % (through_app_name, through_model_name) fields = OrderedDict() fields['id'] = { 'field_type': AutoField, 'primary_key': True, } fields[from_] = { 'field_type': ForeignKey, 'related_model': '%s.%s' % (parent_model.app_name, parent_model._meta.object_name), 'related_name': '%s+' % through_model_name, } fields[to] = { 'field_type': ForeignKey, 'related_model': related_model, 'related_name': '%s+' % through_model_name, } through_model_sig = { 'meta': { 'db_table': field._get_m2m_db_table(parent_model._meta), 'managed': True, 'auto_created': True, 'app_label': through_app_name, 'unique_together': ((from_, to),), 'pk_column': 'id', }, 'fields': fields, } field.auto_created = True if through_model_sig: through = MockModel(proj_sig, through_app_name, through_model_name, through_model_sig) field.rel.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
class MockMeta(object): """ A mockup of a models Options object, based on the model signature. The stub argument is used to circumvent recursive relationships. If 'stub' is provided, the constructed model will only be a stub - it will only have a primary key field. """ def __init__(self, proj_sig, app_name, model_name, model_sig): self.object_name = model_name self.app_label = app_name self.meta = { 'order_with_respect_to': None, 'has_auto_field': None, 'db_tablespace': None, 'swapped': False, 'index_together': [], } self.meta.update(model_sig['meta']) self._fields = OrderedDict() self._many_to_many = OrderedDict() self.abstract = False self.managed = True self.proxy = False self._model_sig = model_sig self._proj_sig = proj_sig def setup_fields(self, model, stub=False): for field_name, field_sig in self._model_sig['fields'].items(): if not stub or field_sig.get('primary_key', False): field_type = field_sig.pop('field_type') field = create_field(self._proj_sig, field_name, field_type, field_sig, model) if AutoField == type(field): self.meta['has_auto_field'] = True self.meta['auto_field'] = field field_sig['field_type'] = field_type if ManyToManyField == type(field): self._many_to_many[field.name] = field else: self._fields[field.name] = field field.set_attributes_from_name(field_name) if field_sig.get('primary_key', False): self.pk = field def __getattr__(self, name): if name == 'model_name': return self.object_name return self.meta[name] def get_field(self, name): try: return self._fields[name] except KeyError: try: return self._many_to_many[name] except KeyError: raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, name)) def get_field_by_name(self, name): return (self.get_field(name), None, True, None) def get_fields(self): return self._fields.values() def get_many_to_many_fields(self): return self._many_to_many.values() fields = property(fget=get_fields) local_fields = property(fget=get_fields) local_many_to_many = property(fget=get_many_to_many_fields)
def evolution(self): """Return the mutations needed for resolving the diff. This will attempt to return a hinted evolution, consisting of a series of mutations for each affected application. These mutations will convert the database from the original to the target signatures. Returns: collections.OrderedDict: An ordered dictionary of mutations. Each key is an application label, and each value is a list of mutations for the application. """ mutations = OrderedDict() for app_label, app_changes in six.iteritems(self.changed): app_sig = self.target_project_sig.get_app_sig(app_label) model_changes = app_changes.get('changed', {}) app_mutations = [] for model_name, model_change in six.iteritems(model_changes): model_sig = app_sig.get_model_sig(model_name) # Process the list of added fields for the model. for field_name in model_change.get('added', {}): field_sig = model_sig.get_field_sig(field_name) field_type = field_sig.field_type add_params = field_sig.field_attrs.copy() add_params['field_type'] = field_type if (not issubclass(field_type, models.ManyToManyField) and not field_sig.get_attr_value('null')): # This field requires an initial value. Inject either # a suitable initial value or a placeholder that must # be filled in by the developer. add_params['initial'] = \ self._get_initial_value(app_label=app_label, model_name=model_name, field_name=field_name) if field_sig.related_model: add_params['related_model'] = field_sig.related_model app_mutations.append( AddField(model_name=model_name, field_name=field_name, **add_params)) # Process the list of deleted fields for the model. app_mutations += [ DeleteField(model_name=model_name, field_name=field_name) for field_name in model_change.get('deleted', []) ] # Process the list of changed fields for the model. field_changes = model_change.get('changed', {}) for field_name, field_change in six.iteritems(field_changes): field_sig = model_sig.get_field_sig(field_name) changed_attrs = OrderedDict( (attr, field_sig.get_attr_value(attr)) for attr in field_change) if ('null' in changed_attrs and not field_sig.get_attr_value('null') and not issubclass(field_sig.field_type, models.ManyToManyField)): # The field no longer allows null values, meaning an # initial value is required. Inject either a suitable # initial value or a placeholder that must be filled # in by the developer. changed_attrs['initial'] = \ self._get_initial_value(app_label=app_label, model_name=model_name, field_name=field_name) if 'related_model' in field_change: changed_attrs['related_model'] = \ field_sig.related_model app_mutations.append( ChangeField(model_name=model_name, field_name=field_name, **changed_attrs)) # Process the Meta attribute changes for the model. meta_changed = model_change.get('meta_changed', []) # First, check if the Meta.indexes property has any changes. # They'll all be assembled into a single ChangeMeta. if 'indexes' in meta_changed: change_meta_indexes = [] for index_sig in model_sig.index_sigs: change_meta_index = { 'fields': index_sig.fields, } if index_sig.name: change_meta_index['name'] = index_sig.name change_meta_indexes.append(change_meta_index) app_mutations.append( ChangeMeta(model_name=model_name, prop_name='indexes', new_value=change_meta_indexes)) # Then check Meta.index_together and Meta.unique_together. app_mutations += [ ChangeMeta(model_name=model_name, prop_name=prop_name, new_value=getattr(model_sig, prop_name) or []) for prop_name in ('index_together', 'unique_together') if prop_name in meta_changed ] # Process the list of deleted models for the application. app_mutations += [ DeleteModel(model_name=model_name) for model_name in app_changes.get('deleted', {}) ] if app_mutations: mutations[app_label] = app_mutations return mutations