Reputation: 65530
I have a working Django 1.8 site, and I want to add a RESTful API using django-rest-framework. I would like to support rendering to CSV and JSON formats, and am puzzling over how to do this.
In api/urls.py
I have this:
from django.conf.urls import url, include
from rest_framework import routers
from rest_framework.urlpatterns import format_suffix_patterns
import views
router = routers.DefaultRouter()
urlpatterns = [
url(r'^organisation/$', views.organisation),
]
urlpatterns = format_suffix_patterns(urlpatterns,
allowed=['json', 'csv'])
And I have this in api/views.py
:
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
@api_view(['GET'])
def organisation(request, format=None):
code = request.query_params.get('code', None)
print 'format', format
organisation = Organisation.objects.get(code=code)
serializer = OrgSerializer(organisation)
data = serializer.data
return JSONResponse(data)
But if I go to api/1.0/organisation.csv?code=123
, I still see:
format json
in the console.
What am I doing wrong? And how should I return CSV once I have managed to capture the format? (I suspect I'm probably doing the wrong thing by writing my own JSONResponse
, already.)
Upvotes: 19
Views: 27117
Reputation: 6179
I think that a StreamingHttpResponse
should be the preferred way. With django-storages
and ViewSet
it looks more or less like this.
@action(detail=True, methods=["get"])
def download(self, request, pk=None):
f = self.get_object()
response = StreamingHttpResponse(
streaming_content=f.file.chunks(), content_type="text/csv"
)
response[
"Content-Disposition"
] = f'attachment; filename="{f.name}.csv"'
response.status_code = status.HTTP_200_OK
return response
Also, FileResponse
is basically a StreamingHttpResponse
with some headers etc so both are great for this use case.
Upvotes: 2
Reputation: 4667
If you just need to download CSV (without Model serialization etc)
import csv
from django.http import HttpResponse
from rest_framework.views import APIView
class CSVviewSet(APIView):
def get(self, request, format=None):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="export.csv"'
writer = csv.DictWriter(response, fieldnames=['emp_name', 'dept', 'birth_month'])
writer.writeheader()
writer.writerow({'emp_name': 'John Smith', 'dept': 'Accounting', 'birth_month': 'November'})
writer.writerow({'emp_name': 'Erica Meyers', 'dept': 'IT', 'birth_month': 'March'})
return response
Upvotes: 18
Reputation: 541
This is an old post, but I've seen the accepted answer sets the CSVRenderer
as one of the defaults, which is not usually wanted.
I would implement the view this way:
...
from rest_framework.viewsets import ModelViewSet
from rest_framework.settings import api_settings
from rest_framework_csv.renderers import CSVRenderer
from .... import OrgSerializer
...
class OrganizationViewSet(ModelViewSet):
queryset = Organisation.objects.all()
http_method_names = ['get', '...list all the other verbs you want']
serializer_class = OrgSerializer
renderer_classes = tuple(api_settings.DEFAULT_RENDERER_CLASSES) + (CSVRenderer,)
def get_queryset(self):
if 'code' in self.request.GET:
code = self.request.GET['code']
return Organisation.objects.filter(code=code)
return Organisation.objects.all()
Of course, having the django-rest-framework-csv
installed and OrgSerializer
defined somewhere.
Then you can just set 'rest_framework.renderers.JSONRenderer'
as your default renderer in settings and the rest framework will automatically return csv content if you request it on the HTTP_ACCEPT
header - just for this view.
Upvotes: 12
Reputation: 65530
Got it. The trick is to install djangorestframework-csv, then add the following in settings:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework_csv.renderers.CSVRenderer',
),
}
And then scrap the JSONResponse
function in views.py
and just do return Response(serializer.data)
instead. Very easy in the end.
Upvotes: 9