jazzband / django-polymorphic

Improved Django model inheritance with automatic downcasting
https://django-polymorphic.readthedocs.io
Other
1.65k stars 280 forks source link

get_real_instance_class throws TypeError #202

Open korkmaz opened 8 years ago

korkmaz commented 8 years ago

There is a chance that polymorphic_ctype_id of an object can point a wrong non-proxy ContentType model. If that case happens polymorphic model's get_real_instance_class throws a TypeError instead of RuntimeError.

issubclass method is called with a None argument like issubclass(model_instance, None)

# Protect against bad imports (dumpdata without --natural) or other
# issues missing with the ContentType models.
if model is not None \
and not issubclass(model, self.__class__) \
and not issubclass(model, self.__class__._meta.proxy_for_model):
    raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format(
        self.polymorphic_ctype_id, model, self.pk,
    ))
    return model

Maybe above code should also check self.__class__._meta.proxy_for_model is a None or not before calling issubclass?

vdboor commented 8 years ago

Hi thanks for reporting this! Could you write a test that replicates this problem? That would help a lot to write a proper solution.

amitnabarro commented 8 years ago

I am experiencing similar problems. While i am able to reproduce in a particular system, i don't know how to re-create this in an encapsulated environment

amitnabarro commented 8 years ago

I found out a descrepency in the polymorphic_ctype_id values in the base class. I still don't know what caused the polymorphic object to point to the wrong CotentType object, but at least it explains why the call fails

thenewguy commented 8 years ago

I am hitting this too. Have you solved the problem?

thenewguy commented 8 years ago

Some how for me it appears that the base class is being saved. Then an exception occurs during creating the child. And the base class stays in the database but you can't delete it due to this error. Not sure how this is happening

thenewguy commented 8 years ago

or perhaps the child class is being deleted and the base class is not. But some how the base class exists without a child references. I am verifying this in the admin using the polymorphic filters

amitnabarro commented 8 years ago

I did find out what my problem was for me, which was unrelated to django-polymorphic directly. I am implementing a multi-tenant system using Postgres schemas. As it turns out, you need to be really careful with content types when doing that. Since django-polymorphic relies on it (polymorphic_ctype_id in the base class) one has to make sure that if multiple schemas do not use the same content-type table, then clearing the content-type cache at every request is important. The only problem related to django-polymorphic was the cryptic error message

thenewguy commented 8 years ago

Hrm... well I am only using one content type table.

Any idea how to clean up when this happens? It rarely seems to happen but it breaks any code that happens to retrieve one of these records from the database... which is bad on a list view. And I can't seem to delete the records due to the exception

amitnabarro commented 8 years ago

Since I use Postgres i simply used pgAdmin to find the rogue record and modify it. You can easily do a select distinct query on the polymorphic_ctype_id parameter to see if you even have bad records and then another query to see how many of them are. Well, thats what I did in order to fix my records

thenewguy commented 8 years ago

Sounds simple enough. Thanks for the tip

vsfedorenko commented 6 years ago

I've faced with the same exception when I was trying to open admin page for sub-model. After about 1 hour of mindgames I figured out what is the root cause.

My migration was applied incorrectly: parent and sub-entities had same value for the "polymorphic_ctype" and, as result, type of all the sub-entities was recognized as type of parent. That happened because of I copy-pasted migration from here.

The solution is to change forwards_func source code to handle existing parent-child inheritance relationships as follows:

from django.db import migrations, models
from django.db.models import Q

def forwards_func(apps, schema_editor):
    models = apps.get_app_config('projectid').get_models()
    content_type = apps.get_model('contenttypes', 'ContentType')
    for model in models:
        new_ct = content_type.objects.get_for_model(model)
        objects = model.objects.filter(
            Q(polymorphic_ctype__isnull=True)
            | ~Q(polymorphic_ctype=new_ct))

        objects.update(polymorphic_ctype=new_ct)

class Migration(migrations.Migration):
    .... and etc.

My env:

Django==2.0.4
django-polymorphic==2.0.3
django-rest-polymorphic==0.1.7
marksweb commented 5 years ago

I've come across this as well 👍

At first I assumed a version issue between django-polymorphic, django-filer, django.

The polymorphic_ctype_id for an object in my database links to a content type which isn't a polymorphic (or proxy) model.

This particular project is still in development so databases have been passed between a couple of developers while jobs have been done, so I'm not sure if an object was created in this table, at which point the ctype was a poly/proxy model. Then after that point the databases has been updated by someone else, moved around again & now that ctype isn't a poly model. I suspect this because the newer objects of the same type have a different polymorphic_ctype_id to that original object.

Surely it makes sense for django-polymorphic to fail gracefully in this instance. I've got a more explicit solution, so will setup a fork with a PR & some tests.

teewuane commented 5 years ago

I ran into this while migrating data from sqlite3 to PostgreSQL. I had to look at the django_content_type table. I examined the old table and compared it to the new table. It was out of order. Basically I had child models pointing to the wrong content types. So have a look at your django_content_type table if you are running into this.

JacoBezuidenhout commented 5 years ago

We are experiencing the same problem. We use Postgresql Schemas and uses request middleware to switch between different schemas.

Keep in mind that the Model -> Content Type ID links differ from schema to schema due to new models made after the first tenant was created.

class File(PolymorphicModel, mixins.IconsMixin):

When I restart UWSGI and then on Tenant 1 goto the Filer->Folder page in the admin then it works well. Now when I go to Tenant 2 then it seems that django-polymorphic is trying to use the content type id of the File model in Tenant 1 to lookup the real instance class.

Could it be that the ctype_id is being cached somehow?

nassim-fox commented 3 years ago

I'm facing the same issue when calling a polymorphic class has anyone found a solution