Saving a file from FileResponse in Django - django

How to save file in FileField from FileResponse in Django.
I have the simplest PDF function to create PDF file in Django (according to the documentation).
import io
from django.http import FileResponse
from reportlab.pdfgen import canvas
def some_view(request):
# Create a file-like buffer to receive PDF data.
buffer = io.BytesIO()
# Create the PDF object, using the buffer as its "file."
p = canvas.Canvas(buffer)
# Draw things on the PDF. Here's where the PDF generation happens.
# See the ReportLab documentation for the full list of functionality.
p.drawString(100, 100, "Hello world.")
# Close the PDF object cleanly, and we're done.
p.showPage()
p.save()
# FileResponse sets the Content-Disposition header so that browsers
# present the option to save the file.
buffer.seek(0)
return FileResponse(buffer, as_attachment=True, filename='hello.pdf')
It returns PDF. But how to save this PDF in FileField?
I need something like this:
Models.py
class FileModel(models.Model):
file = models.FileField()
Views.py
def some_view(request):
[...same code as above to line buffer.seek(0)...]
obj = FileModel()
obj.file = FileResponse(buffer, as_attachment=True, filename='hello.pdf')
obj.save()

For this task Django provides the File wrapper object:
from django.core.files import File
def some_view(request):
# ...same code as above to line buffer.seek(0)...
obj = FileModel()
obj.file = File(buffer, name='hello.pdf')
obj.save()

Related

Django Rest Framework upload file and in response show the url without database ref

I am trying to upload file directly to server with my expected name and trying to give the file url send in response without saving its ref database field.
This is my views:
class DocumentUploadView(APIView):
parser_classes = [MultiPartParser]
def post(self, request, format=None):
file_obj = request.data['file']
return Response(
status=204
)
I tried to upload with postman and i see no data is saved in my server media folder.
Can anyone help me in this case? I dont want to save the url of the file in database imageField or or FileField, i just want to upload it to save it directly in server and in response, it should send me the url.
Can anyone help me in this case?
You are storing the file in memory but not in any specific directory. If you really want to serve the file via API without any database reference, you would need to write it explicitly into your media folder and then add the field manually to your response object. This approach might help guide you to your solution, please note that I did not test it myself.
from django.conf import settings
import os
class DocumentUploadView(APIView):
parser_classes = [MultiPartParser]
def post(self, request, format=None):
file_obj = request.FILES['file']
path = os.path.join(settings.MEDIA_ROOT, file_obj.name)
with open(path, 'w') as infile:
str_repr = file_obj.read().decode() # Assuming text based file
infile.write(str_repr)
full_url = f'{request.schema}://{request.get_host()}/{path}'
return Response({'file': full_url}, status=201)

How to view API data within a GET method that's created using POST method in Django (without a model)?

I have created a DRF API that allows me to submit an image using the POST method via POSTMAN. The image is not stored in the model. After it's submission, I want to view the image's name in the browser using the Django Rest Framework. After reading sources in the net, I found out that people used the GET method in order to view all the data in a model. However, I don't have a model (don't require it for now) so how can I implement this requirement?
The result should be something like this:
{
"image_name": <"name_of_the_image_stored">
}
This is what I had done so far:
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import ImgSerializer
from rest_framework import status
from rest_framework.parsers import FileUploadParser
class ImageAPI(APIView):
parser_classes = (FileUploadParser,)
def post(self, request):
#key is 'image' when uploading in POSTMAN
file = self.request.data
data = file['image']
if data:
uploaded_file = data
fs = FileSystemStorage(location=settings.PRIVATE_STORAGE_ROOT)
filename = fs.save(uploaded_file.name, uploaded_file)
data = [{"image_name": filename}]
serializer = ImgSerializer(data, many = True).data
return Response(serializer, status = 200)
else:
return Response("Please upload an image", status = 400)
def get(self, request):
#What should I do here in order to view the submitted image above?
serializers.py:
from rest_framework import serializers
class ImgSerializer(serializers.Serializer):
image_name = serializers.CharField()
urls.py:
from upload.views import ImageAPI
from rest_framework.urlpatterns import format_suffix_patterns
urlpatterns = [
path("api/", ImageAPI.as_view(), name = "api"),
]
urlpatterns = format_suffix_patterns(urlpatterns)
First of all parser_classes should be an attribute of ImageAPI class, as I can see you've created it as a local variable, which won't do what you want. According to docs the request.data property should be a dictionary with a single key file containing the uploaded file. And regarding viewing the saved image, here you can find some ways to do that.
This is an example:
...
def get(self, request, *args, **kwargs):
# here I assume you've sent the name of the image using query params,
# but there are other better ways to do that
image_name = request.GET.get('image')
# here you should read the file from your storage
image_file = <read_image_file_by_given_name(image_name)>
return HttpResponse(image_file, content_type='image/png')

Django 1.11: Flip image horizontally before saving into a Django model

