Simplifying the Testing of Unmanaged Database Models in Django (Updated for Django 4.2 in 2024)
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.