How to write migration to change primary key of model with ManyToManyField

断了今生、忘了曾经 提交于 2019-12-01 19:36:04

问题


I have a UserProfile model that refers to my User model with a OneToOneField. I also use the post_save signal to auto create the UserProfile when the user is created. This works great apart from when creating a user through the admin (where I use an inline) when I get an error about duplicate profile. This answer recommends setting the primary key to be the OneToOneField referring to user.

So before:

class UserProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    # ...
    subjects = models.ManyToManyField(Subject, null=True, blank=True)

After

class UserProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, primary_key=True)
    # ...
    subjects = models.ManyToManyField(Subject, null=True, blank=True)

I'm trying to do that using the migrations in Django 1.7, but life is complicated by the fact that the profile has a number of ManyToManyField - so they all refer to the id field of the UserProfile model. Using makemigrations creates the migrations for making the user the primary key, and dropping the old id field, but it ignores the ManyToManyField.

I'm currently going down a rabbit hole of using lots of RunSQL statements in the migration to modify the through table for the ManyToManyField. I've just hit another error where the name of the constraint is not the same in one table as another.

So my question is: is there a method in Django migrations that will do the work of changing the through table so it refers to the new primary key, updating all the constraints, keys etc? If not, what's the best way to handle this situation?

I'm using Django 1.7 with MySQL.


回答1:


So I ended up with SQL to fix it. The core of my solution is below - basically I

  • create an index on user_id in the new profile
    • this index needs to exist before I can reference it as a foreign key
  • create a new through table
    • I started with the output of SHOW CREATE TABLE userprofile_userprofile_subjects (MySQL specific)
    • I modified the key names and constraint names slightly
  • copy all the data into the new through table
  • drop the old through table
  • rename the new through table to have the name of the old through table
  • finally do the operations that django migrations automatically generated for me

I hope this helps someone else. And I'd still be interested to know about a better solution.

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        # ...
    ]

    operations = [
        migrations.RunSQL(
            'ALTER TABLE userprofile_userprofile '
            'ADD INDEX `userprofile_userprofile_1234abcd` (user_id)'
        ),
        migrations.RunSQL (
            'CREATE TABLE userprofile_temp_table ('
            '`id` int(11) NOT NULL AUTO_INCREMENT, '
            '`userprofile_id` int(11) NOT NULL, '
            '`subject_id` int(11) NOT NULL, '
            'PRIMARY KEY (`id`), '
            'UNIQUE KEY `userprofile_userprofile_subjects_userprofile_us_7ded3060_uniq` (`userprofile_id`,`subject_id`), '
            'KEY `userprofile_userprofile_subject_1be9924f` (`userprofile_id`), '
            'KEY `userprofile_userprofile_subject_e5a9504a` (`subject_id`), '
            'CONSTRAINT `subject_id_refs_id_69796996` FOREIGN KEY (`subject_id`) REFERENCES `otherapp_subject` (`id`), '
            'CONSTRAINT `userprofile_user_id_refs_user_id_1234abcd` FOREIGN KEY (`userprofile_id`) REFERENCES `userprofile_userprofile` (`user_id`) '
            ') ENGINE=InnoDB AUTO_INCREMENT=35500 DEFAULT CHARSET=utf8 '
        ),
        migrations.RunSQL (
            'INSERT INTO userprofile_temp_table '
            '(userprofile_id, subject_id) '
            '('
            '  SELECT userprofile_userprofile.user_id, userprofile_userprofile_subjects.subject_id'
            '    FROM userprofile_userprofile_subjects'
            '    INNER JOIN userprofile_userprofile'
            '    ON userprofile_userprofile_subjects.userprofile_id ='
            '        userprofile_userprofile.id'
            ')'
        ),
        migrations.RunSQL (
            'DROP TABLE `userprofile_userprofile_subjects`'
        ),
        migrations.RunSQL (
            'RENAME TABLE `userprofile_temp_table` TO `userprofile_userprofile_subjects`'
        ),
        migrations.RemoveField(
            model_name='userprofile',
            name='id',
        ),
        migrations.AlterField(
            model_name='userprofile',
            name='user',
            field=models.OneToOneField(
                primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL
            ),
            preserve_default=True,
        ),
    ]


来源:https://stackoverflow.com/questions/33779439/how-to-write-migration-to-change-primary-key-of-model-with-manytomanyfield

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!