I'm doing this application where I take a user's image and then flip it horizontally using ImageOps from the Pillow library. To do so I made a model like above:
from django.db import models
class ImageClient(models.Model):
image = models.ImageField(null=False, blank=False)
I made a form using ImageField with a html form with enctype="multipart/form-data" and in my views I did the following:
from django.shortcuts import render, redirect
from .forms import ImageForm
from .models import ImageClient
from PIL import Image, ImageOps
def new(request):
"""
Returns mirror image from client.
"""
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
image = Image.open(form.cleaned_data['image'])
image = ImageOps.mirror(image)
form_image = ImageClient(image=image)
form_image.save()
return redirect('img:detail', pk=form_image.id)
else:
form = ImageForm()
return render(request, 'img/new_egami.html', {'form':form})
....
As you see, when a check if the form is valid, I open the form's image and flip it horizontally (using ImageOps.mirror()) then I save it. But I always getting this error 'Image' object has no attribute '_committed'. I know the Image object is from Pillow, but I do not understand this error. Can someone explain and/or solve this error?
The error is raised because the image is a PIL Image object, whereas Django is expecting and requires its own File object. You could save the Image object to an absolute file path and then refer to it, but there are more efficient ways here and here. Here is an adaptation of #madzohan's answer in the latter link for your image operation:
# models.py
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image, ImageOps
class ImageClient(models.Model):
image = models.ImageField(null=False, blank=False, upload_to="image/path/")
def save(self, *args, **kwargs):
pil_image_obj = Image.open(self.image)
new_image = ImageOps.mirror(pil_image_obj)
new_image_io = BytesIO()
new_image.save(new_image_io, format='JPEG')
temp_name = self.image.name
self.image.delete(save=False)
self.image.save(
temp_name,
content=ContentFile(new_image_io.getvalue()),
save=False
)
super(ImageClient, self).save(*args, **kwargs)
and views.py:
...
if form.is_valid():
new_image = form.save()
return redirect('img:detail', pk=new_image.id)

Displaying a raw image using CBVs in Django

I want to do some image manipulation, but before I do I want to render the image. This is what I've tried, but it does not work. The page is blank, no errors, why?
class ImgThumbnail(DetailView):
queryset = Images.objects.all()
def render_to_response(self, context, **response_kwargs):
from PIL import Image
import requests
from io import BytesIO
response = requests.get('http://example.com/media/images/{}.jpg'.format(self.pk))
img = Image.open(BytesIO(response.content))
return HttpResponse(img, content_type='image/jpg')
You should use StreamingHttpResponse. In a way like:
(dynamic content type and content length as bonus)
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
from wsgiref.util import FileWrapper
from django.http import HttpResponse, StreamingHttpResponse
class ImgThumbnail(DetailView):
queryset = Images.objects.all()
def get(self, request, *args, **kwargs):
r = requests.get('http://example.com/media/images/{}.jpg'.format(self.pk))
if not r.status_code == 200:
return HttpResponse('', status=r.status_code)
wrapper = FileWrapper(StringIO(r.content))
response = StreamingHttpResponse(wrapper, content_type=r.headers.get('Content-Type'))
response['Content-Length'] = r.headers.get('Content-Length')
return response
DetailView doesn't have a render_to_response method, so there is no point in defining your own.
You're not using any of the functionality of DetailView here anyway. You should inherit from the base View class, and put your code in the get method.

Mongoengine FileField saving to disk?

Mongoengine stores FileField and ImageField to GridFS. What's the easiest approach to replicate the functionality of the original File/Image Field?
EDIT:
So this is the class I have in place at the moment. I'm able to load files and save them to disk, Mongo holds the path to the file in database.
I'm falling over on "to_python" as I believe it needs to create an object of the proxy_class but I can't see how, if all I'm getting is a path to the file (as the value passed in).
import os
import datetime
from mongoengine.python_support import str_types
from django.db.models.fields.files import FieldFile
from django.core.files.base import File
from django.core.files.storage import default_storage
from mongoengine.base import BaseField
from mongoengine.connection import get_db, DEFAULT_CONNECTION_NAME
from django.utils.encoding import force_text
#from django.utils.encoding import force_str
class DJFileField(BaseField):
proxy_class = FieldFile
def __init__(self,
db_alias=DEFAULT_CONNECTION_NAME,
name=None,
upload_to='',
storage=None,
**kwargs):
self.db_alias = db_alias
self.storage = storage or default_storage
self.upload_to = upload_to
if callable(upload_to):
self.generate_filename = upload_to
super(DJFileField, self).__init__(**kwargs)
def __get__(self, instance, owner):
# Lots of information on whats going on here can be found
# on Django's FieldFile implementation, go over to GitHub to
# read it.
file = instance._data.get(self.name)
if isinstance(file, str_types) or file is None:
attr = self.proxy_class(instance, self, file)
instance._data[self.name] = attr
elif isinstance(file, File) and not isinstance(file, FieldFile):
file_copy = self.proxy_class(instance, self, file.name)
file_copy.file = file
file_copy._committed = False
instance._data[self.name] = file_copy
elif isinstance(file, FieldFile) and not hasattr(file, 'field'):
file.instance = instance
file.field = self
file.storage = self.storage
# That was fun, wasn't it?
return instance._data[self.name]
def __set__(self, instance, value):
instance._data[self.name] = value
# The 3 methods below get used by the FieldFile proxy_object
def get_directory_name(self):
return os.path.normpath(force_text(datetime.datetime.now().strftime(self.upload_to)))
def get_filename(self, filename):
return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
def generate_filename(self, instance, filename):
return os.path.join(self.get_directory_name(), self.get_filename(filename))
def to_mongo(self, value):
# Store the path in MongoDB
# I also used this bit to actually save the file to disk.
# The value I'm getting here is a FileFiled and it all looks
# pretty good at this stage even though I'm not 100% sure
# of what's going on.
import ipdb; ipdb.set_trace()
if not value._committed and value is not None:
value.save(value.name, value)
return value.path
return value.path
def to_python(self, value):
# Now this is the real problem, value is the path that got saved
# in mongo. No idea how to return a FileField obj from here.
# self.instance and instance throw errors.
I think it would be a good addition - maybe called LocalFileField to make it more framework agnostic and if you provided tests and docs it would make a great addition to https://github.com/MongoEngine/extras-mongoengine
The only reason I'm not sold on having it in core - is if you are running a replicaset the file would still only be stored on one node.