15

我有两个类,其中一个是另一个类的后代,我想让它们两个兄弟类都来自同一个基类。

前:

from django.db import models

class A(models.Model):
    name = models.CharField(max_length=10)

class B(models.Model):
    title = models.CharField(max_length=10)

后:

from django.db import models

class Base(models.Model):
    name = models.CharField(max_length=10)

class A(Base):
    pass

class B(Base):
    title = models.CharField(max_length=10)

当我生成模式迁移时,这是输出,包括我对问题的回答:

+ Added model basetest.Base
? The field 'B.a_ptr' does not have a default specified, yet is NOT NULL.
? Since you are removing this field, you MUST specify a default
? value to use for existing rows. Would you like to:
?  1. Quit now, and add a default to the field in models.py
?  2. Specify a one-off value to use for existing columns now
?  3. Disable the backwards migration by raising an exception.
? Please select a choice: 3
- Deleted field a_ptr on basetest.B
? The field 'B.base_ptr' does not have a default specified, yet is NOT NULL.
? Since you are adding this field, you MUST specify a default
? value to use for existing rows. Would you like to:
?  1. Quit now, and add a default to the field in models.py
?  2. Specify a one-off value to use for existing columns now
? Please select a choice: 2
? Please enter Python code for your one-off default value.
? The datetime module is available, so you can do e.g. datetime.date.today()
>>> 37
+ Added field base_ptr on basetest.B
? The field 'A.id' does not have a default specified, yet is NOT NULL.
? Since you are removing this field, you MUST specify a default
? value to use for existing rows. Would you like to:
?  1. Quit now, and add a default to the field in models.py
?  2. Specify a one-off value to use for existing columns now
?  3. Disable the backwards migration by raising an exception.
? Please select a choice: 3
- Deleted field id on basetest.A
? The field 'A.name' does not have a default specified, yet is NOT NULL.
? Since you are removing this field, you MUST specify a default
? value to use for existing rows. Would you like to:
?  1. Quit now, and add a default to the field in models.py
?  2. Specify a one-off value to use for existing columns now
?  3. Disable the backwards migration by raising an exception.
? Please select a choice: 3
- Deleted field name on basetest.A
? The field 'A.base_ptr' does not have a default specified, yet is NOT NULL.
? Since you are adding this field, you MUST specify a default
? value to use for existing rows. Would you like to:
?  1. Quit now, and add a default to the field in models.py
?  2. Specify a one-off value to use for existing columns now
? Please select a choice: 2
? Please enter Python code for your one-off default value.
? The datetime module is available, so you can do e.g. datetime.date.today()
>>> 73
+ Added field base_ptr on basetest.A
Created 0002_auto__add_base__del_field_b_a_ptr__add_field_b_base_ptr__del_field_a_i.py. You can now apply this migration with: ./manage.py migrate basetest

我不知道如何回答有关 B.base_ptr 和 A.base_ptr 的默认值的问题。我给出的任何常量都会导致迁移在运行时失败,输出如下:

FATAL ERROR - The following SQL query failed: CREATE TABLE "_south_new_basetest_a" ()
The error was: near ")": syntax error
RuntimeError: Cannot reverse this migration. 'B.a_ptr' and its values cannot be restored.

顺便说一下,这是我使用 sqlite3 时的结果。使用 Postgres 会给出这样的结果:

FATAL ERROR - The following SQL query failed: ALTER TABLE "basetest_a" ADD COLUMN "base_ptr_id" integer NOT NULL PRIMARY KEY DEFAULT 73;
The error was: could not create unique index "basetest_a_pkey"
DETAIL:  Key (base_ptr_id)=(73) is duplicated.

Error in migration: basetest:0002_auto__add_base__del_field_b_a_ptr__add_field_b_base_ptr__del_field_a_i
IntegrityError: could not create unique index "basetest_a_pkey"
DETAIL:  Key (base_ptr_id)=(73) is duplicated.

我应该为 base_ptr 使用什么值来使这个迁移工作?谢谢!

4

3 回答 3

16

如果base不会自行实例化,您可以使用abstract = Trueprop to轻松解决问题class Meta

示例代码:

from django.db import models

class Base(models.Model):
    name = models.CharField(max_length=10)
    class Meta:
        abstract = True

class A(Base):
    pass

class B(Base):
    title = models.CharField(max_length=10)
于 2016-09-16T20:43:09.440 回答
12

您可以在不同的阶段执行此操作。

第 1 阶段:在代码中创建您的“基础”模型。在 A 和 B 模型上,将base_ptr作为可为空的 FK 添加到 Base(名称base_ptr是通过小写 class-name 来制作的Base,相应地调整您的名称)。在新列上指定db_column='base_ptr',因此您不会_id添加后缀。不要改变父母身份:保持以前B的孩子A身份(还没有孩子班级)。添加迁移以进行相应的数据库更改,然后运行它。ABase

第 2 阶段:创建数据迁移,复制相关数据。您可能应该将所有A数据复制到Base,删除冗余A记录(那些为B实例提供服务的记录),并在剩余记录中(两者AB)将 id 复制到base_ptr. 请注意,子类B使用两个表——它的id字段来自A的表,并且在它自己的表上有一个a_ptr作为 FK的字段——所以如果你从toA复制值,你的更新操作会更有效率。确保复制到 base_ptr 发生在复制到表之后,这样就不会违反 FK 约束。a_ptrbase_ptrBase

第 3 阶段:现在再次更改模型 - 删除显式base_ptrFK 并将父级更改为您喜欢的方式,并创建第三次迁移(自动模式迁移)。请注意,将父级设置为Base隐式定义了一个不可为空的base_ptr字段,因此对于base_ptr字段,您只需将可空字段更改为不可为空,不需要默认值。

仍应要求您提供默认值- 当父级从to更改时a_ptr,隐式 FK from BtoA被删除;向后迁移需要默认值。您可以做一些使向后迁移失败的事情,或者,如果您确实想支持它,请添加一个显式可空到,就像您之前使用的列一样。然后可以在第四次迁移中删除此可为空的列。ABasea_ptrBbase_ptr

于 2014-02-16T22:26:48.287 回答
-1

或者,您可以通过将以下内容添加到 meta 来将类指定为抽象类:

class Meta:
    abstract =True
于 2021-02-25T13:07:25.297 回答