As we are done with relations registry population in ProjectState
. It’s time to start working on adding changes to SchemaEditor
so that it may work with ModelStates
.
I have made a few observations which are as follows:
A. In django.db.backends.schema.BaseDatabaseSchemaEditor
- In
table_sql()
we need to manually find the db_params
if model is an instance of ModelState
. The code may look like this:
if isinstance(model, ModelState) and field.is_relation:
related_model = field.related_model
if related_model == 'self':
related_model = model.name_lower
if "." in related_model:
to_model_key = tuple(related_model.lower().split('.'))
else:
to_model_key = tuple([model.app_label, related_model.lower()])
to_model_state = self.project_state.models[to_model_key]
try:
to_field = to_model_state.get_field(field.to_fields[0])
except KeyError:
to_field = to_model_state._meta.pk
db_params = {"type": to_field.rel_db_type(connection=connection), "check": []}
- we need to find
to_column
also using the similar method as follows:
if isinstance(model, ModelState):
related_model = field.related_model
if related_model == 'self':
related_model = model.name_lower
if "." in related_model:
to_model_key = tuple(related_model.lower().split('.'))
else:
to_model_key = tuple([model.app_label, related_model.lower()])
to_model = self.project_state.models[to_model_key]
to_table = to_model._meta.db_table
try:
to_column = to_model.get_field(field.remote_field.field_name).column
except KeyError:
to_column = to_model._meta.pk.column
In order to use the above-mentioned column property of the field, we need to initialize column to every field in ModelState.__init__()
.
- In
create_model()
the making of m2m models can be replaced with the below-mentioned code if model is an instance of ModelState
.
if isinstance(model, ModelState):
for field in model._meta.local_many_to_many:
if field.remote_field.through == None:
model_state = self.project_state.models[(
model.app_label, "".join([model.name_lower, "_", field.name.lower()]))]
else:
model_state = self.project_state.models[tuple(field.remote_field.through.split("."))]
if model_state.auto_created:
self.create_model(model_state)
B. In django.db.migrations.state
-
We need to add a variable with name auto_created
in ModelState
to deal with auto_created
m2m models.
-
We need to create a _meta
function in ModelState
class which will return the instance of ModelStateOptions
which is defined in the next point
-
We need to create a ModelStateOptions
class which will have property functions like pk
, app_label
, swappable
, db_table
etc.
-
We need to add functions similar to the below mentioned ones in the ModelState
class.
def get_field_by_name(self, name):
return self.get_field(name)
def get_local_fields(self):
fields = [field for field in self.fields.values() if not field.many_to_many]
return fields
def get_local_many_to_many(self):
fields = [field for field in self.fields.values() if field.many_to_many]
return fields
C. In django.db.migrations.operations.base
- We need to create a context manager to pass
ProjectState
instance to the schema editor.
@contextmanager
def patch_project_state(schema_editor, project_state):
schema_editor.project_state = project_state
try:
yield
finally:
del schema_editor.project_state
D. In django.db.migrations.operations.models
- We need to automatically create
ModelStates
of auto created m2m through models in CreateModel.state_forwards
. The logic of which would look something like this:
for name, field in self.fields:
if field.many_to_many and field.remote_field.through == None:
this_model_name = "".join([app_label, ".", self.name])
to_model = field.related_model
if this_model_name == to_model:
to_field_name = "".join(["to", "_", field.related_model])
from_field_name = "".join(["from", "_", field.related_model])
to_field = models.ForeignKey(this_model_name, on_delete=models.CASCADE, auto_created=True)
from_field = models.ForeignKey(this_model_name, on_delete=models.CASCADE, auto_created=True)
else:
to_field_name = "".join([to_model.split(".")[1], "_", "id"])
from_field_name = "".join([to_model.split(".")[1], "_", "id"])
to_field = models.ForeignKey(to_model, on_delete=models.CASCADE, auto_created=True)
from_field = models.ForeignKey(this_model_name, on_delete=models.CASCADE, auto_created=True)
fields = [("id", models.AutoField(primary_key=True)),
(to_field_name, to_field),
(from_field_name, from_field)]
through_model_name = "".join([self.name.lower(), "_", name])
through_model_state = ModelState(
app_label,
through_model_name,
list(fields),
options={"db_table":field.db_table},
auto_created=True,
)
state.add_model(through_model_state)
This is all I had in my mind since I started working on schema_editor
. I think this is a big change and will need proper planning and discussion before implementation. I would like to have suggestions from fellow developers.