Search code examples
pythondjangofactory-boy

django: how to specify database dynamically for factory boy


I am setting up a Django application with a lot of databases, and some of them are using the same models (they are not replicas). I already configured my routers and everything is working great. The problem appears when making the tests as I want to use factory-boy.

On a different project I could setup the database inside Meta but now, I have to select on which database to create an instance dynamically (if not, I would have to create a DjangoModelFactory for each database, which wouldn't be pretty).

Is there an (easier) way to specify the database dynamically for each creation?


Solution

  • As far as I know factory_boy (version <=2.10.0) doesn't provide anything like that.

    Though, your problem is the perfect use case to use a context manager. It will allow you to set the database dynamically wherever you need and only under the desired scope, and also DRY!:

    # factoryboy_utils.py
    
    @classmethod
    def _get_manager(cls, model_class):
        return super(cls, cls)._get_manager(model_class).using(cls.database)
    
    class DBAwareFactory(object):
        """
        Context manager to make model factories db aware
    
        Usage:
            with DBAwareFactory(PersonFactory, 'db_qa') as personfactory_on_qa:
                person_on_qa = personfactory_on_qa()
                ...
        """
        def __init__(self, cls, db):
            # Take a copy of the original cls
            self.original_cls = cls
            # Patch with needed bits for dynamic db support
            setattr(cls, 'database', db)
            setattr(cls, '_get_manager', _get_manager)
            # save the patched class
            self.patched_cls = cls
    
        def __enter__(self):
            return self.patched_cls
    
        def __exit__(self, type, value, traceback):
            return self.original_cls
    

    and then, in your tests, you can do something like:

    from factoryboy_utils import DBAwareFactory
    
    class MyTest(TestCase):
    
       def test_mymodel_on_db1(self):
           ...
           with DBAwareFactory(MyModelFactory, 'db1') as MyModelFactoryForDB1:
               mymodelinstance_on_db1 = MyModelFactoryForDB1()
               # whatever you need with that model instance
               ...
           # something else here
    
       def test_mymodel_on_db2(self):
           ...
           with DBAwareFactory(MyModelFactory, 'db2') as MyModelFactoryForDB2:
               mymodelinstance_on_db2 = MyModelFactoryForDB2()
               # whatever you need with that model instance
               ...
           # something else here