Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have an existing, functional Django application that has been running in DEBUG mode for the last several months. When I change the site to run in production mode, I begin getting the following Exception emails sent to me when I hit a specific view that tries to create a new Referral model object.

Traceback (most recent call last):

 File "/usr/local/lib/python2.7/dist-packages/Django-1.4.2-py2.7.egg/django/core/handlers/base.py", line 111, in get_response
   response = callback(request, *callback_args, **callback_kwargs)

 File "/usr/local/lib/python2.7/dist-packages/Django-1.4.2-py2.7.egg/django/contrib/auth/decorators.py", line 20, in _wrapped_view
   return view_func(request, *args, **kwargs)

 File "/var/django/acclaimd2/program/api.py", line 807, in put_interview_request
   referral = Referral()

 File "/usr/local/lib/python2.7/dist-packages/Django-1.4.2-py2.7.egg/django/db/models/base.py", line 349, in __init__
   val = field.get_default()

 File "/usr/local/lib/python2.7/dist-packages/Django-1.4.2-py2.7.egg/django/db/models/fields/related.py", line 955, in get_default
   if isinstance(field_default, self.rel.to):

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

As you can see, merely trying to instantiate a Referral model object triggers this exception. Here is the model in question:

class Referral (models.Model):
    opening = models.ForeignKey(Opening,related_name='referrals',null=False,blank=False)
    origin_request = models.ForeignKey('common.request',related_name='referrals',null=True,default=None)
    candidate = models.ForeignKey(User,related_name='referrals',null=False,blank=False)
    intro = models.TextField(max_length=1000,null=False,blank=False)
    experience = models.TextField(max_length=5000,null=False,blank=False)
    email = models.CharField(max_length=255,null=False,blank=False)
    phone = models.CharField(max_length=255,null=False,blank=True,default='')

    def __unicode__(self):
        return u"%s" % self.id

Is this a bug in Django or am I unknowingly doing something that I ought not? Anyone have any suggestions for fixes or workarounds?

like image 475
Derek Edwards Avatar asked Jan 17 '13 19:01

Derek Edwards


4 Answers

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.

like image 80
Derek Edwards Avatar answered Nov 08 '22 19:11

Derek Edwards


In my case i had a model 'Entity' and a 'User' which inherited 'AbstractBaseUser'. A 'User' model had a entity field with ForeignKey to Entity configured that way:

entity = m.ForeignKey('core.Entity', on_delete=m.SET_NULL, null=True)

in the end the workaround was to change it to

from core.models import Entity
entity = m.ForeignKey(Entity, on_delete=m.SET_NULL, null=True)

I don't know the cause of the problem, but it just works that way

like image 23
Nickolai Kozhenin Avatar answered Nov 08 '22 18:11

Nickolai Kozhenin


Another cause:

Make sure that all Foreign Keys are in the same reference models in the same application (app label). I was banging my head against the wall for a while on this one.

class Meta:
    db_table = 'reservation'
    app_label = 'admin'
like image 1
keithhackbarth Avatar answered Nov 08 '22 20:11

keithhackbarth


As it has already been highlighted in other answers, the most common cause seems to be referring to a model using the 'app.Model' syntax.

However, it might still be quite a big challenge to figure out which model exactly is causing the trouble.

I found that a quick solution to that question is to go directly to the place where the exception is raised (django.db.models.fields.related.ForeignKey.get_default) and add the following print statetements

    def get_default(self):
        """Return the to_field if the default value is an object."""
        field_default = super().get_default()
        # Add this line:
        print(f'Here is our small little bug: {field_default}, {self.remote_field.model}')
        if isinstance(field_default, self.remote_field.model):
            return getattr(field_default, self.target_field.attname)
        return field_default

Now the name of the problematic model is printed out and finding the relevant place in your code is no longer a big issue.

Tested on Django 2.2.

like image 1
Eerik Sven Puudist Avatar answered Nov 08 '22 20:11

Eerik Sven Puudist