How to set Content-Disposition header using Django Rest Framework - django

I am serving an image using the Django REST framework. Unfortunately it downloads instead of displays. I guess I have to set the header Content-Disposition = 'inline'. How do I do this in the View or the Renderer?
class ImageRenderer(renderers.BaseRenderer):
media_type = 'image/*'
format = '*'
charset = None
render_style = 'binary'
def render(self, data, media_type=None, renderer_context=None):
return data
class ImageView(APIView):
renderer_classes = (ImageRenderer, )
def get(self, request, format=None):
image=MyImage.objects.get(id=1)
image_file = image.thumbnail_png.file
return Response(image)

According to this page in the Django docs, you can set the Content-Disposition header in this way:
response = Response(my_data, content_type='image/jpeg')
response['Content-Disposition'] = 'attachment; filename="foo.jpeg"'

Related

How to pass data saved from a POST method to the GET method using REST API Django (without a model)?

I have created an API that allows me to upload an image using the POST method in POSTMAN. After submission, I want to display that image name after making a GET request. I am not using any model and I don't intend to grab the image from the directory it is stored in; since I will be uploading images in a server later.
I have looked at multiple sources. A few examples are this, and this.
This is my current code so far but not successful:
views.py:
class API(APIView):
parser_classes = (MultiPartParser,)
def get(self, request, *args, **kwargs):
name = self.request.GET.get('image')
if name:
return Response({"img_name": name}, status=200)
return Response({"img_name" : None}, status = 400)
def post(self, request):
file = self.request.data
img_file = file['image'] #store the image data in this variable
if img_file:
uploaded_file = img_file
img = [{"image_name": uploaded_file}]
serializer = ImgSerializer(img, many = True).data
return Response(serializer, status = 200)
else:
return Response("Please upload", status = 400)
serializers.py:
from rest_framework import serializers
class ImgSerializer(serializers.Serializer):
image_name = serializers.CharField()
My expected result within GET request should be like this:
{'image_name' : 'image_name_from_POST_Request'}
But I am getting this result instead:
None
How can I pass data from the POST request to the GET request using Django's rest framework? Is there an efficient way to deploy this requirement without using a model?
I figured it out. I just created a JSON file in the POST method and stored the necessary data in it. Finally, in order to view the data within the GET method, I opened the file and returned it as a Response.
views.py:
class API(APIView):
parser_classes = (MultiPartParser,)
def get(self, request):
with open('data.txt') as json_file:
data = json.load(json_file)
if data:
return Response(data, status=200)
return Response({"name" : None}, status = 400)
def post(self, request):
posted_file = self.request.data
img_file = posted_file['image']
if img_file:
uploaded_file = img_file
data = [{"image_name": uploaded_file}]
json_data = {"image_name": uploaded_file}
data = {}
data['key'] = []
data['key'].append(json_data)
with open('data.txt', 'w') as outfile:
json.dump(image, outfile)
serializer = ImgSerializer(image, many = True).data
return Response(serializer, status = 200)
else:
return Response(serializer.errors, status = 400)

Django REST framework, content negotiation

