django-rest-polymorphic icon indicating copy to clipboard operation
django-rest-polymorphic copied to clipboard

Recursion error on __instancecheck__ when making sub-serializers inherit from a PolymorphicSerializer child

Open etal37 opened this issue 2 years ago • 1 comments

Hi everyone,

Here is my situation:

  1. In my Django models, I have a ParentModel class that inherits from django-polymorphic's PolymorphicModel. ParentModel is also inherited by some children, such as ChildModelA, ChildModelB, etc. ParentModel is not abstract and ChildModelA, B are not proxy models. I need this setup because ParentModel has attributes that I need to link to its children. When I run ParentModel.objects.all() in the console, django-polymorphic behaves as expected and returns me a collection of ChildModelA, ChildModelB, etc., even if they actually inherit the "polymorphic traits" of the 2nd-level parent.
  2. However, django-rest-polymorphic strikes me with RecursionError: maximum recursion depth exceeded in __instancecheck__ when I try to replicate this setup for my corresponding serializer classes.
  3. I realized that this is fixed by not making my children serializer classes inherit ParentModelSerializer, but that defeats the purpose of what I want to achieve. So, I don't know if what I'm asking for is a bug fix or a feature request.

Thank you in advance for your time.

Here is the code to clarify (field declarations are not accurate for the sake of simplicity):

  • In the models file:
class ParentModel(PolymorphicModel):
    attributeA = models.CharField()
    attributeB = models.IntField()

class ChildModelA(ParentModel):
    attributeC = models.ForeignKey()
    attributeD = models.URLField()

class ChildModelB(ParentModel):
...
  • When running ParentModel.objects.all():
[<ChildModelA: ChildModelA object (3739843)>, <ChildModelB: ChildModelB object (3098393)>, ...]
  • In the serializers file:
class ParentModelSerializer(serializers.ModelSerializer, PolymorphicSerializer):
    attributeA = serializers.CharField()
    attributeB = serializers.IntField()

    class Meta:
        model = ParentModel

        fields = [
            "attributeA",
            "attributeB",
        ]

class ChildModelASerializer(ParentModelSerializer):
    attributeC = serializers.HyperlinkedRelatedField()
    attributeD = serializers.URLField()

    class Meta:
        model = ChildModelA

        fields = [
            "attributeC",
            "attributeD",
        ]

class ChildModelBSerializer(ParentModelSerializer):
...

ParentModelSerializer.model_serializer_mapping = {
    ChildModelA: ChildModelASerializer,
    ChildModelB: ChildModelBSerializer
    ...
}
  • Stacktrace when attempting to access any ParentModel or ChildModel resource using DRF:
Stacktrace
Internal Server Error: /api/v1/parentmodel/33fe48b6-eb67-40e0-8910-cafce5b61ea5/
Traceback (most recent call last):
  File "/home/dev/Devel/project/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/generics.py", line 208, in get
    return self.retrieve(request, *args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/mixins.py", line 55, in retrieve
    serializer = self.get_serializer(instance)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_framework/generics.py", line 110, in get_serializer
    return serializer_class(*args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_polymorphic/serializers.py", line 40, in __init__
    serializer = serializer(*args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_polymorphic/serializers.py", line 40, in __init__
    serializer = serializer(*args, **kwargs)
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_polymorphic/serializers.py", line 40, in __init__
    serializer = serializer(*args, **kwargs)
  [Previous line repeated 472 more times]
  File "/home/dev/Devel/project/lib/python3.9/site-packages/rest_polymorphic/serializers.py", line 22, in __new__
    if not isinstance(cls.resource_type_field_name, string_types):
RecursionError: maximum recursion depth exceeded in __instancecheck__

etal37 avatar Apr 16 '22 11:04 etal37

I came up with this workaround but I think we can all agree this looks kinda hacky...

In the serializers file:

class ParentModelSerializer(PolymorphicSerializer):
    pass

class ParentModelSerializerFieldsMixin(serializers.ModelSerializer):
    attributeA = serializers.CharField()
    attributeB = serializers.IntField()

    class Meta:
        model = ParentModel

        fields = [
            "attributeA",
            "attributeB",
        ]

class ChildModelASerializer(ParentModelSerializerFieldsMixin):
    attributeC = serializers.HyperlinkedRelatedField()
    attributeD = serializers.URLField()

    class Meta:
        model = ChildModelA

        fields = [
            "attributeC",
            "attributeD",
        ]

class ChildModelBSerializer(ParentModelSerializerFieldsMixin):
...

ParentModelSerializer.model_serializer_mapping = {
    ChildModelA: ChildModelASerializer,
    ChildModelB: ChildModelBSerializer
    ...
}

etal37 avatar Apr 16 '22 11:04 etal37