Instantiating Django model raises TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

UPDATE (with solution below)

I've been digging into the Django model code and it seems like there is a bug that creates a race condition when using "app.model"-based identifiers for the related field in a ForeignKey. When the application is running in production mode as opposed to DEBUG, the ForeignKey.get_default method referenced in the exception above tries to check if the provided default value is an instance of the related field (self.rel.to):

def get_default(self):
    "Here we check if the default value is an object and return the to_field if so."
    field_default = super(ForeignKey, self).get_default()
    if isinstance(field_default, self.rel.to):
        return getattr(field_default, self.rel.get_related_field().attname)
    return field_default

Initially when a ForeignKey is instantiated with a string-based related field, self.rel.to is set to the string-based identifier. There is a separate function in related.py called add_lazy_relation that, under normal circumstances, attempts to convert this string-based identifier into a model class reference. Because models are loaded in a lazy fashion, it's possible that this conversion can be deferred until the AppCache is fully loaded.

Therefore, if get_default is called on a string-based ForeignKey relation before AppCache is fully populated, it's possible for a TypeError exception to be raised. Apparently putting my application into production mode was enough to shift the timing of model caching that this error started occurring for me.

SOLUTION

It seems this is genuinely a bug in Django, but here's how to get around it if you do ever run into this problem. Add the following code snippet immediately before you instantiate a troublesome model:

from django.db.models.loading import cache as model_cache
if not model_cache.loaded:
    model_cache._populate()

This checks the loaded flag on the AppCache to determine if the cache if fully populated. If it is not, we will force the cache to fully populate now. And the problem will be solved.