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

时间:2015-10-21 10:57:51

标签: 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)

这包含迁移和数据。现在我需要使用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模型创建新的迁移。我尝试通过在ordering表中添加Authorship列并在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'),
    ),
]

尝试迁移时,会给出KeyError: ('app_name', u'authorship')我敢打赌其他因素会受到影响,从而导致错误。

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

3 个答案:

答案 0 :(得分:20)

有一种方法可以在没有数据迁移的情况下添加“直通”。 我设法基于this @MatthewWilkes' answer

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

  1. 仅使用Authorshipbook字段创建author模型。指定表名称以使用与您已有的自动生成的M2M表相同的名称。添加“通过”参数。

    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表中。

  5. 修改 显然,对于自动M2M表,DB中的列名称与模型定义的表格略有不同。 (我正在使用Django 1.9.3。)

    在描述的程序之后,我还必须手动将字段的列名更改为two_words=models.ForeignKey(...)twowords_id的双字名称(two_words_id)。

答案 1 :(得分:10)

看起来没有必要通过选项而无需进行数据迁移。所以不得不求助于数据迁移方法,我从@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(Books, through=Authorship, related_name='authors_lst') # different related name is needed.
    
  • 添加数据迁移,将所有数据从旧表复制到新Authorship表。

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

答案 2 :(得分:2)

迁移有时会很混乱。

如果您想通过直通改变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问题。