Handling images via REST API - python

I'm trying to create a REST API with django-rest-framework which will be handling a few things, the one being sending and receiving images from the frontend. I found this article which tries to explain the idea, but it uses class-based approach in its' views.py and ideally I'd like to stick to a function-based one as I've already done some work that way (not including JWT authorization) and I'd prefer it to stay. I have no clue how to make my backend legible of receiving and sending images, could you please try to provide me with some code snippets (or better yet, articles) on how to do so? Thanks in advance!
One thing to mention is that ideally I want to have an endpoint which will handle creating a new object which will come with an image (a plant to be specific) and an endpoint which would handle updating (changing) the object's image.
My models.py:
class Plant(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=150)
date_added = models.DateField(auto_now_add=True)
description = models.TextField()
img = models.ImageField(blank=True, null=True, upload_to=upload_path)
plant_species = models.CharField(max_length=150)
last_watered = models.IntegerField(default=0)
how_often = models.IntegerField(default=0)
tracked=models.BooleanField(default=True)
My views.py:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
#api_view(['GET'])
def getRoutes(request):
routes = [
'/api/token',
'/api/token/refresh',
'/api/plants_data',
'/api/update_plant/<str:pk>'
]
return Response(routes)
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def getPlants(request):
user = request.user
plants = user.plant_set.all()
serializer = PlantSerializer(plants, many=True)
return Response(serializer.data)
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def updatePlantTracking(request, pk):
plant = Plant.objects.get(id=pk)
serializer = PlantSerializer(instance=plant, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=400)

send images or any media files in json string from frontend and make a serializer field to handle the json string.
import base64
import uuid
from rest_framework.fields import Field
from rest_framework import serializers
class Base64ContentField(Field):
"""
For image, send base64 string as json
"""
def to_internal_value(self, data):
try:
format, datastr = data.split(';base64,')
ext = format.split('/')[-1]
file = ContentFile(base64.b64decode(datastr), name=str(uuid.uuid4())+'.'+ext)
except:
raise serializers.ValidationError('Error in decoding base64 data')
return file
def to_representation(self, value):
if not value:
return None
return value.url
Your PlantSerializer looks like
class PlantSerializer(serializers.ModelSerializer):
img = Base64ContentField(required=False)
class Meta:
model = Plant
fields = '__all__'
Your post data with img field will look like this
{
"name": "some name",
"img": "data:image/png;base64,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",
# ... other fields
}
You can convert images to base64 from this link; In the frontend like reactJS they have packages to convert image to base64 string

Related

Problem sending information using POST method in django rest framework

Hi i'm knew in django rest framework:
views.py:
from django.shortcuts import render
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializers import BookModelSerializers
# for posting data
class PostModelData(APIView):
def post(self, request):
serializer = BookModelSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
models.py:
from django.db import models
from datetime import datetime
class Book(models.Model):
name = models.CharField(max_length=150, blank=True)
store_name = models.CharField(max_length=50, blank=True)
description = models.TextField(blank=True)
image = models.ImageField(
default='', upload_to='store_image/', null=True, blank=True)
fav = models.BooleanField(default=False, blank=True)
created_at = models.DateTimeField(default=datetime.now())
def __str__(self):
return self.name
urls.py:
from django.urls import path
from .views import GetAllData, Getfavdata, UpdateFavData, PostModelData
urlpatterns = [
path('getalldata/', GetAllData.as_view()),
path('getfavdata/', Getfavdata.as_view()),
path('updatefavdata/<int:pk>/', UpdateFavData.as_view()),
path('postmodeldata/', PostModelData.as_view()),
]
serializers.py:
from rest_framework import serializers
from .models import Book
class BookModelSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
When I post ('name','store_name','description','fav') with postman, the data that is stored is without details.
I wanted to know what the problem is?
I also removed (blank = True) in models.py, nothing is saved.
enter image description here
Ah, I looked at the picture your attached and found what's wrong.
You're sending your data as GET parameters in the URL. E.g. https://localhost:8000?name=lorem&storename=lorem2.
However, you have defined your view method to expect a POST request:
class PostModelData(APIView):
#Use this method for POST requests
def post(self, request):
pass
#Use this method for GET requests
def get(self, request):
pass
In this case though, when you want to receive data from the user and save it POST request is the correct choice, you just put your data in the wrong place.
Instead do this:
1: Change your code in your view
class PostModelData(APIView):
def post(self, request):
#If you want to send MULTIPLE entries per request
serializer = BookModelSerializers(data=request.data, many=True)
#If you want to send ONE entry per request
serializer = BookModelSerializers(data=request.data, many=False)
2: Go to Postman and do the following steps.
3: Remove all parameters from url so you just have http://localhost:8000/api/v1/postmodeldata/
4: Right next to the URL, make sure you have POST selected.
5: Under the URL bar, click on Body.
6: In the radiobuttons change to raw. And in the dropdown menu right next to it select JSON.
7: In the textarea write something like in this picture:
Example request
8: Click Send and you should get a 201 response from the server.
class BookModelSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
def create(self, validated_data):
book = Book.objects.create(**validated_data)
book.save()
return book

