Reputation: 121
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
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
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
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
Reputation: 15926
Why not just import your model?
from myproj.models import MyModel
MyModel.objects.filter(field=value).update(field=new_value)
Upvotes: 1