Fix weird exceptions when running Django tests

If you are reading this article then you probably got around to writing tests for your Django project and when running them using python test you encountered an exception like this

django.contrib.admin.sites.AlreadyRegistered: The model Country is already registered

or this

RuntimeError: Model class doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

The reason why you get these exceptions is that you structured your admin and/or models as packages and imported the individual admin and/or model classes in their respective files.

This might seem like a reasonable thing to do, but it confuses the test runner when it’s looking for tests. The structure makes it load the admin files multiple times what results in the AlreadyRegistered exception. However, your admin files has to be read, so that your models are registered. One way to avoid getting this exception is to import the files in the ready function of your app config instead of the file. An example is shown below.

from django.apps import AppConfig

class WorldConfig(AppConfig):
name = 'world'

def ready(self):
from world.admin import countries
from world.admin import continents

Importing the model classes in the file of the models package confuses the test runner when reading this file as for some reason it can’t identify to which app these models belong. This is why you get the RuntimeError. If you remove the imports from this file everything will work just fine. Migrations for new models will be created as long as you use them somewhere. The only disadvantage is that you will have to specify the full path when importing the models. I.e. you will have to import models like shown on the second line below instead of the shorter version on the first line.

from world.models import Country
from world.models.countries import Country

The simplest solution is to keep the models and admin as simple modules and potentially split your app into multiple smaller apps when the files become too large. However, sometimes this might not be the best option.

In that case the easiest way to avoid these errors is to explicitly specify the names of the apps you want to test. For example if you have two apps called "world" and "people" you can run tests for them like this

python my_app/ test world people

The biggest issue with this approach is that it won’t work if you run the tests from the directory where your file is. Having to explicitly list all of your apps is also not ideal.

The solution I originally came up with is to use django-nose package to run the tests. You can tell Django to use Nose test runner by adding the following line to your settings.

TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

However, this alone doesn’t solve the issue, as Nose test runner looks for the tests the same way as the default Django one. The advantage of using Nose is that we can use any of the available Nose extensions, so we just need to find one that allows us to tell Nose where not to look. A tiny package called nose-pattern-exclude allows you to do exactly that. Once installed running the tests using the following command will work just fine.

python test --exclude-path=../*/admin --exclude-path=../*/models

If you want to avoid having to specify the exclude paths every time you run the tests, you can set these as default arguments for Nose in your settings by adding the following.


With these settings you can now run the tests with python test and everything will work as you would expect. I know this solution is not optimal as it requires installing two additional packages for testing. However, I think it might be a better option than having to change your code just to make the test runner work. Please let me know in the comments if you have better solutions or what do think about mine.

Backend developer at Utility Warehouse. Oxford graduate in Mathematics and Computer Science. Working with Golang, gRPC, Kubernetes, Python and Django.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store