I think I have a rather obscure case that is not covered by Django. I'm using Django 1.11.20 and this problem surfaces when using mySQL, with sqlite there is no problem (since that does not use foreignkey restraints)
So here's the setup.
- I have a model named 'Function' and it has an automatic primary key field 'id' (made by Django)
I have a model named 'Training' and on that there is a ManyToMany relation called 'subscribe_functions' which points to Function.
class Training(RegistrationBlock):
subscribe_functions = models.ManyToManyField('Function', blank=False, limit_choices_to={'avail_option': True}, related_name="trainings", verbose_name=_('Functiongroups that may register'), help_text=_('People in the selected functions may register their ' 'availability'))
So Django made a nice intermediary table for this and on that table there is a foreign key constraint that the column 'function_id' points to Function.id.
And here's the problem.
Now I want to add a parent model to Function without loosing data and actually move some data to the parent. So I make the parent model ('CoreFunction') and migrate it (it's in a separate app) and then in my migration for Function I do the following things:
rename the field 'id' on Function to 'corefunction_ptr'
migrations.RenameField( model_name='function', old_name='id', new_name='corefunction_ptr' ),
Transfer some data from child model to parent model via a
RunPython
operationand then do this:
migrations.AlterField( model_name='function', name='corefunction_ptr', field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='rlgcore.CoreFunction'), ),
Essentially turning the corefunction_ptr field into the primary_key field and pointing it to the parent model.
When running this migration on a mySQL database I get the error that there is a foreignkey constraint in place and that the corefunction_ptr field can not be modified.
_mysql_exceptions.OperationalError: (1846, 'ALGORITHM=COPY is not supported. Reason: Columns participating in a foreign key are renamed. Try ALGORITHM=INPLACE.')
Digging trough my database I found that this is the constraint on the intermediary table training_subscribefunctions.
I pinpointed it to this query (which gives the same error when throwing it in phpmyadmin).
ALTER TABLE 'pe_function' CHANGE 'corefunction_ptr' 'corefunction_ptr_id' integer NOT NULL;
However, there was another model linked to Function via a ForeignKey and for that table the constraint was dropped and would be recreated again later. (I ran python manage.py sqlmigrate [appname] [migrationname
] to see what was happening)
I found the code in Django where the decision is made if a foreignkey restraint should be dropped, but because it is determined to be a reverse many2many the decision results in False.
So the question is
How can I write code in my migration file to check if there are more restraints to the model I am trying to rename the id field for, then drop the restraint and later add it again?
I have found a way to find the restraints pointing to a table via a raw SQL query. However I'd have to store all found info somewhere (global variables?) to be able to add the same restraint again later. I'd much rather use the Django code itself (for example from BaseDatabaseSchemaEditor._alter_field) but I don't know how.
I know that the functions you can feed to a RunPython
operation get a schema_editor, but I don't know how/where to get and how to pass the parameters _delete_constraint_sql
and _create_fk_sql
expect.
I'd like to fix this with as little hardcoding of fields/table names/model names etc. as possible.
Any thoughts?