UpdateAPIView not raising 404 when not exists in Django REST Framework - django

I'm using Django 2.2 and Django REST Framework.
I have an endpoint to update existing record, extending UpdateAPIView
class FakeOrderPaymentView(generics.UpdateAPIView):
serializer_class = OrderPaymentSerializer
permission_classes = (IsAuthenticated,)
def get_object(self):
log.debug(self.request.data)
order_id = self.request.data.get('order_id', None)
if not order_id:
raise Http404
return self.serializer_class.Meta.model.objects.filter(pk=order_id).first()
def perform_update(self, serializer):
return serializer.save()
def update(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer)
instance_serializer = OrderListSerializer(instance)
return Response(instance_serializer.data)
and the OrderPaymentSerializer
class OrderPaymentSerializer(serializers.ModelSerializer):
order_id = serializers.IntegerField(write_only=True, required=False)
order_status = serializers.CharField(write_only=True, required=True)
class Meta:
model = Order
fields = [
'order_status',
'order_id'
]
def create(self, validated_data):
log.debug(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
status = validated_data.pop('order_status', None)
order_id = validated_data.pop('order_id', None)
instance.status = Order.STATUS.COMPLETED
return super().update(instance, validated_data)
The order_id and order_status are not model fields. They are just to carry data.
If order_id is not in the PUT data or order_id is blank. It should raise 404 response.
But, it's not calling get_object() method before update().
Also, even if I pass order_id in the PUT data. The perform_update() method is calling serializer's create() method instead of update() method.
When is serializer's update() method called?
Why get_object() is not calling automatically?

When is serializer's update() method called?
As the documentation states:
Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class
Since you are not providing an instance when instantiating the serializer, calling save will translate into create instead of update.
Why get_object() is not calling automatically?
Because you view's update method is overridden and you didn't call get_object. Default DRF update is:
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)

Related

Django rest framework use kwargs in serializer

I have a model Ingredient which has ForeignKey field. I use modelSerializer to pass data. I'm passing recipe_id in url like this recipes/{recipe.id}/ingredients. My current solution is to modify request.data in the view but I'm sure if it's corrent way for such a common case.
What's the best way to pass Recipe to serializer?
models.py
class Ingredient(TimeStamp):
name = models.CharField(max_length=50)
quantity = models.PositiveSmallIntegerField()
recipe = models.ForeignKey(
Recipe,
on_delete=models.CASCADE,
related_name='ingredients',
)
serializers.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = [
'id',
'name',
'quantity',
'unit',
'recipe'
]
views.py
class IngredientViewSet(viewsets.ModelViewSet):
serializer_class = IngredientSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
request.data['recipe'] = self.kwargs['pk']
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
don't forget to pass the serializer the context dict
you can create your own or get the default one:
context = self.get_serializer_context()
serializer = self.get_serializer(data=request.data, context=context)
if you'll at the source code for get_serializer_context:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
it adds the view class and the request to the serializer context.
in the serializer, I override the create method and pass the creation data dict the related object id.
class S(ModelSerializer):
def create(self, validated_data):
validated_data['recipe_id'] = self.kwargs['pk']
return super(S, self).create(validated_data)
and no need to override the create of the view

Django Rest FrameWork Add Model With User Foreign Key

I'm using Django version 3 and the Rest Framework, i have a model with a user foreignkey.
so every time a model is saved the user also need to be saved.
To be able to add or to edit a model via rest you need to be authenticated using token authentication.
I'm using ClassBasedViews, the problem is that i can't find a way to add a model because in my serializer the field user is excluded because i don't want it to be editable.
models.py:
class Chambre(models.Model):
local_id=models.PositiveIntegerField(unique=True)
nom=models.CharField(max_length=255)
user=models.ForeignKey(User,on_delete=models.CASCADE,blank='true')
class Meta:
unique_together = ('local_id', 'user',)
serializers.py:
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
views.py:
class ChambreListApi(APIView):
"""
List all chambres, or create a new chambre.
"""
authentication_classes=(TokenAuthentication,)
permission_classes=(IsAuthenticated,)
def get(self, request, format=None):
chambres = Chambre.objects.filter(user=request.user)
serializer = ChambreSerializer(chambres, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
if serializer.is_valid():
serializer.save(commit=False)
serializer.user=request.user
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Try moving the logic that sets the user to the serializer. By default GenericAPIView will pass the request to the serializer via the context.
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
When I followed the accepted solution posted by schillingt, I had to add context={'request': request} when I instantiated the serializer. The Django REST Framework docs mention adding context to a serializer here.
serializers.py
class NoteCreateSerializer(serializer.ModelSerializer):
class Meta:
model = Note
exclude = ['owner',]
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
views.py
#api_view['POST']
def create_note(request):
note_serializer = NoteCreateSerializer(
data = request.data,
context = {'request': request} # this will populate 'self.context'
# in the serializer instance
)
# ...
Thanks so much for this solution, though! It ended an hours-long struggle.
thanks for the reply that was very useful, i've solved it by adding method to serializer.py like this :
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def set_the_user(self,request):
self.user=request.user
def create(self, validated_data):
validated_data["user"] = self.user
return super().create(validated_data)
and in the views.py :
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
serializer.set_the_user(request)
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)

