When building and testing Django applications we find ourselves importing models fairly frequently.
from third_party.models import UsefulModel from my_project.my_app.models import SomeModel from my_project.my_other_app.models import SomeOtherModel # etc
Most Django developers are used to this — the explicit imports don't feel like a big deal in production code.
But what if you load models dynamically and you don't know which app they will be coming from?
The most common use-case is swappable user models. Normally to access the user model we would write:
from django.contrib.auth import get_user_model User = get_user_model()
That would be a bit annoying to type everywhere in your test modules on top of all the production ones...
pytest-django already offers a django_user_model fixture that converts this into a neat little fixture, so in your tests you can simply do:
# from the pytest-django docs: def test_new_user(django_user_model): django_user_model.objects.create( username="someone", password="something")
But I like mine better!
User is one example, but maybe you have more interchangeable apps, and in your app context there is no guarantee where a model could really be imported from... Maybe the project will be using
my_amazing.blog_app.models.Comment or it may be using
Django has you covered with that, as you can request a model just by label and name:
from django.apps import apps Comment = apps.get_model("blog_app.Comment")
This will ensure the correct
Comment model is imported and you don't really need to know where it comes from.
Oooh, but it's just like
get_user_model all over again 😤😤😤
You could make things a bit easier by removing the need to import
@pytest.fixture def get_model(): from django.apps import apps return get_model def test_comment(get_model): comment = get_model("blog_app.Comment")() # ...
Or you can even create a fixture that will do this for you:
from django.apps import apps @pytest.fixture def Comment(): return get_model("blog_app.Comment") def test_comment(Comment): comment = Comment()
That looks much neater already!
What if you have many models that could be overridden like this?
Putting this concept on steroids, we could add something like the following to our
from django.apps import apps def create_model_fixture(model): def _fixture(): return model _fixture.__name__ = model._meta.object_name return _fixture for app_label, models in apps.all_models.items(): for model in models.values(): vars()[model._meta.object_name] = pytest.fixture( create_model_fixture(model) )
Then in our tests we can access any model by it's object name, like:
def test_stuff(Comment, BlogPost): comment = Comment() post = BlogPost() # ... assert some stuff
Gotcha/DISCLAIMER: The code above is a bit "magic" and assumes that there are no models with the same name registered at the same time. If that is a potential issue you could amend the example above to name your fixtures more explicitly, ex to
That's it. ✨ ✨ Happy testing! ✨✨