I am trying to get my endpoint to return a uri-list when asked for that and a json string as default. I am testing this in a unit test looking a bit like:
[...]
headers = {'Accept': 'text/uri-list'}
response = self.client.get('/api/v1/licenses/', headers=headers)
[...]
I have written a URIListRenderer like this:
from rest_framework import renderers
class URIListRenderer(renderers.BaseRenderer):
media_type = 'text/uri-list'
def render(self, data, media_type='None', renderer_context=None):
return "\n".join(data).encode()
Next I am trying to get my Response in my View to be rendered using my renderer:
class RestLicenses(APIView):
"""
List all licenses, or create a new license
"""
permission_classes = (IsAuthenticated,)
parser_classes = (MultiPartParser,)
renderer_classes = (JSONRenderer, URIListRenderer,)
def get(self, request, format=None,):
models = LicenseModel.objects.all()
if len(models) == 0 :
return Response('[]',status=204)
if request.META.get('headers') is not None :
if request.META.get('headers').get('Accept') == 'text/uri-list' :
result = [];
for m in models :
result.append(reverse('downloadLicense', args=[m.pk], request=request))
return Response(result, status=200)
serializer = LicenseJSONSerializer(request, models, many=True)
serializer.is_valid()
return HttpResponse(JSONRenderer().render(serializer.data), content_type='application/json', status=200)
But it seems impossible to get it to choose any other renderer than the first one in the list. How do I make it choose my URIListRenderer and not the json one?
Your unit test is not setting the headers correctly. As described here, you should use CGI style headers when using the Django test client:
response = self.client.get('/api/v1/licenses/', HTTP_ACCEPT='text/uri-list')
The content negotiation uses the real HTTP Accept header. In your code, you check that "headers" is set, but that's not the real HTTP Accept header. It should be:
if request.META.get('HTTP_ACCEPT') == "text/uri-list":
...
This is a block of code from the method finalize_response:
if isinstance(response, Response):
if not getattr(request, 'accepted_renderer', None):
neg = self.perform_content_negotiation(request, force=True)
request.accepted_renderer, request.accepted_media_type = neg
response.accepted_renderer = request.accepted_renderer
response.accepted_media_type = request.accepted_media_type
response.renderer_context = self.get_renderer_context()
As you can see, before it performs content negotiaton, it checks to see if the view has already set the renderer needed and if not tries to perform the negotiation by itself.
So you can do this in your get method:
if request.META.get('headers') is not None :
if request.META.get('headers').get('Accept') == 'text/uri-list' :
request.accepted_renderer = URIListRenderer
result = [];
for m in models :
result.append(reverse('downloadLicense', args=[m.pk], request=request))
return Response(result, status=200)
Another thing you should probably do is to place your custom renderer class before JSONRenderer in the renderer_classes list. In that way, it will first check the special case before the more general case during content negotiation. Is suspect that the request format also matches JSOnRender and it overshadows the custom renderer. Hope this helps

Using django rest framework to send an image

I want to use DRF to send an image to response to a certain url, for this purpose I have written these codes:
#Renderer class
class ImageRenderer(renderers.BaseRenderer):
media_type = 'image/png'
format = 'image'
def render(self, data, media_type=None, renderer_context=None):
return data
#view class
class ShowImage(APIView):
renderer_classes = (ImageRenderer,)
def get(self, request, format=None):
print ('format', format)
if format == 'image':
image_file = open('path_to_image', 'rb')
response = HttpResponse(image_file, content_type='image/png')
response['Content-Disposition'] = 'attachment; filename={}'.format('image_filename')
#urls.py
urlpatterns = format_suffix_patterns([
url(r'image/?$', views.ShowImage.as_view())
])
But my problem is that always the input format is None although the request.accepted_media_type shows me image/png
I tried these requests using httpie:
http -vj 127.0.0.1:8000/api/image Accept:image/png
http -vj 127.0.0.1:8000/api/image.image Accept:image/png
But I could not get the format I expected. What is my mistake in using DRF?

Retrieving images from GridFS using django-tastypie-mongoengine

