Amitabh Ghuwalewala
Amitabh Ghuwalewala

Reputation: 121

How do I access my model's custom manager in a Django data migration context?

I have a custom model manager used in several of my models. This manager helps speed up DB inserts. I need to perform a data migration, and it involves migrating several millions of records/objects. I need my custom manager in my data migration. Does anyone know how to get it. In the data migration context if I run model.objects this gives me back Django's model manager.

Upvotes: 6

Views: 1874

Answers (4)

michaeljtbrooks
michaeljtbrooks

Reputation: 172

This got me today.

How to use a custom manager's method in a custom RunPython Django migration

Assuming you've got this model with its custom manager:

# e.g. in myapp.managers file
class MyModelManager(models.Manager):
    def do_something_special():
        # ... your clever database manipulation

# in myapp.models file
class MyModel(models.Model):
    #... various fields

    objects = MyModelManager()

Errors like this occur when you're trying to use a Model by directly importing it into a migration:

def my_custom_migration_function__forwards(apps, schema_editor):
    from myapp.models import MyModel
    MyModel.objects.do_something_special()  # Custom Manager method


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_an_earlier_migration')
    ]

    operations = [
        #... Other migration steps such as CreateModel or AddField etc...,
        migrations.RunPython(my_custom_migration_function__forwards, reverse_code=migrations.RunPython.noop)
    ]


# Raises: 
#    'do_something_special': Could not find an 'apps.get_model("...", "MyModel")'
#    Importing the model directly is incorrect for data migrations.
#    'do_something_special': Model variable name MyModel is different from the model class name that was found in the apps.get_model(...)

Errors like this occur when you've got a custom model method that the migration can't see:

def my_custom_migration_function__forwards(apps, schema_editor):
    MyModel = apps.get_model('myapp', 'MyModel')
    MyModel.objects.do_something_special()  # Custom Manager method

# Raises: *AttributeError: Manager has no do_something_special*

To build on 43TesseractsAnswer, the solution is in four parts:

A) Put use_in_migrations = True into the Manager class:

# e.g. in myapp.managers.py file
class MyModelManager(models.Manager):
    use_in_migrations = True   # << Add this

B) In the terminal, generate a migration with makemigrations

./manage.py makemigrations

You'll find a new migration generated with a migrations.AlterModelManagers step in it

C) Graft the migrations.AlterModelManagers step from the new migration into the right place in your custom migration:

def my_custom_migration_function__forwards(apps, schema_editor):
    from myapp.models import MyModel
    MyModel.objects.do_something_special()  # Custom Manager method


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_an_earlier_migration')
    ]

    operations = [
        #... Other migration steps such as CreateModel or AddField etc...,
        migrations.AlterModelManagers(  # << GRAFT THIS IN
            name='mymodel',
            managers=[
                ('objects', 
myapp.managers.MyModelManager()),
            ],
        ),
        migrations.RunPython(my_custom_migration_function__forwards, reverse_code=migrations.RunPython.noop)
    ]

D) Delete the automatically created migration

You've grafted the step into the earlier migration so don't need it again.

Upvotes: 1

43Tesseracts
43Tesseracts

Reputation: 4937

You can also use the Manager's use_in_migrations attribute (docs):

class MyManager(models.Manager):
    use_in_migrations = True

class MyModel(models.Model):
    objects = MyManager()

Upvotes: 2

Amitabh Ghuwalewala
Amitabh Ghuwalewala

Reputation: 121

As of now the approach I am using, and which seems to work reliably is to instantiate a local Manager for the model, then set manager's model attribute to the model I am interested in:

class MyManager(Manager):
    ...
    def my_create_func(self):
        ...

class MyModel(Model):
    ...
    objects = MyManager()

def data_migration(apps, schema_editor):
    model = apps.get_model(...)
    manager = MyManager()
    manager.model = model
    manager.my_create_func()

Upvotes: 6

2ps
2ps

Reputation: 15926

Why not just import your model?

from myproj.models import MyModel
MyModel.objects.filter(field=value).update(field=new_value)

Upvotes: 1

Related Questions