如何通过 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做到了。
因此,将其转换为您的数据模型:
仅使用 book
和 author
字段创建 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 添加选项的主要内容,如果未能解决你的问题,请参考以下文章