I have a project in Django, and I'm using mongoengine to save images into a Mongo database using GridFSStorage.
All ok so far, but the problem is... when trying to retrieve the images via http request, with a REST API made with django-tastypie-mongoengine, I get back a json object like this:
{"file": "<GridFSProxy: 516ed7cf56ba7d01eb09f522>", "id": "516ed7cf56ba7d01eb09f524", "resource_uri": "/api/v1/pic/516ed7cf56ba7d01eb09f524/"}
Does anybody know how could I get the file from GridFS via http request?
Many thanks!
You'll need to write your own view, but you can make it seem like it's part of the API. First, the view:
def api_image(pk):
obj = get_object_or_404(Model, pk=pk)
image_file = obj.file
return Response(image_file.read(),
mime_type='image/png') # or whatever the MIME type is
Then, you can map it in your urls.py:
url('^/api/v1/pic/(?P<pk>\w+)/file/$', api_image)
And to make sure tastypie shows what you want in the output:
def dehydrate_file(self, bundle):
return '/api/v1/pic/%s/file/' % (bundle.obj.id)
Just make sure the fake API view appears ahead of your actual API definitions, and you should be all set!
Paul's hint was very useful. Here i have implemented this completely in tastypie manner for uploading and downloading images.
Here you go..
1. Overriding deseriazer to support 'multipart'.
class MultipartResource(object):
def deserialize(self, request, data, format=None):
if not format:
format = request.META.get('CONTENT_TYPE', 'application/json')
if format == 'application/x-www-form-urlencoded':
return request.POST
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
2. Model class
class Research(Document):
user = ReferenceField(User)
academic_year = StringField(max_length=20)
subject = StringField(max_length=150)
topic = StringField(max_length=50)
pub_date = DateTimeField()
authored = StringField(max_length=20)
research_level = StringField(max_length=20)
paper_presented = BooleanField()
thesis_written = BooleanField()
proof_image = ImageField()
3. Resource class
class ResearchResource(MultipartResource, MongoEngineResource):
class Meta:
queryset = Research.objects.all()
list_allowed_methods = ['get','post']
resource_name = 'research'
authentication = SessionAuthentication()
authorization = Authorization()
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/$" % self._meta.resource_name,
self.wrap_view('dispatch_list'), name="api_dispatch_list"),
#url to download image file.
url(r"^(?P<resource_name>%s)/(?P<pk>\w+)/file/$"% self._meta.resource_name,
self.wrap_view('get_image'), name="api_get_image"),
]
#Preparing image url dynamically
def dehydrate_proof_image(self, bundle):
return '/api/v1/%s/%s/file/' % (self._meta.resource_name,bundle.obj.id)
#view will call based on image url to download image.
def get_image(self, request, **kwargs):
obj = Research.objects.get(id=kwargs['pk'])
image_file = obj.proof_image
return HttpResponse(image_file.read(), content_type="image/jpeg"))
Hope this will be very useful for everyone in future. :)

Django - serving an ical file

I'm server an icalender file through django_ical. Problem is that the file is named download.ics. I'm trying to change this to MyCalender.ics. If found this old snippet. I would prefer using django_ical, because it ingerates nicely with django syndication.
cal = vobject.iCalendar()
cal.add('method').value = 'PUBLISH' # IE/Outlook needs this
for event in event_list:
vevent = cal.add('vevent')
icalstream = cal.serialize()
response = HttpResponse(icalstream, mimetype='text/calendar')
response['Filename'] = 'filename.ics' # IE needs this
response['Content-Disposition'] = 'attachment; filename=filename.ics'
In django_ical the ICalFeed is inherited from django.contrib.syndication.views.Feed
In your app you inherit from ICalFeed to provide items, item_title and other methods that generate data for ics file.
You can override the __call__ method. The call to super will return you HttpResponse and you will add custom headers to it.
The code will be something like:
class EventFeed(ICalFeed):
"""
A simple event calender
"""
product_id = '-//example.com//Example//EN'
timezone = 'UTC'
def items(self):
return Event.objects.all().order_by('-start_datetime')
# your other fields
def __call__(self, request, *args, **kwargs):
response = super(EventFeed, self).__call__(request, *args, **kwargs)
if response.mimetype == 'text/calendar':
response['Filename'] = 'filename.ics' # IE needs this
response['Content-Disposition'] = 'attachment; filename=filename.ics'
return response
This code is not tested, so there might be some typos. Also you need to catch if there were errors in call to super. I do it by response.mimetype == 'text/calendar' but maybe there is a better way to do it