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
Related
I have this serializer and I use it to get post detail of a post belonging to a user. The owner of the post is not the user that is currently logged in. I want to check if the post is bookmarked by the currently logged in user. The currently logged in user's id is passed in the request but I cannot find it in this context.
Here is the serializer:
class UserPostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, read_only=True, required=False)
profile = serializers.SerializerMethodField()
bookmarked = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
"id",
"category",
"body",
"images",
"video",
"profile",
"published",
"bookmarked",
"created_at",
"updated_at",
]
depth=1
def get_profile(self, obj):
profile_obj = Profile.objects.get(id=obj.user.profile.id)
profile = ShortProfileSerializer(profile_obj)
return profile.data
def get_bookmarked(self, obj):
breakpoint()
bookmark = Bookmark.objects.filter(owner=obj.user.id, post=obj.id,marktype='post')
if bookmark:
return True
else:
return False
The problem is obj.user.id is the owner of the post. I need the logged in user whose id is passed in the url. Here is the model for the bookmark:
class Bookmark(models.Model):
marktype = models.CharField(max_length=50)
post = models.OneToOneField(Post, on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
verbose_name = "bookmark"
verbose_name_plural = "bookmarks"
ordering = ["created_at"]
db_table = "bookmarks"
def __str__(self):
return "{}'s bookmark".format(self.owner.username)
and here is the URL:
path("posts/<int:user>/home/", HomeView.as_view(), name="home"),
This self.context['request'].user returns the owner of the post and not the logged in user.
How do I get the id of the currently logged in user or the user whose id I pass in the URL please?
Maybe do you can use filters to the Viewset:
urls.py
path("posts/home/", HomeView.as_view(), name="home")
viewsets.py
from rest_framework import viewsets
from .models import Post
from .serializers import, UserPostSerializer
from .filters import OwnerFilter
class HomeView(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = UserPostSerializer
filter_backends = (OwnerFilter,)
filters.py
from rest_framework.filters import BaseFilterBackend
class OwnerFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
owner = request.query_params.get('owner', None)
if not owner:
return queryset.all()
else:
try:
return queryset.filter(bookmarked__owner__id=owner)
except Exception:
return queryset.none()
Running
Then access the URL:
/posts/home/?owner=OWNER_ID_HERE
Solved it and you can get any kwargs from the view that handles the request. In my case adding the following to the get_bookmarked function gives me the id I send in the URL:
loggeduser = self.context.get('view').kwargs.get('user')
I'm trying to make my nested serializer to be able to process updating when I'm only updating for the parent model. The default action in drf is that the serializer cant tell if ur child model is trying to updating or create, there's a solution where you just turn off the validation, which I don't think is ideal for the database and iots implication.
I've scour about this topic for like 2 days and I think the ideal approach to this is to override the is_valid method to be able to just return the object.
Idea from :
Django REST Framework ModelSerializer get_or_create functionality
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
class MyModelSerializer(serializers.ModelSerializer):
def is_valid(self, raise_exception=False):
if hasattr(self, 'initial_data'):
# If we are instantiating with data={something}
try:
# Try to get the object in question
obj = Security.objects.get(**self.initial_data)
except (ObjectDoesNotExist, MultipleObjectsReturned):
# Except not finding the object or the data being ambiguous
# for defining it. Then validate the data as usual
return super().is_valid(raise_exception)
else:
# If the object is found add it to the serializer. Then
# validate the data as usual
self.instance = obj
return super().is_valid(raise_exception)
else:
# If the Serializer was instantiated with just an object, and no
# data={something} proceed as usual
return super().is_valid(raise_exception)
class Meta:
model = models.MyModel
The problem is whenever I try to implment I get the error:
Field 'id' expected a number but got [{'id': 18, 'title': 'aDDD', 'body': 'aDDD', 'slug': 'aDDDbabe', 'author': 1, 'question': 25}].
obj = Question.objects.get(**self.initial_data)
This code triggers it, however When I
obj = Question.answers.get(**self.initial_data)
I get 'ReverseManyToOneDescriptor' object has no attribute 'get' .
Since mine is a nested serializer I would have to find another way to make this work. Any advice where I can fix it? :(
serializers.py
from rest_framework import serializers
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/serializers.py#L839
from rest_framework.fields import ( # NOQA # isort:skip
CreateOnlyDefault, CurrentUserDefault, SkipField, empty
)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from .models import Question, Answer
class AnswerSerializer(serializers.ModelSerializer):
"""Serialize Answer model"""
class Meta:
model = Answer
fields = ('id','title', 'body', 'slug', 'author', 'question')
# https://stackoverflow.com/questions/57249850/overwriting-nested-serializers-create-method-throws-typeerror-create-got-mul
read_only_fields = ('question',)
lookup_field = 'slug'
# https://stackoverflow.com/questions/55031552/how-to-access-child-entire-record-in-parent-model-in-django-rest-framework
class QuestionSerializer(serializers.ModelSerializer):
"""Serialize Question model"""
#This answer variable and the fields 'answer' you refer to has to be the SAME but
#These can change exp: 'aaa'
answers = AnswerSerializer(read_only=False, many=True,)
class Meta:
model = Question
fields = ('id','title', 'body', 'slug', 'author', 'category', 'answers',)
lookup_field = 'slug'
def is_valid(self, raise_exception=False):
if hasattr(self, 'initial_data'):
# If we are instantiating with data={something}
try:
# Try to get the object in question
obj = Question.objects.get(**self.initial_data)
except (ObjectDoesNotExist, MultipleObjectsReturned):
print("LOLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL")
return print(obj)
else:
print("LOLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL")
print(obj)
else:
return super().is_valid(raise_exception)
def create(self, validated_data):
answers_data = validated_data.pop('answers')
question = Question.objects.create(**validated_data)
for answer_data in answers_data:
#The above stackoverflow link at the answer serializers is realted to this
Answer.objects.create(question=question, **answer_data)
return question
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.body = validated_data.get('body', instance.body)
instance.slug = validated_data.get('slug', instance.slug)
instance.author = validated_data.get('author', instance.author)
instance.category = validated_data.get('category', instance.category)
instance.save()
# https://django.cowhite.com/blog/create-and-update-django-rest-framework-nested-serializers/
answers_data = validated_data.pop('answers')
aas = (instance.answers).all()
print("######")
print(aas)
print("######")
print(instance.answers.get().slug)
print("#########################################")
aas2 = list(aas)
print("######")
for f in validated_data:
print(f)
print(validated_data.items())
print(dir(validated_data))
print("######")
for answer_data in answers_data:
aas3 = aas2.pop(0)
aas3.title= answer_data.get('title', aas3.title)
aas3.body= answer_data.get('body', aas3.body)
aas3.slug= answer_data.get('slug', aas3.slug)
aas3.author= answer_data.get('author', aas3.author)
aas3.question= answer_data.get('question', aas3.question)
aas3.save()
return instance
views.py
from django.shortcuts import render
from .models import Question, Answer
from django.conf import settings
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from .serializers import QuestionSerializer
#from .permissions import UpdateOwnPrice
from rest_framework.permissions import IsAdminUser
class QuestionViewSet(viewsets.ModelViewSet):
"""CRUD """
serializer_class = QuestionSerializer
queryset = Question.objects.all()
authentication_classes = (TokenAuthentication,)
#permission_classes = (UpdateOwnPrice,)
lookup_field = 'slug'
extra_kwargs = {'slug': {'validators': []},}
models.py
from django.db import models
from django.conf import settings
from django.db.models import Q
from django.utils import timezone
from django.urls import reverse
class Category(models.Model):
name= models.CharField(max_length=100)
def __str__(self):
return self.name
class Question(models.Model):
title= models.CharField(max_length= 100)
body= models.TextField()
slug= models.SlugField(unique= True)
date_posted= models.DateTimeField(default=timezone.now)
author= models.ForeignKey(settings.AUTH_USER_MODEL, on_delete= models.CASCADE, related_name = 'questions')
category= models.ForeignKey(Category, on_delete= models.CASCADE, related_name = 'questions')
#objects= QnAManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('home')
class Answer(models.Model):
title= models.CharField(max_length= 100)
body= models.TextField()
slug= models.SlugField(unique= True)
date_posted= models.DateTimeField(default=timezone.now)
author= models.ForeignKey(settings.AUTH_USER_MODEL, on_delete= models.CASCADE, related_name = 'answers')
question= models.ForeignKey(Question, on_delete= models.CASCADE, related_name = 'answers')
#objects= QnAManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('home')
In my case,I use JWT authentication, and when I create new "Post"(my model), I want automatically set author to user that request it.But when I do it, I got an error
{
"author": [
"This field is required."
]
}
I know,I'm not passing user, but I want to set it automatically, and I dont know how.
I just want to know how to avoid error, because when I pass value, that allows me to go ahead, the user is set automatically from context.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = ('title','content',author','category','likedBy')
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
def create(self, validated_data):
user = self.context['request'].user
post = Post.objects.create(
author=user, title=validated_data['title'], content=validated_data['content'])
post.category.set(validated_data['category'])
return post
My create view
class PostCreate(generics.CreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
Model
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
and when I create
You can make author a read-only field, or if you're just using this serializer to create users and not retreive them. You can just remove 'author' from fields in the serializer meta.
Read-only field
from rest_framework import serializers
class PostSerializer(FlexFieldsModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Post
fields = ('title','content','author','category','likedBy')
I'm starting to work with Django and I followed the tutorial available in the Django Rest Framework website and now I'm trying to adapt the tutorial to do something a little more complex.
I'm trying to create a "Like" system for a Social Network. A User can create Posts (UserPosts) and Like other user's posts.
I'm creating new UserPosts (using the command line) this way:
http -a admin:Pass1234 POST http://127.0.0.1:8000/posts/ description="I'm just a random comment"
And everything works just fine.
The problem is when I try to create a Like instance. In this case I need to pass a UserPost id, so I'm doing the same as I did to create a new comment:
http -a admin:Pass1234 POST http://127.0.0.1:8000/likes/ post="1"
But when I do this I get the following error:
"post": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got unicode."
]
}
The models are the following:
class UserPost(models.Model):
owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=100, blank=True, default='')
def save(self, *args, **kwargs):
options = self.description and {'description': self.description} or {}
super(UserPost, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
class Like(models.Model):
owner = models.ForeignKey('auth.User', related_name='likes', on_delete=models.CASCADE)
post = models.ForeignKey(UserPost, related_name='likes', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
options = self.post and {'post': self.post}
super(Like, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
The serializers:
class UserPostSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = UserPost
fields = ('url', 'id', 'description', 'owner', 'timestamp')
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
post = UserPostSerializer(source='post')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
The Views:
class PostViewSet(viewsets.ModelViewSet):
queryset = UserPost.objects.all()
serializer_class = UserPostSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class LikeViewSet(viewsets.ModelViewSet):
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Routers and urls:
router = DefaultRouter()
router.register(r'posts', views.PostViewSet)
router.register(r'users', views.UserViewSet)
router.register(r'likes', views.LikeViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Any idea of what is happening?
Thanks!
You need to remove post = UserPostSerializer from your LikeSerializer:
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
so that django-rest-framework uses a PrimaryKeyRelatedField for the related UserPost (which is the default for related models with ModelSerializer).
Then you can create your Like entry with post="1" as parameter.
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.