如何通过django中的迁移和数据向现有ManyToManyField添加选项

chh*_*yal 20 django django-models django-migrations

我无法在文档或在线中找到对特定问题的引用.

我有很多关系.

class Books(models.Model):
    name = models.CharField(max_length=100)

class Authors(models.Model):
    name = models.CharField(max_length=100)
    books = models.ManyToManyField(Books)
Run Code Online (Sandbox Code Playgroud)

这有迁移和数据.现在我需要使用through选项,以便在表中添加一个包含多对多关系的额外字段.

class Authorship(models.Model):
    book = models.ForeignKey(Books)
    author = models.ForeignKey(Authors)
    ordering = models.PositiveIntegerField(default=1)

class Authors(models.Model):
    name = models.CharField(max_length=100)
    books = models.ManyToManyField(Books, through=Authorship)
Run Code Online (Sandbox Code Playgroud)

当我运行迁移时,django会为Authorship模型创建新的迁移.我尝试通过orderingAuthorship表中添加列并在表中更改books列来手动创建迁移文件,Authors但是我遇到了一些迁移问题.

operations = [
    migrations.AddField(
        model_name='authorship',
        name='ordering',
        field=models.PositiveIntegerField(default=1),
    ),
    migrations.AlterField(
        model_name='authors',
        name='books',
        field=models.ManyToManyField(to='app_name.Books', through='app_name.Authorship'),
    ),
]
Run Code Online (Sandbox Code Playgroud)

在尝试迁移时,KeyError: ('app_name', u'authorship')我认为还有其他因素会受到影响,从而导致错误.

我错过了什么?有没有其他方法可以解决这个问题?

小智 22

有一种方法可以在没有数据迁移的情况下添加"直通".我设法根据@MatthewWilkes的答案做到了.

因此,要将其转换为您的数据模型:

  1. Authorship仅使用bookauthor字段创建模型.指定表名称以使用与您已有的自动生成的M2M表相同的名称.添加'through'参数.

    class Authorship(models.Model):
        book = models.ForeignKey(Books)
        author = models.ForeignKey(Authors)
    
        class Meta:
            db_table = 'app_name_authors_books'
    
    class Authors(models.Model):
        name = models.CharField(max_length=100)
        books = models.ManyToManyField(Books, through=Authorship)
    
    Run Code Online (Sandbox Code Playgroud)
  2. 生成迁移,但不要运行它.

  3. 编辑生成的迁移并将迁移操作包装到一个migrations. SeparateDatabaseAndState操作中,其中包含state_operations字段内的所有操作(database_operations左侧为空).你最终会得到这样的东西:

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            migrations.CreateModel(
                name='Authorship',
                fields=[
                    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                    ('book', models.ForeignKey(to='app_name.Books')),
                ],
                options={
                    'db_table': 'app_name_authors_books',
                },
            ),
            migrations.AlterField(
                model_name='authors',
                name='books',
                field=models.ManyToManyField(through='app_name.Authorship', to='app_name.Books'),
            ),
            migrations.AddField(
                model_name='authorship',
                name='author',
                field=models.ForeignKey( to='app_name.Author'),
            ),
        ])
    ]
    
    Run Code Online (Sandbox Code Playgroud)
  4. 您现在可以运行迁移并将额外ordering字段添加到M2M表.

编辑: 显然,对于自动M2M表,数据库中的列名称与模型定义的表格略有不同.(我使用的是Django 1.9.3.)

所描述的过程之后,我还与一个2字名称(手动改变字段的列名two_words=models.ForeignKey(...))从twowords_idtwo_words_id.

  • 在 Django 3.0 文档中,他们包含了这个特定的情况:https://docs.djangoproject.com/en/3.0/howto/writing-migrations/#change-a-manytomanyfield-to-use-a-through-model 使用SeparateDatabaseAndState 方式。但进行迁移然后用SeparateDatabaseAndState 包装它的技巧仍然很棒,谢谢。 (6认同)
  • 是的,这种方法有效.对于列名,我只使用了与前一列名匹配的db_column. (2认同)
  • 我不是“非常确定我知道我在做什么”,所以有人可以解释什么是 `SeparateDatabaseAndState` 以及它在这种情况下在做什么吗? (2认同)

chh*_*yal 11

看起来没有办法通过选项而无需进行数据迁移.所以不得不采用数据迁移方法,我从@pista329的答案中得到了一些想法,并使用以下步骤解决了这个问题.

  • 创建Authorship模型

    class Authorship(models.Model):
        book = models.ForeignKey(Books)
        author = models.ForeignKey(Authors)
        ordering = models.PositiveIntegerField(default=1)
    
    Run Code Online (Sandbox Code Playgroud)
  • 保持原始的ManyToManyField关系,但使用上面定义的模型通过模型添加另一个字段:

    class Authors(models.Model):
        name = models.CharField(max_length=100)
        books = models.ManyToManyField(Books)
        published_books = models.ManyToManyField(Books, through=Authorship, related_name='authors_lst') # different related name is needed.
    
    Run Code Online (Sandbox Code Playgroud)
  • 添加数据迁移以将旧表中的所有数据复制到新Authorship表.

在此之后,可以删除模型上的books字段,Authors因为我们有新的字段命名published_books.