Django Rest Framework Api View GET

In my codes, I have a model Tweet, and in tweet_list_view, I want to show the list of tweets as API view.
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
return Response(serializer.data)
This is what I got as a result.
AssertionError at /tweets/
When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation.
You should either call `.is_valid()` first, or access `.initial_data` instead.
So I called the .is_valid method like following:
#api_view(['GET'])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(data=qs, many=True)
if serializer.is_valid():
return Response(serializer.data, status=201)
return Response({}, status=400)
Then I get:
TemplateDoesNotExist at /tweets/
rest_framework/api.html
At serializers.py
class TweetSerializer(serializers.ModelSerializer):
class Meta:
model = Tweet
fields = ['content', 'date_posted', 'likes']
models.py
class Tweet(models.Model):
content = models.TextField(blank=True, null=True)
image = models.FileField(upload_to='images/', blank=True, null=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
date_posted = models.DateTimeField(default=timezone.now)
likes = models.IntegerField(default=0)
def __str__(self):
return self.content
class Meta:
ordering = ['-date_posted']
It's looking for a template, but it's supposed to use the default Django template. Is there any way to fix this problem?
Update:
forgot the # in front of the api_view. Added it. Also added the renderer_class (jsonrenderer) to make sure avoiding the error.
You need to use the data attribute of the serializer only when you have a post, put or patch view. In your case just try it without the data attribute and it should be fine
from rest_framework.renderers import JSONRenderer
#api_view(['GET'])
#renderer_classes([JSONRenderer])
def tweet_list_view(request, *args, **kwargs):
qs = Tweet.objects.all().order_by('-date_posted')
serializer = TweetSerializer(qs, many=True)
return Response(serializer.data)
Here you can see it in the tutorial example of the official django rest framework docs

Return URL in POST-method when working with base64ImageField and nested serializers

First of all, I'm pretty new to Django and equally new to backend development so please go easy on me.
I'm setting up a backend for a mobile application in which a user should be able to post an advert with an image attached to it over Http Requests. Right now, this works like it should, a user sends a POST request containing all the needed information and gets a response back containing all the relevant information, except for the image which is null. I know that the image successfully gets uploaded to the server, however I can't figure out how to return the URL of the image back into the response.
The following is my code:
models.py
class Advert(models.Model):
owner=models.ForeignKey(User, on_delete=models.CASCADE, related_name="adverts")
book_title = models.CharField(max_length=250)
price = models.PositiveSmallIntegerField()
created_at = models.DateTimeField(default=now())
def __str__(self):
return self.book_title + ' - ' + self.contact_info
class AdvertImage(models.Model):
advert = models.ForeignKey(Advert, on_delete=models.CASCADE, related_name="image", null=True)
image = models.ImageField(upload_to = 'ad_images', null=True)
def __str__(self):
return self.image.name
My serializers looks as following:
serializers.py
from rest_framework import serializers
from .models import Advert, AdvertImage
from drf_extra_fields.fields import Base64ImageField
from django.contrib.auth.models import User
class AdvertPostSerializer(serializers.ModelSerializer):
image = Base64ImageField(max_length=None, use_url=True, required=False)
class Meta:
model = Advert
fields = (
'id',
'price',
'book_title',
'image')
def create(self, validated_data):
try:
image_data = validated_data.pop('image')
except:
image_data = None
advert = Advert.objects.create(**validated_data)
if image_data is not None:
image = AdvertImage.objects.create(advert=advert, image=image_data)
return advert
And this is my view:
views.py
class AdvertViewSet(viewsets.ModelViewSet):
queryset = Advert.objects.all()
permission_classes = (AllowAny,)
def get_serializer_class(self):
if self.action == 'create':
return AdvertPostSerializer
return AdvertSerializer
#action(methods=['get'], detail=False)
def newest(self,request):
newest = self.get_queryset().order_by('created_at').last()
serializer = self.get_serializer_class()(newest)
return Response(serializer.data)
def perform_create(self, serializer):
return serializer.save(owner=self.request.user)
To illustrate what happens, here is a POST request:
POST http://localhost:8000/post-advert/
"Authorization: Token 980436128332ce3"
book_title=my book
price=1000
image=data:image/png;base64,iVBORHJvZmlsZSB0e//+egAAAAASUVORK5CYII=
And this is the response:
{
"book_title": "my book",
"id": 45,
"image": null,
"price": 1000,
}
Looking in the database and sending a second GET-request to another view shows that everything is uploaded as it should and the foreign keys and whatnot works like they should, it's just that I have a really hard time figuring out how to send back the URL of the image as a response to a successful POST.
Alright so I managed to come up with a (hacky?) solution.
In serializers.py i put my Base64Field as read_only=True:
class AdvertPostSerializer(serializers.ModelSerializer):
image = Base64ImageField(max_length=None, use_url=True, required=False, read_only=True)
...
Then, in my views.py for my AdvertViewSet, I've overwritten my create() method as such:
def create(self, request, format='json'):
serializer = PostAdvertSerializer(data=request.data)
if serializer.is_valid():
advert = serializer.save()
if advert:
json = serializer.data
advertImageURL = AdvertImage.objects.get(advert=advert).image.url
json['img_link'] = request.build_absolute_uri(advertImageURL)
return Response(json, status=status.HTTP_201_CREATED)
And this returns the full path to my image!
Because your get_queryset() method in
newest = self.get_queryset().order_by('created_at').last()
returns Advert model object:
class AdvertViewSet(viewsets.ModelViewSet):
queryset = Advert.objects.all()
which do not have image field. Then you are creating AdvertPostSerializer object and initializing it with newest queryset which is queryset of Advert model without your image.
serializer = self.get_serializer_class()(newest)
You can somehow obtain AdvertImage object inside #newest action and try to add it to response, but think you can create only one model Advert with your image field and one serializer for it, where you will define Base64ImageField.

DRF using serializers with a dynamic primary key

Our API has a model defined:
class Job(models.Model):
file = models.FileField('File')
xml = models.FileField('XML')
There is a basic serializer:
class XmlSerializer(serializers.ModelSerializer)
file = serializers.FileField(read_only=True)
xml = serializers.FileField(required=True)
class Meta:
model = Job
fields = '__all__'
We don't want to change the file but we do want to change the xml field. The xml is uploaded by a system that doesn't know the primary key. Of course we need this to update the model.
I have the following code at the moment:
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
pk = 200
serializer = XmlSerializer(request.data)
return Response({})
pk = 200 serves as an example instead of the code we use to parse the xml. I know this doesn't work but it's to show my intention (more or less).
I have tried to use
id = serializers.SerializerMethodField()
def get_id(self, obj):
return 200
without success.
How do I get this primary key into the the serializer?
I was making it much too difficult. The solution was pretty easy.
class ProcessXml(mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = XmlSerializer
def post(self, request, format=None):
id = magic_xml_parser_function()
job = get_object_or_404(Job, pk=id)
serializer = XmlSerializer(job, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
magic_xml_parser_function() contains the id we found in the xml. This solved it for us.

Django Rest Framework File Upload

I am using Django Rest Framework and AngularJs to upload a file. My view file looks like this:
class ProductList(APIView):
authentication_classes = (authentication.TokenAuthentication,)
def get(self,request):
if request.user.is_authenticated():
userCompanyId = request.user.get_profile().companyId
products = Product.objects.filter(company = userCompanyId)
serializer = ProductSerializer(products,many=True)
return Response(serializer.data)
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(data=request.DATA)
As the last line of post method should return all the data, I have several questions:
how to check if there is anything in request.FILES?
how to serialize file field?
how should I use parser?
Editor's note:
This answer uses pre_save, which no longer exists in Django REST framework 3.0.
In a sufficiently new version of Django REST framework, MultiPartParser should be available by default, which allows uploading file with no special handling. See an answer below for an example.
I'm using the same stack and was also looking for an example of file upload, but my case is simpler since I use the ModelViewSet instead of APIView. The key turned out to be the pre_save hook. I ended up using it together with the angular-file-upload module like so:
# Django
class ExperimentViewSet(ModelViewSet):
queryset = Experiment.objects.all()
serializer_class = ExperimentSerializer
def pre_save(self, obj):
obj.samplesheet = self.request.FILES.get('file')
class Experiment(Model):
notes = TextField(blank=True)
samplesheet = FileField(blank=True, default='')
user = ForeignKey(User, related_name='experiments')
class ExperimentSerializer(ModelSerializer):
class Meta:
model = Experiment
fields = ('id', 'notes', 'samplesheet', 'user')
// AngularJS
controller('UploadExperimentCtrl', function($scope, $upload) {
$scope.submit = function(files, exp) {
$upload.upload({
url: '/api/experiments/' + exp.id + '/',
method: 'PUT',
data: {user: exp.user.id},
file: files[0]
});
};
});
Use the FileUploadParser, it's all in the request.
Use a put method instead, you'll find an example in the docs :)
class FileUploadView(views.APIView):
parser_classes = (FileUploadParser,)
def put(self, request, filename, format=None):
file_obj = request.FILES['file']
# do some stuff with uploaded file
return Response(status=204)
Finally I am able to upload image using Django. Here is my working code
views.py
class FileUploadView(APIView):
parser_classes = (FileUploadParser, )
def post(self, request, format='jpg'):
up_file = request.FILES['file']
destination = open('/Users/Username/' + up_file.name, 'wb+')
for chunk in up_file.chunks():
destination.write(chunk)
destination.close() # File should be closed only after all chuns are added
# ...
# do some stuff with uploaded file
# ...
return Response(up_file.name, status.HTTP_201_CREATED)
urls.py
urlpatterns = patterns('',
url(r'^imageUpload', views.FileUploadView.as_view())
curl request to upload
curl -X POST -S -H -u "admin:password" -F "file=#img.jpg;type=image/jpg" 127.0.0.1:8000/resourceurl/imageUpload
From my experience, you don't need to do anything particular about file fields, you just tell it to make use of the file field:
from rest_framework import routers, serializers, viewsets
class Photo(django.db.models.Model):
file = django.db.models.ImageField()
def __str__(self):
return self.file.name
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = models.Photo
fields = ('id', 'file') # <-- HERE
class PhotoViewSet(viewsets.ModelViewSet):
queryset = models.Photo.objects.all()
serializer_class = PhotoSerializer
router = routers.DefaultRouter()
router.register(r'photos', PhotoViewSet)
api_urlpatterns = ([
url('', include(router.urls)),
], 'api')
urlpatterns += [
url(r'^api/', include(api_urlpatterns)),
]
and you're ready to upload files:
curl -sS http://example.com/api/photos/ -F 'file=#/path/to/file'
Add -F field=value for each extra field your model has. And don't forget to add authentication.
After spending 1 day on this, I figured out that ...
For someone who needs to upload a file and send some data, there is no straight fwd way you can get it to work. There is an open issue in JSON API specs for this. One possibility I have seen is to use multipart/related as shown here, but I think it's very hard to implement in DRF.
Finally what I implemented was to send the request as FormData. You would send each file as file and all other data as text.
Now for sending the data as text you have two choices. case 1) you can send each data as a key-value pair or case 2) you can have a single key called data and send the whole JSON as a string in value.
The first method would work out of the box if you have simple fields but it will be an issue if you have nested serializes. The multipart parser won't be able to parse the nested fields.
Below I am providing the implementation for both the cases
models.py
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
caption = models.TextField(max_length=1000)
media = models.ImageField(blank=True, default="", upload_to="posts/")
tags = models.ManyToManyField('Tags', related_name='posts')
serializers.py -> no special changes needed, not showing my serializer here as it's too lengthy because of the writable ManyToMany Field implementation.
views.py
class PostsViewset(viewsets.ModelViewSet):
serializer_class = PostsSerializer
#parser_classes = (MultipartJsonParser, parsers.JSONParser) use this if you have simple key value pair as data with no nested serializers
#parser_classes = (parsers.MultipartParser, parsers.JSONParser) use this if you want to parse json in the key value pair data sent
queryset = Posts.objects.all()
lookup_field = 'id'
Now, if you are following the first method and are only sending non-Json data as key-value pairs, you don't need a custom parser class. DRF'd MultipartParser will do the job. But for the second case or if you have nested serializers (like I have shown) you will need a custom parser as shown below.
utils.py
from django.http import QueryDict
import json
from rest_framework import parsers
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# for case1 with nested serializers
# parse each field with json
for key, value in result.data.items():
if type(value) != str:
data[key] = value
continue
if '{' in value or "[" in value:
try:
data[key] = json.loads(value)
except ValueError:
data[key] = value
else:
data[key] = value
# for case 2
# find the data field and parse it
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
This serializer would basically parse any JSON content in the values.
The request example in postman for both cases:
Case 1
Case 2
If anyone interested in the easiest example with ModelViewset for Django Rest Framework.
The Model is,
class MyModel(models.Model):
name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
imageUrl = models.FileField(db_column='image_url', blank=True, null=True, upload_to='images/')
class Meta:
managed = True
db_table = 'MyModel'
The Serializer,
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = "__all__"
And the View is,
class MyModelView(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
Test in Postman,
models.py
from django.db import models
import uuid
class File(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
file = models.FileField(blank=False, null=False)
def __str__(self):
return self.file.name
serializers.py
from rest_framework import serializers
from .models import File
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = "__all__"
views.py
from django.shortcuts import render
from rest_framework.parsers import FileUploadParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from .serializers import FileSerializer
class FileUploadView(APIView):
permission_classes = []
parser_class = (FileUploadParser,)
def post(self, request, *args, **kwargs):
file_serializer = FileSerializer(data=request.data)
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)
urls.py
from apps.files import views as FileViews
urlpatterns = [
path('api/files', FileViews.FileUploadView.as_view()),
]
settings.py
# file uload parameters
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Send a post request to api/files with a your file attached to a form-data field file. The file will be uploaded to /media folder and a db record will be added with id and file name.
I solved this problem with ModelViewSet and ModelSerializer. Hope this will help community.
I also preffer to have validation and Object->JSON (and vice-versa) login in serializer itself rather than in views.
Lets understand it by example.
Say, I want to create FileUploader API. Where it will be storing fields like id, file_path, file_name, size, owner etc in database. See sample model below:
class FileUploader(models.Model):
file = models.FileField()
name = models.CharField(max_length=100) #name is filename without extension
version = models.IntegerField(default=0)
upload_date = models.DateTimeField(auto_now=True, db_index=True)
owner = models.ForeignKey('auth.User', related_name='uploaded_files')
size = models.IntegerField(default=0)
Now, For APIs this is what I want:
GET:
When I fire the GET endpoint, I want all above fields for every uploaded file.
POST:
But for user to create/upload file, why she has to worry about passing all these fields. She can just upload the file and then, I suppose, serializer can get rest of the fields from uploaded FILE.
Searilizer:
Question: I created below serializer to serve my purpose. But not sure if its the right way to implement it.
class FileUploaderSerializer(serializers.ModelSerializer):
# overwrite = serializers.BooleanField()
class Meta:
model = FileUploader
fields = ('file','name','version','upload_date', 'size')
read_only_fields = ('name','version','owner','upload_date', 'size')
def validate(self, validated_data):
validated_data['owner'] = self.context['request'].user
validated_data['name'] = os.path.splitext(validated_data['file'].name)[0]
validated_data['size'] = validated_data['file'].size
#other validation logic
return validated_data
def create(self, validated_data):
return FileUploader.objects.create(**validated_data)
Viewset for reference:
class FileUploaderViewSet(viewsets.ModelViewSet):
serializer_class = FileUploaderSerializer
parser_classes = (MultiPartParser, FormParser,)
# overriding default query set
queryset = LayerFile.objects.all()
def get_queryset(self, *args, **kwargs):
qs = super(FileUploaderViewSet, self).get_queryset(*args, **kwargs)
qs = qs.filter(owner=self.request.user)
return qs
I'd like to write another option that I feel is cleaner and easier to maintain. We'll be using the defaultRouter to add CRUD urls for our viewset and we'll add one more fixed url specifying the uploader view within the same viewset.
**** views.py
from rest_framework import viewsets, serializers
from rest_framework.decorators import action, parser_classes
from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework_csv.parsers import CSVParser
from posts.models import Post
from posts.serializers import PostSerializer
class PostsViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
parser_classes = (JSONParser, MultiPartParser, CSVParser)
#action(detail=False, methods=['put'], name='Uploader View', parser_classes=[CSVParser],)
def uploader(self, request, filename, format=None):
# Parsed data will be returned within the request object by accessing 'data' attr
_data = request.data
return Response(status=204)
Project's main urls.py
**** urls.py
from rest_framework import routers
from posts.views import PostsViewSet
router = routers.DefaultRouter()
router.register(r'posts', PostsViewSet)
urlpatterns = [
url(r'^posts/uploader/(?P<filename>[^/]+)$', PostsViewSet.as_view({'put': 'uploader'}), name='posts_uploader')
url(r'^', include(router.urls), name='root-api'),
url('admin/', admin.site.urls),
]
.- README.
The magic happens when we add #action decorator to our class method 'uploader'. By specifying "methods=['put']" argument, we are only allowing PUT requests; perfect for file uploading.
I also added the argument "parser_classes" to show you can select the parser that will parse your content. I added CSVParser from the rest_framework_csv package, to demonstrate how we can accept only certain type of files if this functionality is required, in my case I'm only accepting "Content-Type: text/csv".
Note: If you're adding custom Parsers, you'll need to specify them in parsers_classes in the ViewSet due the request will compare the allowed media_type with main (class) parsers before accessing the uploader method parsers.
Now we need to tell Django how to go to this method and where can be implemented in our urls. That's when we add the fixed url (Simple purposes). This Url will take a "filename" argument that will be passed in the method later on. We need to pass this method "uploader", specifying the http protocol ('PUT') in a list to the PostsViewSet.as_view method.
When we land in the following url
http://example.com/posts/uploader/
it will expect a PUT request with headers specifying "Content-Type" and Content-Disposition: attachment; filename="something.csv".
curl -v -u user:pass http://example.com/posts/uploader/ --upload-file ./something.csv --header "Content-type:text/csv"
Some of the solutions are deprecated (request.data should be used for Django 3.0+). Some of them do not validate the input. Also, I would appreciate a solution with swagger annotation. So I recommend using the following code:
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response
from rest_framework.views import APIView
class FileUploadAPI(APIView):
parser_classes = (MultiPartParser, )
class InputSerializer(serializers.Serializer):
image = serializers.ImageField()
#swagger_auto_schema(
request_body=InputSerializer
)
def put(self, request):
input_serializer = self.InputSerializer(data=request.data)
input_serializer.is_valid(raise_exception=True)
# process file
file = input_serializer.validated_data['image']
return Response(status=204)
I have used this view to upload file to aws. Here upload_file is a helper function while overall you can use this view to get upload the file in form-data.
class FileUploadView(GenericAPIView):
def post(self, request):
try:
file = request.data['file']
if file.content_type == 'image/png' or file.content_type == 'image/jpeg':
file_name = upload_file(file)
return Response({"name": file_name}, status=status.HTTP_202_ACCEPTED)
else:
raise UnsupportedMediaType(file.content_type)
except KeyError:
return Response("file missing.", status=status.HTTP_404_NOT_FOUND)
Best Straightforward Way to handle single upload file or multiple files in a single request is this
#api_view(['POST'])
def file_list(request): # use APIview or function based view or any view u want
# for single file
file = request.FILES["file"]
print(file)
# Do what ever you want with it
# for multiple file
files = request.FILES.getlist('file')
for file in files:
print(file)
# Do what ever you want with it
from rest_framework import status
from rest_framework.response import Response
class FileUpload(APIView):
def put(request):
try:
file = request.FILES['filename']
#now upload to s3 bucket or your media file
except Exception as e:
print e
return Response(status,
status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status, status.HTTP_200_OK)
If you are using ModelViewSet, well actually you are done! It handles every things for you! You just need to put the field in your ModelSerializer and set content-type=multipart/form-data; in your client.
BUT as you know you can not send files in json format. (when content-type is set to application/json in your client). Unless you use Base64 format.
So you have two choices:
let ModelViewSet and ModelSerializer handle the job and send the request using content-type=multipart/form-data;
set the field in ModelSerializer as Base64ImageField (or) Base64FileField and tell your client to encode the file to Base64 and set the content-type=application/json
from rest_framework import status, generics
from rest_framework.response import Response
from rest_framework import serializers
import logging
logger = logging.getLogger(__name__)`enter code here`
class ImageUploadSerializer(serializers.Serializer):
file = serializers.FileField()
class UploadImages(generics.GenericAPIView):
serializer_class = ImageUploadSerializer
permission_classes = [IsAuthenticated, ]
def post(self, request):
try:
data = self.serializer_class(data=request.data)
if data.is_valid() is False:
return Response({'error': ERROR_MESSAGES.get('400')}, status=status.HTTP_400_BAD_REQUEST)
is_file_upload_success, file_item = save_aws_article_image(data.validated_data.get('file'),
request.user, upload_type)
if is_file_upload_success:
logger.info('{0} file uploaded {1}'.format(file_item['file_obj'].path, datetime.now()))
return Response({'path': file_item['file_obj'].path, 'id': file_item['file_obj'].uuid,
'name': file_item['file_obj'].name},
status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(e, exc_info=True)
return Response({"error": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
In django-rest-framework request data is parsed by the Parsers.
http://www.django-rest-framework.org/api-guide/parsers/
By default django-rest-framework takes parser class JSONParser. It will parse the data into json. so, files will not be parsed with it.
If we want files to be parsed along with other data we should use one of the below parser classes.
FormParser
MultiPartParser
FileUploadParser
def post(self,request):
serializer = ProductSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
This is the one of the approach I've applied hopefully it'll help.
class Model_File_update(APIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = [IsAuthenticated] # it will check if the user is authenticated or not
authentication_classes = [JSONWebTokenAuthentication] # it will authenticate the person by JSON web token
def put(self, request):
id = request.GET.get('id')
obj = Model.objects.get(id=id)
serializer = Model_Upload_Serializer(obj, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=200)
else:
return Response(serializer.errors, status=400)
You can generalize #Nithin's answer to work directly with DRF's existing serializer system by generating a parser class to parse specific fields which are then fed directly into the standard DRF serializers:
from django.http import QueryDict
import json
from rest_framework import parsers
def gen_MultipartJsonParser(json_fields):
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# find the data field and parse it
qdict = QueryDict('', mutable=True)
for json_field in json_fields:
json_data = result.data.get(json_field, None)
if not json_data:
continue
data = json.loads(json_data)
if type(data) == list:
for d in data:
qdict.update({json_field: d})
else:
qdict.update({json_field: data})
return parsers.DataAndFiles(qdict, result.files)
return MultipartJsonParser
This is used like:
class MyFileViewSet(ModelViewSet):
parser_classes = [gen_MultipartJsonParser(['tags', 'permissions'])]
# ^^^^^^^^^^^^^^^^^^^
# Fields that need to be further JSON parsed
....
A DRF viewset fileupload example with React(axios) to send an audioBlob:
class MyViewSet(viewsets.ModelViewSet):
parser_classes = (MultiPartParser, FormParser)
queryset = MyModel.objects.all().order_by('created_at')
serializer_class = MySerializer
serializer:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
model:
class MyModel(models.Model):
sentence = models.ForeignKey(Sentence, related_name="voice_sentence", on_delete=models.CASCADE)
voice_record = models.FileField(blank=True, default='')
created_at = models.DateTimeField(auto_now_add=True)
axios:
export const sendSpeechText = async (audioBlob: any) => {
const headers = {
'Content-Type': 'application/json',
'Content-Disposition': 'attachment; filename=audiofile.webm'
}
const audiofile = new File([audioBlob], "audiofile.webm", { type: "audio/webm" })
const formData = new FormData();
formData.append("sentence", '1');
formData.append("voice_record", audiofile);
return await axios.post(
SEND_SPEECH_URL,
formData,
{
crossDomain: true,
headers: headers
},
)
}
NOTE: voice_record in formData should be the same in your model
There are majorly 3 ways for upload files in drf
suppose you have Tag model with title and logo fields and TagSerializer
class Tag(models.Model):
title = models.CharField(max_length=10, default='')
file = models.FileField(upload_to='tag/', blank=True, null=True, )
class TagSerializer(rest_serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
you can choose one of them according to your situation.
1- using serializer:
class UploadFile(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
serializer = TagSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
2- using write method :
def save_file(file: InMemoryUploadedFile, full_path):
with open(full_path, 'wb+') as f:
for chunk in file.chunks():
f.write(chunk)
class UploadFile(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
file: InMemoryUploadedFile = request.FILES['file']
# define file_save_path variable
full_path = file_save_path + file.name
save_file(file, full_path)
return Response(serializer.data, status=status.HTTP_200_OK)
3- using FileSystemStorage:
class UploadFile(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request):
file: InMemoryUploadedFile = request.FILES['file']
f = FileSystemStorage()
# this will save file in MEDIA_ROOT path
f.save(file.name, file)
return Response(serializer.data, status=status.HTTP_200_OK)
For the users who want to use or prefer a Function-Based Views for uploading files.
This is a Complete Guide from Creating Models > views > Serializers > URLs and Testing the endpoint with Postman. I have put the comments inside the code where required.
# models.py
# Imports
from django.db import models
import os
def document_path_and_name(instance, filename):
''' Change the filename to 'instance_id + document_name '''
ext = filename.split('.')[-1]
filename = "%s_%s.%s" % (instance.id, instance.document_name, ext)
''' if document_name is 'doucment one' in pdf and id is 1
then filname will be saved as = 1_document_one.pdf '''
return os.path.join('files/', filename)
class Document(models.Model):
# I'm using document_name and id to give the filename that would be save with
# this using document_path_and_name function.
# you can modify on your need.
document_name = models.CharField(max_length=100)
file = models.FileField(upload_to=document_path_and_name)
def __str__(self):
return self.document_name
We don't need a Serializer to validate the file upload here but would need one if we need to serialize the response. So let's go with a simple ReadOnly Serializer in this case.
# serializers.py
# imports
from rest_framework import serializers
class DocumentSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
document_name = serializers.CharField(read_only=True)
file = serializers.URLField(read_only=True)
Now in the api_view, we will be using the MultiPartParser decorator to upload files via a POST request. We would need a document_name and a file for this function to upload the file correctly as we had set the Model.
# views.py
# imports
from rest_framework.decorators import api_view, parser_classes
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
from .models import Document
from .serializers import DocumentSerializer
#api_view(['POST'])
#parser_classes([MultiPartParser])
def upload_document(request, filename, format=None):
"""
A view that can accept POST requests with .media_type: multipart/form-data content.
"""
file = request.FILES['file']
doc = Document.objects.create(document_name=filename, file=file)
# Do any thing else here
serializer = DocumentSerializer(doc, many=False)
return Response(serializer.data)
So, We will be passing document_name in the URL param, we can call it anything but I defined it as the filename.
and our API ENDPOINT or Url will be like;
# imports
from django.urls import path
from .views import upload_document
urlpatterns = [
path('upload_document/<str:filename>/', upload_document),
]
So to test this via Postman, go to your valid API endpoint like below
I'm passing the filename for the document_name you can pass anything. You would notice that the actual file name is something else in pdf format in the screenshot below. That will be replaced with the help of our document_path_and_name function to id_document_name. So here the save filename is 1_filename.pdf
Now just make a request and your file will be uploaded to your directed file storage path. And you will get the JSON Response from DocumentSerializer.
The main thing which was responsible for the file upload is the MultiPartParser decorator. Must visit the Docs for more details.
If you're using ViewSets, you can add a custom action to handle file uploads:
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.parsers import FileUploadParser
from rest_framework import viewsets
...
class SomeViewSet(viewsets.ModelViewSet):
serializer_class = ...
permission_classes = [...]
queryset = ...
#action(methods=['put'], detail=True, parser_classes=[FileUploadParser])
def upload_file(self, request, pk=None):
obj = self.get_object()
obj.file = request.data['file']
obj.save()
return Response(status=204)
This keeps everything within the ViewSet. You'll get an endpoint that looks something like this api/item/32/upload_file/.
The reason you'd use FileUploadParser as opposed to other options like multipart is if you're uploading from a native app for example and don't want to rely on a multi part encoder.

Categories