I'm trying to create an endpoint to upload images(using postman) to a specific folder using django rest framework. This is my settings for the folder,
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
This is my model,
class UserMedia(models.Model):
user = models.OneToOneField(User, related_name='medias', on_delete=models.CASCADE, )
profile_image_web = models.FileField(null=True)
profile_image_android = models.FileField(null=True)
profile_image_ios = models.FileField(null=True)
thumbnail = models.FileField(null=True)
This is the Serializer,
class UserMediaSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserMedia
fields = (
'profile_image_web', 'profile_image_ios', 'profile_image_android', 'thumbnail',
)
This is the api,
class CreateUpdateUserMedia(views.APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request, **kwargs):
serializer = UserMediaSerializer(request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now when I try to upload ONE image corresponding to one of the fields using POSTMAN, this is the error I get.
'Cannot call `.is_valid()` as no `data=` keyword argument was '
AssertionError: Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.
Which is perfectly understandable, but I dont know how to fix it.
Here are my questions,
How do I correctly upload images using django rest framework.
I don't expect this api to be called with 4 images together, but 4 times using one image at a time, how do I pass the relevant name and
modify the serializer accordingly.
How do I provide a subpath to the root media directory.
Finally I want the serializer to display the full image url, how do I do that?
You are using serializer = UserMediaSerializer(request.data) but you should call it by serializer = UserMediaSerializer(data=request.data)
For uploading images in Django rest framework you should either upload images on S3 and pass s3 url in DRF API or use base64 field in serializer and send base64 encoded value of image in API
import uuid
import base64
import imghdr
from django.utils.translation import ugettext_lazy as _
from django.core.files.base import ContentFile
from rest_framework import serializers
ALLOWED_IMAGE_TYPES = (
"jpeg",
"jpg",
"png",
"gif"
)
class Base64ImageField(serializers.ImageField):
"""
A django-rest-framework field for handling image-uploads through raw post data.
It uses base64 for en-/decoding the contents of the file.
"""
def to_internal_value(self, base64_data):
# Check if this is a base64 string
if not base64_data:
return None
if isinstance(base64_data, basestring):
# Try to decode the file. Return validation error if it fails.
try:
decoded_file = base64.b64decode(base64_data)
except TypeError:
raise serializers.ValidationError(_("Please upload a valid image."))
# Generate file name:
file_name = str(uuid.uuid4())[:12] # 12 characters are more than enough.
# Get the file name extension:
file_extension = self.get_file_extension(file_name, decoded_file)
if file_extension not in ALLOWED_IMAGE_TYPES:
raise serializers.ValidationError(_("The type of the image couldn't been determined."))
complete_file_name = file_name + "." + file_extension
data = ContentFile(decoded_file, name=complete_file_name)
return super(Base64ImageField, self).to_internal_value(data)
raise serializers.ValidationError('This is not an base64 string')
def to_representation(self, value):
# Return url including domain name.
return value.name
def get_file_extension(self, filename, decoded_file):
extension = imghdr.what(filename, decoded_file)
extension = "jpg" if extension == "jpeg" else extension
return extension
Updated
You should use ImageField (not FileField) for images.
You can use above field directly in serializer like any other field.
class UserMediaSerializer(serializers.ModelSerializer):
profile_image_web = Base64ImageField(required=False)
class Meta:
model = models.UserMedia
fields = ('profile_image_web', 'profile_image_ios', 'profile_image_android', 'thumbnail')
Related
I have a model called "Post" that looks for example like this:
# models.py
from django.db import models
from easy_thumbnails.fields import ThumbnailerImageField
class Post(models.Model):
name = models.CharField(max_length=255)
cover = ThumbnailerImageField(upload_to='posts')
Then I have a serializer for the model:
# serializers.py
class PostSerializer(serializers.ModelSerializer):
cover = ThumbnailSerializer(alias='small')
class Meta:
model = Post
fields = ['id', 'name', 'cover']
Using the Thumbnail serializer:
from rest_framework import serializers
from easy_thumbnails.templatetags.thumbnail import thumbnail_url
class ThumbnailSerializer(serializers.ImageField):
""" Serializer for thumbnail creation using easy-thumbnails (Needed when using Django Rest Framework) """
def __init__(self, alias, *args, **kwargs):
super().__init__(*args, **kwargs)
self.read_only = True
self.alias = alias
def to_representation(self, value):
if not value:
return None
url = thumbnail_url(value, self.alias)
request = self.context.get('request', None)
if request is not None:
return request.build_absolute_uri(url)
return url
Finally I have a view:
# views.py
class PostView(generics.RetrieveAPIView):
queryset = Post.objects.filter(enabled=True)
serializer_class = PostSerializer
Now inside my test I try creating a post and fetching the data (im using PyTest):
# tests.py
def test_post_endpoint(client):
post = Post.objects.create(
name="Post 1",
cover="posts/test_image.jpg",
)
response = client.get('/posts/')
assert response.status_code == 200
print(response.data['cover'])
# This prints: http://testserver/posts/
# Instead of: http://testserver/posts/test_image.small.jpg
I also tried using:
cover=SimpleUploadedFile(
name='test_image.jpg',
content=open(image_path, 'rb').read(),
content_type='image/jpeg'
)
But this ended up uploading the image to S3 which I dont want since its just a test and it should not upload anything to the cloud.
How can I get a proper response for the cover data? Something like this:
'http://testserver/posts/test_image.small.jpg'
It seems you want to modify Django's MEDIA_ROOT and MEDIA_URL path in the setting.py for test purposes. These values can be specified based on environment variables, like this:
# in settings.py
import os
...
if os.environ.get("TEST_ENV", '') == 'TRUE':
MEDIA_URL = 'http://testserver'
# or something like that
else:
MEDIA_URL = '<your default url>'
before getting started with tests, the environment variable should be set:
export TEST_ENV=TRUE
this blog post might be helpful.
also, Django's docs on how to handle files with MEDIA_URL may help.
this third-party package for splitting Django settings may also help.
I am building a react frontend server and a django backend server.
I transferred the image file from react fronted to Django backend using axios.
However, the contents of the image file in the backend media folder are not visible.
So, I compared the size of the original file before transmission and the size of the transferred file in the media folder.
The size of the original file was 687,687, and the size of the transferred file was slightly increased to 687,870.
However, rather than transferring from a react frontend server,
www.localhost:8000/admin
I connected to django backend server admin and uploaded the file, and it was confirmed that it uploaded normally.
I think react axios seems to have some dummy data in the process of transferring files.
react axios frontend code.
export function uploadFile(file) {
let url = `${ROOT_URL}/upload/${file.name}`;
const formData = new FormData();
formData.append('file', file)
const config = { headers: { 'Content-Type': 'multipart/form-data' } };
return (dispatch) => {
axios.post(url, formData).then(response => {
dispatch({
type: UPLOAD_FILE,
payload: response
})
})
django rest framework backend code
models.py
class File(models.Model):
file = models.FileField(blank=False, null=False)
def __str__(self):
return self.file.name
serializer.py
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = ("__all__")
views.py
class FileUploadView(CreateAPIView):
parser_classes = (FileUploadParser, )
def post(self, request, filename, format=None):
file_serializer = FileSerializer(data=request.data)
file_obj = request.data['file']
print(file_obj.size)
if file_serializer.is_valid():
file_serializer.save()
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I want to know why the file has increased in capacity and why the file is broken.
Help.
================================================
Picture , Compare hex data of two files
Upload a picture comparing the hex values of both files
The hex data in front of the transferred file.
content-disposition: form-data; name="" ..........
This seems to be the data that is transmitted. Not a PNG file.
The same thing happened when transferring files to postman as well as react axios.
I think I need to modify the code in the django backend. Please help me.
parser_classes = (FileUploadParser, )
i deleted this code on views.py, and it work.
================================================
and. i tried many ways.
from django import forms
from .models import File
class FileForm(forms.ModelForm):
class Meta:
model = File
fields = ('file', )
create FileForm.
def fileUpload(request):
form = FileForm(request.POST, request.FILES)
form.save()
it also work.
This time, it seems to be a chance to read the DRF document in detail in django.
I hope this article helps. For new developers like myself
I need to return generated file download as a Django REST Framework response. I tried the following:
def retrieve(self, request, *args, **kwargs):
template = webodt.ODFTemplate('test.odt')
queryset = Pupils.objects.get(id=kwargs['pk'])
serializer = StudentSerializer(queryset)
context = dict(serializer.data)
document = template.render(Context(context))
doc = converter().convert(document, format='doc')
res = HttpResponse(
FileWrapper(doc),
content_type='application/msword'
)
res['Content-Disposition'] = u'attachment; filename="%s_%s.zip"' % (context[u'surname'], context[u'name'])
return res
But it returns a msword document as json.
How do I make it start downloading as file instead?
Here's an example of returning a file download directly from DRF. The trick is to use a custom renderer so you can return a Response directly from the view:
from django.http import FileResponse
from rest_framework import viewsets, renderers
from rest_framework.decorators import action
class PassthroughRenderer(renderers.BaseRenderer):
"""
Return data as-is. View should supply a Response.
"""
media_type = ''
format = ''
def render(self, data, accepted_media_type=None, renderer_context=None):
return data
class ExampleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Example.objects.all()
#action(methods=['get'], detail=True, renderer_classes=(PassthroughRenderer,))
def download(self, *args, **kwargs):
instance = self.get_object()
# get an open file handle (I'm just using a file attached to the model for this example):
file_handle = instance.file.open()
# send file
response = FileResponse(file_handle, content_type='whatever')
response['Content-Length'] = instance.file.size
response['Content-Disposition'] = 'attachment; filename="%s"' % instance.file.name
return response
Note I'm using a custom endpoint download instead of the default endpoint retrieve, because that makes it easy to override the renderer just for this endpoint instead of for the whole viewset -- and it tends to make sense for list and detail to return regular JSON anyway. If you wanted to selectively return a file download you could add more logic to the custom renderer.
This may work for you:
file_path = file_url
FilePointer = open(file_path,"r")
response = HttpResponse(FilePointer,content_type='application/msword')
response['Content-Disposition'] = 'attachment; filename=NameOfFile'
return response.
For FrontEnd code refer this
I am using DRF and i found a view code to download file, which would be like
from rest_framework import generics
from django.http import HttpResponse
from wsgiref.util import FileWrapper
class FileDownloadListAPIView(generics.ListAPIView):
def get(self, request, id, format=None):
queryset = Example.objects.get(id=id)
file_handle = queryset.file.path
document = open(file_handle, 'rb')
response = HttpResponse(FileWrapper(document), content_type='application/msword')
response['Content-Disposition'] = 'attachment; filename="%s"' % queryset.file.name
return response
and url.py will be
path('download/<int:id>/',FileDownloadListAPIView.as_view())
I am using React.js in frontend and i get a response like
handleDownload(id, filename) {
fetch(`http://127.0.0.1:8000/example/download/${id}/`).then(
response => {
response.blob().then(blob => {
let url = window.URL.createObjectURL(blob);
let a = document.createElement("a");
console.log(url);
a.href = url;
a.download = filename;
a.click();
});
});
}
and after i got successful in downloading a file which also opens correctly and i hope this gonna work. Thanks
For me, using Python 3.6, Django 3.0, and DRF 3.10, The problem came from using the wrong type of response. I needed to use a django.http.HttpResponse, as seen below:
from django.http import HttpResponse
...
with open('file.csv', 'r') as file:
response = HttpResponse(file, content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=file.csv'
return response
I solved my problem by saving file in media folder and sending of the link of it to front-end.
#permission_classes((permissions.IsAdminUser,))
class StudentDocxViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs):
template = webodt.ODFTemplate('test.odt')
queryset = Pupils.objects.get(id=kwargs['pk'])
serializer = StudentSerializer(queryset)
context = dict(serializer.data)
document = template.render(Context(context))
doc = converter().convert(document, format='doc')
p = u'docs/cards/%s/%s_%s.doc' % (datetime.now().date(), context[u'surname'], context[u'name'])
path = default_storage.save(p, doc)
return response.Response(u'/media/' + path)
And handled this like in my front-end (AngularJS SPA)
$http(req).success(function (url) {
console.log(url);
window.location = url;
})
In models.py
class Attachment(models.Model):
file = models.FileField(upload_to=attachment_directory_path, blank=True, null=True)
...
#property
def filename(self):
return self.file.name.split('/')[-1:][0]
in views.py
import mimetypes
from django.http import FileResponse
class AttachmentViewSet(ModelViewSet):
...
#action(methods=['GET'], detail=True)
def download(self, request, **kwargs):
att = self.get_object()
file_handle = att.file.open()
mimetype, _ = mimetypes.guess_type(att.file.path)
response = FileResponse(file_handle, content_type=mimetype)
response['Content-Length'] = att.file.size
response['Content-Disposition'] = "attachment; filename={}".format(att.filename)
return response
and in frontend, I used axios for download files. api is axios client.
export function fileDownload(url, filename){
return api.get(url, { responseType: 'blob' })
.then((response)=>{
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
})
}
hope that it helps
Using django-downloadview this can be done like so:
from rest_framework.decorators import action
from django_downloadview import ObjectDownloadView
class DocumentViewSet(viewsets.ReadOnlyModelViewSet):
#action(detail=True)
def download(self, request, pk):
return ObjectDownloadView.as_view(
model=, # your model here
)(request, pk=pk)
The viewset can then be registered via DRF routers.
I am newbie in Django and Django REST Framework. I have the following serializer class which I am using to upload a file along other information. But, while I run the API endpoint with uploaded file, the result is something like this:
HTTP 415 Unsupported Media Type
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"detail": "Unsupported media type \"multipart/form-data; boundary=----WebKitFormBoundaryybZ07gjZAqvcsZw3\" in request."
}
I tried hard by googling to solve this issue, but cannot come out in a solution, so here is my serializer and API views.
Serializer:
class ExampleSerializer(serializers.Serializer):
example_id = serializers.IntegerField()
description = serializers.CharField(allow_blank=True)
example_file = serializers.FileField(allow_empty_file=True)
def create_requirement_line(self):
request = self.context['request']
requirement_line = ExampleService().example_method(
example_id=self.validated_data['example_id'],
description=self.validated_data['description'],
example_file=self.validated_data['example_file']
)
return requirement_line
View:
class RequirementLineAPIView(BaseCreateAPIView):
serializer_class = ExampleSerializer
parser_classes = (FormParser,)
def post(self, request, format=None,*args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
try:
example_variable = serializer.example_method()
return Response(example_variable, status=status.HTTP_200_OK)
except ValidationError as e:
return Response(e.message, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
You should use the MultiPartParser instead of the FormParser if you're sending multipart/form-data.
Raised if there are no parsers that can handle the content type of the request data when accessing request.DATA or request.FILES.
check Django REST Framework2 documentation
import suitable parser
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
class SampleView(APIView):
parser_classes = (MultiPartParser,FormParser,JSONParser)
Try Using FileField parser
Using Parsers in django rest
I have a fully functional Django web form that will upload and store images on Amazon S3.
How can I also create an additional re-sized copies of the same image that is being uploaded and upload them at the same time to Amazon S3?
Ideally, I will have two additional sizes (120x90, 240x180) saved to /small/ and /medium/ folders on S3.
For the upload to S3, I'm using django-storages and boto.
I have looked around and tried different ways, but it seems that all the thumb generating codes floating around are dealing with locally stored files or images that are already stored somewhere online.
My Code:
models.py
def content_file_name(instance, filename):
ext = filename.split('.')[-1]
name = uuid.uuid4().hex
filename = "%s_%s.%s" % (instance.business_id, name, ext)
path = 'originals'
return os.path.join(path, filename)
class Photo(models.Model):
filename = models.ImageField(upload_to=content_file_name)
phototitle = models.CharField(max_length=50, null=True)
class Meta:
db_table = 'photo'
views.py
def create(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return ...
else:
form = UploadForm()
return render(request, 'upload.html', {
'form': form,
})
form.py
class UplaodForm(ModelForm):
class Meta:
model = Photo
fields = "__all__"
settings.py
AWS_STORAGE_BUCKET_NAME = '...'
AWS_ACCESS_KEY_ID = '...'
AWS_SECRET_ACCESS_KEY = '...'
...
INSTALLED_APPS = (
...
'storages',
My solution works with any storage, but I tested it for AWS via django-storage and boto3. I created a Storage named ThumbnailStorage inheriting from the default one (whatever it is) and added the creation of the thumbnails in method _save, so that I have the name and the content. As a last step I declared this ThumbnailStorage as the default Storage for the field:
the storage
import os
import io
from django.core.files.storage import get_storage_class
from PIL import Image
default_storage = get_storage_class()
class ThumbnailStorage(default_storage):
def _save(self, name, content):
if hasattr(content, 'content_type'):
if content.content_type.startswith('image/'):
self.generate_thumbnails(name, content)
return super()._save(name, content)
def generate_thumbnails(self, name, content):
name1, ext1 = os.path.splitext(name)
fname, ext = os.path.splitext(content.name)
format = (ext1 if ext1 != '.jpg' else '.jpeg').strip('.')
im = Image.open(content)
w, h = im.size
im.thumbnail((120, 90)) # <<< choose your dims
dest_filename = f'/small/{name}' # set your name
fh = default_storage().open(dest_filename, 'wb')
sfile = io.BytesIO()
im.save(sfile, format=format)
fh.write(sfile.getvalue())
fh.close()
the model
class Photo(models.Model):
filename = models.ImageField(
upload_to=content_file_name,
storage=TumbnailStorage() # <<< this is where the work is done
)
...
Note that jpg is not accepted by Pillow, so you need to translate to jpeg and that I was not able to im.save(fh) when the format is jpeg (while it works on png) so I had to add io.StringIO trick