I'm using Django REST framework to create an API that supports JSON and CSV output.
I have this line in my urls.py:
url(r'^api/events/$', views.EventsView.as_view(), name='events'),
EventsView looks like this:
class EventsView(APIView):
    def dispatch(self, request, *args, **kwargs):
        return super(EventsView, self).dispatch(request, *args, **kwargs)
    def get(self, request):
        logger.info("Here")
        events = EventsQuery(request)
        if events.is_valid():
            events.build_response()
        return events.get_response()
If I visit /api/events/?format=json I get a set of results as valid JSON, and I see "Here" logged to my log file.
If I visit /api/events/?format=csv I get a 404 response with a JSON body of
{
"detail": "Not found."
}
...and nothing is logged.
The lack of logging is what's throwing me. It's like it's not even getting to the EventsView class, but how could changing a querystring value in the URL stop it being routed to that class? And how do I find out where it IS being routed to?
Edit: The content of EventsQuery.get_response() is:
def get_response(self):
    if self.has_error:
        self.response = {
            'success': self.success,
            'errors': self.errors
        }
        resp_status = status.HTTP_400_BAD_REQUEST
    else:
        resp_status = status.HTTP_200_OK
    return Response(
        self.response, status=resp_status, content_type=self.content_type
    )
I have just come across this issue and dove in to understand it.
The root cause is that in DRF the 'format' url parameter is used to determine which renderer class to use in the content negotiation phase, and the content negotiator returns a 404 if none of the renderer classes have a format attribute matching the format url query parameter. Take a look at the code in rest_framework.negotiation.DefaultContentNegotiation.filter_renderers method.
Someone else had this issue in the DRF datatables repo - https://github.com/izimobil/django-rest-framework-datatables/issues/4
To repeat for posterity what I posted there - you can get around this in (at least) two ways. The most robust way would be to create your own content negotation class which follows a different login in filtering the renderers. For my purposes, where I just wanted to use the format kwarg to determine which serialiser class to use in a viewset, I was happy to work around the issue by creating a dummy renderer class with the format attribute that I wanted to use. Here's an example:
from rest_framework.renderers import JSONRenderer
class GeoJsonRenderer(JSONRenderer):
    format = "geojson"
class MyViewSet(ModelPermissionsMixin, ModelViewSet):
    def get_renderers(self):
        return super().get_renderers() + [GeoJsonRenderer()]
    def get_serializer_class(self):
        format = self.request.query_params.get("format")
        if format and format == "geojson":
            return GeoJsonSerialiser
        return super().get_serializer_class()
I'm still no clearer on the root cause, but I've worked around the issue by using format_suffix_patterns to pass the format parameter, rather than passing it as a querystring param. I did confirm - by overriding APIView.handle_exception() - that the parent class was raising a 404 exception, but I've no idea why.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With