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

Posted

技术标签:

【中文标题】如何通过 django 中的迁移和数据向现有的 ManyToManyField 添加选项【英文标题】:How to add through option to existing ManyToManyField with migrations and data in django 【发布时间】:2016-01-20 08:16:51 【问题描述】:

我在文档或在线中找不到对特定问题的参考。

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

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 选项,以便在包含多对多关系的表中添加一个额外的字段。

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 表中添加ordering 列并在Authors 表中更改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') 我敢打赌还有其他事情会受到影响,因此会出现错误。

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

【问题讨论】:

【参考方案1】:

迁移有时会很混乱。

如果你想通过 through 改变 m2m 字段,我建议将改变的字段 Authors.books 重命名为 Authors.book。当makemigrations 询问您是否将名称从书更改为​​书? [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 有关重命名的问题的答案。

【讨论】:

这需要数据迁移,否则我会丢失现有数据,不是吗? 是的,因为您将删除字段。如果您不想丢失数据,请不要删除books,只需添加book。然后通过 SQL 将数据从books 迁移到book【参考方案2】:

看起来没有办法在不进行数据迁移的情况下使用 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 可能会丢失原始字段中的数据。

添加数据迁移,将旧表中的所有数据复制到新的Authorship 表中。

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

【讨论】:

"需要不同的相关名称。"!!!!!!这就是我一直在寻找的!!为什么 Django 没有明确说明添加具有相同(或没有)相关名称的新直通关系会导致关系数据丢失。谢谢你。经过一整天的痛苦测试,你救了我。【参考方案3】:

有一种方法可以在不迁移数据的情况下添加“通过”。 我设法根据this @MatthewWilkes' answer做到了。

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

    仅使用 bookauthor 字段创建 Authorship 模型。指定表名以使用与您已有的自动生成的 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)
    

    生成迁移,但不要运行它。

    编辑生成的迁移并将迁移操作包装到 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'),
            ),
        ])
    ]
    

    您现在可以运行迁移并将额外的 ordering 字段添加到您的 M2M 表中。

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

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

【讨论】:

是的,这种方法有效。对于列名,我只使用了与前一个列名匹配的 db_column。 也许值得一提的是来自 Django docs 的关于使用 SeparateDatabaseAndState 的警告:“除非您非常确定自己知道自己在做什么,否则不要使用此操作。” 我不是“很确定我知道自己在做什么”,所以有人可以解释一下SeparateDatabaseAndState 是什么以及在这种情况下它在做什么吗? realpython.com 在本系列的第二部分中对 SeparationDatabaseAndState 进行了很好的解释:realpython.com/django-migrations-a-primer(此链接到第一部分,尚未涵盖 SeparationDatabaseAndState) 在 Django 的 3.0 文档中,他们包含了这个特定的案例:docs.djangoproject.com/en/3.0/howto/writing-migrations/… 使用了separateDatabaseAndState 方式。但是,进行迁移然后用 SeparationDatabaseAndState 包装它的技巧仍然很棒,谢谢。

以上是关于如何通过 django 中的迁移和数据向现有的 ManyToManyField 添加选项的主要内容,如果未能解决你的问题,请参考以下文章

向现有的 matplotlib 散点图添加点

向现有的数据库中添加文件组和数据文件

如何向现有的 Azure 表存储添加新列

Django(21)migrate报错的解决方案

向现有的pyspark数据框添加一列

向现有的 pandas DF 添加名称(字符串)列