check changes before saving into database

I am using Django 1.11 and DRF 3.6.2 and just started developing an API...
I am trying to check what are the changes to be performed in the database with the data being sent.
class IndividualViewSet(viewsets.ModelViewSet):
"""Individual ViewSet."""
serializer_class = serializers.IndividualSerializer
queryset = models.Individual.objects.all()
def update(self, request, equipment_serial, pk=None):
queryset = models.Individual.objects.get(pk=pk)
serializer = serializers.IndividualSerializer(queryset, data=request.data["entities"][0])
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_200_OK)
return Response(status.HTTP_400_BAD_REQUEST)
def perform_update(self, serializer):
old_obj = self.get_object()
new_data_dict = serializer.validated_data
if old_obj.name != new_data_dict['name']:
# logic for different data
# ...
serializer.save()
However, with the code as above, the perform_update function is never being called by serializer.save() on the update function.
According to the docs, ModelViewSet is inherited from GenericAPIView and it has UpdateModelMixin, which should automatically calls the perform_update function before saving
My questions surrounds on why it is happen and how should I do in order to accomplish the desired behaviour.
This is because you are overriding the update method in your custom viewset. This is the original code for the UpdateModelMixin that the ModelViewSet mixes in:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# refresh the instance from the database.
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
In this original version, perform_update is called whenever in the update method. If you override that method and still want to call perform_update, you need to put it there.

Django update ViewSet

Currently I am developing a image gallery django project. The user can upload images, and later upload a 'result' to each of the images.
I know that I have to override the update(...) function, but I think I need help here with the Base64ImageFiled.
Step by step:
User uploads image (result = null)
Image gets stored in cloud
User uploads result to a specified image (need help here)
Here is my current structure:
class Image(models.Model):
project = models.ForeignKey(Project)
image = models.ImageField(upload_to='images')
result = models.ImageField(upload_to='results')
class ImageSerializer(serializers.ModelSerializer):
project = ProjectSerializer(read_only=True, required=False)
image = Base64ImageField(max_length=None, use_url=False)
result = Base64ImageField(max_length=None, use_url=False, required=False)
class ProjectImagesViewSet(viewsets.ViewSet):
queryset = Image.objects.select_related('project').all()
serializer_class = ImageSerializer
def list(self, request, project_pk=None):
queryset = self.queryset.filter( project__name = project_pk)
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def update(self, request, pk=None, project_pk=None):
print(request.data['result'])
???
Django Rest Framework convention is to use partial_update instead of update (PATCH request method). If you will send PATCH request with image to the same url as for retrieve url is should get updated. if you require additional features on update then you can define:
class ProjectImagesViewSet(viewsets.ViewSet):
#.....
def partial_update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serialize(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
#.... Your code ....
serializer.save()
return Response(serializer.data)
EDIT:
Try those changes:
class ImageSerializer(serializers.ModelSerializer):
project = ProjectSerializer(read_only=True, required=False)
image = Base64ImageField(max_length=None, use_url=False)
result = Base64ImageField(max_length=None, use_url=False, required=False)
class Meta:
model = Image
class ProjectImagesViewSet(viewsets.ViewSet):
queryset = Image.objects.select_related('project').all()
serializer_class = ImageSerializer
def list(self, request, project_pk=None):
queryset = self.queryset.filter(project__name=project_pk)
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def update(self, request, pk=None, project_pk=None):
print(request.data['result'])
def partial_update(self, request, *args, **kwargs):
instance = self.queryset.get(pk=kwargs.get('pk'))
serializer = self.serializer_class(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

How I can pass over a field validation in a serializer in Django Rest Framework

I have a Location model and I need to create a Location without specifying a user, if user in empty then the user will be placed in request.user in the viewset.
This is my model:
class Location(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
serializer:
class LocationSerializer(serializers.ModelSerializer):
def is_valid(self):
if not 'user' in self.init_data:
# avoid this validation.. I manage this in the viewset
pass
return not self.errors
class Meta:
model = Location
and viewset
class LocationViewSet(ModelViewSet):
"""
API endpoint that allows location to be created or viewed.
"""
model = Location
serializer_class = LocationSerializer
renderer_classes = (JSONRenderer, JSONPRenderer)
def get_queryset(self):
return self.request.user.locations.filter(deleted=False)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
if not self.object.user:
self.object.user = request.user
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Thanks for any suggestion
You can use required=False in serializer:
class LocationSerializer(serializers.ModelSerializer):
user = serializers.RelatedField(required=False)
EDIT
Also you can simplify your viewset with:
class LocationViewSet(ModelViewSet):
def pre_save(self, obj):
if obj.user_id is None:
obj.user = self.request.user
This avoid copying code from DRF core.