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

How to add through option to existing ManyToManyField with migrations and data in django

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

我有一个现有的多对多关系。

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

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

这有迁移和数据。现在我需要使用 through 选项,以便在 table 中添加一个额外的字段来保持多对多关系。

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)

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

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'),
    ),
]

尝试迁移时,它给出 KeyError: ('app_name', u'authorship') 我敢打赌还有其他东西受到影响,因此会出现错误。

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

迁移有时会很混乱。

如果您想通过更改 m2m 字段,我建议将更改字段 Authors.books 重命名为 Authors.book。当 makemigrations 询问时,是否将名称从 books 更改为 book? [yN],选择“N”作为编号。Django 将删除 books 并创建 book 字段而不是更改。

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)
    book = models.ManyToManyField("Books", through="Authorship")

如果您无论如何都想使用 books,请将 book 更改为 books 并使用 y 重复迁移过程作为 makemigrations 关于重命名问题的答案。

看起来没有办法在不进行数据迁移的情况下使用 through 选项。因此不得不求助于数据迁移方法,我从@pista329 的回答中吸取了一些想法并使用以下步骤解决了这个问题。

  • 创建Authorship模型

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

      class Authors(models.Model):
          name = models.CharField(max_length=100)
          books = models.ManyToManyField(Books)
          published_books = models.ManyToManyField(
              to=Books,
              through=Authorship,
              related_name='authors_lst' # different related name is needed.
          )
    

    重要提示:您必须使用与现有 ManyToManyField 不同的 related_name。如果不这样做,Django 可能会丢失原始字段中的数据。

  • 添加数据迁移以将所有数据从旧 table 复制到新 Authorship table.

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

的新字段

有一种方法可以在不进行数据迁移的情况下添加 "through"。 我设法根据 this @MatthewWilkes' answer.

做到了

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

  1. 仅使用 bookauthor 字段创建 Authorship 模型。指定 table 名称以使用与您已有的自动生成的 M2M table 相同的名称。添加 '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)
    
  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'),
            ),
        ])
    ]
    
  4. 您现在可以 运行 迁移并将额外的 ordering 字段添加到您的 M2M table。

编辑: 显然,对于自动 M2M tables 和模型定义的 tables,数据库中的列名生成略有不同。 (我使用的是 Django 1.9.3。)

在描述的过程之后,我还必须手动将具有 2 个单词名称 (two_words=models.ForeignKey(...)) 的字段的列名从 twowords_id 更改为 two_words_id