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.
For upload multiple files you have to do just one thing – get the list of files from the request and iterate each of them to upload. So use the below code snippets instead of request. FILES['file'] to get all files in the views file. You need to hold CTRL (Windows System) to select multiple files.
JSONParser. Parses JSON request content. request. data will be populated with a dictionary of data. .media_type: application/json.
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 "[email protected];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 its very hard to implement it in drf.
Finally what i had 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 key value pair or case 2) you can have a single key called data and send the whole json as string in value.
The first method would work out of the box if you have simple fields, but will be a issue if you have nested serializes. The multipart parser wont 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 its too lengthy because of the writable ManyToMany Field implimentation.
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 is 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 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 post man 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With