In django rest framework, I am able to upload single file using danialfarid/ng-file-upload
views.py:
class PhotoViewSet(viewsets.ModelViewSet):
serializer_class = PhotoSerializer
parser_classes = (MultiPartParser, FormParser,)
queryset=Photo.objects.all()
def perform_create(self, serializer):
serializer.save(blogs=Blogs.objects.latest('created_at'),
image=self.request.data.get('image'))
serializers.py:
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = Photo
models.py:
class Photo(models.Model):
blogs = models.ForeignKey(Blogs, related_name='blogs_img')
image = models.ImageField(upload_to=content_file_name)
When I try to upload multiple file. I get in
chrome developer tools:
Request Payload
------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[0]"; filename="datacable.jpg"
Content-Type: image/jpeg
------WebKitFormBoundaryjOsYUxPLKB1N69Zn
Content-Disposition: form-data; name="image[1]"; filename="datacable2.jpg"
Content-Type: image/jpeg
Response:
{"image":["No file was submitted."]}
I don't know how to write serializer for uploading multiple file.
I manage to solve this issue and I hope it will help community
serializers.py:
class FileListSerializer ( serializers.Serializer ) :
image = serializers.ListField(
child=serializers.FileField( max_length=100000,
allow_empty_file=False,
use_url=False )
)
def create(self, validated_data):
blogs=Blogs.objects.latest('created_at')
image=validated_data.pop('image')
for img in image:
photo=Photo.objects.create(image=img,blogs=blogs,**validated_data)
return photo
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = Photo
read_only_fields = ("blogs",)
views.py:
class PhotoViewSet(viewsets.ModelViewSet):
serializer_class = FileListSerializer
parser_classes = (MultiPartParser, FormParser,)
queryset=Photo.objects.all()
I dont know it very well, but this is working...
This is for my viewset.
def perform_create(self, serializer):
obj = serializer.save()
for f in self.request.data.getlist('files'):
mf = MyFile.objects.create(file=f)
obj.files.add(mf)
Here is how you upload multiple files on blogs api:
models.py
class Blogs(models.Model):
...
class Photo(models.Model):
blogs = models.ForeignKey(Blogs, related_name='blogs_img')
image = models.ImageField(upload_to=content_file_name)
serializers.py
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = Photo
fields = ['blogs', 'image',]
class BlogsSerializer(serializers.ModelSerializer):
photos = serializers.SerializerMethodField()
def get_photos(self, obj):
photos = Photo.objects.filter(blogs=obj)
return PhotoSerializer(photos, many=True, read_only=False).data
class Meta:
model = Blogs
fields = [
...
'photos',
]
views.py
class BlogsViewSet(viewsets.ModelViewSet):
serializer_class = BlogsSerializer
queryset = Blogs.objects.all()
def create(self, request, *args, **kwargs):
instance_data = request.data
data = {key: value for key, value in instance_data.items()}
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
if request.FILES:
photos = dict((request.FILES).lists()).get('photos', None)
if photos:
for photo in photos:
photo_data = {}
photo_data["blogs"] = instance.pk
photo_data["image"] = photo
photo_serializer = PhotoSerializer(data=photo_data)
photo_serializer.is_valid(raise_exception=True)
photo_serializer.save()
return Response(serializer.data)
I have solved the issue with this solution
models.py:
class Product(models.Model):
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
class Images(models.Model):
product = model.ForeignKey('Product', related_name="images", on_delete=models.CASCADE)
image = models.ImageField(upload_to=upload_path)
serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.Images
fields = '__all__'
views.py
class ImagesViewSet(ModelViewSet):
queryset = models.Images.objects.all()
serializer_class = serializers.ImageSerializer
# overwrite create method from the CreateModelMixin
def create(self, request, *args, **kwargs):
data = request.data
images = data.getlist('image')
# if no images call parent method it will return error
if not images:
return super().create(request, *args, **kwargs)
# verify only without creating the images
serializer_lst = []
for image in images:
data['image'] = image
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
serializer_lst.append(serializer)
serializers_data = [] # this is to collect data for all created images and return as list in the response
for serializer in serializer_lst:
self.perform_create(serializer)
serializers_data.append(serializer.data)
headers = self.get_success_headers(serializer.data)
return Response(serializers_data, status=status.HTTP_201_CREATED, headers=headers)
It took me a while to find out an effective solution to this,
I would like to share with you what finally worked for me.
I am using reactjs and DRF.
Here is my model :
class MediaFile(models.Model):
article = models.ForeignKey(Articles, on_delete=models.CASCADE, null=True)
caption = models.CharField(max_length=500, null=True, blank=True)
file = models.FileField('photo of article', upload_to=set_filename,
blank=True, null=True, default='')
added = models.DateTimeField(auto_now_add=True)
The views are standard viewsets.ModelViewSet
class MediaFilesViewSet(viewsets.ModelViewSet):
serializer_class = FileListSerializer
parser_classes = (parsers.MultiPartParser, parsers.FormParser,)
queryset=MediaFile.objects.all()
In ArticleSerializer I added:
def create(self, validated_data):
request = self.context.get('request')
user = request.user
instance = Articles.objects.create(**validated_data)
instance.publisher = user
instance.save()
images = request.FILES
if images:
try:
for f in images.getlist('mediafiles'):
instance.mediafile_set.get_or_create(file=f, caption=f.name)
instance.save()
except Exception as e:
print(e)
return instance
The FRONTEND structure:
postChangeImage = (event) =>{
this.setState({
is_images: true
});
let files = event.target.files;
const files_array = Object.values(files);
this.setState(
{imagefile: files}
);
console.log('IMAGES ARRAY', files_array);
files_array.map(value => {
const urls = URL.createObjectURL(value);
this.setState((prevState)=>(
{postfolio:[urls, ...prevState.postfolio]}
));
});
};
and POSTING:
for (let i=0; i< this.state.imagefile.length; i++) {
form_data.append(`mediafiles`, this.state.imagefile[i]);
}
The best answer to this question did not work for me, but Charles' suggestion worked very well. In my case, I needed to upload multiple files and assign them to a specific batch. Each batch is assigned to a particular user.
Below is more context using ReactJS to make the POST request, along with the serializers used and Postman window:
api.py
from convert_files.models import File
from rest_framework import viewsets, permissions
from rest_framework.parsers import MultiPartParser, JSONParser
from .serializers import BatchSerializer
class BatchViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
def perform_create(self, serializer):
obj = serializer.save(owner=self.request.user)
for f in self.request.data.getlist('files'):
mf = File.objects.create(office_file=f)
obj.files.add(mf)
parser_classes = (MultiPartParser, JSONParser, )
serializer_class = BatchSerializer
http_method_names = ['get','post','delete','put','patch', 'head']
def get_queryset(self):
return self.request.user.batches.all()
serializers.py
from rest_framework import serializers
from convert_files.models import File, Batch
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = '__all__'
class BatchSerializer(serializers.ModelSerializer):
files = FileSerializer(many=True, required = False)
class Meta:
model = Batch
fields = '__all__'
models.py
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from .extra import ContentTypeRestrictedFileField
def make_upload_path(instance, filename):
"""Generates upload path for FileField"""
return settings.OFFICE_OUTPUT_FILES_URL + "/%s" % (filename)
class Batch(models.Model):
name = models.CharField(max_length=100, blank=True)
description = models.TextField(blank=True)
date_posted = models.DateTimeField(default=datetime.datetime.now)
owner = models.ForeignKey(User, related_name="batches",
on_delete=models.CASCADE, null=True)
class File(models.Model):
name = models.CharField(max_length=100, blank=True)
office_file = ContentTypeRestrictedFileField(
upload_to = make_upload_path,
content_types = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel','application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
max_upload_size = 10485760,
)
files = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True,
related_name='files', related_query_name='files')
FileUpload.js
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { addBatch } from '../actions/batches';
function FileUpload() {
const dispatch = useDispatch();
let formData = new FormData()
const onDrop = useCallback((acceptedFiles) => {
for (var i = 0; i < acceptedFiles.length; i++) {
formData.append("files", acceptedFiles[i], acceptedFiles[i].name)
}
dispatch(addBatch(formData));
})
...
Postman
Image of POST request in Postman for Multiple File Upload to DRF
Working with "dictionary (array) of images"
Ok, so today I tried Arindam's solution.. it worked perfectly, but after a while, I figgured out that my frontend (port 3000) makes a GET request to itself looking for an image that is not there, and not at the backend(port 8000).. (eg. GET http://localhost:3000/media/images/products/default.png - Returns 404: Not found).. What worked for me was to change the code around a bit and this is my solution..
in models.py
class Product(models.Model):
title = models.CharField(max_length=255)
description = models.CharField(max_length=255)
price = models.FloatField()
quantity = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=False)
slug = models.SlugField(max_length=255, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
...
class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='images')
image = models.ImageField("Image", upload_to=upload_to, default='products/default.png')
in serializers.py
...
class ProductImageSerializer(serializers.ModelSerializer):
class Meta:
model = ProductImage
fields = ['id', 'image', 'product']
extra_kwargs = {
'product': {'required': False},
}
class ProductSerializer(serializers.ModelSerializer):
images = ProductImageSerializer(many=True, required=False)
class Meta:
model = Product
fields = ['id', 'title', 'description', 'images', 'price', 'quantity', 'active', 'slug', 'created_at', 'modified_at']
read_only_fields = ['slug']
#lookup_field = 'slug'
def create(self, validated_data):
product = Product.objects.create(**validated_data)
try:
# try to get and save images (if any)
images_data = dict((self.context['request'].FILES).lists()).get('images', None)
for image in images_data:
ProductImage.objects.create(product=product, image=image)
except:
# if no images are available - create using default image
ProductImage.objects.create(product=product)
return product
in views.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
permission_classes = [permissions.AllowAny]
serializer_class = ProductSerializer
parser_classes = [MultiPartParser, FormParser]
#lookup_field = 'slug'
edit: in settings.py
import os
...
BASE_DIR = Path(__file__).resolve().parent.parent
...
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
MEDIA_URL = '/media/'
I am posting this here to help someone (or me again in the future) with multiple files upload or multiple images upload as I spent 2 days looking up tutorials and answeres online to help me solve this issue... I may not be doing everything perfectly as I just recently started exploring Django REST Framework (and Python), but I hope it helps.
Related
I would like to create my own endpoint for POST request to two related tables. I have two tables User and Userattribute.
models.py
class User(models.Model):
email = models.CharField(unique=True, max_length=180)
roles = models.JSONField(default=dict)
password = models.CharField(max_length=255, blank=True, null=True)
name = models.CharField(max_length=255, blank=True, null=True)
firebase_id = models.CharField(max_length=255, blank=True, null=True)
created_at = models.DateTimeField(default=now)
progress_sub_step = models.IntegerField(blank=True, null=True)
step_available_date = models.DateTimeField(blank=True, null=True)
progress_step = models.IntegerField(blank=True, null=True)
active = models.IntegerField(default=1)
last_login_at = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = 'user'
class Userattribute(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name = 'attribute')
attribute = models.ForeignKey(Attribute, on_delete=models.CASCADE)
The table Userattribute contains the field user which is OnetoOne to Id primary key from User table.
I tried to implement POST to two tables in serializers.py In the commented section there is a create definition which works perfectly for me. However, I wouldlike to move it to views.py as register_in_course endpoint
serializers.py
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ['tytul', 'opis', 'po_premierze']
class UserattributeSerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['user', 'attribute']
class UASerializer(serializers.ModelSerializer):
class Meta:
model = Userattribute
fields = ['attribute']
class UserSerializer(serializers.ModelSerializer):
attribute = UASerializer(many = False)
class Meta:
model = User
fields = ['email', 'name', 'firebase_id', 'attribute']
# This is what workks perfectly for me, and I want to move it to views.py
# VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
# def create(self, validated_data):
# attribute_data = validated_data.pop('attribute')
# user = User.objects.create(**validated_data)
# Userattribute.objects.create(user=user, **attribute_data)
# return user
Current views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
user_id = User.objects.filter(firebase_id = request.data['firebase_id'])['id']
attribute = Userattribute.objects.create(user = user_id, attribute = request.data['attribute']['attribute'])
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
Using endpoint register_in_course to POST I get following error:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly.
urls.py
from django.urls import include, path
from django.conf.urls import url
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'userattribute', views.UserattributeViewSet)
urlpatterns = [
url('', include(router.urls))
]
i removed one line user_id variable and changed attribute variable. please check, maybe it should solve your problem, because you have already have Assigned variable as a User object..
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object()
user = User.objects.create(email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id'])
attribute = Userattribute.objects.create(user = user, attribute = request.data['attribute']['attribute']) # changed this line
user = user.attribute.add(attribute)
serializer = UserSerializer(user, many = false)
return Response(serializer.data)
This issue is caused by calling get_object in a view that is defined with detail=False:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
data = self.get_object() # The problem is caused by this line
It seems you don't need this data, as you are using request.data.
So you can define your view like this:
#action(detail = False, methods = ['post'])
def register_in_course(self, request, **kwargs):
user = User.objects.create(
email=request.data['email'],
name=request.data['name'],
firebase_id=request.data['firebase_id']
)
Userattribute.objects.create(
user=user,
attribute = request.data.get('attribute', {}).get('attribute', {})
)
return Response(UserSerializer(user).data)
How can i upload images when user is trying to create announcement. I need to upload images to my computer and assign them to the announcement that he is trying to create.
Models.py
class Announcement(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
photo = models.ManyToManyField(Photo, blank=True)
def nameFile(instance, filename):
return '/'.join(['images', str(instance.name), filename])
class Photo(models.Model):
name = models.CharField(max_length=100)
image = models.ImageField(upload_to=nameFile, blank=True, null=True)
Views.py
class AnnouncementCreate(CreateAPIView):
permission_classes = [IsAuthenticated]
queryset = models.Announcement.objects.all()
serializer_class = AnnouncementSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class PhotoViewSet(ListAPIView):
queryset = models.Photo.objects.all()
serializer_class = PhotoSerializer
def post(self, request, *args, **kwargs):
file = request.data.get('image')
image = models.Photo.objects.create(image=file)
return HttpResponse(json.dumps({'message': "Uploaded"}), status=200)
Serializers.py
class AnnouncementSerializer(serializers.ModelSerializer):
parameters = ParameterSerializer(many=True, required=False)
photo = PhotoSerializer(many=True, required=False)
class Meta:
model = Announcement
fields = ['id', 'name', 'address', 'date',
'price', 'description', 'author', 'parameters', 'photo']
class PhotoSerializer(serializers.ModelSerializer):
class Meta:
model = Photo
fields = ('name', 'image')
The CreateModelMixin of the CreateAPIView calls the serializer listed. Therefore, you need to serialize and validate the image inside the AnnouncementSerializer using the PhotoSerializer. This can be done by overriding the create method of the AnnouncementSerializer:
class AnnouncementSerializer(serializers.ModelSerializer):
parameters = ParameterSerializer(many=True, required=False)
photo = PhotoSerializer(
many=True,
required=False,
read_only=True) # Set read_only to True
class Meta:
model = Announcement
fields = ['id', 'name', 'address', 'date',
'price', 'description', 'author', 'parameters', 'photo']
def create(self, validated_data):
"""
Serializes an image if it has been uploaded, then passes the image
back to the validated_data to finish creating the Announcement.
"""
image_data = self.context['request'].FILES
if image_data:
serializer = PhotoSerializer(data=image_data)
serializer.is_valid(raise_exception=True)
image = serializer.save()
validated_data['image'] = image
return super().create(validated_data)
That should be all you have to do.
I want a viewset that handles a post request that creates some nested objects using the post data.
I have these models, serializers, and views:
Models:
class Connection(models.Model):
from portfolio.models import Portfolio
user = models.ForeignKey(User, related_name='exchange_connections', on_delete=models.CASCADE)
portfolios = models.ManyToManyField(Portfolio)
class ConnectionSettings(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='settings', on_delete=models.CASCADE)
import_past_transactions = models.BooleanField(default=False, blank=True)
class ConnectionCredentials(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='credentials', on_delete=models.CASCADE)
key = models.CharField(max_length=300, blank=False, null=False)
secret = models.CharField(max_length=300, blank=False, null=False)
passphrase = models.CharField(max_length=300, blank=True, null=True)
Serializers:
class ConnectionCredentialsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionCredentials
fields = '__all__'
class ConnectionSettingsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionSettings
fields = '__all__'
class ConnectionSerializer(serializers.ModelSerializer):
credentials = ConnectionCredentialsSerializer()
settings = ConnectionSettingsSerializer()
class Meta:
model = Connection
fields = '__all__'
def create(self, validated_data):
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Views:
class ConnectionViewSet(viewsets.ViewSet):
serializer_class = serializers.ConnectionSerializer
queryset = exchange_models.Connection.objects.all()
permission_classes = (IsAuthenticated, core_permissions.IsMineOnly)
def list(self):
return HttpResponse(self.request.user.exchange_connections_set)
def retrieve(self, request, pk=None):
serialized_data = self.serializer_class(exchange_models.Connection.objects.get(id=pk)).data
return HttpResponse(serialized_data)
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.create(serializer.data)
serializer.save()
return Response({'status': 'connection created.'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
URLs:
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'connections', views.ConnectionViewSet)
urlpatterns = [
path('', include(router.urls)),
]
POST Request:
When I send the post request, the portfolios and the user already exist. So I should only give primary keys to these rows in my request.
But I need to create new rows for Settings and Credentials for which I should pass data in the request.
By default nested serializers are read-only. You will have to customize the create method to make it writable.
class ConnectionSerializer(serializers.ModelSerializer):
portfolios = PortfolioSerializer(many=True)
credentials = ConnectionCredentialsSerializer(many=False)
settings = ConnectionSettings(many=False)
class Meta:
model = models.Connection
exclude = ('user',)
read_only_fields = ('date_created', 'last_updated')
def create(self, validated_data):
portfolios = validated_data.pop('portfolios')
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
for portfolio in portfolios:
Portfolio.objects.create(exchange_connection=connection, **portfolio)
for credential in credentials:
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
for setting in settings:
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Provided that following are the model names relative to serializers.
PortfolioSerializer -> Portfolio,
ConnectionCredentialsSerializer -> ConnectionCredentials
In my project I have the Post and Category Model and full working JWT Authentication.
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category, related_name='posts')\
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
I want to create a view, that creates a new Post object, in which author will be assigned to Token owner that I pass in authorization (Bearer Token ) postman.image.example. I dont know how to do it please help. Sorry for my english.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['id', 'created']
expandable_fields = {
'category': ('blog.CategorySerializer', {'many': True}),
'comments': ('blog.CommentSerializer', {'many': True}),
'images': ('blog.ImageSerializer', {'many': True}),
}
From what I understand, you want to automatically associate the request.user as the author of the post (s)he creates. Whether your auth is JWT-based or session-based does not influenced that (as long as it is set up correctly).
For this you need to pass the request object to your serializer, here is the trick:
# serializers.py
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['id', 'created', 'author'] # set author as read-only field
expandable_fields = {
'category': ('blog.CategorySerializer', {'many': True}),
'comments': ('blog.CommentSerializer', {'many': True}),
'images': ('blog.ImageSerializer', {'many': True}),
}
def create(self, validated_data):
# here you get the user from the request
user = self.context['request'].user
return Post.objects.create(author=user, **validated_data)
# views.py
from .models import Post
from .serializers import PostSerializer
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class PostCreate(generics.CreateAPIView):
queryset=Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
def get_serializer_context(self):
# this is the trick since you want to pass the request object to your serializer
context = super().get_serializer_context()
context.update({"request": self.request})
return context
In a Django Project I have simple UpdateAPIView
class ProfileUpdateAPIView(UpdateAPIView):
serializer_class = ProfileUpdateSerializer
authentication_classes = ( CsrfExemptSessionAuthentication, BasicAuthentication, TokenAuthentication,)
permission_classes = ((IsAuthenticated,))
and a simple model
def image_upload_fun(instance, filename):
return 'profile_pics/user_{0}.{1}'.format(str(instance.phone_number),filename.split(".")[1])
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,null=True, blank=True)
profile_pic = models.ImageField(upload_to=image_upload_fun, null=True, blank=True)
phone_number = models.CharField(max_length=12, unique=True, null=False, blank=False)
It does neither create new file nor it updates profile_pic_field.Although through admin panel it(image) gets updated or created easily
My request call is
Headers:
Authorization:Token 7554057effce1fcbc313f9a96be99ae17efb8eaf
Body:
phone_number:92123123132
profile_pic :[[my file]]
serializers.py
class ProfileUpdateSerializer(serializers.ModelSerializer):
profile_pic_url = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ["name", "email", "phone_number","gender", "date_of_birth", "profile_pic_url"]
def get_profile_pic_url(self, obj):
try:
return self.context["request"].build_absolute_uri("/").strip("/")+str(obj.profile_pic.url)
except Exception as E:
print(E)
return str("")
By default django-rest-framework uses JSONParsor. It will not parse uploaded files. To parse files we have to use MultiPartParser and FormParser like below
from rest_framework.parsers import MultiPartParser, FormParser
class ProfileUpdateAPIView(UpdateAPIView):
serializer_class = ProfileUpdateSerializer
authentication_classes = (
CsrfExemptSessionAuthentication,
BasicAuthentication,
TokenAuthentication
)
permission_classes = (IsAuthenticated,)
parser_classes = (MultiPartParser, FormParser)
Request using python requests
import requests
headers = {
"Content-Type": "multipart/form-data",
"Authorization": "Token <your token>"
}
data = {
"phone_number": "987654231",
"profile_pic": open("path/to/image.png", "r").read()
}
r = requests.post("http://localhost:8000/api/end-point/", data=data, headers=headers)
print(r.json())
I did a mistake in defining fields of ProfileUpdateSerializer
I didn't included profile_pic field in the fields of the serializer
Editing the serializer to add "profile_pic" field worked to upload image
class ProfileUpdateSerializer(serializers.ModelSerializer):
profile_pic_url = serializers.SerializerMethodField()
class Meta:
model = Profile
fields = ["name", "email", "phone_number","gender", "date_of_birth", "profile_pic","profile_pic_url"]
def get_profile_pic_url(self, obj):
try:
return self.context["request"].build_absolute_uri("/").strip("/")+str(obj.profile_pic.url)
except Exception as E:
print(E)
return str("")
I thought the serializer was just used to show a response after update.