【问题标题】:Migrating data from "Many-To-Many" to "Many-To-Many Through" in django在 django 中将数据从“多对多”迁移到“多对多通过”
【发布时间】:2012-07-13 01:27:14
【问题描述】:

我有一个模型

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

但是 south 删除了现有的表。如何保留现有的 m-t-m 关系?

【问题讨论】:

标签: django django-models django-south


【解决方案1】:

Django 文档有 this exact case 作为迁移的应用程序。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 关系的表现在将具有不同的名称和所有新列。如果表中已有数据,则这些列具有默认值很重要。 我发现这是在不丢失任何数据的情况下执行此操作的最直接方法。

【讨论】:

    【解决方案2】:

    在 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 创建自动迁移并修改代码;将操作列表移动到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 创建新的自动迁移

    【讨论】:

    • django 文档链接关于separatedatabaseandstate:docs.djangoproject.com/en/1.7/ref/migration-operations/…
    • 效果很好。根据我的用法,我希望 state_operations 包含: (1) CreateModel('CategoryEntry', ...) - 确保 db_table 与现有的直通表名匹配。 (2) AddField(...) 用于 from 和 to 字段。确保这些字段名称与 Django 默认使用的名称匹配。对于两个不同模型之间的关系,它将是categoryentry;同一模型之间的关系将是 from_categoryto_category。 (3) AlterUniqueTogether(...) 用于 from 和 to 字段。 (4)AlterField(...)为指向直通表的M2M字段。
    • 现在应该接受答案,因为 Django
    • 在我尝试运行第二次迁移时使用 Django 1.11,它告诉我该表不存在。我错过了什么??
    • 似乎缺少一些步骤...我猜在#2之后您需要编辑entry字段以使用through并指向CategoryEntry。我认为您需要添加到state_operationsCategory 上的entry 字段的更改。 @flyingfred0 也提到了这一点。
    【解决方案3】:

    我会这样做:

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

    2. 进行数据迁移,将现有 M2M 表中的所有数据复制到步骤 1 中创建的表中。为此,请运行 datamigration 命令,并在相应地自动生成迁移脚本。

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

    【讨论】:

      【解决方案4】:
      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 并像往常一样创建进一步的迁移。

      【讨论】:

      • 代替 p.1 Create your intermediate model without any extra fields, for now 您可以运行 ./manage.py inspectdb > temp_models.py 并从那里获取表定义
      猜你喜欢
      • 1970-01-01
      • 2018-12-26
      • 2011-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多