I'm getting a serializer error:
"Upload a valid image. The file you uploaded was either not an image
or a corrupted image"
When trying to serialize an uploaded image
My code:
Models:
class Post(models.Model):
text = models.TextField(max_length=10000)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
image = models.ImageField(blank=False, null=False, upload_to='test')
View:
class CreateNewPostAPIView(generics.CreateAPIView):
serializer_class = serializers.NewPostSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
if self.request.data.get('image', None):
context['image'] = self.request.data.pop('image', None)
return context
Serializers:
class NewPostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
post = Post.objects.create(**validated_data)
image = self.context['image'][0] # working with only one image for now
# image is of type <class'django.core.files.uploadedfile.InMemoryUploadedFile'>
serializer = ImageSerializer(data={'post': post.pk, 'image': image})
is_valid = serializer.is_valid() # it's always False!
return post
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('post', 'image')
I already tried doing that without a serializer and it's working with this code:
class NewPostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
post = Post.objects.create(**validated_data)
image = self.context['image']
Image.objects.create(post=post.pk, image=image)
return post
But I need to do it using a serializer.
The easy way for this would be like:
Models:
from django.db import models
# Create your models here.
class Post(models.Model):
text = models.TextField()
def __str__(self):
return self.text[:50]
class Image(models.Model):
post = models.ForeignKey(
Post, on_delete=models.CASCADE,
related_name='images', editable=False
)
image = models.ImageField(upload_to='images/')
def __str__(self):
return self.image.name
def delete(self, *args, **kwargs):
self.image.delete()
super().delete(*args, **kwargs)
Serializer:
from rest_framework import serializers
from .models import Post, Image
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = "__all__"
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = "__all__"
class PostWithImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(write_only=True)
# This is for the GET request or the response of the POST request
# We can also work with a separate serializer for such cases
images = ImageSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = "__all__"
def create(self, validated_data):
image_data = validated_data.pop('image')
post = Post.objects.create(**validated_data)
Image.objects.create(post=post, image=image_data)
return post
Views as:
from rest_framework.response import Response
from rest_framework import status
from .serializers import *
# Create your views here.
class AddPostWithAnImageView(APIView):
serializer_class = PostWithImageSerializer
def post(self, request):
serializer = self.serializer_class(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)
For testing purposes, please use Postman where you can upload images with so much ease. The request form should only require the text and the image fields.
Thanks to #pKiran, who gave me an idea, now I have a working code for saving one or more images. It's, also, quite concise and readable. There's quite a lot going on here with so little of code. Django REST is quite confusing.
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('image', 'thumb')
class NewPostSerializer(serializers.ModelSerializer):
image = serializers.ListField(child=serializers.ImageField(), write_only=True, required=False)
def create(self, validated_data):
images = validated_data.pop('image', None)
post = Post.objects.create(**validated_data)
if images:
img_models = [Image(post=post,
image=image, thumb=make_thumb(image))
for image in images]
Image.objects.bulk_create(img_models)
return post
Related
I got a model that looks like this:
class Book(models.Model):
author = models.CharField(max_length=100)
chapters = models.IntegerField()
name = models.CharField(max_length=100)
and a "grand child" linked together through a Chapter model:
class Verse(models.Model):
chapter = models.ForeignKey(Chapter, on_delete=CASCADE)
verse = models.TextField()
verse_number = models.IntegerField()
I wish to get the count of all verses that belongs to a book, and I'm fetching them like this:
Book.objects.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))
However, I'm not sure how to get this into my serializer. I was thinking of using a SerializerMethodField The goal is to get the total_num_verses as a key/value pair in my json response
class BookSerializer(serializers.ModelSerializer):
total_verse_count = serializers.SerializerMethodField()
class Meta:
model = Book
fields = "__all__"
# this doesn't work..
def get_total_verse_count(self, obj):
print(obj.get_verses)
return self.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))
I just get 'BookSerializer' object has no attribute 'annotate'
Should I make a #property method in the Book model class itself?
views.py is just a plain APIView
class BookAPIView(APIView):
"""
List all books
"""
def get(self, request, format=None):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
An effective way to do that is adding a readonly=True field in the serializer:
class BookSerializer(serializers.ModelSerializer):
total_verse_count = serializers.IntegerField(read_only=True)
class Meta:
model = Book
fields = ['author', 'chapters', 'name', 'total_verse_count']
In the view, you then pass the annotated queryset to the serializer:
class BookAPIView(APIView):
def get(self, request, format=None):
books = Book.objects.annotate(
total_verse_count=Count('chapter__verse')
)
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)
My goal very much resembles to what has been asked in this question but from the perspective of DRF, rather than forms.
So basically the question is, how can I get the newly created object in the following code snippet:
TestSerializer(serializers.ModelSerializer)
class Meta:
fields = '__all__'
model = TestModel
class TestView(generics.CreateAPIView):
serializer_class = TestSerializer
def create(self, request, *args, **kwargs):
response = super(TestView, self).create(request, *args, **kwargs)
created_model_instance = .... ?
print(created_model_instance.id)
return response
You can override perform_create and use serializer.save to get the created object, like:
class TestView(generics.CreateAPIView):
serializer_class = TestSerializer
def perform_create(self, serializer):
"""Some doc here!"""
obj = serializer.save()
print(obj.id)
I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)
I am creating APIView for book list and add, delete.
Is this serialization the right way?
I want to show a list of books, but I do not know what to put in '?'.
member/models.py
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.EmailField(unique=True, max_length=256)
nickname = models.CharField(max_length=256)
is_staff = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
mybook = models.ManyToManyField(Book)
book/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=256)
author = models.CharField(max_length=200)
def __str__(self):
return self.title
book/seralizers.py
from rest_framework import serializers
from book.models import Book
class MyBookSerializer(serializers.ModelSerializer):
mybook = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Book
fields = (
'title',
'author',
)
apiview.py
class MyBook(APIView):
def get(self, request):
mybook = Book.objects.filter(myuser=?)
serializer = MyBookSerializer(mybook)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
pass
def delete(self, request):
pass
If you want create a CRUD for model, depends on a request.user you can simply use ModelViewSet:
from rest_framework import viewsets
class MyBookViewSet(viewsets.ModelViewSet):
serializer_class = MyBookSerializer
def get_queryset(self, request):
return Book.objects.filter(myuser=self.request.user)
That's all for view. You only need to specify router for a viewset.
urls.py:
from rest_framework import routers
router = routers.DefaultRouter()
router.register('books', MyBookViewSet)
urlpatterns = []
urlpatterns += router.urls
Include this usl file in a root urls and you are done.
You also need to specify related_name for m2m field if you want to use myuser name:
mybook = models.ManyToManyField(Book, related_name='myuser')
And you don't need this field in a serializer:
mybook = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
viewsets docs
routers docs