Cannot create list of objects in django rest framework - python

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"})

Related

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.

How to upload multiple files with django rest api?

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

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

Cannot assign "<User: kdkd#gmail.com>": "Movie.owner" must be a "Suppliers" instance

I am trying to assign an owner as an object and I must be doing it wrong because it is still raising
Cannot assign "<User: kdkd#gmail.com>": "Movie.owner" must be a "Suppliers" instance.
Request Method: POST
This is my serializer for my Movie model.
class MovieTicketSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ['owner', 'title', 'price', 'date', 'description', 'seat', 'choice', 'active']
def create(self, validated_data):
owner = self.context['request'].user
movie = Movie.objects.create(owner=owner, active=True, **validated_data)
return movie
And this is the View
#api_view(['POST', ])
#permission_classes([IsAuthenticated, ])
def movie_ticket_detail(request):
if request.method == 'POST':
serializer = MoveTicketSerializer(data=request.data, context={'request': request})
data = {}
if serializer.is_valid():
ticket = serializer.save()
data['request'] = ' ticket is instantiated '
data['title'] = ticket.title
data['owner'] = ticket.owner
else:
data = serializer.errors
return Response(data)
class TicketModel(models.Model):
owner = models.ForeignKey(Suppliers, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=30, default='TICKET')
price = models.IntegerField()
date = models.CharField(max_length=20)
description = models.TextField(max_length=300, blank=False)
posted = models.DateTimeField(default=now, editable=False)
active = models.BooleanField(default=False)
class Meta:
abstract = True
class Movie(TicketModel):
seat = models.IntegerField(null=True)
choice = models.IntegerField(choices=TYPE_OF_MOVIES_CONCERT_OTHERS, default=1)
def __repr__(self):
return {'title': self.title, 'price': self.price, 'description': self.description, 'date': self.date}
You can not return model instances or queryet instead you need to return native python data type in the format json, xml or other content types. First Create Serialzier
class SupplierSerializer(ModelSerializer):
class Meta:
model = Supplier
fields = '__all__' # Or sepecific fields ['id','name']
Then Use serializer as below.
# you need to get Supplier object
supplier_obj = get_object_or_404(Supplier,user=ticket.owner)
data['owner'] = SupplierSerializer(supplier_obj).data
OR
supplier_obj = get_object_or_404(Supplier,user=ticket.owner)
data['owner'] = supplier_obj.id # just return id
def create(self, validated_data):
owner = Suppliers.objects.get(email=self.context['request'].user)
movie = Movie.objects.create(owner=owner, active=True, **validated_data)
return HttpResponse(movie, content_type="text/json-comment-filtered")
is there something wrong with implementing it like this. it works.

DRF, "required": "This field is required."

I can't get a valid response, always get this error
{"required": "This field is required.", "null": "This field may not be null.", "not_a_list": "Expected a list of items but got type \"{input_type}\".", "empty": "This list may not be empty."}
Here is some code (models, serializers, views)
models
class Task(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=500, blank=True, default='')
pub_datetime = models.DateTimeField()
priority = models.CharField(choices=PRIORITY_CHOICES, max_length=1)
status = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, blank=True, default=None, null=True)
def __str__(self):
return self.name
serializers
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
agenda = serializers.ReadOnlyField(source='agenda.name')
class Meta:
model = Task
fields = ('id', 'name', 'description', 'pub_datetime', 'priority', 'status', 'owner', 'agenda')
views
class TaskListView(generics.ListCreateAPIView):
serializer_class = TaskSerializer
permission_classes = (permissions.IsAuthenticated,)
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'task_list.html'
def get(self, request, *args, **kwargs):
queryset = Task.objects.filter(owner=self.request.user)
if self.request.accepted_renderer.format == 'html':
return Response({'tasks': queryset})
else:
serializer = TaskSerializer(data=queryset, many=True)
if serializer.is_valid():
return JsonResponse(serializer.data, safe=False)
else:
return JsonResponse(serializer.error_messages)
def post(self, request, *args, **kwargs):
name = request.data['name']
description = request.data['description']
priority = request.data['priority']
new_task = Task.objects.create(name=name, description=description, pub_datetime=datetime.datetime.now(),
priority=priority, status=False, owner=self.request.user)
new_task.save()
return redirect('task-list')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
I'm not sure, at what place I'm wrong. Can someone help?
just text to avoid errors
The queryset or object instance should be passed to serializer as instance, not data, so:
queryset = Task.objects.filter(owner=self.request.user)
serializer = TaskSerializer(instance=queryset, many=True)
Also, I believe that serializer can't validate instance, so in this case you should be able to just return return JsonResponse(serializer.data)

Categories