21

我有一个模型

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             )

我希望重构每个关系都有额外的数据:

class Category(models.Model):
    title           = models.CharField(...)
    entry           = models.ManyToManyField(Entry,null=True,blank=True,
                                             related_name='category_entries',
                                             through='CategoryEntry',
                                             )

但是南删除了现有的表。如何保留现有的 mtm 关系?

4

4 回答 4

32

在 Django 1.7+ 内置迁移中,“代码状态”(即模型的代码定义)的计算方式不同,需要不同的解决方案。

在 South(Django pre-1.7)中,整个“代码状态”保存在每个迁移中——但在 Django 1.7+ 内置迁移中,它是通过查看整个迁移集得出的,因此您需要指定“代码状态”状态”迁移中的更改而不更改数据库。

像上面一样,这需要通过几个步骤来完成。

  1. 创建一个中间模型,如上面的答案:

    class CategoryEntry(models.Model):
        category = models.ForeignKey(Category, on_delete=models.CASCADE)
        entry = models.ForeignKey(Entry, on_delete=models.CASCADE)   
    
        class Meta:
             db_table = 'main_category_entries'   #change main_ to your application
             unique_together = ('category', 'entry')
    
  2. django-admin.py makemigrations使用并修改代码创建自动迁移;将操作列表移动到操作的state_operations参数中migrations.SeparateDatabaseAndState,并将database_operations列表留空。它应该看起来像:

    class Migration(migrations.Migration):
        operations = [
            migrations.SeparateDatabaseAndState(
                state_operations=[ 
                    migrations.CreateModel(CategoryEntry..)
                    ...
                ],
                database_operations=[]
            ),
        ]
    
  3. 编辑CategoryEntry以包含您想要的内容并创建一个新的自动迁移django-admin.py makemigrations

于 2015-06-18T11:14:54.187 回答
23
  1. 现在创建没有任何额外字段的中间模型。给它一个唯一的约束来匹配现有的,并指定表名来匹配现有的:

    class CategoryEntry(models.Model):
        category = models.ForeignKey(Category)
        entry = models.ForeignKey(Entry)   
    
        class Meta:
            db_table='main_category_entries'   #change main_ to your application
            unique_together = (('category', 'entry'))
    
  2. 运行 South 模式迁移。

  3. 编辑生成的模式迁移脚本并注释掉所有向前和向后的条目,因为您将重新使用现有的交集表。添加pass完成方法。

  4. 运行迁移。

  5. 更新任何现有代码。正如它在https://docs.djangoproject.com/en/dev/topics/db/models/#many-to-many-relationships中所说,“与普通的多对多字段不同,您不能使用添加,创建或分配以创建关系”,因此您需要修改任何现有的应用程序代码,例如

    c.entry.add(e)
    

    可能变成:

    try:
        categoryentry = c.categoryentry_set.get(entry = e)
    except CategoryEntry.DoesNotExist:
        categoryentry = CategoryEntry(category=c, entry=e)
        categoryentry.save()
    

    和:

    e.category_entries.add(c)
    

    可能变成:

    categoryentry = CategoryEntry(category=c, entry=e)  #set extra fields here
    categoryentry.save()                
    

    和:

    c.entry.remove(e)
    

    可能变成:

    categoryentry = c.categoryentry_set.get(entry = e)
    categoryentry.delete()
    
  6. 完成此初始伪迁移后,您应该能够将额外的字段添加到CategoryEntry并像往常一样创建进一步的迁移。

于 2012-08-16T11:24:29.680 回答
4

我会通过以下方式做到这一点:

  1. CategoryEntry类添加到模型中,并执行自动模式迁移。这将添加一个包含属性的空表CategoryEntry。需要注意的是,较旧的 M2M 表保持不变,因为through='CategoryEntry'尚未添加。

  2. 执行数据迁移,将现有 M2M 表中的所有数据复制到步骤 1 中创建的表中。为此,请运行datamigration命令,并相应地在自动生成的迁移脚本中编辑方法forward()和方法。backward()

  3. 现在添加through='CategoryEntry'部分(只是您想要的方式),并进行架构迁移。这将删除旧的 M2M 表。

于 2012-12-12T13:35:43.143 回答
3

Django 文档将这种确切的情况作为 migrations.SeparateDatabaseAndState 操作的应用程序。我完全按照文档所说的做了,但是 Django 一直抛出一个异常,说用于 M2M 映射的表不存在。

我通过中间模型的 Meta 类的“db_table”属性分配了一个表名,这导致了问题(不知道为什么)。然后我明白了,Django文档中示例所示的SQL代码是将Django分配给标准M2M关系表中的M2M关系表的名称更改为Django分配给所使用的中间模型对应的表的新名称。

            database_operations=[
            # Old table name from checking with sqlmigrate, new table
            # name from AuthorBook._meta.db_table.
            migrations.RunSQL(
                sql='ALTER TABLE core_book_authors RENAME TO core_authorbook',
                reverse_sql='ALTER TABLE core_authorbook RENAME TO core_book_authors',
            ),
        ],

在此示例中,“core_book_authors”是旧名称,“core_authorbook”是 M2M 关系表的新名称。如果您在迁移中不包含此代码,您将无法向中间模型添加额外的字段(我认为这是拥有自定义 M2M 关系的主要原因),因为 Django 将查找新表名.

总结一下我使用“通过”将标准 M2M 关系更改为自定义关系所做的工作:

  1. 创建没有额外字段(只有两个外键)的中间模型,并指定现在通过该模型建立 M2M 关系(使用“通过”)。
  2. 运行命令py manage.py makemigrations。我将此自动生成的迁移更改为看起来像上面引用的文档中的迁移。
  3. 运行命令py manage.py migrate
  4. 在中间模型中添加了我需要的所有额外字段。
  5. 运行命令py manage.py makemigrations
  6. 运行命令py manage.py migrate

以前表示标准 M2M 关系的表现在将具有不同的名称和所有新列。如果表中已有数据,则这些列具有默认值很重要。我发现这是在不丢失任何数据的情况下最直接的方法。

于 2020-06-05T21:31:52.673 回答