将字段更改为ManyToMany时的Django数据迁移

时间:2010-02-08 19:50:24

标签: django django-models django-migrations

我有一个Django应用程序,我想在其中将一个字段从ForeignKey更改为ManyToManyField。我想保留我的旧数据。最简单/最好的流程是什么?如果重要,我使用sqlite3作为我的数据库后端。

如果我对问题的总结不清楚,这是一个例子。说我有两个型号:

class Author(models.Model):  
    author = models.CharField(max_length=100) 

class Book(models.Model):  
    author = models.ForeignKey(Author)  
    title = models.CharField(max_length=100)

说我的数据库中有很多数据。现在,我想按如下方式更改Book模型:

class Book(models.Model):  
    author = models.ManyToManyField(Author)  
    title = models.CharField(max_length=100) 

我不想“丢失”我之前的所有数据。

实现这一目标的最佳/最简单方法是什么?

2 个答案:

答案 0 :(得分:58)

我意识到这个问题很老,当时数据迁移的最佳选择是使用South。现在Django有自己的migrate命令,过程略有不同。

我已将这些模型添加到名为books的应用中 - 如果不是您的情况,请相应调整。

首先,将字段添加到Bookrelated_name至少一个或两个(或者它们会发生冲突):

class Book(models.Model):  
    author = models.ForeignKey(Author, related_name='book')
    authors = models.ManyToManyField(Author, related_name='books')
    title = models.CharField(max_length=100) 

生成迁移:

$ ./manage.py makemigrations
Migrations for 'books':
  0002_auto_20151222_1457.py:
    - Add field authors to book
    - Alter field author on book

现在,创建一个空迁移来保存数据本身的迁移:

./manage.py makemigrations books --empty
    Migrations for 'books':
0003_auto_20151222_1459.py:

并添加以下内容。要准确了解其工作原理,请查看Data Migrations上的文档。注意不要覆盖迁移依赖性。

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


def make_many_authors(apps, schema_editor):
    """
        Adds the Author object in Book.author to the
        many-to-many relationship in Book.authors
    """
    Book = apps.get_model('books', 'Book')

    for book in Book.objects.all():
        book.authors.add(book.author)


class Migration(migrations.Migration):

    dependencies = [
        ('books', '0002_auto_20151222_1457'),
    ]

    operations = [
        migrations.RunPython(make_many_authors),
    ]

现在从模型中删除author字段 - 它应如下所示:

class Book(models.Model):
    authors = models.ManyToManyField(Author, related_name='books')
    title = models.CharField(max_length=100)

为此创建一个新的迁移,并全部运行:

$ ./manage.py makemigrations
Migrations for 'books':
  0004_remove_book_author.py:
    - Remove field author from book

$ ./manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: messages, staticfiles
  Apply all migrations: admin, auth, sessions, books, contenttypes
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying books.0002_auto_20151222_1457... OK
  Applying books.0003_auto_20151222_1459... OK
  Applying books.0004_remove_book_author... OK

就是这样。之前在book.author处可用的作者现在应该位于book.authors.all()的查询集中。

答案 1 :(得分:1)

你应该做的最好和最简单的事情可能是:

创建具有不同名称的多对多字段

