'Django migrations RunPython not able to call model methods

I'm creating a data migration using the RunPython method. However when I try to run a method on the object none are defined. Is it possible to call a method defined on a model using RunPython?



Solution 1:[1]

did you call your model like said in the documentation ?

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 Because at this point, you can't import your Model directly :

from yourappname.models import Person

Update

The internal Django code is in this file 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)

There is only fields that are clones in a "fake" model instance:

MyModel.__module__ = '__fake__'

Github Django

Solution 2:[2]

The fine print is laid in Historical Models

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

It was quite a surprise when I first encountered it during migration and didn't read the fine print because it seems to contradict their Design Philosophy (adding functions around models)

Solution 3:[3]

As of Django 1.8, you can make model managers available to migrations by setting use_in_migrations = True on the model manager. See the migrations documentation.

Solution 4:[4]

This does not answer the OP, but might still be of use to someone.

Not only are custom model methods unavailable in migrations, but the same holds for other model attributes, such as class "constants" used for model field choices. See examples in the docs.

In this specific edge case, we cannot access the historical values of the choices directly, during migration, but we can get the historical values from the model field, using the model _meta api, because those values are contained in migrations.

Given Django's 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,
    )

We can get the historic value of Student.FRESHMAN inside a migration as follows:

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

Solution 5:[5]

Something useful that worked for me when you have many complex methods calling each other and you need them available via your object:

First copy those model methods over into your migration file

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

def B(self):
    return self.name

def C(self):
    return self.description

Then in your migration function:

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)
    ]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 devnull
Solution 2 user2829759
Solution 3 Ryan Knight
Solution 4 djvg
Solution 5 rymanso