How to upload multiple files with django rest api? - python

I'm trying to upload multiple images with django rest api. I followed the following approach. But when I select one or more files and try to send them to the server as form data, I get the following error message:
AttributeError at /api/photo/ 'bytes' object has no attribute 'name'
Model:
class Photo(models.Model):
image = models.ImageField(upload_to='audio_stories/')
Serializer:
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):
image=validated_data.pop('image')
for img in image:
photo=Photo.objects.create(image=img,**validated_data)
return photo
View:
class PhotoViewSet(viewsets.ModelViewSet):
serializer_class = FileListSerializer
parser_classes = (MultiPartParser, FormParser,)
queryset=Photo.objects.all()
URL
router.register('api/photo', PhotoViewSet, 'photocreate')
I dont know how to approach the error, as i have nothing in my code that relates to "name"?

The error seemed to be in the serializer. I had to set the use_url=True.
Serializer:
class FileListSerializer ( serializers.Serializer ) :
image = serializers.ListField(
child=serializers.FileField( max_length=100000,
allow_empty_file=False,
use_url=True )
)
def create(self, validated_data):
image=validated_data.pop('image')
for img in image:
photo=Photo.objects.create(image=img,**validated_data)
return photo
Extended Answer
The above answer works but produces an large null array.
In order to make the code work I had to seperate my two models in Story and Story_Media. Each instance of Story Media contains a single image and provides a FK to the Story.
class Story (models.Model):
title = models.CharField(max_length=100, blank=True)
description = models.TextField(blank=True)
date_posted = models.DateTimeField(default=timezone.now)
def __str__(self):
return f'{self.id} Story'
class Story_Media (models.Model):
story = models.ForeignKey(Story,on_delete=models.CASCADE, null=True, related_name = 'story_media', related_query_name = 'story_media')
file = models.FileField(upload_to='story_media/', null=True, validators=[validate_file_extension_image])
isTitlePicture = models.BooleanField(blank=False, null=True)
def __str__(self):
return f'{self.id} Media'
In my serializer, a new Sotry_Media instance is created for each image included in the incoming data. In my case, it was necessary to create a story even if no images were uploaded, so the two conditions are included.
# Story Serializer_Media_Serializer
class Story_Media_Serializer (serializers.ModelSerializer):
class Meta:
model = Story_Media
fields = ('id','isTitlePicture', 'file',)
# Story Serializer
class StoryCreateUpdateSerializer (serializers.ModelSerializer):
story_media = Story_Media_Serializer(many=True, required = False)
class Meta:
model = Story
fields = ('title','description', )
def create(self, validated_data):
current_user = self.context["request"].user
# Story contains images
if 'story_media' in validated_data:
story_media = validated_data.pop('story_media')
story_instance = Story.objects.create(author=current_user, **validated_data)
for img in story_media:
Story_Media.objects.create(**img, story=story_instance)
return story_instance
# Story is not containing images
if 'story_media'not in validated_data:
story_instance = Story.objects.create(author=current_user, **validated_data)
return story_instance
class StoryCreateUpdateViewSet(viewsets.ModelViewSet):
serializer_class = StoryCreateUpdateSerializer
http_method_names = ['post','delete','put','patch', 'head']
queryset = Story.objects.all()
permission_classes = [
permissions.IsAuthenticated, PostOwnerPermssion
]

Maybe you should consider implementing like i did which is working for me
Model
class Photo(models.Model):
title = models.CharField(max_length=255, blank=True)
image = models.ImageField(upload_to='hotel_photos')
hotel = models.ForeignKey(
Hotels, on_delete=models.CASCADE, null=True, blank=True,)
class Meta:
verbose_name_plural = 'Photos'
def __str__(self):
"""Prints the name of the Photo"""
return f'{self.hotel} photos'
Serializer
class PhotoSerializer(ModelSerializer):
class Meta:
model = Photo
fields = (
"image",
"hotel",
)
Helper function
def modify_input_for_multiple_files(hotel, image):
dict = {}
dict['hotel'] = hotel
dict['image'] = image
return dict
Views.py
class PhotoUploadView(ListCreateAPIView):
"""
This API view handles multiple images upload form
It will also display images for specific hotel
"""
parser_classes = (MultiPartParser, FormParser)
# http_method_names = ['get', 'post', 'head']
def get(self, request):
all_images = Photo.objects.all()
permission_classes = (IsAdminOrOwner,)
serializer_class = PhotoSerializer(all_images, many=True)
return JsonResponse(serializer.data, safe=False)
def post(self, request, *args, **kwargs):
hotel = request.data.get('hotel')
images = dict((request.data).lists())['image']
flag = 1
arr = []
for img_name in images:
modified_data = modify_input_for_multiple_files(hotel, img_name)
file_serializer = PhotoSerializer(data=modified_data)
if file_serializer.is_valid():
file_serializer.save()
arr.append(file_serializer.data)
else:
flag = 0
if flag == 1:
return Response(arr, status=status.HTTP_201_CREATED)
else:
return Response(arr, status=status.HTTP_400_BAD_REQUEST)
Hope this can help you or anyone stuck. In case of any question shoot it please

