RecursionError for circular self relationship
dwoldenberg1 opened this issue · comments
I am experiencing a Maximum Recursion Depth Exceeded Error
when using this library to create filter classes for API views using django rest framework. I followed the documentation on how to create this type of filter relationship and still experience this recursion error: https://github.com/philipn/django-rest-framework-filters#recursive--circular-relationships . The error occurs specifically when trying to filter on objects by id only (that have a recursive filter).
I am fairly certain this is an issue with the library and not our implementation because this issue first occurred as a bug in an actual class with the fields modeled out but I was able to recreate the same issue by simply adding a RelatedFilter
creating a circular reference to the class it is in without actually referencing a real field in the model the filter is for (the error occurs regardless of the fields in the model).
Code usage:
Data:
{
'id' : 1,
'name': 'fred',
'best_friend': 2
}
{
'id':2,
'name':'daniel'
}
Filter Class Code:
# in filters.py
classNameFilter(rest_framework_filters.filterset.FilterSet):
best_friend = RelatedFilter('people.filters.NameFilter', field_name='best_friend', queryset=Name.objects.all())
....
# in views_api.py
class NameViewSet(viewsets.ModelViewSet):
serializer_class = NameSerializer
filter_class = NameFilter
filter_backends = (DjangoFilterBackend, )
...
Working API Call:
$curl "https://app.local/api/names/?best_friend__name=daniel"
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
'id':2,
'name':'daniel'
}
]
}
Broken API Call
$curl "https://app.local/api/names/?best_friend=2"
RecursionError at /api/names/
maximum recursion depth exceeded
Request Method: GET
Request URL: https://app.local/api/names/?best_friend=2
Django Version: 2.2.10
Python Executable: /usr/bin/python3
Python Version: 3.6.8
Python Path: ['/app', '/usr/local/bin', '/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/local/lib64/python3.6/site-packages', '/usr/local/lib/python3.6/site-packages', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages', '/app']
...
Traceback:
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
34. response = get_response(request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
115. response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
113. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/lib64/python3.6/contextlib.py" in inner
52. return func(*args, **kwds)
File "/usr/local/lib/python3.6/site-packages/django/views/decorators/csrf.py" in wrapped_view
54. return view_func(*args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/rest_framework/viewsets.py" in view
114. return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
505. response = self.handle_exception(exc)
File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in handle_exception
465. self.raise_uncaught_exception(exc)
File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in raise_uncaught_exception
476. raise exc
File "/usr/local/lib/python3.6/site-packages/rest_framework/views.py" in dispatch
502. response = handler(request, *args, **kwargs)
File "/usr/local/lib/python3.6/site-packages/rest_framework/mixins.py" in list
38. queryset = self.filter_queryset(self.get_queryset())
File "/usr/local/lib/python3.6/site-packages/rest_framework/generics.py" in filter_queryset
150. queryset = backend().filter_queryset(self.request, queryset, self)
File "/usr/local/lib/python3.6/site-packages/django_filters/rest_framework/backends.py" in filter_queryset
90. filterset = self.get_filterset(request, queryset, view)
File "/usr/local/lib/python3.6/site-packages/django_filters/rest_framework/backends.py" in get_filterset
36. return filterset_class(**kwargs)
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in __init__
93. self.related_filtersets = self.get_related_filtersets()
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in get_related_filtersets
235. prefix=self.form_prefix,
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in __init__
93. self.related_filtersets = self.get_related_filtersets()
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in get_related_filtersets
235. prefix=self.form_prefix,
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in __init__
93. self.related_filtersets = self.get_related_filtersets()
File "/usr/local/lib/python3.6/site-packages/rest_framework_filters/filterset.py" in get_related_filtersets
235. prefix=self.form_prefix,
...
Relevant version information:
Django Version: 2.2.10
django-filter==2.2.0
django-filters==0.2.1
djangorestframework-filters==1.0.0.dev0
Great catch. The related param filtering works by matching on its related prefix. e.g., best_friend__name
is correctly handled because it's prefixed with best_friend__
. Of course, best_friend
isn't prefixed by itself so the relationship check was failing. Adding a simple edge case check for when the param name is the relationship name fixed this.
Fixed in #343. I'll be issuing a 1.0.0.dev.1 prerelease shortly.