在django中将数据从“多对多”迁移到“多对多通”

时间:2012-07-13 07:54:36

标签: django django-models django-south

我有一个模特

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

但南方删除了现有的表格。如何保留现有的m-t-m关系?

4 个答案:

答案 0 :(得分:26)

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

在South(Django pre-1.7)中,整个"代码状态"在每次迁移中保存 - 但在Django 1.7+内置迁移中,它是从查看整个迁移集中得出的,因此您需要指定"代码状态"在不改变数据库的情况下改变迁移。

如上所述,这需要在几个步骤中完成。

  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. 使用django-admin.py makemigrations创建自动迁移并修改代码;将操作列表移动到migrations.SeparateDatabaseAndState操作的state_operations参数,并将database_operations列表留空。它应该看起来像:

    class Migration(migrations.Migration):
        operations = [
            migrations.SeparateDatabaseAndState(
               state_operations=[ 
                   migrations.CreateModel(CategoryEntry..)
                   ...
               ],
               database_operations=[]
            ),
        ]
    
  3. 修改CategoryEntry以包含您想要的内容,并使用django-admin.py makemigrations

  4. 创建新的自动迁移

答案 1 :(得分:21)

  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. 运行南架构迁移。

  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并正常创建进一步的迁移。

答案 2 :(得分:2)

我是按照以下方式做的:

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

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

  3. 现在添加backward()部分(就像您想要的那样),并进行模式迁移。这将删除旧的M2M表。

答案 3 :(得分:1)

Django文档具有this exact case作为对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”是M2M关系表的旧名称,而“ core_authorbook”是新名称。如果您未在迁移中包含此代码,则您将无法向中间模型添加额外的字段(并且我认为这是拥有自定义M2M关系的主要原因),因为Django会查找新的表名

总结一下我使用'through'将标准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关系的表现在将具有不同的名称和所有新列。如果表中已经有数据,则这些列必须具有默认值,这一点很重要。 我发现这是在不丢失任何数据的情况下最简单的方法。