Django 迁移 RunPython 无法调用模型方法

Django migrations RunPython not able to call model methods

我正在使用 RunPython 方法创建数据迁移。但是,当我尝试 运行 时,定义了对象 none 上的一个方法。是否可以使用 RunPython 调用在模型上定义的方法?

您是否像文档中所说的那样调用您的模型?

def combine_names(apps, schema_editor):
    # We can't import the Person model directly as it may be a newer
    # version than this migration expects. We use the historical version.
    Person = apps.get_model("yourappname", "Person")
    for person in Person.objects.all():
        person.name = "%s %s" % (person.first_name, person.last_name)
        person.save()

Data-Migration 因为在这一点上,你不能直接导入你的模型:

from yourappname.models import Person

更新

Django 内部代码在这个文件中 django/db/migrations/state.py django.db.migrations.state.ModelState#construct_fields

def construct_fields(self):
    "Deep-clone the fields using deconstruction"
    for name, field in self.fields:
        _, path, args, kwargs = field.deconstruct()
        field_class = import_string(path)
        yield name, field_class(*args, **kwargs)

"fake" 模型实例中只有字段是克隆:

MyModel.__module__ = '__fake__'

Github Django

模型方法在迁移中不可用,包括数据迁移。

但是有解决方法,应该与调用模型方法非常相似。您可以在迁移中定义函数,模拟您要使用的那些模型方法。

如果你有这个方法:

class Order(models.Model):
    '''
    order model def goes here
    '''

    def get_foo_as_bar(self):
        new_attr = 'bar: %s' % self.foo
        return new_attr

您可以在迁移脚本中编写函数,例如:

def get_foo_as_bar(obj):
    new_attr = 'bar: %s' % obj.foo
    return new_attr


def save_foo_as_bar(apps, schema_editor):
    old_model = apps.get_model("order", "Order")

    for obj in old_model.objects.all():
        obj.new_bar_field = get_foo_as_bar(obj)
        obj.save()

然后在迁移中使用它:

class Migration(migrations.Migration):

    dependencies = [
        ('order', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(save_foo_as_bar)
    ]

这样迁移就可以了。会有一些重复的代码,但这并不重要,因为数据迁移应该是应用程序特定状态下的一次性操作。

从 Django 1.8 开始,您可以通过在模型管理器上设置 use_in_migrations = True 使模型管理器可用于迁移。参见migrations documentation

细则在Historical Models

Because it’s impossible to serialize arbitrary Python code, these historical models will not have any custom methods that you have defined.

当我在迁移过程中第一次遇到它并且没有阅读细则时,我感到非常惊讶,因为它似乎与他们的Design Philosophy(围绕模型添加功能)相矛盾

这不会回答 OP,但可能对某些人仍然有用。

不仅自定义模型方法在迁移中不可用,其他模型属性也是如此,例如 class 用于模型字段 choices 的“常量”。请参阅 docs.

中的示例

在这种特殊情况下,我们无法访问 historical values of the choices directly, during migration, but we can get the historical values from the model field, using the model _meta api,因为这些值包含在迁移中。

鉴于 Django 的 Student example:

class Student(models.Model):
    FRESHMAN = 'FR'
    ...
    YEAR_IN_SCHOOL_CHOICES = [(FRESHMAN, 'Freshman'), ...]
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default=FRESHMAN,
    )

我们可以在迁移中获取 Student.FRESHMAN 的历史值,如下所示:

...
Student = apps.get_model('my_app', 'Student')
YEAR_IN_SCHOOL_CHOICES = Student._meta.get_field('year_in_school').choices
...

当您有许多相互调用的复杂方法并且您需要通过您的对象使用它们时,对我有用的东西:

首先将这些模型方法复制到您的迁移文件中

def A(self):
    return self.B() + self.C()

def B(self):
    return self.name

def C(self):
    return self.description

然后在你的迁移函数中:

def do_something_to_your_objects(apps, schema_editor):
    MyModel = apps.get_model("my_app", "MyModel")
    MyModel.A = A
    MyModel.B = B
    MyModel.C = C
    
    for my_object in MyModel.objects.all():
         my_object.name_and_decription = my_object.C()
         my_object.save()

class Migration(migrations.Migration):

    dependencies = [
        ('initial', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(do_something_to_your_objects)
    ]