Related

Django rest filter by serializermethodfield with custom filter

As declared in question title, i got task to filter results by field not presented in model but calculated by serializer.
The model:
class Recipe(models.Model):
tags = models.ManyToManyField(
Tag,
related_name='recipe_tags'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='author_recipes'
)
ingredients = models.ManyToManyField(
Ingredient,
related_name='recipe_ingredients'
)
name = models.CharField(max_length=200)
image = models.ImageField()
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)]
)
class Meta:
ordering = ("-id",)
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
def __str__(self):
return self.name
Here is the view code:
class RecipeViewSet(ModelViewSet):
queryset = Recipe.objects.all()
permission_classes = [IsAdminOrAuthorOrReadOnly, ]
serializer_class = RecipeInSerializer
pagination_class = LimitPageNumberPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['tags', ]
filter_class = RecipeFilter
Serializer:
class RecipeOutSerializer(serializers.ModelSerializer):
tags = ManyRelatedField(child_relation=TagSerializer())
author = CustomUserSerializer()
ingredients = serializers.SerializerMethodField()
is_favorite = serializers.SerializerMethodField()
is_in_shopping_cart = serializers.SerializerMethodField()
class Meta:
fields = '__all__'
model = Recipe
def get_ingredients(self, obj):
ingredients = IngredientAmount.objects.filter(recipe=obj)
return GetIngredientSerializer(ingredients, many=True).data
def get_is_favorite(self, obj):
request = self.context.get("request")
if request.user.is_anonymous:
return False
return Favorite.objects.filter(recipe=obj, user=request.user).exists()
def get_is_in_shopping_cart(self, obj):
request = self.context.get("request")
if not request or request.user.is_anonymous:
return False
return ShoppingCart.objects.filter(recipe=obj, user=request.user).exists()
And custom filter code:
class RecipeFilter(rest_framework.FilterSet):
tags = ModelMultipleChoiceFilter(
field_name='tags__slug',
to_field_name="slug",
queryset=Tag.objects.all()
)
favorite = BooleanFilter(field_name='is_favorite', method='filter_favorite')
def filter_favorite(self, queryset, name, value):
return queryset.filter(is_favorite__exact=True)
class Meta:
model = Recipe
fields = ['tags', ]
Target is is_favorited field that return boolean value. I tried writing func in custom filter class that return queryset but didnt work, neither documentation helped me with examples. Hope for your help.
We can use queryset annotate:
from django.db import models
from rest_framework import serializers
class RecipeViewSet(ModelViewSet):
def get_queryset(self):
user = self.request.user
user_id = user.id if not user.is_anonymous else None
return Recipe.objects.all().annotate(
total_favorite=models.Count(
"favorite",
filter=models.Q(favorite__user_id=user_id)
),
is_favorite=models.Case(
models.When(total_favorite__gte=1, then=True),
default=False,
output_field=BooleanField()
)
)
class RecipeOutSerializer(serializers.ModelSerializer)
is_favorite = serializers.BooleanField(read_only=True)
class Meta:
model = Recipe
fields = (
# ...
is_favorite,
)
class RecipeFilter(rest_framework.FilterSet):
favorite = BooleanFilter(field_name='is_favorite')

Why is PUT request not updating the Django rest framework database?

