Editor's note: This post was originally published in September, 2010 and was updated in December, 2024 to incorporate changes in Django and improvements suggested by our readers. It has also been tested for compatibility as of the Django 4.2 release.
Sometimes, when building a web application in Django, one needs to connect to a legacy database whose tables already exist. To support this use case, Django has the concept of "unmanaged models; which let you connect the Django ORM to tables that it assumes to exist and not attempt to create).
This can make automated testing difficult, because you might not have the SQL on hand to create an empty copy of the legacy database for testing purposes.
We have previously written about this way back in 2010, however Django has changed a lot since then.
Let's revisit how to handle unmanaged models and testing in Django 4.2 in 2024.
Creating an unmanaged model in Django
By default, Django model is managed. You can turn it into an unmanaged model by setting the managed Meta attribute to False.
from django.db import models
class MyUnmanagedModel(models.Model):
name = models.CharField(max_length=100)
class Meta:
managed = False
We'll then run makemigrations, which will produce a migration file for this model, and you could see that it includes the "managed": False in the options.
class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name="MyUnmanagedModel",
fields=[
...
("name", models.CharField(max_length=100)),
],
options={
"managed": False,
},
),
]
Testing the unmanaged model
We'll now write a simple test case for the unmanaged model.
from apps.unmanaged.models import MyUnmanagedModel
class TestUnManagedModel(TestCase):
def test_example(self):
example = MyUnmanagedModel.objects.create(name="example")
self.assertEqual(example.name, "example")
But when we run the test, we'll get an error like this.
django.db.utils.ProgrammingError: relation "unmanagedapp_myunmanagedmodel" does not exist in the test database.
For testing purposes, we actually want Django to treat this model as a "managed" model, and create the tables, so that we can run tests against it. However, in production, we want to treat this model as an unmanaged model.
After some research, and trial-and-error, we found a solution that works for us.
Creating the IS_MANAGED flag for testing
Instead of setting the managed attribute to False in the model definition, we're setting it to a variable that is set to True during testing, but False in production.
First, we need a way to know whether we're currently running tests or not. We created the following variable in our settings file:
import sys
IS_RUNNING_TESTS = sys.argv[1:2] == ["test"]
Then, in our model, we created another variable that is set to True when we're running tests.
IS_MANAGED = True if settings.IS_RUNNING_TESTS else False
Then, we set the managed attribute to this variable.
class MyUnmanagedModel(models.Model):
name = models.CharField(max_length=100)
class Meta:
managed = IS_MANAGED
But this is not enough. Remember the migration file? It was created with the value of "managed": False. Therefore, when Django sees this migration file during the test, it will not create the table for this model. But we also don't want to set this to True in the migration file, because we don't want to create the table in production.
The solution is to set the IS_MANAGED variable in the migration file itself.
from django.conf import settings
IS_MANAGED = True if settings.IS_RUNNING_TESTS else False
class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name="MyUnmanagedModel",
fields=[
(
...
],
options={
"managed": IS_MANAGED,
},
),
]
Since we have previously ran the migration before with the value set to False, we need to revert the migration:
python manage.py migrate unmanagedapp zero
With the migration reverted, and updated using the IS_MANAGED variable, we can now run the migration again. After that, we can run the test and this time, because of the IS_MANAGED flag, Django will create the table for this model during testing, and the test will pass.
This is how we handle unmanaged models and testing in Django in 2024. If you have a better way, we'd love to hear about it. Please share your thoughts in the comments below.