Description
We have a couple custom @action
-annotated count
functions, with detail=False
. They do show up in the browsable API, however, no OPTIONS
button is rendered for them.
I noticed this while trying to figure out how to get it to show the "Filters" button for those actions, because basically I'm trying to get some aggregated information (count, using some grouping) on a filtered query.
This is what I found out:
Our base code, where filtering works fine via URL, but neither "OPTIONS", nor "Filters" appears as a button:
# Settings:
from configurations import Configuration
class Config(Configuration):
...
REST_FRAMEWORK = {
...
"DEFAULT_FILTER_BACKENDS": [
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
"django_filters.rest_framework.DjangoFilterBackend",
],
}
...
# View set:
class SomeViewSet(ModelViewSet):
...
@action(
detail=False,
methods=[HTTPMethod.GET],
pagination_class=None,
)
def count(self, request):
raw_data = self.filter_queryset(
self.get_queryset()
.values("status")
.annotate(count=Count("status"))
.order_by("status")
)
data = {
"grouping": ["status"],
"items": {item["status"]: item["count"] for item in raw_data},
}
return Response(status=status.HTTP_200_OK, data=data)
Changing the annotation as follows will cause an error when querying the API:
# import settings in the file...
from django.conf import settings
...
@action(
detail=False,
methods=[HTTPMethod.GET],
filter_backends=settings.REST_FRAMEWORK.get("DEFAULT_FILTER_BACKENDS")),
pagination_class=None,
)
The error says:
File "/.../backend-django/.venv/lib/python3.13/site-packages/rest_framework/generics.py", line 154, in filter_queryset
queryset = backend().filter_queryset(self.request, queryset, self)
~~~~~~~^^
TypeError: 'str' object is not callable
Now, changing the DEFAULT_FILTER_BACKENDS
config to point to actual classes, like the following, will result in an "OPTIONS" button being shown, and the request now runs without error again. However, this change causes other problems such as pagination on other (e.g. list) endpoints to be entirely skipped:
from configurations import Configuration
from django.utils.module_loading import import_string
class Config(Configuration):
...
REST_FRAMEWORK = {
...
"DEFAULT_FILTER_BACKENDS": [
import_string(item)
for item in [
"rest_framework.filters.SearchFilter",
"rest_framework.filters.OrderingFilter",
"django_filters.rest_framework.DjangoFilterBackend",
]
],
}
...
This is of course undesirable, so let's not do that. Another attempt, with the above change reverted, changing the @action
annotation instead. Unfortunately, no "OPTIONS" or "Filters" button show up with this:
@action(
detail=False,
methods=[HTTPMethod.GET],
filter_backends=tuple(import_string(item) for item in settings.REST_FRAMEWORK.get("DEFAULT_FILTER_BACKENDS")),
)
I'm not sure what's going on, but it seems to me that something is wrong with the logic behind whether or not to show the "OPTIONS" and "Filters" buttons.