I am trying to update the database using a PUT request. Currently, I am able to update the database from the Django Admin successfully but I want to do same using a PUT request.
When ever I make a PUT request, I get a 200 OK response with no errors but the data is not updating in the database. I dont know why. I am confused. Someone please help me. Thank you.
models.py
class User_Order(models.Model):
order = models.OneToOneField(Orders, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
id = models.IntegerField(primary_key=True)
shirts = models.IntegerField(default=0)
shorts = models.IntegerField(default=0)
trousers = models.IntegerField(default=0)
total_units = models.CharField(max_length=2000, blank=True)
shirts_amount = models.CharField(max_length=2000, blank=True)
shorts_amount = models.CharField(max_length=2000, blank=True)
trousers_amount = models.CharField(max_length=2000, blank=True)
total = models.CharField(max_length=200,blank=True)
verified = models.BooleanField(null=True)
doing_laundry = models.BooleanField(null=True)
delivery_underway = models.BooleanField(null=True)
delivered = models.BooleanField(null=True)
address = models.TextField(default='')
time = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
self.user = self.order.user
self.id = self.order.id
self.shirts = self.order.shirts
self.shorts = self.order.shorts
self.trousers = self.order.trousers
self.total_units = self.order.total_units
self.shirts_amount = self.order.shirts_amount
self.shorts_amount = self.order.shorts_amount
self.trousers_amount = self.order.trousers_amount
self.total = self.order.total
self.verified = self.order.verified
self.doing_laundry = self.order.doing_laundry
self.delivery_underway = self.order.delivery_underway
self.delivered = self.order.delivered
self.address = self.order.address
super().save(*args, **kwargs)
def __str__(self):
return f'{self.order.user.username} Order'
serializers.py
class UserUser_OrderSerializer(serializers.ModelSerializer):
class Meta:
model = User_Order
fields = '__all__'
views.py
#api_view(['PUT'])
def UserOrdersUpdate(request, pk):
permission_classes = [IsAuthenticated]
user_order = User_Order.objects.get(id=pk)
if request.method == 'PUT':
serializer = UserUser_OrderSerializer(instance=user_order, data=request.data, many = False, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
Maybe it's because in User_Order.save() method you replace all fields from User_Order where all the data from serializer is stored with Order instance values.
Also you can use class based view UpdateAPIView to simplify it:
class UserOrdersUpdateView(generics.ListCreateAPIView):
queryset = User_Order.objects.all()
serializer_class = UserUser_OrderSerializer
permission_classes = [IsAuthenticated]
Docs:
UpdateAPIView
Used for update-only endpoints for a single model instance.
Provides put and patch method handlers.
Extends: GenericAPIView, UpdateModelMixin.

Cannot create list of objects in django rest framework

I am using ListSerializer for updating and creating list of objects, update() works fine but cannot create list of objects (bulk_create).
models.py
class TutorUser(models.Model):
tutor_user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='tutor')
full_name = models.CharField(max_length=255, blank=True)
phone_number = models.CharField(max_length=14, blank=True)
class WorkExperiance(models.Model):
tutor_work = models.ForeignKey(TutorUser,
related_name='tutor_work',
on_delete=models.CASCADE)
organization = models.CharField(max_length=255, blank=True)
start_year = models.IntegerField(null=True, blank=True)
serializers.py
class WorkExperianceListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
tutor_work_mapping = {tutor_work.id: tutor_work for tutor_work in instance}
data_mapping = {item['id']: item for item in validated_data}
ret = []
for tutor_work_id, data in data_mapping.items():
print(tutor_work_id)
tutor_work = tutor_work_mapping.get(tutor_work_id, None)
# print(tutor_work_id)
if tutor_work is None:
ret.append(self.child.create(data))
# print(ret)
else:
ret.append(self.child.update(tutor_work, data))
for tutor_work_id, tutor_work in tutor_work_mapping.items():
if tutor_work_id not in data_mapping:
tutor_work.delete()
class WorkExperianceSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=False)
class Meta:
list_serializer_class = WorkExperianceListSerializer
model = WorkExperiance
fields = [
'id',
'organization',
'start_year',
]
def update(self, instance, validated_data):
instance.organization = validated_data.get('organization', instance.organization)
instance.start_year = validated_data.get('start_year', instance.start_year)
instance.save()
return instance
views.py
class TutorWorkExperiance(APIView):
def get_object(self, request):
tutor = TutorUser.objects.get(tutor_user__id=request.user.id)
tutor_work = WorkExperiance.objects.filter(tutor_work=tutor)
return tutor_work
def put(self, request):
serializer = WorkExperianceSerializer(self.get_object(request), data = request.data, partial = True, many=True)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)
return Response({"data": "Not valid"})
In my opinion the problem is here with ID, because WorkExperiance model foreign key with TutorUser model, and I cannot pass its ID when object is created. I am using only put method for both creating and updating.
Problem solved.
The issue is requesting post without ID, cause id = serializers.IntegerField(read_only=False) defined in serializer, and also forgot to pass TutorUser Id which foreign key with WorkExperiance.
views.py:
def put(self, request):
tutor = TutorUser.objects.get(tutor_user__id=request.user.id)
serializer = WorkExperianceSerializer(self.get_object(request), data = request.data, partial = True, many=True)
if serializer.is_valid(raise_exception=True):
serializer.save(tutor_user=tutor)
return Response(serializer.data)
return Response({"data": "Not valid"})

How do I dynamically filter fields returned in django rest api get request based on the user making the request?