CREATE FUNCTION [dbo].[XMLTable]( 
    @x XML 
) 
RETURNS TABLE 
AS RETURN 
/*---------------------------------------------------------------------- 
This INLINE TVF uses a recursive CTE that processes each element and 
attribute of the XML document passed in. 
----------------------------------------------------------------------*/ 
WITH cte AS ( 
    /*------------------------------------------------------------------ 
    Anchor part of the recursive query. Retrieves the root element 
    of the XML document 
    ------------------------------------------------------------------*/ 
    SELECT 
        1 AS lvl, 
        x.value('local-name(.)','NVARCHAR(MAX)') AS Name, 
        CAST(NULL AS NVARCHAR(MAX)) AS ParentName,
        CAST(1 AS INT) AS ParentPosition,
        CAST(N'Element' AS NVARCHAR(20)) AS NodeType, 
        x.value('local-name(.)','NVARCHAR(MAX)') AS FullPath, 
        x.value('local-name(.)','NVARCHAR(MAX)') 
            + N'[' 
            + CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS NVARCHAR) 
            + N']' AS XPath, 
        ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS Position,
        x.value('local-name(.)','NVARCHAR(MAX)') AS Tree, 
        x.value('text()[1]','NVARCHAR(MAX)') AS Value, 
        x.query('.') AS this,        
        x.query('*') AS t, 
        CAST(CAST(1 AS VARBINARY(4)) AS VARBINARY(MAX)) AS Sort, 
        CAST(1 AS INT) AS ID 
    FROM @x.nodes('/*') a(x) 
    UNION ALL 
    /*------------------------------------------------------------------ 
    Start recursion. Retrieve each child element of the parent node 
    ------------------------------------------------------------------*/ 
    SELECT 
        p.lvl + 1 AS lvl, 
        c.value('local-name(.)','NVARCHAR(MAX)') AS Name, 
        CAST(p.Name AS NVARCHAR(MAX)) AS ParentName,
        CAST(p.Position AS INT) AS ParentPosition,
        CAST(N'Element' AS NVARCHAR(20)) AS NodeType, 
        CAST( 
            p.FullPath 
            + N'/' 
            + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX) 
        ) AS FullPath, 
        CAST( 
            p.XPath 
            + N'/' 
            + c.value('local-name(.)','NVARCHAR(MAX)') 
            + N'[' 
            + CAST(ROW_NUMBER() OVER(
                PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)')
                ORDER BY (SELECT 1)) AS NVARCHAR    ) 
            + N']' AS NVARCHAR(MAX) 
        ) AS XPath, 
        ROW_NUMBER() OVER(
                PARTITION BY c.value('local-name(.)','NVARCHAR(MAX)')
                ORDER BY (SELECT 1)) AS Position,
        CAST( 
            SPACE(2 * p.lvl - 1) + N'|' + REPLICATE(N'-', 1)
            + c.value('local-name(.)','NVARCHAR(MAX)') AS NVARCHAR(MAX) 
        ) AS Tree, 
        CAST( c.value('text()[1]','NVARCHAR(MAX)') AS NVARCHAR(MAX) ) AS Value, 
        c.query('.') AS this,        
        c.query('*') AS t, 
        CAST( 
            p.Sort 
            + CAST( (lvl + 1) * 1024 
            + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS VARBINARY(4) 
        ) AS VARBINARY(MAX) ) AS Sort, 
        CAST( 
            (lvl + 1) * 1024 
            + (ROW_NUMBER() OVER(ORDER BY (SELECT 1)) * 2) AS INT 
        ) 
    FROM cte p 
    CROSS APPLY p.t.nodes('*') b(c)        
), cte2 AS ( 
    SELECT 
        lvl AS Depth, 
        Name AS NodeName, 
        ParentName,
        ParentPosition,
        NodeType, 
        FullPath, 
        XPath, 
        Position,
        Tree AS TreeView, 
        Value, 
        this AS XMLData, 
        Sort, ID 
    FROM cte 
    UNION ALL 
    /*------------------------------------------------------------------ 
    Attributes do not need recursive calls. So add the attributes 
    to the query output at the end. 
    ------------------------------------------------------------------*/ 
    SELECT 
        p.lvl, 
        x.value('local-name(.)','NVARCHAR(MAX)'), 
        p.Name,
        p.Position,
        CAST(N'Attribute' AS NVARCHAR(20)), 
        p.FullPath + N'/@' + x.value('local-name(.)','NVARCHAR(MAX)'), 
        p.XPath + N'/@' + x.value('local-name(.)','NVARCHAR(MAX)'), 
        1,
        SPACE(2 * p.lvl - 1) + N'|' + REPLICATE('-', 1) 
            + N'@' + x.value('local-name(.)','NVARCHAR(MAX)'), 
        x.value('.','NVARCHAR(MAX)'), 
        NULL, 
        p.Sort, 
        p.ID + 1 
    FROM cte p 
    CROSS APPLY this.nodes('/*/@*') a(x) 
) 
SELECT 
    ROW_NUMBER() OVER(ORDER BY Sort, ID) AS ID, 
    ParentName, ParentPosition,Depth, NodeName, Position,  
    NodeType, FullPath, XPath, TreeView, Value, XMLData
FROM cte2;
go
SELECT * FROM dbo.XMLTable(' 
<employees> 
    <emp name="jacob"/> 
    <emp name="steve"> 
        <phone>123</phone>
     some text                      
    </emp> 
</employees> 
') 

编写一个小函数将foreignkey值转换为M2M值:

authors = models.ManyToManyField(Author)

运行后,您可以从表中删除作者字段并再次运行迁移。