I have a photoshoot api that allows photographers to post shoot photos from which preview and watermarded versions are derived. Editors and clients can both select which photos will be edited, but the editor should only see the preview without watermark and the clients should only see the watermarked versions.
I was wondering how these different distincitons can be made in the DRF queryset.
My model:
class Unedited(models.Model):
shoot = models.ForeignKey(
Shoot, on_delete=models.CASCADE, null=True, related_name="shoot_photo"
)
original = models.ImageField(null=True, blank=False, upload_to=shoot_upload_to)
preview = models.ImageField(null=True, blank=True, upload_to=shoot_upload_to)
watermarked = models.ImageField(null=True, blank=True, upload_to=shoot_upload_to)
upload_time = models.DateTimeField(auto_now_add=True)
My Serializer:
class UneditedSerializer(serializers.ModelSerializer):
class Meta:
model = Unedited
fields = "__all__"
def create(self, validated_data):
validated_data["preview"] = reduce_resolution(validated_data["original"])
validated_data["watermarked"] = add_watermark(validated_data["preview"])
img_obj = Unedited.objects.create(**validated_data)
img_obj.save()
return img_obj
My view:
class UneditedViewSet(viewsets.ModelViewSet):
if not TESTING:
permission_classes = (PhotosPermission,)
serializer_class = UneditedSerializer
def get_queryset(self):
return Unedited.objects.filter(**self.request.query_params)
I was able to solve this issue by creating separate serializers with different fields for the different cases.
UneditedSerializer remained as is but I created other serializers:
class UneditedMarkedSerializer(serializers.ModelSerializer):
class Meta:
model = Unedited
fields = ("watermarked", "shoot", "upload_time")
class UneditedPreviewSerializer(serializers.ModelSerializer):
class Meta:
model = Unedited
fields = ("preview", "shoot", "upload_time")
I then modified the viewset to check against the user making the request to determine which serializer to use. Like so:
class UneditedViewSet(viewsets.ModelViewSet):
if not TESTING:
permission_classes = (PhotosPermission,)
serializer_class = UneditedSerializer
def get_queryset(self):
return generic_queryset(Unedited, self.request.query_params)
def list(self, request):
queryset = self.get_queryset()
if request.user.is_anonymous or request.user.role == "CLIENT":
serializer = UneditedMarkedSerializer(queryset, many=True)
elif request.user.role == "EDITOR":
print(request.user.role)
serializer = UneditedPreviewSerializer(queryset, many=True)
elif request.user.role in ("ADMIN", "STAFF"):
serializer = UneditedSerializer(queryset, many=True)
return Response(serializer.data)
And now it works as intended.

PATCH (partial=true) doesn't work in Django

I have a PATCH endpoint to change some non required data for a "Site". Through the endpoint you should be able to edit the description and supplier from a Site. The description is already working on the existing project. When I try to add the supplier to the PATCH, it doesn't update it..
View:
class AdminSiteDetailView(GenericAPIView):
def get_permissions(self):
return IsAuthenticated(),
def get_serializer_class(self):
return AdminSiteDetailSerializer
#swagger_auto_schema(
responses={
200: openapi.Response(
_("Successfully"),
AdminSiteDetailSerializer,
)
}
)
def get(self, request, site_pk):
"""
GET the data from the site.
"""
#somecode
#swagger_auto_schema(
responses={
200: openapi.Response(
_("Successfully"),
AdminSiteDetailSerializer,
)
}
)
def patch(self, request, site_pk):
"""
PATCH the description of the site.
"""
site = get_object_or_404(Site, pk=site_pk)
serializer_class = self.get_serializer_class()
serializer = serializer_class(site, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
site.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Serializer:
class AdminSiteSerializer(serializers.ModelSerializer):
supplier = serializers.SerializerMethodField()
class Meta:
model = Site
fields = [
"id",
"name",
"supplier",
]
#staticmethod
def get_supplier(site):
if not site.supplier:
return None
return SupplierSerializer(site.supplier).data
class AdminSiteDetailSerializer(AdminSiteSerializer):
class Meta(AdminSiteSerializer.Meta):
fields = AdminSiteSerializer.Meta.fields + ["description"]
class SupplierSerializer(serializers.ModelSerializer):
class Meta:
model = Supplier
fields = ("id", "name")
model:
class Site(models.Model):
class Meta:
ordering = ("name",)
name = models.CharField(max_length=250)
description = models.TextField(blank=True, null=True)
supplier = models.ForeignKey(
Supplier, on_delete=SET_NULL, blank=True, null=True, related_name="sites"
)
SerializerMethodField is read-only field. So you cannot use it to update data. Instead of using it in your case you can override serializer's to_representation method:
class AdminSiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = [
"id",
"name",
"supplier",
]
def to_representation(self, instance):
data = super().to_representation(instance)
if not instance.supplier:
data['supplier'] = None
data['supplier'] = SupplierSerializer(site.supplier).data
return data

Categories